@dev.smartpricing/platform-layer 0.0.3 → 0.0.5
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 +167 -159
- package/_shared/mfa.ts +30 -0
- package/app/components/LayerProductSwitcher.vue +25 -0
- package/app/composables/apiClient.composable.ts +8 -3
- package/app/composables/useProductSwitcher.composable.ts +70 -0
- package/app/mutations/useMfaSendEmailCode.mutation.ts +10 -0
- package/app/mutations/useMfaVerifyEmail.mutation.ts +10 -0
- package/nuxt.config.ts +12 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -38,24 +38,26 @@ pnpm add nuxt-ui-layer@"github:smartpricing/smartness-nuxt-ui#v1.6.3"
|
|
|
38
38
|
```ts
|
|
39
39
|
// nuxt.config.ts
|
|
40
40
|
export default defineNuxtConfig({
|
|
41
|
-
extends: [
|
|
42
|
-
})
|
|
41
|
+
extends: ["@dev.smartpricing/platform-layer"],
|
|
42
|
+
});
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
### 2. Configure Runtime Environment
|
|
46
46
|
|
|
47
47
|
Set these environment variables (or define them in `nuxt.config.ts` under `runtimeConfig.public`):
|
|
48
48
|
|
|
49
|
-
| Variable
|
|
50
|
-
|
|
51
|
-
| `NUXT_PUBLIC_BACKEND_BASE_URL` | Backend API base URL
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
49
|
+
| Variable | Description | Default |
|
|
50
|
+
| ------------------------------ | ------------------------------- | ---------- |
|
|
51
|
+
| `NUXT_PUBLIC_BACKEND_BASE_URL` | Backend API base URL | `''` |
|
|
52
|
+
| `NUXT_PUBLIC_CSRF_COOKIE_NAME` | CSRF cookie name | `smt_csrf` |
|
|
53
|
+
| `NUXT_PUBLIC_ENVIRONMENT` | Environment | `local` |
|
|
54
|
+
| `NUXT_PUBLIC_PLATFORM_URL` | Platform URL for auth redirects | `''` |
|
|
54
55
|
|
|
55
56
|
```bash
|
|
56
57
|
# .env
|
|
57
58
|
NUXT_PUBLIC_BACKEND_BASE_URL=https://api.smartpricing.com
|
|
58
|
-
|
|
59
|
+
NUXT_PUBLIC_CSRF_COOKIE_NAME=smt_csrf
|
|
60
|
+
ENVIRONMENT=local
|
|
59
61
|
NUXT_PUBLIC_PLATFORM_URL=https://app.smartpricing.com
|
|
60
62
|
```
|
|
61
63
|
|
|
@@ -63,13 +65,13 @@ NUXT_PUBLIC_PLATFORM_URL=https://app.smartpricing.com
|
|
|
63
65
|
|
|
64
66
|
The layer auto-registers these [Nuxt modules](https://nuxt.com/docs/guide/concepts/modules):
|
|
65
67
|
|
|
66
|
-
| Module
|
|
67
|
-
|
|
68
|
-
| [`@pinia/nuxt`](https://pinia.vuejs.org/ssr/nuxt.html)
|
|
69
|
-
| [`@pinia/colada-nuxt`](https://pinia-colada.esm.dev)
|
|
70
|
-
| [`@nuxtjs/i18n`](https://i18n.nuxtjs.org)
|
|
71
|
-
| [`@vueuse/nuxt`](https://vueuse.org/nuxt/README.html)
|
|
72
|
-
| [`nuxt-ui-layer`](https://github.com/smartpricing/smartness-nuxt-ui) | Smartness design system
|
|
68
|
+
| Module | Purpose | Docs |
|
|
69
|
+
| -------------------------------------------------------------------- | --------------------------------------- | ------------------------------------------------- |
|
|
70
|
+
| [`@pinia/nuxt`](https://pinia.vuejs.org/ssr/nuxt.html) | State management | [Pinia docs](https://pinia.vuejs.org) |
|
|
71
|
+
| [`@pinia/colada-nuxt`](https://pinia-colada.esm.dev) | Async data caching, queries & mutations | [Pinia Colada docs](https://pinia-colada.esm.dev) |
|
|
72
|
+
| [`@nuxtjs/i18n`](https://i18n.nuxtjs.org) | Internationalization (en, it, de, es) | [Nuxt i18n docs](https://i18n.nuxtjs.org) |
|
|
73
|
+
| [`@vueuse/nuxt`](https://vueuse.org/nuxt/README.html) | Utility composables | [VueUse docs](https://vueuse.org) |
|
|
74
|
+
| [`nuxt-ui-layer`](https://github.com/smartpricing/smartness-nuxt-ui) | Smartness design system | — |
|
|
73
75
|
|
|
74
76
|
All composables, queries, and mutations are **auto-imported** — no manual imports needed.
|
|
75
77
|
|
|
@@ -85,26 +87,34 @@ All composables, queries, and mutations are **auto-imported** — no manual impo
|
|
|
85
87
|
|
|
86
88
|
```ts
|
|
87
89
|
interface ApiClientOptions {
|
|
88
|
-
baseURL: string
|
|
89
|
-
csrf?: ApiClientCsrfConfig
|
|
90
|
-
auth?: ApiClientAuthConfig
|
|
91
|
-
errorSerializer?: (context: {
|
|
90
|
+
baseURL: string;
|
|
91
|
+
csrf?: ApiClientCsrfConfig;
|
|
92
|
+
auth?: ApiClientAuthConfig;
|
|
93
|
+
errorSerializer?: (context: {
|
|
94
|
+
status: number;
|
|
95
|
+
statusText: string;
|
|
96
|
+
data: unknown;
|
|
97
|
+
}) => unknown;
|
|
98
|
+
onRequest?: FetchHook<FetchContext>;
|
|
99
|
+
onResponseError?: FetchHook<FetchContext & { response: Response }>;
|
|
92
100
|
}
|
|
93
101
|
```
|
|
94
102
|
|
|
95
|
-
| Option
|
|
96
|
-
|
|
97
|
-
| `baseURL`
|
|
98
|
-
| `csrf`
|
|
99
|
-
| `auth`
|
|
100
|
-
| `errorSerializer` | `(context) => unknown`
|
|
103
|
+
| Option | Type | Description |
|
|
104
|
+
| ----------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
105
|
+
| `baseURL` | `string` | Base URL prepended to all requests. Slash handling is automatic ([ofetch docs](https://github.com/unjs/ofetch#baseurl)). |
|
|
106
|
+
| `csrf` | `ApiClientCsrfConfig` | CSRF token injection config. |
|
|
107
|
+
| `auth` | `ApiClientAuthConfig` | Auth config for token refresh and MFA. When omitted, returns a raw `$fetch` instance with no auth handling. |
|
|
108
|
+
| `errorSerializer` | `(context) => unknown` | Custom error serializer. Receives `{ status, statusText, data }` from [`FetchError`](https://github.com/unjs/ofetch#%EF%B8%8F-access-to-raw-response). |
|
|
109
|
+
| `onRequest` | `FetchHook<FetchContext>` | Custom [`onRequest` interceptor](https://github.com/unjs/ofetch#%EF%B8%8F-interceptors) appended after built-in hooks (cookie forwarding, CSRF). |
|
|
110
|
+
| `onResponseError` | `FetchHook<FetchContext & { response: Response }>` | Custom [`onResponseError` interceptor](https://github.com/unjs/ofetch#%EF%B8%8F-interceptors) called after auth/MFA handling. |
|
|
101
111
|
|
|
102
112
|
#### CSRF Config
|
|
103
113
|
|
|
104
114
|
```ts
|
|
105
115
|
interface ApiClientCsrfConfig {
|
|
106
|
-
cookieName: string
|
|
107
|
-
headerName?: string // default: 'X-CSRF-Token'
|
|
116
|
+
cookieName: string;
|
|
117
|
+
headerName?: string; // default: 'X-CSRF-Token'
|
|
108
118
|
}
|
|
109
119
|
```
|
|
110
120
|
|
|
@@ -114,17 +124,17 @@ The client reads the CSRF token from the specified cookie via Nuxt's [`useCookie
|
|
|
114
124
|
|
|
115
125
|
```ts
|
|
116
126
|
interface ApiClientAuthConfig {
|
|
117
|
-
refreshBaseURL: string
|
|
118
|
-
refreshEndpoint?: string
|
|
119
|
-
platformURL: string
|
|
120
|
-
shouldRefresh?: (status: number, data: unknown) => boolean
|
|
121
|
-
onRefreshFailed?: () => void
|
|
122
|
-
mfa?: ApiClientMfaConfig
|
|
127
|
+
refreshBaseURL: string;
|
|
128
|
+
refreshEndpoint?: string; // default: '/api/auth/refresh'
|
|
129
|
+
platformURL: string;
|
|
130
|
+
shouldRefresh?: (status: number, data: unknown) => boolean;
|
|
131
|
+
onRefreshFailed?: () => void;
|
|
132
|
+
mfa?: ApiClientMfaConfig;
|
|
123
133
|
}
|
|
124
134
|
|
|
125
135
|
interface ApiClientMfaConfig {
|
|
126
|
-
shouldChallenge: (status: number, data: unknown) => boolean
|
|
127
|
-
challengePath?: string
|
|
136
|
+
shouldChallenge: (status: number, data: unknown) => boolean;
|
|
137
|
+
challengePath?: string; // default: '/auth/mfa-challenge'
|
|
128
138
|
}
|
|
129
139
|
```
|
|
130
140
|
|
|
@@ -144,20 +154,20 @@ On the server, the client reads the incoming request cookies via Nuxt's [`useReq
|
|
|
144
154
|
|
|
145
155
|
```ts
|
|
146
156
|
const client = createApiClient({
|
|
147
|
-
baseURL:
|
|
148
|
-
csrf: { cookieName:
|
|
157
|
+
baseURL: "https://api.example.com",
|
|
158
|
+
csrf: { cookieName: "my_csrf" },
|
|
149
159
|
auth: {
|
|
150
|
-
refreshBaseURL:
|
|
151
|
-
platformURL:
|
|
152
|
-
onRefreshFailed: () => navigateTo(
|
|
160
|
+
refreshBaseURL: "https://api.example.com",
|
|
161
|
+
platformURL: "https://app.example.com",
|
|
162
|
+
onRefreshFailed: () => navigateTo("/login"),
|
|
153
163
|
},
|
|
154
164
|
errorSerializer: ({ status, data }) => ({
|
|
155
165
|
code: status,
|
|
156
|
-
message: (data as any)?.error?.message ??
|
|
166
|
+
message: (data as any)?.error?.message ?? "Unknown error",
|
|
157
167
|
}),
|
|
158
|
-
})
|
|
168
|
+
});
|
|
159
169
|
|
|
160
|
-
const users = await client<User[]>(
|
|
170
|
+
const users = await client<User[]>("/users", { query: { active: true } });
|
|
161
171
|
```
|
|
162
172
|
|
|
163
173
|
---
|
|
@@ -202,12 +212,12 @@ const filtered = await client<User[]>('/api/users', {
|
|
|
202
212
|
|
|
203
213
|
```vue
|
|
204
214
|
<script setup lang="ts">
|
|
205
|
-
const client = useApiClient()
|
|
215
|
+
const client = useApiClient();
|
|
206
216
|
|
|
207
217
|
const { data, status, error } = useQuery({
|
|
208
|
-
key: [
|
|
209
|
-
query: () => client<MyData>(
|
|
210
|
-
})
|
|
218
|
+
key: ["custom-data"],
|
|
219
|
+
query: () => client<MyData>("/api/custom-endpoint"),
|
|
220
|
+
});
|
|
211
221
|
</script>
|
|
212
222
|
```
|
|
213
223
|
|
|
@@ -215,21 +225,20 @@ const { data, status, error } = useQuery({
|
|
|
215
225
|
|
|
216
226
|
```vue
|
|
217
227
|
<script setup lang="ts">
|
|
218
|
-
const client = useApiClient()
|
|
228
|
+
const client = useApiClient();
|
|
219
229
|
|
|
220
230
|
const { mutateAsync, status } = useMutation({
|
|
221
231
|
mutation: (body: CreateItemRequest) =>
|
|
222
|
-
client<CreateItemResponse>(
|
|
223
|
-
method:
|
|
232
|
+
client<CreateItemResponse>("/api/items", {
|
|
233
|
+
method: "POST",
|
|
224
234
|
body,
|
|
225
235
|
}),
|
|
226
|
-
})
|
|
236
|
+
});
|
|
227
237
|
|
|
228
238
|
async function handleSubmit(data: CreateItemRequest) {
|
|
229
239
|
try {
|
|
230
|
-
const result = await mutateAsync(data)
|
|
231
|
-
}
|
|
232
|
-
catch (error) {
|
|
240
|
+
const result = await mutateAsync(data);
|
|
241
|
+
} catch (error) {
|
|
233
242
|
// FetchError with parsed body in error.data
|
|
234
243
|
}
|
|
235
244
|
}
|
|
@@ -246,17 +255,17 @@ async function handleSubmit(data: CreateItemRequest) {
|
|
|
246
255
|
|
|
247
256
|
#### Return Values
|
|
248
257
|
|
|
249
|
-
| Property
|
|
250
|
-
|
|
251
|
-
| `signOut`
|
|
252
|
-
| `logoutStatus` | `Ref<'idle' \| 'pending' \| 'success' \| 'error'>` | [Mutation status](https://pinia-colada.esm.dev/guide/mutations.html)
|
|
253
|
-
| `logoutError`
|
|
258
|
+
| Property | Type | Description |
|
|
259
|
+
| -------------- | -------------------------------------------------- | --------------------------------------------------------------------------- |
|
|
260
|
+
| `signOut` | `() => Promise<void>` | Calls `POST /api/auth/logout` then redirects to `{PLATFORM_URL}/auth/login` |
|
|
261
|
+
| `logoutStatus` | `Ref<'idle' \| 'pending' \| 'success' \| 'error'>` | [Mutation status](https://pinia-colada.esm.dev/guide/mutations.html) |
|
|
262
|
+
| `logoutError` | `Ref<Error \| null>` | Error if logout failed |
|
|
254
263
|
|
|
255
264
|
#### Usage
|
|
256
265
|
|
|
257
266
|
```vue
|
|
258
267
|
<script setup lang="ts">
|
|
259
|
-
const { signOut, logoutStatus } = useAuth()
|
|
268
|
+
const { signOut, logoutStatus } = useAuth();
|
|
260
269
|
</script>
|
|
261
270
|
|
|
262
271
|
<template>
|
|
@@ -276,42 +285,42 @@ Each query file also exports a **key factory** (e.g., `sessionKeys`, `billingOve
|
|
|
276
285
|
|
|
277
286
|
### Session & User
|
|
278
287
|
|
|
279
|
-
| Composable
|
|
280
|
-
|
|
281
|
-
| `useSessionQuery()`
|
|
282
|
-
| `usePermissionsQuery()`
|
|
288
|
+
| Composable | Endpoint | Response Type | Key Factory |
|
|
289
|
+
| ------------------------ | --------------------------- | ------------------------- | ------------------------ |
|
|
290
|
+
| `useSessionQuery()` | `GET /api/me` | `MeContextResponse` | `sessionKeys.me()` |
|
|
291
|
+
| `usePermissionsQuery()` | `GET /api/me/permissions` | `PermissionsResponse` | `permissionsKeys.all()` |
|
|
283
292
|
| `useCrossSellingQuery()` | `GET /api/me/cross-selling` | `GetCrossSellingResponse` | `crossSellingKeys.all()` |
|
|
284
293
|
|
|
285
294
|
### Organization
|
|
286
295
|
|
|
287
|
-
| Composable
|
|
288
|
-
|
|
296
|
+
| Composable | Endpoint | Response Type | Key Factory |
|
|
297
|
+
| ----------------------------- | ----------------------- | ----------------------------- | ------------------------------ |
|
|
289
298
|
| `useOrganizationQuery(orgId)` | `GET /api/orgs/{orgId}` | `OrganizationContextResponse` | `organizationKeys.byId(orgId)` |
|
|
290
299
|
|
|
291
300
|
**Params:** `orgId: MaybeRefOrGetter<string>` — enabled only when `orgId` is truthy.
|
|
292
301
|
|
|
293
302
|
### Billing
|
|
294
303
|
|
|
295
|
-
| Composable
|
|
296
|
-
|
|
297
|
-
| `useBillingOverviewQuery(orgId)`
|
|
298
|
-
| `useBillingDocumentsQuery(orgId, params?)`
|
|
299
|
-
| `useLegalEntityQuery(orgId, id)`
|
|
300
|
-
| `useBillingDocumentPdfQuery(orgId, type, id)` | `GET /api/orgs/{orgId}/billing/documents/{type}/{id}/pdf` | `BillingDocumentPdfResponse`
|
|
304
|
+
| Composable | Endpoint | Response Type | Key Factory |
|
|
305
|
+
| --------------------------------------------- | --------------------------------------------------------- | ------------------------------ | ---------------------------------------------- |
|
|
306
|
+
| `useBillingOverviewQuery(orgId)` | `GET /api/orgs/{orgId}/billing` | `BillingOverviewResponse` | `billingOverviewKeys.byOrg(orgId)` |
|
|
307
|
+
| `useBillingDocumentsQuery(orgId, params?)` | `GET /api/orgs/{orgId}/billing/documents` | `ListBillingDocumentsResponse` | `billingDocumentsKeys.byOrg(orgId, params)` |
|
|
308
|
+
| `useLegalEntityQuery(orgId, id)` | `GET /api/orgs/{orgId}/billing/legal-entities/{id}` | `LegalEntityDetail` | `legalEntityKeys.byId(orgId, id)` |
|
|
309
|
+
| `useBillingDocumentPdfQuery(orgId, type, id)` | `GET /api/orgs/{orgId}/billing/documents/{type}/{id}/pdf` | `BillingDocumentPdfResponse` | `billingDocumentPdfKeys.byId(orgId, type, id)` |
|
|
301
310
|
|
|
302
311
|
#### Billing Documents Filter Params
|
|
303
312
|
|
|
304
313
|
```ts
|
|
305
314
|
interface BillingDocumentsQueryParams {
|
|
306
|
-
legalEntityId?: string
|
|
307
|
-
customerId?: string
|
|
308
|
-
limit?: number
|
|
309
|
-
after?: string
|
|
310
|
-
before?: string
|
|
311
|
-
subscriptionIds?: string[]
|
|
312
|
-
type?: string
|
|
313
|
-
status?: string
|
|
314
|
-
search?: string
|
|
315
|
+
legalEntityId?: string;
|
|
316
|
+
customerId?: string;
|
|
317
|
+
limit?: number;
|
|
318
|
+
after?: string;
|
|
319
|
+
before?: string;
|
|
320
|
+
subscriptionIds?: string[];
|
|
321
|
+
type?: string;
|
|
322
|
+
status?: string;
|
|
323
|
+
search?: string;
|
|
315
324
|
}
|
|
316
325
|
```
|
|
317
326
|
|
|
@@ -319,20 +328,20 @@ interface BillingDocumentsQueryParams {
|
|
|
319
328
|
|
|
320
329
|
```vue
|
|
321
330
|
<script setup lang="ts">
|
|
322
|
-
const route = useRoute()
|
|
331
|
+
const route = useRoute();
|
|
323
332
|
|
|
324
333
|
// Static query — fetches immediately
|
|
325
|
-
const { data: session, status } = useSessionQuery()
|
|
334
|
+
const { data: session, status } = useSessionQuery();
|
|
326
335
|
|
|
327
336
|
// Dynamic query — re-fetches when orgId changes
|
|
328
|
-
const { data: org } = useOrganizationQuery(() => route.params.orgId as string)
|
|
337
|
+
const { data: org } = useOrganizationQuery(() => route.params.orgId as string);
|
|
329
338
|
|
|
330
339
|
// Conditional query with filter params
|
|
331
|
-
const filters = ref<BillingDocumentsQueryParams>({ limit: 20 })
|
|
340
|
+
const filters = ref<BillingDocumentsQueryParams>({ limit: 20 });
|
|
332
341
|
const { data: docs } = useBillingDocumentsQuery(
|
|
333
342
|
() => route.params.orgId as string,
|
|
334
343
|
filters,
|
|
335
|
-
)
|
|
344
|
+
);
|
|
336
345
|
</script>
|
|
337
346
|
|
|
338
347
|
<template>
|
|
@@ -348,16 +357,16 @@ Use the exported key factories with [`useQueryCache()`](https://pinia-colada.esm
|
|
|
348
357
|
|
|
349
358
|
```vue
|
|
350
359
|
<script setup lang="ts">
|
|
351
|
-
import { useQueryCache } from
|
|
360
|
+
import { useQueryCache } from "@pinia/colada";
|
|
352
361
|
|
|
353
|
-
const queryCache = useQueryCache()
|
|
362
|
+
const queryCache = useQueryCache();
|
|
354
363
|
|
|
355
|
-
const { mutateAsync: updateProfile } = useUpdateProfileMutation()
|
|
364
|
+
const { mutateAsync: updateProfile } = useUpdateProfileMutation();
|
|
356
365
|
|
|
357
366
|
async function handleSave(data: UpdateProfileRequest) {
|
|
358
|
-
await updateProfile(data)
|
|
367
|
+
await updateProfile(data);
|
|
359
368
|
// Invalidate session to refetch updated user data
|
|
360
|
-
queryCache.invalidateQueries({ key: sessionKeys.me() })
|
|
369
|
+
queryCache.invalidateQueries({ key: sessionKeys.me() });
|
|
361
370
|
}
|
|
362
371
|
</script>
|
|
363
372
|
```
|
|
@@ -368,62 +377,62 @@ async function handleSave(data: UpdateProfileRequest) {
|
|
|
368
377
|
|
|
369
378
|
All mutations use [`useMutation()`](https://pinia-colada.esm.dev/guide/mutations.html) from Pinia Colada. Each returns:
|
|
370
379
|
|
|
371
|
-
| Property
|
|
372
|
-
|
|
373
|
-
| `mutate`
|
|
374
|
-
| `mutateAsync` | `(params) => Promise<TResult>`
|
|
375
|
-
| `status`
|
|
376
|
-
| `asyncStatus` | `Ref<'idle' \| 'loading'>`
|
|
377
|
-
| `data`
|
|
378
|
-
| `error`
|
|
379
|
-
| `variables`
|
|
380
|
-
| `reset`
|
|
380
|
+
| Property | Type | Description |
|
|
381
|
+
| ------------- | -------------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
382
|
+
| `mutate` | `(params) => void` | Fire-and-forget — errors handled via `onError` hook |
|
|
383
|
+
| `mutateAsync` | `(params) => Promise<TResult>` | Returns promise — use `try/catch` for errors |
|
|
384
|
+
| `status` | `Ref<'idle' \| 'pending' \| 'success' \| 'error'>` | Overall [mutation status](https://pinia-colada.esm.dev/guide/mutations.html) |
|
|
385
|
+
| `asyncStatus` | `Ref<'idle' \| 'loading'>` | Whether a request is in-flight |
|
|
386
|
+
| `data` | `Ref<TResult \| undefined>` | Last successful response |
|
|
387
|
+
| `error` | `Ref<Error \| null>` | Last error |
|
|
388
|
+
| `variables` | `Ref<TParams \| undefined>` | Last mutation parameters |
|
|
389
|
+
| `reset` | `() => void` | Reset to initial state |
|
|
381
390
|
|
|
382
391
|
### Authentication Mutations
|
|
383
392
|
|
|
384
|
-
| Composable
|
|
385
|
-
|
|
386
|
-
| `useLoginMutation()`
|
|
387
|
-
| `useLogoutMutation()`
|
|
388
|
-
| `useRefreshMutation()`
|
|
389
|
-
| `useActivateMutation()`
|
|
390
|
-
| `useAccountingLoginMutation()` | `POST` | `/api/auth/accounting-login` | `AccountingLoginRequest` | `void`
|
|
393
|
+
| Composable | Method | Endpoint | Request | Response |
|
|
394
|
+
| ------------------------------ | ------ | ---------------------------- | ------------------------ | -------- |
|
|
395
|
+
| `useLoginMutation()` | `POST` | `/api/auth/login` | `LoginRequest` | `void` |
|
|
396
|
+
| `useLogoutMutation()` | `POST` | `/api/auth/logout` | — | `void` |
|
|
397
|
+
| `useRefreshMutation()` | `POST` | `/api/auth/refresh` | — | `void` |
|
|
398
|
+
| `useActivateMutation()` | `POST` | `/api/auth/activate` | `ActivateRequest` | `void` |
|
|
399
|
+
| `useAccountingLoginMutation()` | `POST` | `/api/auth/accounting-login` | `AccountingLoginRequest` | `void` |
|
|
391
400
|
|
|
392
401
|
### MFA Mutations
|
|
393
402
|
|
|
394
|
-
| Composable
|
|
395
|
-
|
|
396
|
-
| `useMfaSetupMutation()`
|
|
397
|
-
| `useMfaStepUpMutation()`
|
|
398
|
-
| `useMfaFinalizeMutation()`
|
|
399
|
-
| `useMfaDisableMutation()`
|
|
403
|
+
| Composable | Method | Endpoint | Request | Response |
|
|
404
|
+
| ----------------------------------------- | ------ | ----------------------------------------- | ----------------------------------- | ------------------------------------ |
|
|
405
|
+
| `useMfaSetupMutation()` | `POST` | `/api/auth/mfa/setup` | — | `MfaSetupResponse` |
|
|
406
|
+
| `useMfaStepUpMutation()` | `POST` | `/api/auth/otp` | `MfaStepUpRequest` | `MfaStepUpResponse` |
|
|
407
|
+
| `useMfaFinalizeMutation()` | `POST` | `/api/auth/mfa/finalize` | `MfaFinalizeRequest` | `MfaFinalizeResponse` |
|
|
408
|
+
| `useMfaDisableMutation()` | `POST` | `/api/auth/mfa/disable` | `MfaDisableRequest` | `MfaDisableResponse` |
|
|
400
409
|
| `useMfaRegenerateRecoveryCodesMutation()` | `POST` | `/api/auth/mfa/regenerate-recovery-codes` | `MfaRegenerateRecoveryCodesRequest` | `MfaRegenerateRecoveryCodesResponse` |
|
|
401
410
|
|
|
402
411
|
### Profile Mutations
|
|
403
412
|
|
|
404
|
-
| Composable
|
|
405
|
-
|
|
406
|
-
| `useUpdateProfileMutation()`
|
|
407
|
-
| `useChangePasswordMutation()`
|
|
408
|
-
| `useRequestPasswordResetMutation()` | `POST`
|
|
409
|
-
| `useResetPasswordMutation()`
|
|
413
|
+
| Composable | Method | Endpoint | Request | Response |
|
|
414
|
+
| ----------------------------------- | ------- | ---------------------------------- | ----------------------- | ----------------------- |
|
|
415
|
+
| `useUpdateProfileMutation()` | `PATCH` | `/api/me` | `UpdateProfileRequest` | `UpdateProfileResponse` |
|
|
416
|
+
| `useChangePasswordMutation()` | `POST` | `/api/me/change-password` | `ChangePasswordRequest` | `{ ok: true }` |
|
|
417
|
+
| `useRequestPasswordResetMutation()` | `POST` | `/api/auth/request-password-reset` | `ForgotPasswordRequest` | `void` |
|
|
418
|
+
| `useResetPasswordMutation()` | `POST` | `/api/auth/reset-password` | `ResetPasswordRequest` | `void` |
|
|
410
419
|
|
|
411
420
|
### Billing Mutations
|
|
412
421
|
|
|
413
|
-
| Composable
|
|
414
|
-
|
|
415
|
-
| `useCreatePaymentMethodMutation()`
|
|
416
|
-
| `useSetPrimaryPaymentMethodMutation()` | `PATCH`
|
|
417
|
-
| `useRemovePaymentMethodMutation()`
|
|
418
|
-
| `useCreateSetupIntentMutation()`
|
|
422
|
+
| Composable | Method | Endpoint | Request | Response |
|
|
423
|
+
| -------------------------------------- | -------- | ------------------------------------------------------------- | ------------------------------------------------- | --------------------------------- |
|
|
424
|
+
| `useCreatePaymentMethodMutation()` | `POST` | `/api/orgs/{orgId}/billing/payment-methods` | `{ orgId, body: CreatePaymentMethodRequest }` | `CreatePaymentMethodsResponse` |
|
|
425
|
+
| `useSetPrimaryPaymentMethodMutation()` | `PATCH` | `/api/orgs/{orgId}/billing/payment-methods/primary` | `{ orgId, body: SetPrimaryPaymentMethodRequest }` | `SetPrimaryPaymentMethodResponse` |
|
|
426
|
+
| `useRemovePaymentMethodMutation()` | `DELETE` | `/api/orgs/{orgId}/billing/payment-methods/{paymentMethodId}` | `{ orgId, paymentMethodId }` | `void` |
|
|
427
|
+
| `useCreateSetupIntentMutation()` | `POST` | `/api/orgs/{orgId}/billing/payment-methods/setup-intent` | `{ orgId, body: CreateSetupIntentRequest }` | `CreateSetupIntentResponse` |
|
|
419
428
|
|
|
420
429
|
### Organization Mutations
|
|
421
430
|
|
|
422
|
-
| Composable
|
|
423
|
-
|
|
424
|
-
| `useUpdateLegalEntityMutation()`
|
|
425
|
-
| `useRequestLegalEntityChangeMutation()`
|
|
426
|
-
| `useRequestSubscriptionCancellationMutation()` | `POST`
|
|
431
|
+
| Composable | Method | Endpoint | Request | Response |
|
|
432
|
+
| ---------------------------------------------- | ------- | -------------------------------------------------------------- | ------------------------------------------------------ | --------------------------------------- |
|
|
433
|
+
| `useUpdateLegalEntityMutation()` | `PATCH` | `/api/orgs/{orgId}/billing/legal-entities/{id}` | `{ orgId, id, body: UpdateLegalEntityRequest }` | `LegalEntityDetail` |
|
|
434
|
+
| `useRequestLegalEntityChangeMutation()` | `POST` | `/api/orgs/{orgId}/billing/legal-entities/{id}/change-request` | `{ orgId, id, body: RequestLegalEntityChangeRequest }` | `{ emails_status: 'sent' \| 'failed' }` |
|
|
435
|
+
| `useRequestSubscriptionCancellationMutation()` | `POST` | `/api/orgs/{orgId}/subscriptions/request-cancellation` | `{ orgId, body: SubscriptionCancellationRequest }` | `SubscriptionCancellationResponse` |
|
|
427
436
|
|
|
428
437
|
### Mutation Usage Examples
|
|
429
438
|
|
|
@@ -431,14 +440,13 @@ All mutations use [`useMutation()`](https://pinia-colada.esm.dev/guide/mutations
|
|
|
431
440
|
|
|
432
441
|
```vue
|
|
433
442
|
<script setup lang="ts">
|
|
434
|
-
const { mutateAsync: login, status, error } = useLoginMutation()
|
|
443
|
+
const { mutateAsync: login, status, error } = useLoginMutation();
|
|
435
444
|
|
|
436
445
|
async function handleLogin(email: string, password: string) {
|
|
437
446
|
try {
|
|
438
|
-
await login({ email, password })
|
|
439
|
-
await navigateTo(
|
|
440
|
-
}
|
|
441
|
-
catch (err) {
|
|
447
|
+
await login({ email, password });
|
|
448
|
+
await navigateTo("/dashboard");
|
|
449
|
+
} catch (err) {
|
|
442
450
|
// error.value is also set reactively
|
|
443
451
|
}
|
|
444
452
|
}
|
|
@@ -448,7 +456,7 @@ async function handleLogin(email: string, password: string) {
|
|
|
448
456
|
<form @submit.prevent="handleLogin(email, password)">
|
|
449
457
|
<p v-if="error">{{ error.message }}</p>
|
|
450
458
|
<button :disabled="status === 'pending'" type="submit">
|
|
451
|
-
{{ status ===
|
|
459
|
+
{{ status === "pending" ? "Signing in..." : "Sign In" }}
|
|
452
460
|
</button>
|
|
453
461
|
</form>
|
|
454
462
|
</template>
|
|
@@ -458,14 +466,14 @@ async function handleLogin(email: string, password: string) {
|
|
|
458
466
|
|
|
459
467
|
```vue
|
|
460
468
|
<script setup lang="ts">
|
|
461
|
-
const { mutateAsync: setupMfa } = useMfaSetupMutation()
|
|
462
|
-
const { mutateAsync: finalizeMfa } = useMfaFinalizeMutation()
|
|
469
|
+
const { mutateAsync: setupMfa } = useMfaSetupMutation();
|
|
470
|
+
const { mutateAsync: finalizeMfa } = useMfaFinalizeMutation();
|
|
463
471
|
|
|
464
472
|
// Step 1: Get QR code / secret
|
|
465
|
-
const setupData = await setupMfa()
|
|
473
|
+
const setupData = await setupMfa();
|
|
466
474
|
|
|
467
475
|
// Step 2: User enters TOTP code, finalize
|
|
468
|
-
await finalizeMfa({ otp: userCode })
|
|
476
|
+
await finalizeMfa({ otp: userCode });
|
|
469
477
|
</script>
|
|
470
478
|
```
|
|
471
479
|
|
|
@@ -473,21 +481,22 @@ await finalizeMfa({ otp: userCode })
|
|
|
473
481
|
|
|
474
482
|
```vue
|
|
475
483
|
<script setup lang="ts">
|
|
476
|
-
import { useQueryCache } from
|
|
484
|
+
import { useQueryCache } from "@pinia/colada";
|
|
477
485
|
|
|
478
|
-
const queryCache = useQueryCache()
|
|
479
|
-
const orgId = computed(() => route.params.orgId as string)
|
|
486
|
+
const queryCache = useQueryCache();
|
|
487
|
+
const orgId = computed(() => route.params.orgId as string);
|
|
480
488
|
|
|
481
|
-
const { mutateAsync: removeMethod, status } = useRemovePaymentMethodMutation()
|
|
489
|
+
const { mutateAsync: removeMethod, status } = useRemovePaymentMethodMutation();
|
|
482
490
|
|
|
483
491
|
async function handleRemove(paymentMethodId: string) {
|
|
484
492
|
try {
|
|
485
|
-
await removeMethod({ orgId: orgId.value, paymentMethodId })
|
|
493
|
+
await removeMethod({ orgId: orgId.value, paymentMethodId });
|
|
486
494
|
|
|
487
495
|
// Refetch billing data after removing payment method
|
|
488
|
-
queryCache.invalidateQueries({
|
|
489
|
-
|
|
490
|
-
|
|
496
|
+
queryCache.invalidateQueries({
|
|
497
|
+
key: billingOverviewKeys.byOrg(orgId.value),
|
|
498
|
+
});
|
|
499
|
+
} catch (err) {
|
|
491
500
|
// Handle error
|
|
492
501
|
}
|
|
493
502
|
}
|
|
@@ -498,17 +507,16 @@ async function handleRemove(paymentMethodId: string) {
|
|
|
498
507
|
|
|
499
508
|
```vue
|
|
500
509
|
<script setup lang="ts">
|
|
501
|
-
const { mutate, mutateAsync, status, error } = useUpdateProfileMutation()
|
|
510
|
+
const { mutate, mutateAsync, status, error } = useUpdateProfileMutation();
|
|
502
511
|
|
|
503
512
|
// Fire-and-forget — errors are swallowed, check error ref reactively
|
|
504
|
-
mutate({ name:
|
|
513
|
+
mutate({ name: "New Name" });
|
|
505
514
|
|
|
506
515
|
// Async — errors rethrown, use try/catch
|
|
507
516
|
try {
|
|
508
|
-
const result = await mutateAsync({ name:
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
console.error('Update failed:', err)
|
|
517
|
+
const result = await mutateAsync({ name: "New Name" });
|
|
518
|
+
} catch (err) {
|
|
519
|
+
console.error("Update failed:", err);
|
|
512
520
|
}
|
|
513
521
|
</script>
|
|
514
522
|
```
|
|
@@ -526,10 +534,10 @@ If your app already provides a module the layer includes, [disable it](https://n
|
|
|
526
534
|
```ts
|
|
527
535
|
// nuxt.config.ts
|
|
528
536
|
export default defineNuxtConfig({
|
|
529
|
-
extends: [
|
|
530
|
-
i18n: false,
|
|
531
|
-
pinia: false,
|
|
532
|
-
})
|
|
537
|
+
extends: ["@dev.smartpricing/platform-layer"],
|
|
538
|
+
i18n: false, // disable layer's i18n
|
|
539
|
+
pinia: false, // disable layer's pinia
|
|
540
|
+
});
|
|
533
541
|
```
|
|
534
542
|
|
|
535
543
|
### Override i18n Locales
|
package/_shared/mfa.ts
CHANGED
|
@@ -58,6 +58,30 @@ export const MfaRegenerateRecoveryCodesResponseSchema = z.object({
|
|
|
58
58
|
recovery_codes: z.array(z.string()),
|
|
59
59
|
})
|
|
60
60
|
|
|
61
|
+
// ── Send email code ────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
export const MfaSendEmailCodeRequestSchema = z.object({
|
|
64
|
+
enrollmentId: z.string(),
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
export const MfaSendEmailCodeResponseSchema = z.object({
|
|
68
|
+
sent: z.literal(true),
|
|
69
|
+
email_masked: z.string(),
|
|
70
|
+
expires_in_seconds: z.number(),
|
|
71
|
+
retry_after_seconds: z.number(),
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// ── Verify email ───────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
export const MfaVerifyEmailRequestSchema = z.object({
|
|
77
|
+
enrollmentId: z.string(),
|
|
78
|
+
emailCode: z.string(),
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
export const MfaVerifyEmailResponseSchema = z.object({
|
|
82
|
+
verified: z.literal(true),
|
|
83
|
+
})
|
|
84
|
+
|
|
61
85
|
// ── Types ───────────────────────────────────────────────────────────
|
|
62
86
|
|
|
63
87
|
export type MfaStepUpRequest = z.infer<typeof MfaStepUpRequestSchema>
|
|
@@ -73,3 +97,9 @@ export type MfaDisableResponse = z.infer<typeof MfaDisableResponseSchema>
|
|
|
73
97
|
|
|
74
98
|
export type MfaRegenerateRecoveryCodesRequest = z.infer<typeof MfaRegenerateRecoveryCodesRequestSchema>
|
|
75
99
|
export type MfaRegenerateRecoveryCodesResponse = z.infer<typeof MfaRegenerateRecoveryCodesResponseSchema>
|
|
100
|
+
|
|
101
|
+
export type MfaSendEmailCodeRequest = z.infer<typeof MfaSendEmailCodeRequestSchema>
|
|
102
|
+
export type MfaSendEmailCodeResponse = z.infer<typeof MfaSendEmailCodeResponseSchema>
|
|
103
|
+
|
|
104
|
+
export type MfaVerifyEmailRequest = z.infer<typeof MfaVerifyEmailRequestSchema>
|
|
105
|
+
export type MfaVerifyEmailResponse = z.infer<typeof MfaVerifyEmailResponseSchema>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { SuiteProduct } from 'nuxt-ui-layer/types'
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{
|
|
5
|
+
product: SuiteProduct
|
|
6
|
+
collapsed?: boolean
|
|
7
|
+
}>()
|
|
8
|
+
|
|
9
|
+
const { enabledProducts, navigateToProduct } = useProductSwitcher()
|
|
10
|
+
|
|
11
|
+
const selectedProduct = shallowRef<SuiteProduct>(props.product)
|
|
12
|
+
|
|
13
|
+
watch(selectedProduct, (value) => {
|
|
14
|
+
if (value !== props.product)
|
|
15
|
+
navigateToProduct(value)
|
|
16
|
+
})
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<SNavigationProducts
|
|
21
|
+
v-model="selectedProduct"
|
|
22
|
+
:products="enabledProducts"
|
|
23
|
+
:collapsed="collapsed"
|
|
24
|
+
/>
|
|
25
|
+
</template>
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
export function useApiClient() {
|
|
2
|
-
const { BACKEND_BASE_URL,
|
|
2
|
+
const { BACKEND_BASE_URL, CSRF_COOKIE_NAME, PLATFORM_URL, ENVIRONMENT } = useRuntimeConfig().public
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
const baseCsrfName = (CSRF_COOKIE_NAME as string | undefined) || 'smt_csrf'
|
|
6
|
+
const environment = (ENVIRONMENT as string | undefined) || 'local'
|
|
7
|
+
const csrfCookieName = environment === 'prod' ? baseCsrfName : `${baseCsrfName}_${environment}`
|
|
3
8
|
|
|
4
9
|
return createApiClient({
|
|
5
10
|
baseURL: BACKEND_BASE_URL as string,
|
|
6
11
|
csrf: {
|
|
7
|
-
cookieName:
|
|
12
|
+
cookieName: csrfCookieName
|
|
8
13
|
},
|
|
9
14
|
auth: {
|
|
10
15
|
refreshBaseURL: BACKEND_BASE_URL as string,
|
|
@@ -16,7 +21,7 @@ export function useApiClient() {
|
|
|
16
21
|
},
|
|
17
22
|
},
|
|
18
23
|
onRefreshFailed: () => {
|
|
19
|
-
if (import.meta.client) {
|
|
24
|
+
if (import.meta.client && !window.location.pathname.startsWith('/auth/login')) {
|
|
20
25
|
window.location.assign(`${PLATFORM_URL}/auth/login`)
|
|
21
26
|
}
|
|
22
27
|
},
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { SuiteProduct } from 'nuxt-ui-layer/types'
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
export function useProductSwitcher() {
|
|
5
|
+
const { data: permissions, status } = usePermissionsQuery()
|
|
6
|
+
const config = useRuntimeConfig().public
|
|
7
|
+
|
|
8
|
+
const productUrls: Record<string, string> = {
|
|
9
|
+
config: config.PLATFORM_APP_URL,
|
|
10
|
+
pricing: config.PRICING_APP_URL,
|
|
11
|
+
connect: config.CONNECT_APP_URL,
|
|
12
|
+
chat: config.CHAT_APP_URL,
|
|
13
|
+
pms: config.PMS_APP_URL,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** All enabled product keys (from /permission + always-on "config"). */
|
|
17
|
+
const enabledProducts = computed<SuiteProduct[]>(() => {
|
|
18
|
+
// "config" is a new SuiteProduct value added in smartness-nuxt-ui — cast
|
|
19
|
+
// needed until the updated nuxt-ui-layer package is published.
|
|
20
|
+
const enabled: SuiteProduct[] = ['config' as SuiteProduct]
|
|
21
|
+
|
|
22
|
+
if (!permissions.value)
|
|
23
|
+
return enabled
|
|
24
|
+
|
|
25
|
+
const products = permissions.value.products
|
|
26
|
+
const mapping: Record<string, SuiteProduct> = {
|
|
27
|
+
pricing: 'pricing',
|
|
28
|
+
connect: 'connect',
|
|
29
|
+
chat: 'chat',
|
|
30
|
+
smartpms: 'pms',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for (const [key, product] of Object.entries(products)) {
|
|
34
|
+
if (product.enabled) {
|
|
35
|
+
const mapped = mapping[key]
|
|
36
|
+
if (mapped)
|
|
37
|
+
enabled.push(mapped)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return enabled
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const isLoading = computed(() => status.value === 'pending')
|
|
45
|
+
|
|
46
|
+
function navigateToProduct(product?: SuiteProduct) {
|
|
47
|
+
if (!product) return
|
|
48
|
+
|
|
49
|
+
const isEnabled = enabledProducts.value.includes(product)
|
|
50
|
+
if (!isEnabled) {
|
|
51
|
+
window.open(`${window.location.origin}/upgrade`, '_blank', 'noopener,noreferrer')
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const url = productUrls[product]
|
|
56
|
+
if (url)
|
|
57
|
+
location.assign(url)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getProductUrl(product: SuiteProduct): string | undefined {
|
|
61
|
+
return productUrls[product] || undefined
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
enabledProducts,
|
|
66
|
+
isLoading,
|
|
67
|
+
navigateToProduct,
|
|
68
|
+
getProductUrl,
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MfaSendEmailCodeRequest, MfaSendEmailCodeResponse } from '@package/platform-shared'
|
|
2
|
+
|
|
3
|
+
export function useMfaSendEmailCodeMutation() {
|
|
4
|
+
const client = useApiClient()
|
|
5
|
+
|
|
6
|
+
return useMutation({
|
|
7
|
+
mutation: (body: MfaSendEmailCodeRequest) =>
|
|
8
|
+
client<MfaSendEmailCodeResponse>('/api/auth/mfa/send-email-code', { method: 'POST', body }),
|
|
9
|
+
})
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MfaVerifyEmailRequest, MfaVerifyEmailResponse } from '@package/platform-shared'
|
|
2
|
+
|
|
3
|
+
export function useMfaVerifyEmailMutation() {
|
|
4
|
+
const client = useApiClient()
|
|
5
|
+
|
|
6
|
+
return useMutation({
|
|
7
|
+
mutation: (body: MfaVerifyEmailRequest) =>
|
|
8
|
+
client<MfaVerifyEmailResponse>('/api/auth/mfa/verify-email', { method: 'POST', body }),
|
|
9
|
+
})
|
|
10
|
+
}
|
package/nuxt.config.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { dirname, join } from 'node:path'
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
2
3
|
import { fileURLToPath } from 'node:url'
|
|
3
4
|
|
|
4
5
|
const currentDir = dirname(fileURLToPath(import.meta.url))
|
|
6
|
+
const sharedDir = join(currentDir, '_shared')
|
|
5
7
|
|
|
6
8
|
export default defineNuxtConfig({
|
|
7
9
|
extends: ['nuxt-ui-layer'],
|
|
8
10
|
|
|
9
11
|
alias: {
|
|
10
|
-
|
|
12
|
+
// _shared exists only in published tarball (created by prepack script)
|
|
13
|
+
...(existsSync(sharedDir) && { '@package/platform-shared': sharedDir }),
|
|
11
14
|
},
|
|
12
15
|
|
|
13
16
|
modules: [
|
|
@@ -43,8 +46,15 @@ export default defineNuxtConfig({
|
|
|
43
46
|
runtimeConfig: {
|
|
44
47
|
public: {
|
|
45
48
|
BACKEND_BASE_URL: '',
|
|
46
|
-
|
|
49
|
+
CSRF_COOKIE_NAME: '',
|
|
47
50
|
PLATFORM_URL: '',
|
|
51
|
+
ENVIRONMENT: '',
|
|
52
|
+
// Standard product URL convention: {PRODUCT}_APP_URL
|
|
53
|
+
PLATFORM_APP_URL: '',
|
|
54
|
+
PRICING_APP_URL: '',
|
|
55
|
+
CONNECT_APP_URL: '',
|
|
56
|
+
CHAT_APP_URL: '',
|
|
57
|
+
PMS_APP_URL: '',
|
|
48
58
|
},
|
|
49
59
|
},
|
|
50
60
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dev.smartpricing/platform-layer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"main": "./nuxt.config.ts",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"nuxt": "^4.4.2",
|
|
22
|
-
"nuxt-ui-layer": "github:smartpricing/smartness-nuxt-ui#v1.6.
|
|
22
|
+
"nuxt-ui-layer": "github:smartpricing/smartness-nuxt-ui#v1.6.19-platform.0",
|
|
23
23
|
"vue": "latest"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
@@ -29,6 +29,6 @@
|
|
|
29
29
|
"dev": "nuxi dev .playground",
|
|
30
30
|
"dev:prepare": "nuxt prepare .playground",
|
|
31
31
|
"build": "nuxt build .playground",
|
|
32
|
-
"postinstall": "nuxt prepare"
|
|
32
|
+
"postinstall": "nuxt prepare .playground"
|
|
33
33
|
}
|
|
34
34
|
}
|