Show common currencies first in dropdowns and default to USD in install wizard

Currency dropdowns now display the most-traded currencies (USD, EUR, GBP,
JPY, CAD, AUD, CHF, CNY, INR, BRL) at the top, followed by the rest
alphabetically. The install wizard defaults to USD instead of EUR and
formats currency names as "USD - US Dollar" for consistency with the
rest of the app.
This commit is contained in:
Darko Gjorgjijoski
2026-04-10 15:55:46 +02:00
parent 9174254165
commit 42ce99eeba
5 changed files with 128 additions and 12 deletions

View File

@@ -4,20 +4,19 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Resources\CurrencyResource;
use App\Models\Currency;
use App\Services\CurrencyService;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
class CurrenciesController extends Controller
{
/**
* Handle the incoming request.
*
* @return Response
*/
public function __invoke(Request $request)
public function __construct(
private readonly CurrencyService $currencyService,
) {}
public function __invoke(Request $request): AnonymousResourceCollection
{
$currencies = Currency::latest()->get();
$currencies = $this->currencyService->getAllWithCommonFirst();
return CurrencyResource::collection($currencies);
}

View File

@@ -9,6 +9,11 @@ class Currency extends Model
{
use HasFactory;
public const COMMON_CURRENCY_CODES = [
'USD', 'EUR', 'GBP', 'JPY', 'CAD',
'AUD', 'CHF', 'CNY', 'INR', 'BRL',
];
protected $guarded = [
'id',
];

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Services;
use App\Models\Currency;
use Illuminate\Database\Eloquent\Collection;
class CurrencyService
{
/**
* Retrieve all currencies with commonly-used currencies sorted first.
*
* @return Collection<int, Currency>
*/
public function getAllWithCommonFirst(): Collection
{
$currencies = Currency::query()->get();
$commonCodes = Currency::COMMON_CURRENCY_CODES;
$common = $currencies->filter(fn (Currency $c): bool => in_array($c->code, $commonCodes, true))
->sortBy(fn (Currency $c): int => array_search($c->code, $commonCodes));
$rest = $currencies->reject(fn (Currency $c): bool => in_array($c->code, $commonCodes, true))
->sortBy('name');
return $common->concat($rest)->values();
}
}

View File

@@ -137,7 +137,7 @@ import { clearInstallWizardAuth } from '../install-auth'
import { useInstallationFeedback } from '../use-installation-feedback'
interface PreferencesData {
currency: number
currency: number | null
language: string
carbon_date_format: string
time_zone: string
@@ -157,6 +157,7 @@ interface DateFormatOption {
interface CurrencyOption {
id: number
name: string
code: string
}
interface LanguageOption {
@@ -179,7 +180,7 @@ const timeZones = ref<KeyValueOption[]>([])
const fiscalYears = ref<KeyValueOption[]>([])
const currentPreferences = reactive<PreferencesData>({
currency: 3,
currency: null,
language: 'en',
carbon_date_format: 'd M Y',
time_zone: 'UTC',
@@ -223,7 +224,19 @@ onMounted(async () => {
installClient.get(`${API.CONFIG}?key=fiscal_years`),
installClient.get(`${API.CONFIG}?key=languages`),
])
currencies.value = currRes.data.data ?? currRes.data
const rawCurrencies: CurrencyOption[] = currRes.data.data ?? currRes.data
currencies.value = rawCurrencies.map((c) => ({
...c,
name: `${c.code} - ${c.name}`,
}))
if (!currentPreferences.currency) {
const usd = currencies.value.find((c) => c.code === 'USD')
if (usd) {
currentPreferences.currency = usd.id
}
}
dateFormats.value = dateRes.data.data ?? dateRes.data
timeZones.value = tzRes.data.data ?? tzRes.data
fiscalYears.value = fyRes.data.data ?? fyRes.data ?? []

View File

@@ -0,0 +1,70 @@
<?php
use App\Models\Currency;
use App\Models\User;
use Illuminate\Support\Facades\Artisan;
use Laravel\Sanctum\Sanctum;
use function Pest\Laravel\getJson;
beforeEach(function () {
Artisan::call('db:seed', ['--class' => 'DatabaseSeeder', '--force' => true]);
Artisan::call('db:seed', ['--class' => 'DemoSeeder', '--force' => true]);
$user = User::find(1);
$this->withHeaders([
'company' => $user->companies()->first()->id,
]);
Sanctum::actingAs(
$user,
['*']
);
});
test('currencies endpoint returns common currencies first', function () {
$response = getJson('/api/v1/currencies')->assertOk();
$currencies = collect($response->json('data'));
$commonCodes = Currency::COMMON_CURRENCY_CODES;
$commonCount = count($commonCodes);
$firstCodes = $currencies->take($commonCount)->pluck('code')->toArray();
foreach ($commonCodes as $code) {
expect($firstCodes)->toContain($code);
}
});
test('common currencies follow the defined priority order', function () {
$response = getJson('/api/v1/currencies')->assertOk();
$currencies = collect($response->json('data'));
$commonCodes = Currency::COMMON_CURRENCY_CODES;
$commonCount = count($commonCodes);
$firstCodes = $currencies->take($commonCount)->pluck('code')->toArray();
expect($firstCodes)->toBe($commonCodes);
});
test('non-common currencies are sorted by name after common currencies', function () {
$response = getJson('/api/v1/currencies')->assertOk();
$currencies = collect($response->json('data'));
$commonCodes = Currency::COMMON_CURRENCY_CODES;
$commonCount = count($commonCodes);
$restNames = $currencies->skip($commonCount)->pluck('name')->toArray();
$sorted = $restNames;
sort($sorted, SORT_STRING | SORT_FLAG_CASE);
expect($restNames)->toBe($sorted);
});
test('all seeded currencies are returned', function () {
$response = getJson('/api/v1/currencies')->assertOk();
$currencies = collect($response->json('data'));
expect($currencies->count())->toBe(Currency::count());
});