@dev.smartpricing/platform-layer 0.0.2

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.
Files changed (58) hide show
  1. package/_shared/billingCreditNotes.ts +24 -0
  2. package/_shared/billingDocumentSubscriptions.ts +36 -0
  3. package/_shared/billingDocuments.ts +32 -0
  4. package/_shared/billingInvoices.ts +24 -0
  5. package/_shared/billingLegalEntities.ts +109 -0
  6. package/_shared/billingOverview.ts +15 -0
  7. package/_shared/billingPaymentMethods.ts +89 -0
  8. package/_shared/billingSubscriptions.ts +54 -0
  9. package/_shared/common.ts +34 -0
  10. package/_shared/crossSelling.ts +21 -0
  11. package/_shared/index.ts +22 -0
  12. package/_shared/meContext.ts +15 -0
  13. package/_shared/members.ts +23 -0
  14. package/_shared/mfa.ts +75 -0
  15. package/_shared/organizationContext.ts +18 -0
  16. package/_shared/organizations.ts +46 -0
  17. package/_shared/password.ts +22 -0
  18. package/_shared/permissions.ts +16 -0
  19. package/_shared/products.ts +49 -0
  20. package/_shared/properties.ts +16 -0
  21. package/_shared/subscriptionCancellation.ts +17 -0
  22. package/_shared/unitTypes.ts +16 -0
  23. package/_shared/user.ts +77 -0
  24. package/app/components/.gitkeep +0 -0
  25. package/app/composables/apiClient.composable.ts +25 -0
  26. package/app/composables/useAuth.composable.ts +17 -0
  27. package/app/mutations/useAccountingLogin.mutation.ts +10 -0
  28. package/app/mutations/useActivate.mutation.ts +10 -0
  29. package/app/mutations/useChangePassword.mutation.ts +10 -0
  30. package/app/mutations/useCreatePaymentMethod.mutation.ts +13 -0
  31. package/app/mutations/useCreateSetupIntent.mutation.ts +13 -0
  32. package/app/mutations/useLogin.mutation.ts +10 -0
  33. package/app/mutations/useLogout.mutation.ts +7 -0
  34. package/app/mutations/useMfaDisable.mutation.ts +10 -0
  35. package/app/mutations/useMfaFinalize.mutation.ts +10 -0
  36. package/app/mutations/useMfaRegenerateRecoveryCodes.mutation.ts +10 -0
  37. package/app/mutations/useMfaSetup.mutation.ts +10 -0
  38. package/app/mutations/useMfaStepUp.mutation.ts +10 -0
  39. package/app/mutations/useRefresh.mutation.ts +7 -0
  40. package/app/mutations/useRemovePaymentMethod.mutation.ts +11 -0
  41. package/app/mutations/useRequestLegalEntityChange.mutation.ts +13 -0
  42. package/app/mutations/useRequestPasswordReset.mutation.ts +10 -0
  43. package/app/mutations/useRequestSubscriptionCancellation.mutation.ts +13 -0
  44. package/app/mutations/useResetPassword.mutation.ts +10 -0
  45. package/app/mutations/useSetPrimaryPaymentMethod.mutation.ts +13 -0
  46. package/app/mutations/useUpdateLegalEntity.mutation.ts +13 -0
  47. package/app/mutations/useUpdateProfile.mutation.ts +10 -0
  48. package/app/queries/useBillingDocumentPdf.query.ts +22 -0
  49. package/app/queries/useBillingDocuments.query.ts +35 -0
  50. package/app/queries/useBillingOverview.query.ts +17 -0
  51. package/app/queries/useCrossSelling.query.ts +14 -0
  52. package/app/queries/useLegalEntity.query.ts +20 -0
  53. package/app/queries/useOrganization.query.ts +17 -0
  54. package/app/queries/usePermissions.query.ts +14 -0
  55. package/app/queries/useSession.query.ts +14 -0
  56. package/app/utils/apiClient.utils.ts +134 -0
  57. package/nuxt.config.ts +50 -0
  58. package/package.json +34 -0
@@ -0,0 +1,14 @@
1
+ import type { MeContextResponse } from '@package/platform-shared'
2
+
3
+ export const sessionKeys = {
4
+ me: () => ['session', 'me'] as const,
5
+ }
6
+
7
+ export function useSessionQuery() {
8
+ const client = useApiClient()
9
+
10
+ return useQuery({
11
+ key: sessionKeys.me,
12
+ query: () => client<MeContextResponse>('/api/me'),
13
+ })
14
+ }
@@ -0,0 +1,134 @@
1
+ import { FetchError } from 'ofetch'
2
+
3
+ export interface ApiClientCsrfConfig {
4
+ cookieName: string
5
+ headerName?: string
6
+ }
7
+
8
+ export interface ApiClientMfaConfig {
9
+ shouldChallenge: (status: number, data: unknown) => boolean
10
+ challengePath?: string
11
+ }
12
+
13
+ export interface ApiClientAuthConfig {
14
+ refreshBaseURL: string
15
+ refreshEndpoint?: string
16
+ platformURL: string
17
+ shouldRefresh?: (status: number, data: unknown) => boolean
18
+ onRefreshFailed?: () => void
19
+ mfa?: ApiClientMfaConfig
20
+ }
21
+
22
+ export interface ApiClientOptions {
23
+ baseURL: string
24
+ csrf?: ApiClientCsrfConfig
25
+ auth?: ApiClientAuthConfig
26
+ errorSerializer?: (context: { status: number; statusText: string; data: unknown }) => unknown
27
+ }
28
+
29
+ function defaultShouldRefresh(status: number, data: unknown): boolean {
30
+ if (status !== 401) return false
31
+ const code = (data as { error?: { code?: string } } | undefined)?.error?.code
32
+ return code === 'session.expired' || code === 'session.missing'
33
+ }
34
+
35
+ // Module-level refresh dedup — safe because refresh is client-only
36
+ let refreshPromise: Promise<boolean> | null = null
37
+
38
+ export function createApiClient(options: ApiClientOptions) {
39
+ const client = $fetch.create({
40
+ baseURL: options.baseURL,
41
+ credentials: 'include',
42
+ onRequest({ options: reqOpts }) {
43
+ // SSR: forward cookies from incoming request
44
+ if (import.meta.server) {
45
+ const { cookie } = useRequestHeaders(['cookie'])
46
+ if (cookie) {
47
+ reqOpts.headers.set('cookie', cookie)
48
+ }
49
+ }
50
+
51
+ // CSRF: inject token header
52
+ if (options.csrf) {
53
+ const token = useCookie(options.csrf.cookieName).value
54
+ if (token) {
55
+ reqOpts.headers.set(options.csrf.headerName ?? 'X-CSRF-Token', token)
56
+ }
57
+ }
58
+ },
59
+ })
60
+
61
+ // No auth handling needed — return raw $fetch instance
62
+ if (!options.auth) return client
63
+
64
+ const auth = options.auth
65
+ const shouldRefresh = auth.shouldRefresh ?? defaultShouldRefresh
66
+
67
+ function attemptRefresh(): Promise<boolean> {
68
+ if (refreshPromise) return refreshPromise
69
+ refreshPromise = $fetch(auth.refreshEndpoint ?? '/api/auth/refresh', {
70
+ baseURL: auth.refreshBaseURL,
71
+ method: 'POST',
72
+ credentials: 'include',
73
+ })
74
+ .then(() => true)
75
+ .catch(() => false)
76
+ .finally(() => { refreshPromise = null })
77
+ return refreshPromise
78
+ }
79
+
80
+ function serializeAndThrow(error: unknown): never {
81
+ if (options.errorSerializer && error instanceof FetchError) {
82
+ throw options.errorSerializer({
83
+ status: error.statusCode ?? 0,
84
+ statusText: error.statusMessage ?? '',
85
+ data: error.data,
86
+ })
87
+ }
88
+ throw error
89
+ }
90
+
91
+ // Wrap client with refresh + MFA logic (client-only features)
92
+ async function apiFetch<T = unknown>(
93
+ url: string,
94
+ fetchOptions?: Parameters<typeof client>[1],
95
+ ): Promise<T> {
96
+ try {
97
+ return await client(url, fetchOptions) as T
98
+ }
99
+ catch (error) {
100
+ if (!(error instanceof FetchError) || !import.meta.client) {
101
+ serializeAndThrow(error)
102
+ }
103
+
104
+ const status = error.statusCode ?? 0
105
+ const data = error.data
106
+
107
+ // MFA challenge redirect
108
+ if (auth.mfa?.shouldChallenge(status, data)) {
109
+ const returnUrl = encodeURIComponent(window.location.href)
110
+ const path = auth.mfa.challengePath ?? '/auth/mfa-challenge'
111
+ window.location.assign(`${auth.platformURL}${path}?redirect=${returnUrl}`)
112
+ return new Promise<T>(() => {}) // suspend — page is navigating away
113
+ }
114
+
115
+ // Token refresh + single retry
116
+ if (shouldRefresh(status, data)) {
117
+ const refreshed = await attemptRefresh()
118
+ if (refreshed) {
119
+ try {
120
+ return await client(url, fetchOptions) as T
121
+ }
122
+ catch (retryError) {
123
+ serializeAndThrow(retryError)
124
+ }
125
+ }
126
+ auth.onRefreshFailed?.()
127
+ }
128
+
129
+ serializeAndThrow(error)
130
+ }
131
+ }
132
+
133
+ return apiFetch as typeof client
134
+ }
package/nuxt.config.ts ADDED
@@ -0,0 +1,50 @@
1
+ import { dirname, join } from 'node:path'
2
+ import { fileURLToPath } from 'node:url'
3
+
4
+ const currentDir = dirname(fileURLToPath(import.meta.url))
5
+
6
+ export default defineNuxtConfig({
7
+ extends: ['nuxt-ui-layer'],
8
+
9
+ alias: {
10
+ '@package/platform-shared': join(currentDir, '_shared'),
11
+ },
12
+
13
+ modules: [
14
+ '@pinia/nuxt',
15
+ '@pinia/colada-nuxt',
16
+ '@nuxtjs/i18n',
17
+ '@vueuse/nuxt',
18
+ ],
19
+
20
+ i18n: {
21
+ strategy: 'no_prefix',
22
+ locales: [
23
+ { code: 'en', name: 'English', language: 'en' },
24
+ { code: 'it', name: 'Italiano', language: 'it' },
25
+ { code: 'de', name: 'Deutsch', language: 'de' },
26
+ { code: 'es', name: 'Español', language: 'es' },
27
+ ],
28
+ defaultLocale: 'en',
29
+ compilation: {
30
+ strictMessage: false,
31
+ },
32
+ detectBrowserLanguage: {
33
+ useCookie: true,
34
+ },
35
+ },
36
+
37
+ imports: {
38
+ dirs: [
39
+ 'queries','mutations',
40
+ ],
41
+ },
42
+
43
+ runtimeConfig: {
44
+ public: {
45
+ BACKEND_BASE_URL: '',
46
+ ENV_CSRF_COOKIE_NAME: '',
47
+ PLATFORM_URL: '',
48
+ },
49
+ },
50
+ })
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@dev.smartpricing/platform-layer",
3
+ "version": "0.0.2",
4
+ "type": "module",
5
+ "private": false,
6
+ "main": "./nuxt.config.ts",
7
+ "files": [
8
+ "app",
9
+ "_shared",
10
+ "nuxt.config.ts"
11
+ ],
12
+ "dependencies": {
13
+ "@nuxtjs/i18n": "^10.3.0",
14
+ "@pinia/colada": "^1.3.1",
15
+ "@pinia/colada-nuxt": "^1.0.1",
16
+ "@pinia/nuxt": "^0.11.3",
17
+ "@vueuse/core": "^13.9.0",
18
+ "@vueuse/nuxt": "^13.9.0"
19
+ },
20
+ "devDependencies": {
21
+ "nuxt": "^4.4.2",
22
+ "nuxt-ui-layer": "github:smartpricing/smartness-nuxt-ui#v1.6.3",
23
+ "vue": "latest"
24
+ },
25
+ "peerDependencies": {
26
+ "nuxt-ui-layer": ">=1.3.0"
27
+ },
28
+ "scripts": {
29
+ "dev": "nuxi dev .playground",
30
+ "dev:prepare": "nuxt prepare .playground",
31
+ "build": "nuxt build .playground",
32
+ "postinstall": "nuxt prepare"
33
+ }
34
+ }