@86d-store/multi-currency 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +234 -0
- package/dist/admin/components/_shared.d.ts +28 -0
- package/dist/admin/components/_shared.d.ts.map +1 -0
- package/dist/admin/components/_shared.js +15 -0
- package/dist/admin/components/currency-detail.d.ts +6 -0
- package/dist/admin/components/currency-detail.d.ts.map +1 -0
- package/dist/admin/components/currency-detail.js +26 -0
- package/dist/admin/components/currency-form.d.ts +6 -0
- package/dist/admin/components/currency-form.d.ts.map +1 -0
- package/dist/admin/components/currency-form.js +95 -0
- package/dist/admin/components/currency-list.d.ts +2 -0
- package/dist/admin/components/currency-list.d.ts.map +1 -0
- package/dist/admin/components/currency-list.js +35 -0
- package/dist/admin/endpoints/bulk-update-rates.d.ts +15 -0
- package/dist/admin/endpoints/bulk-update-rates.d.ts.map +1 -0
- package/dist/admin/endpoints/bulk-update-rates.js +17 -0
- package/dist/admin/endpoints/create-currency.d.ts +34 -0
- package/dist/admin/endpoints/create-currency.d.ts.map +1 -0
- package/dist/admin/endpoints/create-currency.js +34 -0
- package/dist/admin/endpoints/delete-currency.d.ts +16 -0
- package/dist/admin/endpoints/delete-currency.d.ts.map +1 -0
- package/dist/admin/endpoints/delete-currency.js +17 -0
- package/dist/admin/endpoints/delete-price-override.d.ts +10 -0
- package/dist/admin/endpoints/delete-price-override.d.ts.map +1 -0
- package/dist/admin/endpoints/delete-price-override.js +11 -0
- package/dist/admin/endpoints/get-currency.d.ts +16 -0
- package/dist/admin/endpoints/get-currency.d.ts.map +1 -0
- package/dist/admin/endpoints/get-currency.js +14 -0
- package/dist/admin/endpoints/list-currencies.d.ts +6 -0
- package/dist/admin/endpoints/list-currencies.d.ts.map +1 -0
- package/dist/admin/endpoints/list-currencies.js +7 -0
- package/dist/admin/endpoints/list-price-overrides.d.ts +10 -0
- package/dist/admin/endpoints/list-price-overrides.d.ts.map +1 -0
- package/dist/admin/endpoints/list-price-overrides.js +11 -0
- package/dist/admin/endpoints/rate-history.d.ts +11 -0
- package/dist/admin/endpoints/rate-history.d.ts.map +1 -0
- package/dist/admin/endpoints/rate-history.js +17 -0
- package/dist/admin/endpoints/routes.d.ts +180 -0
- package/dist/admin/endpoints/routes.d.ts.map +1 -0
- package/dist/admin/endpoints/routes.js +26 -0
- package/dist/admin/endpoints/set-base-currency.d.ts +16 -0
- package/dist/admin/endpoints/set-base-currency.d.ts.map +1 -0
- package/dist/admin/endpoints/set-base-currency.js +14 -0
- package/dist/admin/endpoints/set-price-override.d.ts +13 -0
- package/dist/admin/endpoints/set-price-override.d.ts.map +1 -0
- package/dist/admin/endpoints/set-price-override.js +16 -0
- package/dist/admin/endpoints/update-currency.d.ts +35 -0
- package/dist/admin/endpoints/update-currency.d.ts.map +1 -0
- package/dist/admin/endpoints/update-currency.js +27 -0
- package/dist/admin/endpoints/update-rate.d.ts +18 -0
- package/dist/admin/endpoints/update-rate.d.ts.map +1 -0
- package/dist/admin/endpoints/update-rate.js +22 -0
- package/dist/capabilities.d.ts +56 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/schema.d.ts +94 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +50 -0
- package/dist/service-impl.d.ts +5 -0
- package/dist/service-impl.d.ts.map +1 -0
- package/dist/service-impl.js +369 -0
- package/dist/service.d.ts +139 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +1 -0
- package/dist/store/components/_hooks.d.ts +6 -0
- package/dist/store/components/_hooks.d.ts.map +1 -0
- package/dist/store/components/_hooks.js +10 -0
- package/dist/store/components/currency-selector.d.ts +10 -0
- package/dist/store/components/currency-selector.d.ts.map +1 -0
- package/dist/store/components/currency-selector.js +22 -0
- package/dist/store/components/currency-selector.mdx +55 -0
- package/dist/store/components/mdx.d.ts +8 -0
- package/dist/store/components/mdx.d.ts.map +1 -0
- package/dist/store/components/mdx.js +7 -0
- package/dist/store/components/price-display.d.ts +14 -0
- package/dist/store/components/price-display.d.ts.map +1 -0
- package/dist/store/components/price-display.js +30 -0
- package/dist/store/components/price-display.mdx +12 -0
- package/dist/store/endpoints/convert-price.d.ts +22 -0
- package/dist/store/endpoints/convert-price.d.ts.map +1 -0
- package/dist/store/endpoints/convert-price.js +26 -0
- package/dist/store/endpoints/get-product-price.d.ts +22 -0
- package/dist/store/endpoints/get-product-price.d.ts.map +1 -0
- package/dist/store/endpoints/get-product-price.js +26 -0
- package/dist/store/endpoints/list-currencies.d.ts +6 -0
- package/dist/store/endpoints/list-currencies.d.ts.map +1 -0
- package/dist/store/endpoints/list-currencies.js +7 -0
- package/dist/store/endpoints/routes.d.ts +48 -0
- package/dist/store/endpoints/routes.d.ts.map +1 -0
- package/dist/store/endpoints/routes.js +8 -0
- package/package.json +108 -0
package/README.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://86d.app">
|
|
3
|
+
<img src="https://86d.app/icon" height="96" alt="86d" />
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
The Modern Foundation for Commerce
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://x.com/86d_app"><strong>X</strong></a> ·
|
|
13
|
+
<a href="https://www.linkedin.com/company/86d"><strong>LinkedIn</strong></a>
|
|
14
|
+
</p>
|
|
15
|
+
<br/>
|
|
16
|
+
|
|
17
|
+
> [!WARNING]
|
|
18
|
+
> This project is under active development and is not ready for production use. Please proceed with caution. Use at your own risk.
|
|
19
|
+
|
|
20
|
+
📚 **Documentation:** [86d.app/docs/modules/multi-currency](https://86d.app/docs/modules/multi-currency)
|
|
21
|
+
|
|
22
|
+
# @86d-store/multi-currency
|
|
23
|
+
|
|
24
|
+
Multi-currency support for 86d commerce stores. Manage currencies, exchange rates, price conversions, and per-product price overrides.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
The module is included in the 86d monorepo. Enable it in your store's template `config.json`:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"modules": ["multi-currency"]
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import multiCurrency from "@86d-store/multi-currency";
|
|
40
|
+
|
|
41
|
+
// Register with options
|
|
42
|
+
multiCurrency({
|
|
43
|
+
baseCurrency: "USD",
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Configuration
|
|
48
|
+
|
|
49
|
+
| Option | Type | Default | Description |
|
|
50
|
+
|--------|------|---------|-------------|
|
|
51
|
+
| `baseCurrency` | `string` | `"USD"` | Default base currency ISO 4217 code |
|
|
52
|
+
|
|
53
|
+
## Store Endpoints
|
|
54
|
+
|
|
55
|
+
| Endpoint | Method | Description |
|
|
56
|
+
|----------|--------|-------------|
|
|
57
|
+
| `/currencies` | GET | List all active currencies |
|
|
58
|
+
| `/currencies/convert` | POST | Convert an amount between currencies |
|
|
59
|
+
| `/currencies/product-price` | POST | Get a product's price in a specific currency |
|
|
60
|
+
|
|
61
|
+
### Convert Price
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
POST /currencies/convert
|
|
65
|
+
{
|
|
66
|
+
"amount": 100,
|
|
67
|
+
"to": "EUR",
|
|
68
|
+
"from": "USD" // optional, defaults to base currency
|
|
69
|
+
}
|
|
70
|
+
// Response: { "amount": 85, "formatted": "85,00EUR", "currency": "EUR" }
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Get Product Price
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
POST /currencies/product-price
|
|
77
|
+
{
|
|
78
|
+
"productId": "prod-123",
|
|
79
|
+
"basePriceInCents": 9999,
|
|
80
|
+
"currencyCode": "EUR"
|
|
81
|
+
}
|
|
82
|
+
// Response: { "amount": 8499, "formatted": "84,99EUR", "currency": "EUR" }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Admin Endpoints
|
|
86
|
+
|
|
87
|
+
| Endpoint | Method | Description |
|
|
88
|
+
|----------|--------|-------------|
|
|
89
|
+
| `/admin/currencies` | GET | List all currencies |
|
|
90
|
+
| `/admin/currencies/create` | POST | Create a new currency |
|
|
91
|
+
| `/admin/currencies/:id` | GET | Get currency by ID |
|
|
92
|
+
| `/admin/currencies/:id/update` | POST | Update a currency |
|
|
93
|
+
| `/admin/currencies/:id/delete` | POST | Delete a currency |
|
|
94
|
+
| `/admin/currencies/:id/set-base` | POST | Set as base currency |
|
|
95
|
+
| `/admin/currencies/update-rate` | POST | Update a single exchange rate |
|
|
96
|
+
| `/admin/currencies/bulk-update-rates` | POST | Update multiple exchange rates |
|
|
97
|
+
| `/admin/currencies/rate-history` | POST | Get exchange rate history |
|
|
98
|
+
| `/admin/currencies/price-override` | POST | Set a product price override |
|
|
99
|
+
| `/admin/currencies/price-overrides/:productId` | GET | List overrides for a product |
|
|
100
|
+
| `/admin/currencies/price-overrides/:id/delete` | POST | Delete a price override |
|
|
101
|
+
|
|
102
|
+
## Store Components
|
|
103
|
+
|
|
104
|
+
The module exports customer-facing components for MDX templates:
|
|
105
|
+
|
|
106
|
+
### CurrencySelector
|
|
107
|
+
|
|
108
|
+
Dropdown to switch the active currency. Hidden when only one currency is active.
|
|
109
|
+
|
|
110
|
+
```mdx
|
|
111
|
+
<CurrencySelector value={selectedCurrency} onChange={setCurrency} />
|
|
112
|
+
<CurrencySelector compact />
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
| Prop | Type | Default | Description |
|
|
116
|
+
|------|------|---------|-------------|
|
|
117
|
+
| `value` | `string` | — | Currently selected currency code (ISO 4217) |
|
|
118
|
+
| `onChange` | `(code: string) => void` | — | Called when user selects a different currency |
|
|
119
|
+
| `compact` | `boolean` | `false` | Show code only (no full name) |
|
|
120
|
+
|
|
121
|
+
### PriceDisplay
|
|
122
|
+
|
|
123
|
+
Show a price converted to the selected currency, with optional compare-at (sale) display.
|
|
124
|
+
|
|
125
|
+
```mdx
|
|
126
|
+
<PriceDisplay productId="prod-123" basePriceInCents={9999} currencyCode="EUR" />
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
| Prop | Type | Default | Description |
|
|
130
|
+
|------|------|---------|-------------|
|
|
131
|
+
| `productId` | `string` | — | Product ID (used for price override lookup) |
|
|
132
|
+
| `basePriceInCents` | `number` | **required** | Base price in smallest currency unit |
|
|
133
|
+
| `currencyCode` | `string` | — | Target currency code |
|
|
134
|
+
| `compareAtPriceInCents` | `number` | — | Compare-at price for sale display |
|
|
135
|
+
| `className` | `string` | — | Additional CSS class |
|
|
136
|
+
|
|
137
|
+
## Admin UI
|
|
138
|
+
|
|
139
|
+
The module includes admin UI components in `src/admin/components/index.tsx` (client components using `useModuleClient`):
|
|
140
|
+
|
|
141
|
+
| Page | Component | Description |
|
|
142
|
+
|------|-----------|-------------|
|
|
143
|
+
| `/admin/currencies` | `CurrencyList` | Currency list with exchange rates and status indicators |
|
|
144
|
+
| `/admin/currencies/new` | `CurrencyForm` | Create a new currency |
|
|
145
|
+
| `/admin/currencies/:id` | `CurrencyDetail` | Currency detail with rate history and price overrides |
|
|
146
|
+
| `/admin/currencies/:id/edit` | `CurrencyForm` | Edit an existing currency |
|
|
147
|
+
|
|
148
|
+
## Controller API
|
|
149
|
+
|
|
150
|
+
Access via `ctx.context.controllers.multiCurrency`:
|
|
151
|
+
|
|
152
|
+
### Currency Management
|
|
153
|
+
|
|
154
|
+
- `create(params)` — Create a currency (code is auto-uppercased)
|
|
155
|
+
- `getById(id)` / `getByCode(code)` — Lookup
|
|
156
|
+
- `update(id, params)` — Update currency details
|
|
157
|
+
- `delete(id)` — Delete (cannot delete base currency)
|
|
158
|
+
- `list({ activeOnly? })` — List currencies sorted by `sortOrder`
|
|
159
|
+
|
|
160
|
+
### Base Currency
|
|
161
|
+
|
|
162
|
+
- `getBaseCurrency()` — Get the store's base currency
|
|
163
|
+
- `setBaseCurrency(id)` — Set a new base currency (unsets previous)
|
|
164
|
+
|
|
165
|
+
### Exchange Rates
|
|
166
|
+
|
|
167
|
+
- `updateRate({ currencyCode, rate, source? })` — Update rate and record history
|
|
168
|
+
- `bulkUpdateRates(rates[])` — Update multiple rates at once
|
|
169
|
+
- `getRateHistory({ currencyCode, limit? })` — Get historical rates
|
|
170
|
+
|
|
171
|
+
### Price Conversion
|
|
172
|
+
|
|
173
|
+
- `convert({ amount, to, from? })` — Convert between currencies
|
|
174
|
+
- `formatPrice(amount, currencyCode)` — Format with locale settings
|
|
175
|
+
- `getProductPrice({ productId, basePriceInCents, currencyCode })` — Resolve best price
|
|
176
|
+
|
|
177
|
+
### Price Overrides
|
|
178
|
+
|
|
179
|
+
- `setPriceOverride({ productId, currencyCode, price, compareAtPrice? })` — Set fixed price
|
|
180
|
+
- `getPriceOverride(productId, currencyCode)` — Get override
|
|
181
|
+
- `listPriceOverrides(productId)` — List all overrides for a product
|
|
182
|
+
- `deletePriceOverride(id)` — Remove an override
|
|
183
|
+
|
|
184
|
+
## Types
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
interface Currency {
|
|
188
|
+
id: string;
|
|
189
|
+
code: string; // ISO 4217 (e.g., "USD")
|
|
190
|
+
name: string; // Display name
|
|
191
|
+
symbol: string; // Currency symbol
|
|
192
|
+
decimalPlaces: number; // 0 for JPY, 2 for USD
|
|
193
|
+
exchangeRate: number; // Relative to base (base = 1)
|
|
194
|
+
isBase: boolean;
|
|
195
|
+
isActive: boolean;
|
|
196
|
+
symbolPosition: "before" | "after";
|
|
197
|
+
thousandsSeparator: string;
|
|
198
|
+
decimalSeparator: string;
|
|
199
|
+
roundingMode: "round" | "ceil" | "floor";
|
|
200
|
+
sortOrder: number;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
interface PriceOverride {
|
|
204
|
+
id: string;
|
|
205
|
+
productId: string;
|
|
206
|
+
currencyCode: string;
|
|
207
|
+
price: number; // In smallest unit (cents)
|
|
208
|
+
compareAtPrice?: number;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
interface ConvertedPrice {
|
|
212
|
+
amount: number;
|
|
213
|
+
currency: Currency;
|
|
214
|
+
formatted: string;
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Events
|
|
219
|
+
|
|
220
|
+
| Event | Payload | Description |
|
|
221
|
+
|-------|---------|-------------|
|
|
222
|
+
| `currency.created` | `{ id, code }` | New currency added |
|
|
223
|
+
| `currency.updated` | `{ id, code }` | Currency details changed |
|
|
224
|
+
| `currency.deleted` | `{ id, code }` | Currency removed |
|
|
225
|
+
| `currency.baseChanged` | `{ id, code }` | Base currency switched |
|
|
226
|
+
| `currency.rateUpdated` | `{ code, oldRate, newRate, source }` | Exchange rate changed |
|
|
227
|
+
|
|
228
|
+
## Notes
|
|
229
|
+
|
|
230
|
+
- Only one base currency per store. Base rate is always 1.
|
|
231
|
+
- Cross-currency conversion goes through the base rate (EUR -> USD -> GBP).
|
|
232
|
+
- Price overrides take priority over automatic conversion.
|
|
233
|
+
- Zero-decimal currencies (JPY, KRW) are fully supported via `decimalPlaces: 0`.
|
|
234
|
+
- Exchange rate history is recorded automatically on every rate update.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare function useCurrencyApi(): {
|
|
2
|
+
listCurrencies: import("@86d-store/core/client/types").AnyEndpointHook;
|
|
3
|
+
getCurrency: import("@86d-store/core/client/types").AnyEndpointHook;
|
|
4
|
+
createCurrency: import("@86d-store/core/client/types").AnyEndpointHook;
|
|
5
|
+
updateCurrency: import("@86d-store/core/client/types").AnyEndpointHook;
|
|
6
|
+
deleteCurrency: import("@86d-store/core/client/types").AnyEndpointHook;
|
|
7
|
+
setBase: import("@86d-store/core/client/types").AnyEndpointHook;
|
|
8
|
+
updateRate: import("@86d-store/core/client/types").AnyEndpointHook;
|
|
9
|
+
rateHistory: import("@86d-store/core/client/types").AnyEndpointHook;
|
|
10
|
+
};
|
|
11
|
+
export interface Currency {
|
|
12
|
+
id: string;
|
|
13
|
+
code: string;
|
|
14
|
+
name: string;
|
|
15
|
+
symbol: string;
|
|
16
|
+
decimalPlaces: number;
|
|
17
|
+
exchangeRate: number;
|
|
18
|
+
isBase: boolean;
|
|
19
|
+
isActive: boolean;
|
|
20
|
+
symbolPosition: string;
|
|
21
|
+
thousandsSeparator?: string;
|
|
22
|
+
decimalSeparator?: string;
|
|
23
|
+
roundingMode: string;
|
|
24
|
+
sortOrder: number;
|
|
25
|
+
createdAt: string;
|
|
26
|
+
updatedAt: string;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=_shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_shared.d.ts","sourceRoot":"","sources":["../../../src/admin/components/_shared.tsx"],"names":[],"mappings":"AAIA,wBAAgB,cAAc;;;;;;;;;EAkB7B;AAED,MAAM,WAAW,QAAQ;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CAClB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useModuleClient } from "@86d-store/core/client/provider";
|
|
3
|
+
export function useCurrencyApi() {
|
|
4
|
+
const client = useModuleClient();
|
|
5
|
+
return {
|
|
6
|
+
listCurrencies: client.module("multi-currency").admin["/admin/currencies"],
|
|
7
|
+
getCurrency: client.module("multi-currency").admin["/admin/currencies/:id"],
|
|
8
|
+
createCurrency: client.module("multi-currency").admin["/admin/currencies/create"],
|
|
9
|
+
updateCurrency: client.module("multi-currency").admin["/admin/currencies/:id/update"],
|
|
10
|
+
deleteCurrency: client.module("multi-currency").admin["/admin/currencies/:id/delete"],
|
|
11
|
+
setBase: client.module("multi-currency").admin["/admin/currencies/:id/set-base"],
|
|
12
|
+
updateRate: client.module("multi-currency").admin["/admin/currencies/update-rate"],
|
|
13
|
+
rateHistory: client.module("multi-currency").admin["/admin/currencies/rate-history"],
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"currency-detail.d.ts","sourceRoot":"","sources":["../../../src/admin/components/currency-detail.tsx"],"names":[],"mappings":"AAYA,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,+BA0HpE"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCurrencyApi } from "./_shared";
|
|
4
|
+
function formatDate(dateStr) {
|
|
5
|
+
return new Date(dateStr).toLocaleDateString(undefined, {
|
|
6
|
+
year: "numeric",
|
|
7
|
+
month: "short",
|
|
8
|
+
day: "numeric",
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export function CurrencyDetail({ params }) {
|
|
12
|
+
const api = useCurrencyApi();
|
|
13
|
+
const { data, isLoading } = api.getCurrency.useQuery({
|
|
14
|
+
params: { id: params.id },
|
|
15
|
+
});
|
|
16
|
+
const currency = data?.currency;
|
|
17
|
+
if (isLoading) {
|
|
18
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "h-8 w-48 animate-pulse rounded bg-muted/30" }), _jsx("div", { className: "h-48 animate-pulse rounded-lg border border-border bg-muted/30" })] }));
|
|
19
|
+
}
|
|
20
|
+
if (!currency) {
|
|
21
|
+
return (_jsxs("div", { className: "rounded-lg border border-border bg-card p-8 text-center", children: [_jsx("p", { className: "text-muted-foreground text-sm", children: "Currency not found." }), _jsx("a", { href: "/admin/currencies", className: "mt-2 inline-block text-sm underline", children: "Back to currencies" })] }));
|
|
22
|
+
}
|
|
23
|
+
return (_jsxs("div", { children: [_jsxs("div", { className: "mb-6", children: [_jsx("a", { href: "/admin/currencies", className: "text-muted-foreground text-sm hover:underline", children: "\u2190 Back to currencies" }), _jsxs("div", { className: "mt-2 flex items-center gap-3", children: [_jsxs("h1", { className: "font-bold text-2xl text-foreground", children: [currency.name, " (", currency.code, ")"] }), currency.isBase ? (_jsx("span", { className: "inline-flex items-center rounded-full bg-indigo-100 px-2 py-0.5 font-medium text-indigo-800 text-xs dark:bg-indigo-900/30 dark:text-indigo-400", children: "Base Currency" })) : null, _jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 font-medium text-xs ${currency.isActive
|
|
24
|
+
? "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
|
|
25
|
+
: "bg-muted text-muted-foreground"}`, children: currency.isActive ? "Active" : "Inactive" })] })] }), _jsxs("div", { className: "grid gap-6 lg:grid-cols-2", children: [_jsxs("div", { className: "rounded-lg border border-border bg-card p-5", children: [_jsx("h2", { className: "mb-4 font-semibold text-foreground text-sm", children: "Details" }), _jsxs("dl", { className: "space-y-3 text-sm", children: [_jsxs("div", { className: "flex justify-between", children: [_jsx("dt", { className: "text-muted-foreground", children: "Symbol" }), _jsx("dd", { className: "font-medium text-foreground", children: currency.symbol })] }), _jsxs("div", { className: "flex justify-between", children: [_jsx("dt", { className: "text-muted-foreground", children: "Exchange Rate" }), _jsx("dd", { className: "font-mono text-foreground", children: currency.exchangeRate.toFixed(6) })] }), _jsxs("div", { className: "flex justify-between", children: [_jsx("dt", { className: "text-muted-foreground", children: "Decimal Places" }), _jsx("dd", { className: "text-foreground", children: currency.decimalPlaces })] }), _jsxs("div", { className: "flex justify-between", children: [_jsx("dt", { className: "text-muted-foreground", children: "Symbol Position" }), _jsx("dd", { className: "text-foreground", children: currency.symbolPosition })] }), _jsxs("div", { className: "flex justify-between", children: [_jsx("dt", { className: "text-muted-foreground", children: "Rounding" }), _jsx("dd", { className: "text-foreground", children: currency.roundingMode })] }), _jsxs("div", { className: "flex justify-between", children: [_jsx("dt", { className: "text-muted-foreground", children: "Sort Order" }), _jsx("dd", { className: "text-foreground", children: currency.sortOrder })] }), _jsxs("div", { className: "flex justify-between", children: [_jsx("dt", { className: "text-muted-foreground", children: "Created" }), _jsx("dd", { className: "text-foreground", children: formatDate(currency.createdAt) })] })] })] }), _jsxs("div", { className: "rounded-lg border border-border bg-card p-5", children: [_jsx("h2", { className: "mb-4 font-semibold text-foreground text-sm", children: "Actions" }), _jsx("div", { className: "space-y-2", children: _jsx("a", { href: `/admin/currencies/${params.id}/edit`, className: "block rounded-lg border border-border px-4 py-2 text-center text-sm hover:bg-muted", children: "Edit Currency" }) })] })] })] }));
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"currency-form.d.ts","sourceRoot":"","sources":["../../../src/admin/components/currency-form.tsx"],"names":[],"mappings":"AAYA,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAO,+BA6QzE"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { useCurrencyApi } from "./_shared";
|
|
5
|
+
function extractError(err) {
|
|
6
|
+
if (err && typeof err === "object" && "message" in err) {
|
|
7
|
+
return String(err.message);
|
|
8
|
+
}
|
|
9
|
+
return "An unexpected error occurred";
|
|
10
|
+
}
|
|
11
|
+
export function CurrencyForm({ params } = {}) {
|
|
12
|
+
const api = useCurrencyApi();
|
|
13
|
+
const isEdit = Boolean(params?.id);
|
|
14
|
+
const [code, setCode] = useState("");
|
|
15
|
+
const [name, setName] = useState("");
|
|
16
|
+
const [symbol, setSymbol] = useState("");
|
|
17
|
+
const [decimalPlaces, setDecimalPlaces] = useState(2);
|
|
18
|
+
const [exchangeRate, setExchangeRate] = useState(1);
|
|
19
|
+
const [isActive, setIsActive] = useState(true);
|
|
20
|
+
const [symbolPosition, setSymbolPosition] = useState("before");
|
|
21
|
+
const [roundingMode, setRoundingMode] = useState("round");
|
|
22
|
+
const [initialized, setInitialized] = useState(false);
|
|
23
|
+
const [error, setError] = useState("");
|
|
24
|
+
const [saved, setSaved] = useState(false);
|
|
25
|
+
const { data: currencyData, isLoading } = api.getCurrency.useQuery({ params: { id: params?.id ?? "" } }, { enabled: isEdit });
|
|
26
|
+
const currency = currencyData?.currency;
|
|
27
|
+
if (currency && !initialized) {
|
|
28
|
+
setCode(currency.code);
|
|
29
|
+
setName(currency.name);
|
|
30
|
+
setSymbol(currency.symbol);
|
|
31
|
+
setDecimalPlaces(currency.decimalPlaces);
|
|
32
|
+
setExchangeRate(currency.exchangeRate);
|
|
33
|
+
setIsActive(currency.isActive);
|
|
34
|
+
setSymbolPosition(currency.symbolPosition);
|
|
35
|
+
setRoundingMode(currency.roundingMode);
|
|
36
|
+
setInitialized(true);
|
|
37
|
+
}
|
|
38
|
+
const createMutation = api.createCurrency.useMutation();
|
|
39
|
+
const updateMutation = api.updateCurrency.useMutation();
|
|
40
|
+
const handleSubmit = async (e) => {
|
|
41
|
+
e.preventDefault();
|
|
42
|
+
setError("");
|
|
43
|
+
setSaved(false);
|
|
44
|
+
if (!code.trim() || !name.trim() || !symbol.trim()) {
|
|
45
|
+
setError("Code, name, and symbol are required.");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
if (isEdit && params?.id) {
|
|
50
|
+
await updateMutation.mutateAsync({
|
|
51
|
+
params: { id: params.id },
|
|
52
|
+
body: {
|
|
53
|
+
name: name.trim(),
|
|
54
|
+
symbol: symbol.trim(),
|
|
55
|
+
decimalPlaces,
|
|
56
|
+
exchangeRate,
|
|
57
|
+
isActive,
|
|
58
|
+
symbolPosition,
|
|
59
|
+
roundingMode,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
setSaved(true);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
await createMutation.mutateAsync({
|
|
66
|
+
body: {
|
|
67
|
+
code: code.trim().toUpperCase(),
|
|
68
|
+
name: name.trim(),
|
|
69
|
+
symbol: symbol.trim(),
|
|
70
|
+
decimalPlaces,
|
|
71
|
+
exchangeRate,
|
|
72
|
+
isActive,
|
|
73
|
+
symbolPosition,
|
|
74
|
+
roundingMode,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
window.location.href = "/admin/currencies";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
setError(extractError(err));
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
if (isEdit && isLoading) {
|
|
85
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "h-8 w-48 animate-pulse rounded bg-muted/30" }), _jsx("div", { className: "h-64 animate-pulse rounded-lg border border-border bg-muted/30" })] }));
|
|
86
|
+
}
|
|
87
|
+
if (isEdit && !currency && !isLoading) {
|
|
88
|
+
return (_jsxs("div", { className: "rounded-lg border border-border bg-card p-8 text-center", children: [_jsx("p", { className: "text-muted-foreground text-sm", children: "Currency not found." }), _jsx("a", { href: "/admin/currencies", className: "mt-2 inline-block text-sm underline", children: "Back to currencies" })] }));
|
|
89
|
+
}
|
|
90
|
+
return (_jsxs("div", { children: [_jsxs("div", { className: "mb-6", children: [_jsx("a", { href: "/admin/currencies", className: "text-muted-foreground text-sm hover:underline", children: "\u2190 Back to currencies" }), _jsx("h1", { className: "mt-2 font-bold text-2xl text-foreground", children: isEdit ? "Edit Currency" : "Add Currency" })] }), error ? (_jsx("div", { className: "mb-4 rounded-lg border border-red-200 bg-red-50 p-3 text-red-800 text-sm dark:border-red-800 dark:bg-red-900/20 dark:text-red-400", children: error })) : null, saved ? (_jsx("div", { className: "mb-4 rounded-lg border border-green-200 bg-green-50 p-3 text-green-800 text-sm dark:border-green-800 dark:bg-green-900/20 dark:text-green-400", children: "Currency saved successfully." })) : null, _jsxs("form", { onSubmit: handleSubmit, className: "max-w-2xl space-y-4 rounded-lg border border-border bg-card p-5", children: [_jsxs("div", { className: "grid gap-4 sm:grid-cols-3", children: [_jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block font-medium text-sm", children: "Code (ISO 4217)" }), _jsx("input", { type: "text", value: code, onChange: (e) => setCode(e.target.value), placeholder: "USD", maxLength: 3, disabled: isEdit, className: "w-full rounded-md border border-border bg-background px-3 py-2 text-sm uppercase disabled:opacity-50" })] }), _jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block font-medium text-sm", children: "Name" }), _jsx("input", { type: "text", value: name, onChange: (e) => setName(e.target.value), placeholder: "US Dollar", className: "w-full rounded-md border border-border bg-background px-3 py-2 text-sm" })] }), _jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block font-medium text-sm", children: "Symbol" }), _jsx("input", { type: "text", value: symbol, onChange: (e) => setSymbol(e.target.value), placeholder: "$", className: "w-full rounded-md border border-border bg-background px-3 py-2 text-sm" })] })] }), _jsxs("div", { className: "grid gap-4 sm:grid-cols-3", children: [_jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block font-medium text-sm", children: "Decimal Places" }), _jsx("input", { type: "number", value: decimalPlaces, onChange: (e) => setDecimalPlaces(Number.parseInt(e.target.value, 10) || 0), min: 0, max: 8, className: "w-full rounded-md border border-border bg-background px-3 py-2 text-sm" })] }), _jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block font-medium text-sm", children: "Exchange Rate" }), _jsx("input", { type: "number", value: exchangeRate, onChange: (e) => setExchangeRate(Number.parseFloat(e.target.value) || 0), step: "0.000001", min: 0, className: "w-full rounded-md border border-border bg-background px-3 py-2 text-sm" })] }), _jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block font-medium text-sm", children: "Symbol Position" }), _jsxs("select", { value: symbolPosition, onChange: (e) => setSymbolPosition(e.target.value), className: "w-full rounded-md border border-border bg-background px-3 py-2 text-sm", children: [_jsx("option", { value: "before", children: "Before ($100)" }), _jsx("option", { value: "after", children: "After (100$)" })] })] })] }), _jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [_jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block font-medium text-sm", children: "Rounding Mode" }), _jsxs("select", { value: roundingMode, onChange: (e) => setRoundingMode(e.target.value), className: "w-full rounded-md border border-border bg-background px-3 py-2 text-sm", children: [_jsx("option", { value: "round", children: "Round" }), _jsx("option", { value: "ceil", children: "Ceil" }), _jsx("option", { value: "floor", children: "Floor" })] })] }), _jsxs("label", { className: "flex items-center gap-2 self-end pb-2", children: [_jsx("input", { type: "checkbox", checked: isActive, onChange: (e) => setIsActive(e.target.checked), className: "rounded border-border" }), _jsx("span", { className: "font-medium text-sm", children: "Active" })] })] }), _jsx("button", { type: "submit", disabled: createMutation.isPending || updateMutation.isPending, className: "rounded-lg bg-foreground px-4 py-2 font-medium text-background text-sm hover:opacity-90 disabled:opacity-50", children: createMutation.isPending || updateMutation.isPending
|
|
91
|
+
? "Saving..."
|
|
92
|
+
: isEdit
|
|
93
|
+
? "Save Changes"
|
|
94
|
+
: "Create Currency" })] })] }));
|
|
95
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"currency-list.d.ts","sourceRoot":"","sources":["../../../src/admin/components/currency-list.tsx"],"names":[],"mappings":"AAIA,wBAAgB,YAAY,gCA2L3B"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useCurrencyApi } from "./_shared";
|
|
4
|
+
export function CurrencyList() {
|
|
5
|
+
const api = useCurrencyApi();
|
|
6
|
+
const { data, isLoading } = api.listCurrencies.useQuery({});
|
|
7
|
+
const deleteMutation = api.deleteCurrency.useMutation();
|
|
8
|
+
const setBaseMutation = api.setBase.useMutation();
|
|
9
|
+
const currencies = data?.currencies ?? [];
|
|
10
|
+
const handleDelete = async (id) => {
|
|
11
|
+
if (!confirm("Delete this currency? This cannot be undone."))
|
|
12
|
+
return;
|
|
13
|
+
try {
|
|
14
|
+
await deleteMutation.mutateAsync({ params: { id } });
|
|
15
|
+
window.location.reload();
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// silently handled
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const handleSetBase = async (id) => {
|
|
22
|
+
if (!confirm("Set this currency as the base currency?"))
|
|
23
|
+
return;
|
|
24
|
+
try {
|
|
25
|
+
await setBaseMutation.mutateAsync({ params: { id } });
|
|
26
|
+
window.location.reload();
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// silently handled
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
return (_jsxs("div", { children: [_jsxs("div", { className: "mb-6 flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("h1", { className: "font-bold text-2xl text-foreground", children: "Currencies" }), _jsx("p", { className: "mt-1 text-muted-foreground text-sm", children: "Manage store currencies and exchange rates" })] }), _jsx("a", { href: "/admin/currencies/new", className: "rounded-lg bg-foreground px-4 py-2 font-medium text-background text-sm hover:opacity-90", children: "Add Currency" })] }), isLoading ? (_jsx("div", { className: "space-y-3", children: ["k0", "k1", "k2"].map((key) => (_jsx("div", { className: "h-16 animate-pulse rounded-lg border border-border bg-muted/30" }, key))) })) : currencies.length === 0 ? (_jsx("div", { className: "rounded-lg border border-border bg-card p-8 text-center", children: _jsx("p", { className: "text-muted-foreground text-sm", children: "No currencies configured. Add your base currency to get started." }) })) : (_jsx("div", { className: "overflow-x-auto rounded-md border border-border", children: _jsxs("table", { className: "w-full text-left text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-border border-b bg-muted", children: [_jsx("th", { scope: "col", className: "px-4 py-2 font-medium text-muted-foreground", children: "Currency" }), _jsx("th", { scope: "col", className: "px-4 py-2 font-medium text-muted-foreground", children: "Code" }), _jsx("th", { scope: "col", className: "px-4 py-2 font-medium text-muted-foreground", children: "Symbol" }), _jsx("th", { scope: "col", className: "px-4 py-2 font-medium text-muted-foreground", children: "Rate" }), _jsx("th", { scope: "col", className: "px-4 py-2 font-medium text-muted-foreground", children: "Status" }), _jsx("th", { scope: "col", className: "px-4 py-2 font-medium text-muted-foreground", children: "Actions" })] }) }), _jsx("tbody", { className: "divide-y divide-border", children: currencies.map((c) => (_jsxs("tr", { className: "transition-colors hover:bg-muted/50", children: [_jsx("td", { className: "px-4 py-2 text-foreground", children: _jsxs("div", { className: "flex items-center gap-2", children: [c.name, c.isBase ? (_jsx("span", { className: "inline-flex items-center rounded-full bg-indigo-100 px-2 py-0.5 font-medium text-indigo-800 text-xs dark:bg-indigo-900/30 dark:text-indigo-400", children: "Base" })) : null] }) }), _jsx("td", { className: "px-4 py-2 font-mono text-foreground text-xs", children: c.code }), _jsx("td", { className: "px-4 py-2 text-foreground", children: c.symbol }), _jsx("td", { className: "px-4 py-2 font-mono text-foreground text-xs", children: c.isBase ? "1.000000" : c.exchangeRate.toFixed(6) }), _jsx("td", { className: "px-4 py-2", children: _jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 font-medium text-xs ${c.isActive
|
|
33
|
+
? "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"
|
|
34
|
+
: "bg-muted text-muted-foreground"}`, children: c.isActive ? "Active" : "Inactive" }) }), _jsx("td", { className: "px-4 py-2", children: _jsxs("div", { className: "flex gap-1", children: [_jsx("a", { href: `/admin/currencies/${c.id}`, className: "rounded px-2 py-1 text-xs hover:bg-muted", children: "View" }), _jsx("a", { href: `/admin/currencies/${c.id}/edit`, className: "rounded px-2 py-1 text-xs hover:bg-muted", children: "Edit" }), !c.isBase ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", onClick: () => handleSetBase(c.id), className: "rounded px-2 py-1 text-xs hover:bg-muted", children: "Set Base" }), _jsx("button", { type: "button", onClick: () => handleDelete(c.id), className: "rounded px-2 py-1 text-red-600 text-xs hover:bg-red-50 dark:hover:bg-red-900/20", children: "Delete" })] })) : null] }) })] }, c.id))) })] }) }))] }));
|
|
35
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const adminBulkUpdateRates: import("better-call").StrictEndpoint<"/admin/currencies/bulk-update-rates", {
|
|
3
|
+
method: "POST";
|
|
4
|
+
body: z.ZodObject<{
|
|
5
|
+
rates: z.ZodArray<z.ZodObject<{
|
|
6
|
+
currencyCode: z.ZodString;
|
|
7
|
+
rate: z.ZodNumber;
|
|
8
|
+
source: z.ZodOptional<z.ZodString>;
|
|
9
|
+
}, z.core.$strip>>;
|
|
10
|
+
}, z.core.$strip>;
|
|
11
|
+
}, {
|
|
12
|
+
updated: number;
|
|
13
|
+
errors: string[];
|
|
14
|
+
}>;
|
|
15
|
+
//# sourceMappingURL=bulk-update-rates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bulk-update-rates.d.ts","sourceRoot":"","sources":["../../../src/admin/endpoints/bulk-update-rates.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,oBAAoB;;;;;;;;;;;;EAoBhC,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createAdminEndpoint } from "@86d-store/core/api";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export const adminBulkUpdateRates = createAdminEndpoint("/admin/currencies/bulk-update-rates", {
|
|
4
|
+
method: "POST",
|
|
5
|
+
body: z.object({
|
|
6
|
+
rates: z.array(z.object({
|
|
7
|
+
currencyCode: z.string().min(3).max(3),
|
|
8
|
+
rate: z.number().positive(),
|
|
9
|
+
source: z.string().max(100).optional(),
|
|
10
|
+
})),
|
|
11
|
+
}),
|
|
12
|
+
}, async (ctx) => {
|
|
13
|
+
const controller = ctx.context.controllers
|
|
14
|
+
.multiCurrency;
|
|
15
|
+
const result = await controller.bulkUpdateRates(ctx.body.rates);
|
|
16
|
+
return result;
|
|
17
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const adminCreateCurrency: import("better-call").StrictEndpoint<"/admin/currencies/create", {
|
|
3
|
+
method: "POST";
|
|
4
|
+
body: z.ZodObject<{
|
|
5
|
+
code: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
6
|
+
name: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
|
|
7
|
+
symbol: z.ZodString;
|
|
8
|
+
decimalPlaces: z.ZodOptional<z.ZodNumber>;
|
|
9
|
+
exchangeRate: z.ZodOptional<z.ZodNumber>;
|
|
10
|
+
isBase: z.ZodOptional<z.ZodBoolean>;
|
|
11
|
+
isActive: z.ZodOptional<z.ZodBoolean>;
|
|
12
|
+
symbolPosition: z.ZodOptional<z.ZodEnum<{
|
|
13
|
+
before: "before";
|
|
14
|
+
after: "after";
|
|
15
|
+
}>>;
|
|
16
|
+
thousandsSeparator: z.ZodOptional<z.ZodString>;
|
|
17
|
+
decimalSeparator: z.ZodOptional<z.ZodString>;
|
|
18
|
+
roundingMode: z.ZodOptional<z.ZodEnum<{
|
|
19
|
+
round: "round";
|
|
20
|
+
ceil: "ceil";
|
|
21
|
+
floor: "floor";
|
|
22
|
+
}>>;
|
|
23
|
+
sortOrder: z.ZodOptional<z.ZodNumber>;
|
|
24
|
+
}, z.core.$strip>;
|
|
25
|
+
}, {
|
|
26
|
+
error: string;
|
|
27
|
+
status: number;
|
|
28
|
+
currency?: never;
|
|
29
|
+
} | {
|
|
30
|
+
currency: import("../..").Currency;
|
|
31
|
+
error?: never;
|
|
32
|
+
status?: never;
|
|
33
|
+
}>;
|
|
34
|
+
//# sourceMappingURL=create-currency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-currency.d.ts","sourceRoot":"","sources":["../../../src/admin/endpoints/create-currency.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoC/B,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createAdminEndpoint } from "@86d-store/core/api";
|
|
2
|
+
import { sanitizeText } from "@86d-store/core/sanitize";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
export const adminCreateCurrency = createAdminEndpoint("/admin/currencies/create", {
|
|
5
|
+
method: "POST",
|
|
6
|
+
body: z.object({
|
|
7
|
+
code: z
|
|
8
|
+
.string()
|
|
9
|
+
.min(3)
|
|
10
|
+
.max(3)
|
|
11
|
+
.transform((s) => s.toUpperCase()),
|
|
12
|
+
name: z.string().min(1).max(100).transform(sanitizeText),
|
|
13
|
+
symbol: z.string().min(1).max(10),
|
|
14
|
+
decimalPlaces: z.number().int().min(0).max(8).optional(),
|
|
15
|
+
exchangeRate: z.number().positive().optional(),
|
|
16
|
+
isBase: z.boolean().optional(),
|
|
17
|
+
isActive: z.boolean().optional(),
|
|
18
|
+
symbolPosition: z.enum(["before", "after"]).optional(),
|
|
19
|
+
thousandsSeparator: z.string().max(5).optional(),
|
|
20
|
+
decimalSeparator: z.string().max(5).optional(),
|
|
21
|
+
roundingMode: z.enum(["round", "ceil", "floor"]).optional(),
|
|
22
|
+
sortOrder: z.number().int().optional(),
|
|
23
|
+
}),
|
|
24
|
+
}, async (ctx) => {
|
|
25
|
+
const controller = ctx.context.controllers
|
|
26
|
+
.multiCurrency;
|
|
27
|
+
// Check for duplicate code
|
|
28
|
+
const existing = await controller.getByCode(ctx.body.code);
|
|
29
|
+
if (existing) {
|
|
30
|
+
return { error: "Currency with this code already exists", status: 409 };
|
|
31
|
+
}
|
|
32
|
+
const currency = await controller.create(ctx.body);
|
|
33
|
+
return { currency };
|
|
34
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const adminDeleteCurrency: import("better-call").StrictEndpoint<"/admin/currencies/:id/delete", {
|
|
3
|
+
method: "POST";
|
|
4
|
+
params: z.ZodObject<{
|
|
5
|
+
id: z.ZodString;
|
|
6
|
+
}, z.core.$strip>;
|
|
7
|
+
}, {
|
|
8
|
+
error: string;
|
|
9
|
+
status: number;
|
|
10
|
+
success?: never;
|
|
11
|
+
} | {
|
|
12
|
+
success: boolean;
|
|
13
|
+
error?: never;
|
|
14
|
+
status?: never;
|
|
15
|
+
}>;
|
|
16
|
+
//# sourceMappingURL=delete-currency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete-currency.d.ts","sourceRoot":"","sources":["../../../src/admin/endpoints/delete-currency.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;EAkB/B,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createAdminEndpoint } from "@86d-store/core/api";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export const adminDeleteCurrency = createAdminEndpoint("/admin/currencies/:id/delete", {
|
|
4
|
+
method: "POST",
|
|
5
|
+
params: z.object({ id: z.string() }),
|
|
6
|
+
}, async (ctx) => {
|
|
7
|
+
const controller = ctx.context.controllers
|
|
8
|
+
.multiCurrency;
|
|
9
|
+
const result = await controller.delete(ctx.params.id);
|
|
10
|
+
if (!result.deleted) {
|
|
11
|
+
return {
|
|
12
|
+
error: result.error ?? "Failed to delete currency",
|
|
13
|
+
status: 400,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return { success: true };
|
|
17
|
+
});
|