@yca-software/yca-react-core 0.0.1
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 +129 -0
- package/dist/admin/index.d.ts +10 -0
- package/dist/admin/index.js +3 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin-BVYdWtEy.d.ts +3 -0
- package/dist/api/index.d.ts +68 -0
- package/dist/api/index.js +3 -0
- package/dist/api/index.js.map +1 -0
- package/dist/auth/index.d.ts +49 -0
- package/dist/auth/index.js +3 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/chunk-2HJO2ICF.js +339 -0
- package/dist/chunk-2HJO2ICF.js.map +1 -0
- package/dist/chunk-3AYG73DK.js +174 -0
- package/dist/chunk-3AYG73DK.js.map +1 -0
- package/dist/chunk-3SCZ4Z6P.js +26 -0
- package/dist/chunk-3SCZ4Z6P.js.map +1 -0
- package/dist/chunk-3ZLODBRJ.js +21 -0
- package/dist/chunk-3ZLODBRJ.js.map +1 -0
- package/dist/chunk-6LO5O4G6.js +224 -0
- package/dist/chunk-6LO5O4G6.js.map +1 -0
- package/dist/chunk-APZVW5SW.js +75 -0
- package/dist/chunk-APZVW5SW.js.map +1 -0
- package/dist/chunk-EVZBADFR.js +583 -0
- package/dist/chunk-EVZBADFR.js.map +1 -0
- package/dist/chunk-GXWVFQCK.js +52 -0
- package/dist/chunk-GXWVFQCK.js.map +1 -0
- package/dist/chunk-KG4JVAKS.js +13 -0
- package/dist/chunk-KG4JVAKS.js.map +1 -0
- package/dist/chunk-KZH56VPP.js +183 -0
- package/dist/chunk-KZH56VPP.js.map +1 -0
- package/dist/chunk-LP7Q75C6.js +96 -0
- package/dist/chunk-LP7Q75C6.js.map +1 -0
- package/dist/chunk-NL4NAHXG.js +3 -0
- package/dist/chunk-NL4NAHXG.js.map +1 -0
- package/dist/chunk-PLSNCPEC.js +2507 -0
- package/dist/chunk-PLSNCPEC.js.map +1 -0
- package/dist/chunk-QFKVPVFC.js +1391 -0
- package/dist/chunk-QFKVPVFC.js.map +1 -0
- package/dist/chunk-TSXUUD64.js +64 -0
- package/dist/chunk-TSXUUD64.js.map +1 -0
- package/dist/chunk-UCJ76BEC.js +32 -0
- package/dist/chunk-UCJ76BEC.js.map +1 -0
- package/dist/chunk-WFTIYYII.js +3 -0
- package/dist/chunk-WFTIYYII.js.map +1 -0
- package/dist/components/forms/index.d.ts +101 -0
- package/dist/components/forms/index.js +7 -0
- package/dist/components/forms/index.js.map +1 -0
- package/dist/components/marketing/index.d.ts +473 -0
- package/dist/components/marketing/index.js +5 -0
- package/dist/components/marketing/index.js.map +1 -0
- package/dist/components/spa/index.d.ts +275 -0
- package/dist/components/spa/index.js +8 -0
- package/dist/components/spa/index.js.map +1 -0
- package/dist/components/ui/index.d.ts +420 -0
- package/dist/components/ui/index.js +6 -0
- package/dist/components/ui/index.js.map +1 -0
- package/dist/constants/index.d.ts +2 -0
- package/dist/constants/index.js +4 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/cookies-ZS_QYOR7.d.ts +8 -0
- package/dist/dateRangePickerTranslations-B99F2WQA.d.ts +9 -0
- package/dist/errors/index.d.ts +48 -0
- package/dist/errors/index.js +3 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/forms-VpLvEFob.d.ts +18 -0
- package/dist/hooks/index.d.ts +18 -0
- package/dist/hooks/index.js +4 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/index.d.ts +49 -0
- package/dist/lib/index.js +7 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/pagination-CFqoqiAG.d.ts +10 -0
- package/dist/radix-overlays-BlCr-uem.d.ts +10 -0
- package/dist/styles.css +9 -0
- package/dist/theme/marketing.css +750 -0
- package/dist/theme/tokens.css +115 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types-BuNHOJ1h.d.ts +58 -0
- package/dist/types-CN73TWSY.d.ts +51 -0
- package/dist/useAdminListPage-C4ZTTllP.d.ts +26 -0
- package/package.json +152 -0
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# @yca-software/yca-react-core
|
|
2
|
+
|
|
3
|
+
Shared React components and helpers for YCA apps.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @yca-software/yca-react-core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Import styles once in your app entry:
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import '@yca-software/yca-react-core/styles.css';
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Peers:** `react`, `react-dom`, `react-hook-form`, `react-i18next`, `react-router`, `@tanstack/react-query`
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
Prefer subpath imports:
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { Button, Form } from '@yca-software/yca-react-core/ui';
|
|
25
|
+
import { Hero, Navigation, Footer } from '@yca-software/yca-react-core/marketing';
|
|
26
|
+
import { AdminListPage, PageLoader } from '@yca-software/yca-react-core/spa';
|
|
27
|
+
import { EmailField, PermissionsField } from '@yca-software/yca-react-core/forms';
|
|
28
|
+
import { createApiProvider, useAPI } from '@yca-software/yca-react-core/api';
|
|
29
|
+
import { resolveApiError, formatApiError } from '@yca-software/yca-react-core/errors';
|
|
30
|
+
import { createCookieNames } from '@yca-software/yca-react-core/constants';
|
|
31
|
+
import { useAdminListPage, useTranslationNamespace } from '@yca-software/yca-react-core/hooks';
|
|
32
|
+
import { getAdminDetailErrorKind } from '@yca-software/yca-react-core/admin';
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
| Subpath | Contents |
|
|
36
|
+
|---------|----------|
|
|
37
|
+
| `/ui` | Radix + Tailwind UI primitives (~34 components) |
|
|
38
|
+
| `/marketing` | Landing page blocks (Hero, Navigation, PricingSection, …) |
|
|
39
|
+
| `/spa` | Admin lists, loaders, query shells, entity rows, theme |
|
|
40
|
+
| `/forms` | Generic RHF fields (EmailField, PermissionsField, …) |
|
|
41
|
+
| `/api` | HTTP client, `createApiProvider`, `useAPI`, 401 refresh |
|
|
42
|
+
| `/auth` | Cookie helpers, JWT decode |
|
|
43
|
+
| `/errors` | `resolveApiError`, `formatApiError`, mutation/query toast helpers |
|
|
44
|
+
| `/constants` | `createCookieNames`, pagination flatten helpers |
|
|
45
|
+
| `/admin` | `getAdminDetailErrorKind` |
|
|
46
|
+
| `/types` | Shared form/admin types (`RoleOption`, `PermissionGroup`, …) |
|
|
47
|
+
| `/hooks` | `useAdminListPage`, `useTranslationNamespace` |
|
|
48
|
+
| `/lib` | `cn`, dates, pagination, surfaces, radix overlays |
|
|
49
|
+
| `/styles.css` | Theme tokens + `ds-mkt-*` marketing utilities |
|
|
50
|
+
|
|
51
|
+
The root export re-exports all subpaths for convenience.
|
|
52
|
+
|
|
53
|
+
See component READMEs under `src/components/{ui,marketing,spa,forms}/`.
|
|
54
|
+
|
|
55
|
+
## Consumer wiring
|
|
56
|
+
|
|
57
|
+
The library provides **generic mechanisms**. Your SPA binds env, stores, and locales in a thin `src/helpers/` layer (same pattern as `templates/react-spa`):
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
// helpers/error.ts — app owns i18n + toast
|
|
61
|
+
export function formatApiError(error: unknown) {
|
|
62
|
+
return coreFormatApiError(error, i18n.t.bind(i18n));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// helpers/cookie.ts — app owns env
|
|
66
|
+
export const cookieNames = createCookieNames(APP_NAME, ENV);
|
|
67
|
+
|
|
68
|
+
// helpers/hooks/useTranslationNamespace.ts
|
|
69
|
+
export function useTranslationNamespace(ns: string | string[]) {
|
|
70
|
+
return useCoreTranslationNamespace(ns, loadNamespace);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Keep in the app, not the library:** routes, TanStack Query hooks per resource, zustand stores, `locales/**`, billing/Paddle, permission constants, PostHog.
|
|
75
|
+
|
|
76
|
+
## Theming
|
|
77
|
+
|
|
78
|
+
Import styles once, then override CSS variables in your global stylesheet:
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import '@yca-software/yca-react-core/styles.css';
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```css
|
|
85
|
+
:root {
|
|
86
|
+
--radius: 0.875rem;
|
|
87
|
+
--primary: oklch(0.35 0.03 85);
|
|
88
|
+
--border: oklch(0.88 0.006 85);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.dark {
|
|
92
|
+
--background: oklch(0.14 0.01 85);
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Key variables: `--background`, `--foreground`, `--card`, `--primary`, `--muted`, `--border`, `--border-subtle`, `--radius`, `--shadow-surface`, `--shadow-elevated`, `--font-sans`.
|
|
97
|
+
|
|
98
|
+
Browse **Foundation → Theme** in Storybook for a full table and live preview.
|
|
99
|
+
|
|
100
|
+
See [`CHANGELOG.md`](CHANGELOG.md) for visual default changes in the latest release.
|
|
101
|
+
|
|
102
|
+
## Development
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
pnpm install
|
|
106
|
+
pnpm run build
|
|
107
|
+
pnpm run ci
|
|
108
|
+
pnpm run storybook
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Adding modules
|
|
112
|
+
|
|
113
|
+
1. Add source under `src/components/`, `src/hooks/`, `src/lib/`, or kit folders (`api/`, `auth/`, …)
|
|
114
|
+
2. UI components: implement in `ui/<name>/index.tsx` (see `src/components/ui/README.md`)
|
|
115
|
+
3. Marketing blocks: implement in `marketing/<name>/index.tsx`
|
|
116
|
+
4. SPA patterns: implement in `spa/<name>/index.tsx`
|
|
117
|
+
5. Form fields: implement in `forms/<name>/index.tsx`
|
|
118
|
+
6. Add a `tsup` entry in `tsup.config.ts` if you add a new subpath
|
|
119
|
+
7. Add a matching `exports` entry in `package.json`
|
|
120
|
+
|
|
121
|
+
## Release
|
|
122
|
+
|
|
123
|
+
Publishing is automated when you [create a GitHub Release](https://github.com/yca-software/yca-react-core/releases/new):
|
|
124
|
+
|
|
125
|
+
1. Bump `version` in `package.json`
|
|
126
|
+
2. Commit and push to `main`
|
|
127
|
+
3. Tag `vX.Y.Z` and publish the release
|
|
128
|
+
|
|
129
|
+
CI runs build, lint, tests, then publishes to npm with `NPM_TOKEN`.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { A as AdminDetailErrorKind } from '../admin-BVYdWtEy.js';
|
|
2
|
+
|
|
3
|
+
declare function getAdminDetailErrorKind(options: {
|
|
4
|
+
isLoading: boolean;
|
|
5
|
+
isError: boolean;
|
|
6
|
+
hasData: boolean;
|
|
7
|
+
status?: number;
|
|
8
|
+
}): AdminDetailErrorKind | undefined;
|
|
9
|
+
|
|
10
|
+
export { AdminDetailErrorKind, getAdminDetailErrorKind };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { a as ApiClientProviderConfig, F as FetchFn, A as ApiClientConfig, d as RefreshRequestConfig, e as RequestConfig } from '../types-CN73TWSY.js';
|
|
4
|
+
export { b as ApiErrorBody, R as RefreshBodyContext, c as RefreshConfig, f as RequestError } from '../types-CN73TWSY.js';
|
|
5
|
+
|
|
6
|
+
declare function ApiClientProvider({ baseURL, config, children, }: {
|
|
7
|
+
baseURL: string;
|
|
8
|
+
config: ApiClientProviderConfig;
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
}): React.JSX.Element;
|
|
11
|
+
declare function useApiClientConfig(): ApiClientConfig;
|
|
12
|
+
declare function useAPI(): FetchFn;
|
|
13
|
+
|
|
14
|
+
interface CreateApiProviderOptions {
|
|
15
|
+
baseURL: string;
|
|
16
|
+
getAccessToken: () => string | null;
|
|
17
|
+
getRefreshToken: () => string | null;
|
|
18
|
+
/** When true, refresh uses HttpOnly cookie credentials instead of body token. */
|
|
19
|
+
cookieCredentialsEnabled: () => boolean;
|
|
20
|
+
setAccessToken: (token: string) => void;
|
|
21
|
+
onRefreshFailure: () => void;
|
|
22
|
+
getAcceptLanguage?: () => string;
|
|
23
|
+
onRefreshStart?: () => void;
|
|
24
|
+
onRefreshEnd?: () => void;
|
|
25
|
+
/** Refresh endpoint path relative to baseURL. Defaults to `auth/refresh`. */
|
|
26
|
+
refreshEndpoint?: string;
|
|
27
|
+
/** Paths that must not trigger automatic refresh retry on 401. Defaults to `['auth/']`. */
|
|
28
|
+
refreshExcludedPrefixes?: string[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Factory for app `ApiProvider` components. Pass a hook that returns wiring options
|
|
32
|
+
* (cookies, i18n, zustand). Memoize that return value with `useMemo` so the API
|
|
33
|
+
* client context is not rebuilt on every parent render.
|
|
34
|
+
*/
|
|
35
|
+
declare function createApiProvider(useOptions: () => CreateApiProviderOptions): ({ children }: {
|
|
36
|
+
children: ReactNode;
|
|
37
|
+
}) => React.JSX.Element;
|
|
38
|
+
|
|
39
|
+
declare const DEFAULT_REFRESH_ENDPOINT = "auth/refresh";
|
|
40
|
+
declare const DEFAULT_REFRESH_EXCLUDED_PREFIXES: string[];
|
|
41
|
+
interface DefaultRefreshRequestOptions {
|
|
42
|
+
endpoint?: string;
|
|
43
|
+
excludedRetryPrefixes?: string[];
|
|
44
|
+
}
|
|
45
|
+
declare function createDefaultRefreshRequest({ endpoint, excludedRetryPrefixes, }?: DefaultRefreshRequestOptions): RefreshRequestConfig;
|
|
46
|
+
declare function buildRequestUrl(baseURL: string, endpoint: string): string;
|
|
47
|
+
declare function getRequestHeaders(accessToken: string | null, options?: {
|
|
48
|
+
multipart?: boolean;
|
|
49
|
+
acceptLanguage?: string;
|
|
50
|
+
}): HeadersInit;
|
|
51
|
+
declare function getRequestOptions(config: RequestConfig, token: string | null, acceptLanguage?: string): RequestInit;
|
|
52
|
+
declare function performAccessTokenRefresh(params: {
|
|
53
|
+
baseURL: string;
|
|
54
|
+
getRefreshToken: () => string | null;
|
|
55
|
+
request: RefreshRequestConfig;
|
|
56
|
+
useCookieCredentials: boolean;
|
|
57
|
+
setAccessToken: (token: string) => void;
|
|
58
|
+
onFailure: () => void;
|
|
59
|
+
acceptLanguage?: string;
|
|
60
|
+
}): Promise<string>;
|
|
61
|
+
declare function executeConfiguredRefresh(config: ApiClientConfig): Promise<string>;
|
|
62
|
+
|
|
63
|
+
declare function createResponseHandler(config: ApiClientConfig): (response: Response, options?: {
|
|
64
|
+
config?: RequestConfig;
|
|
65
|
+
isRetry?: boolean;
|
|
66
|
+
}) => Promise<unknown>;
|
|
67
|
+
|
|
68
|
+
export { ApiClientConfig, ApiClientProvider, ApiClientProviderConfig, type CreateApiProviderOptions, DEFAULT_REFRESH_ENDPOINT, DEFAULT_REFRESH_EXCLUDED_PREFIXES, type DefaultRefreshRequestOptions, FetchFn, RefreshRequestConfig, RequestConfig, buildRequestUrl, createApiProvider, createDefaultRefreshRequest, createResponseHandler, executeConfiguredRefresh, getRequestHeaders, getRequestOptions, performAccessTokenRefresh, useAPI, useApiClientConfig };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { ApiClientProvider, DEFAULT_REFRESH_ENDPOINT, DEFAULT_REFRESH_EXCLUDED_PREFIXES, buildRequestUrl, createApiProvider, createDefaultRefreshRequest, createResponseHandler, executeConfiguredRefresh, getRequestHeaders, getRequestOptions, performAccessTokenRefresh, useAPI, useApiClientConfig } from '../chunk-6LO5O4G6.js';
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
3
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { C as CookieNames } from '../cookies-ZS_QYOR7.js';
|
|
2
|
+
|
|
3
|
+
interface CookieConsent {
|
|
4
|
+
analytics: boolean;
|
|
5
|
+
timestamp: string;
|
|
6
|
+
}
|
|
7
|
+
interface TokenCookieOptions {
|
|
8
|
+
names: CookieNames;
|
|
9
|
+
domain?: string;
|
|
10
|
+
secure?: boolean;
|
|
11
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
12
|
+
}
|
|
13
|
+
declare function getAccessTokenFromCookies(names: CookieNames): string | null;
|
|
14
|
+
declare function getRefreshTokenFromCookies(names: CookieNames): string | null;
|
|
15
|
+
declare function getCookieConsentFromCookies(names: CookieNames): CookieConsent | null;
|
|
16
|
+
declare function removeAccessTokenCookie(options: TokenCookieOptions): void;
|
|
17
|
+
declare function removeRefreshTokenCookie(options: TokenCookieOptions): void;
|
|
18
|
+
declare function removeCookieConsentCookie(names: CookieNames): void;
|
|
19
|
+
declare function setCookieConsentCookie(names: CookieNames, timestamp: string, analytics: boolean, domain?: string): void;
|
|
20
|
+
declare function setAccessTokenCookie(token: string, options: TokenCookieOptions): void;
|
|
21
|
+
declare function setRefreshTokenCookie(token: string, options: TokenCookieOptions): void;
|
|
22
|
+
declare function setTokens(accessToken: string, refreshToken: string, options: TokenCookieOptions): void;
|
|
23
|
+
|
|
24
|
+
interface JWTAccessTokenPermissionData {
|
|
25
|
+
organizationId: string;
|
|
26
|
+
roleId?: string;
|
|
27
|
+
permissions: string[];
|
|
28
|
+
}
|
|
29
|
+
interface DecodedAccessToken {
|
|
30
|
+
sub: string;
|
|
31
|
+
email?: string;
|
|
32
|
+
permissions?: JWTAccessTokenPermissionData[];
|
|
33
|
+
isAdmin?: boolean;
|
|
34
|
+
impersonatedBy?: string | null;
|
|
35
|
+
impersonatedByEmail?: string | null;
|
|
36
|
+
}
|
|
37
|
+
interface AccessInfo {
|
|
38
|
+
userId: string;
|
|
39
|
+
email: string;
|
|
40
|
+
permissions: JWTAccessTokenPermissionData[];
|
|
41
|
+
isAdmin: boolean;
|
|
42
|
+
impersonatedBy: string | null;
|
|
43
|
+
impersonatedByEmail: string | null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
declare function getAccessInfoFromToken(accessToken: string): AccessInfo | null;
|
|
47
|
+
declare function getInitials(firstName?: string, lastName?: string): string;
|
|
48
|
+
|
|
49
|
+
export { type AccessInfo, type CookieConsent, type DecodedAccessToken, type JWTAccessTokenPermissionData, type TokenCookieOptions, getAccessInfoFromToken, getAccessTokenFromCookies, getCookieConsentFromCookies, getInitials, getRefreshTokenFromCookies, removeAccessTokenCookie, removeCookieConsentCookie, removeRefreshTokenCookie, setAccessTokenCookie, setCookieConsentCookie, setRefreshTokenCookie, setTokens };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { getAccessInfoFromToken, getAccessTokenFromCookies, getCookieConsentFromCookies, getInitials, getRefreshTokenFromCookies, removeAccessTokenCookie, removeCookieConsentCookie, removeRefreshTokenCookie, setAccessTokenCookie, setCookieConsentCookie, setRefreshTokenCookie, setTokens } from '../chunk-APZVW5SW.js';
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
3
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { FormField, FormItem, FormLabel, FormControl, Textarea, FormMessage, Select, Tooltip, Checkbox, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from './chunk-PLSNCPEC.js';
|
|
2
|
+
import { Input } from './chunk-3AYG73DK.js';
|
|
3
|
+
import { cn } from './chunk-GXWVFQCK.js';
|
|
4
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
|
+
import { Info, ChevronDown } from 'lucide-react';
|
|
6
|
+
|
|
7
|
+
function DescriptionField({
|
|
8
|
+
control,
|
|
9
|
+
name,
|
|
10
|
+
label,
|
|
11
|
+
placeholder,
|
|
12
|
+
className,
|
|
13
|
+
disabled
|
|
14
|
+
}) {
|
|
15
|
+
return /* @__PURE__ */ jsx(
|
|
16
|
+
FormField,
|
|
17
|
+
{
|
|
18
|
+
control,
|
|
19
|
+
name,
|
|
20
|
+
render: ({ field }) => /* @__PURE__ */ jsxs(FormItem, { className, children: [
|
|
21
|
+
/* @__PURE__ */ jsx(FormLabel, { children: label }),
|
|
22
|
+
/* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(Textarea, { placeholder, disabled, ...field }) }),
|
|
23
|
+
/* @__PURE__ */ jsx(FormMessage, {})
|
|
24
|
+
] })
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
function EmailField({
|
|
29
|
+
control,
|
|
30
|
+
name,
|
|
31
|
+
label,
|
|
32
|
+
placeholder,
|
|
33
|
+
description,
|
|
34
|
+
autoComplete = "email",
|
|
35
|
+
className
|
|
36
|
+
}) {
|
|
37
|
+
return /* @__PURE__ */ jsx(
|
|
38
|
+
FormField,
|
|
39
|
+
{
|
|
40
|
+
control,
|
|
41
|
+
name,
|
|
42
|
+
render: ({ field }) => /* @__PURE__ */ jsxs(FormItem, { className, children: [
|
|
43
|
+
/* @__PURE__ */ jsx(FormLabel, { children: label }),
|
|
44
|
+
description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-0.5", children: description }),
|
|
45
|
+
/* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(Input, { type: "email", placeholder, autoComplete, ...field }) }),
|
|
46
|
+
/* @__PURE__ */ jsx(FormMessage, {})
|
|
47
|
+
] })
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
function ExpiresAtField({
|
|
52
|
+
control,
|
|
53
|
+
name,
|
|
54
|
+
label,
|
|
55
|
+
placeholder,
|
|
56
|
+
ariaLabel,
|
|
57
|
+
options
|
|
58
|
+
}) {
|
|
59
|
+
return /* @__PURE__ */ jsx(
|
|
60
|
+
FormField,
|
|
61
|
+
{
|
|
62
|
+
control,
|
|
63
|
+
name,
|
|
64
|
+
render: ({ field }) => /* @__PURE__ */ jsxs(FormItem, { children: [
|
|
65
|
+
/* @__PURE__ */ jsx(FormLabel, { children: label }),
|
|
66
|
+
/* @__PURE__ */ jsx(
|
|
67
|
+
Select,
|
|
68
|
+
{
|
|
69
|
+
value: field.value ?? "",
|
|
70
|
+
onValueChange: (next) => {
|
|
71
|
+
field.onChange(next);
|
|
72
|
+
},
|
|
73
|
+
options,
|
|
74
|
+
placeholder,
|
|
75
|
+
"aria-label": ariaLabel ?? label
|
|
76
|
+
}
|
|
77
|
+
),
|
|
78
|
+
/* @__PURE__ */ jsx(FormMessage, {})
|
|
79
|
+
] })
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
function FirstNameField({
|
|
84
|
+
control,
|
|
85
|
+
name,
|
|
86
|
+
label,
|
|
87
|
+
placeholder,
|
|
88
|
+
className
|
|
89
|
+
}) {
|
|
90
|
+
return /* @__PURE__ */ jsx(
|
|
91
|
+
FormField,
|
|
92
|
+
{
|
|
93
|
+
control,
|
|
94
|
+
name,
|
|
95
|
+
render: ({ field }) => /* @__PURE__ */ jsxs(FormItem, { className, children: [
|
|
96
|
+
/* @__PURE__ */ jsx(FormLabel, { children: label }),
|
|
97
|
+
/* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(Input, { placeholder, autoComplete: "given-name", ...field }) }),
|
|
98
|
+
/* @__PURE__ */ jsx(FormMessage, {})
|
|
99
|
+
] })
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
function LastNameField({
|
|
104
|
+
control,
|
|
105
|
+
name,
|
|
106
|
+
label,
|
|
107
|
+
placeholder,
|
|
108
|
+
className
|
|
109
|
+
}) {
|
|
110
|
+
return /* @__PURE__ */ jsx(
|
|
111
|
+
FormField,
|
|
112
|
+
{
|
|
113
|
+
control,
|
|
114
|
+
name,
|
|
115
|
+
render: ({ field }) => /* @__PURE__ */ jsxs(FormItem, { className, children: [
|
|
116
|
+
/* @__PURE__ */ jsx(FormLabel, { children: label }),
|
|
117
|
+
/* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(Input, { placeholder, autoComplete: "family-name", ...field }) }),
|
|
118
|
+
/* @__PURE__ */ jsx(FormMessage, {})
|
|
119
|
+
] })
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
function NameField({
|
|
124
|
+
control,
|
|
125
|
+
name,
|
|
126
|
+
label,
|
|
127
|
+
placeholder,
|
|
128
|
+
className,
|
|
129
|
+
disabled
|
|
130
|
+
}) {
|
|
131
|
+
return /* @__PURE__ */ jsx(
|
|
132
|
+
FormField,
|
|
133
|
+
{
|
|
134
|
+
control,
|
|
135
|
+
name,
|
|
136
|
+
render: ({ field }) => /* @__PURE__ */ jsxs(FormItem, { className, children: [
|
|
137
|
+
/* @__PURE__ */ jsx(FormLabel, { children: label }),
|
|
138
|
+
/* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(Input, { placeholder, disabled, autoComplete: "off", ...field }) }),
|
|
139
|
+
/* @__PURE__ */ jsx(FormMessage, {})
|
|
140
|
+
] })
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
function PasswordField({
|
|
145
|
+
control,
|
|
146
|
+
name,
|
|
147
|
+
label,
|
|
148
|
+
placeholder,
|
|
149
|
+
autoComplete = "current-password",
|
|
150
|
+
rightLabel,
|
|
151
|
+
className
|
|
152
|
+
}) {
|
|
153
|
+
return /* @__PURE__ */ jsx(
|
|
154
|
+
FormField,
|
|
155
|
+
{
|
|
156
|
+
control,
|
|
157
|
+
name,
|
|
158
|
+
render: ({ field }) => /* @__PURE__ */ jsxs(FormItem, { className, children: [
|
|
159
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
160
|
+
/* @__PURE__ */ jsx(FormLabel, { children: label }),
|
|
161
|
+
rightLabel
|
|
162
|
+
] }),
|
|
163
|
+
/* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsx(
|
|
164
|
+
Input,
|
|
165
|
+
{
|
|
166
|
+
type: "password",
|
|
167
|
+
placeholder,
|
|
168
|
+
autoComplete,
|
|
169
|
+
...field
|
|
170
|
+
}
|
|
171
|
+
) }),
|
|
172
|
+
/* @__PURE__ */ jsx(FormMessage, {})
|
|
173
|
+
] })
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
function PermissionsField({
|
|
178
|
+
control,
|
|
179
|
+
name,
|
|
180
|
+
label,
|
|
181
|
+
groups,
|
|
182
|
+
t,
|
|
183
|
+
disabled = false
|
|
184
|
+
}) {
|
|
185
|
+
return /* @__PURE__ */ jsx(
|
|
186
|
+
FormField,
|
|
187
|
+
{
|
|
188
|
+
control,
|
|
189
|
+
name,
|
|
190
|
+
render: ({ field }) => {
|
|
191
|
+
const selected = field.value ?? [];
|
|
192
|
+
const togglePermission = (key, group) => {
|
|
193
|
+
const readKey = getReadKeyForGroup(group);
|
|
194
|
+
const opt = group.find((p) => p.key === key);
|
|
195
|
+
if (!opt) return;
|
|
196
|
+
const next = selected.includes(key) ? selected.filter((k) => k !== key) : [...selected, key];
|
|
197
|
+
if (opt.action === "write" || opt.action === "delete") {
|
|
198
|
+
if (next.includes(key) && readKey && !next.includes(readKey)) {
|
|
199
|
+
next.push(readKey);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (opt.action === "delete") {
|
|
203
|
+
const writeKey = getWriteKeyForGroup(group);
|
|
204
|
+
if (next.includes(key) && writeKey && !next.includes(writeKey)) {
|
|
205
|
+
next.push(writeKey);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
field.onChange(next);
|
|
209
|
+
};
|
|
210
|
+
return /* @__PURE__ */ jsxs(FormItem, { children: [
|
|
211
|
+
/* @__PURE__ */ jsx(FormLabel, { children: label }),
|
|
212
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-4", children: groups.map((group) => {
|
|
213
|
+
const readKey = getReadKeyForGroup(group.permissions);
|
|
214
|
+
const writeKey = getWriteKeyForGroup(group.permissions);
|
|
215
|
+
const readDisabled = readKey ? hasWriteOrDelete(group.permissions, selected) : false;
|
|
216
|
+
const writeDisabled = writeKey ? hasDelete(group.permissions, selected) : false;
|
|
217
|
+
const readChecked = readKey !== null && (selected.includes(readKey) || readDisabled);
|
|
218
|
+
const writeChecked = writeKey !== null && (selected.includes(writeKey) || writeDisabled);
|
|
219
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
220
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
221
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t(group.labelKey) }),
|
|
222
|
+
/* @__PURE__ */ jsx(Tooltip, { content: t(group.descriptionKey), side: "top", align: "start", children: /* @__PURE__ */ jsx(
|
|
223
|
+
"button",
|
|
224
|
+
{
|
|
225
|
+
type: "button",
|
|
226
|
+
className: "text-muted-foreground hover:text-foreground cursor-help inline-flex rounded p-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
227
|
+
"aria-label": t(group.descriptionKey),
|
|
228
|
+
children: /* @__PURE__ */ jsx(Info, { className: "size-3.5", "aria-hidden": true })
|
|
229
|
+
}
|
|
230
|
+
) })
|
|
231
|
+
] }),
|
|
232
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-4 pl-2", children: group.permissions.map((perm) => {
|
|
233
|
+
const isRead = perm.action === "read";
|
|
234
|
+
const isWrite = perm.action === "write";
|
|
235
|
+
const checked = isRead ? readChecked : isWrite ? writeChecked : selected.includes(perm.key);
|
|
236
|
+
const permissionDisabled = isRead && readDisabled || isWrite && writeDisabled;
|
|
237
|
+
const globallyDisabled = disabled || permissionDisabled;
|
|
238
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-2", children: [
|
|
239
|
+
/* @__PURE__ */ jsx(
|
|
240
|
+
Checkbox,
|
|
241
|
+
{
|
|
242
|
+
id: perm.key,
|
|
243
|
+
checked: isRead ? readChecked : checked,
|
|
244
|
+
disabled: globallyDisabled,
|
|
245
|
+
onCheckedChange: () => !globallyDisabled && togglePermission(perm.key, group.permissions)
|
|
246
|
+
}
|
|
247
|
+
),
|
|
248
|
+
/* @__PURE__ */ jsx(
|
|
249
|
+
"label",
|
|
250
|
+
{
|
|
251
|
+
htmlFor: perm.key,
|
|
252
|
+
className: "text-sm font-normal leading-none cursor-pointer peer-disabled:opacity-70",
|
|
253
|
+
children: t(perm.labelKey)
|
|
254
|
+
}
|
|
255
|
+
)
|
|
256
|
+
] }, perm.key);
|
|
257
|
+
}) })
|
|
258
|
+
] }, group.contextKey);
|
|
259
|
+
}) }),
|
|
260
|
+
/* @__PURE__ */ jsx(FormMessage, {})
|
|
261
|
+
] });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
function getReadKeyForGroup(permissions) {
|
|
267
|
+
const readOpt = permissions.find((p) => p.action === "read");
|
|
268
|
+
return readOpt?.key ?? null;
|
|
269
|
+
}
|
|
270
|
+
function getWriteKeyForGroup(permissions) {
|
|
271
|
+
const writeOpt = permissions.find((p) => p.action === "write");
|
|
272
|
+
return writeOpt?.key ?? null;
|
|
273
|
+
}
|
|
274
|
+
function hasWriteOrDelete(permissions, selected) {
|
|
275
|
+
return permissions.some(
|
|
276
|
+
(p) => (p.action === "write" || p.action === "delete") && selected.includes(p.key)
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
function hasDelete(permissions, selected) {
|
|
280
|
+
return permissions.some((p) => p.action === "delete" && selected.includes(p.key));
|
|
281
|
+
}
|
|
282
|
+
function RoleSelectField({
|
|
283
|
+
control,
|
|
284
|
+
name,
|
|
285
|
+
label,
|
|
286
|
+
options,
|
|
287
|
+
className
|
|
288
|
+
}) {
|
|
289
|
+
return /* @__PURE__ */ jsx(
|
|
290
|
+
FormField,
|
|
291
|
+
{
|
|
292
|
+
control,
|
|
293
|
+
name,
|
|
294
|
+
render: ({ field }) => {
|
|
295
|
+
const selectedOption = options.find((opt) => opt.value === field.value);
|
|
296
|
+
const displayValue = selectedOption?.label ?? label;
|
|
297
|
+
return /* @__PURE__ */ jsxs(FormItem, { className, children: [
|
|
298
|
+
/* @__PURE__ */ jsx(FormLabel, { children: label }),
|
|
299
|
+
/* @__PURE__ */ jsx(FormControl, { children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
300
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
301
|
+
"button",
|
|
302
|
+
{
|
|
303
|
+
type: "button",
|
|
304
|
+
role: "combobox",
|
|
305
|
+
"aria-label": label,
|
|
306
|
+
onBlur: field.onBlur,
|
|
307
|
+
className: cn(
|
|
308
|
+
"flex h-9 w-full cursor-pointer items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs outline-none transition-colors",
|
|
309
|
+
"hover:bg-accent/50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
310
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
311
|
+
"aria-invalid:ring-destructive/20 aria-invalid:border-destructive",
|
|
312
|
+
!field.value && "text-muted-foreground"
|
|
313
|
+
),
|
|
314
|
+
children: [
|
|
315
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 truncate text-left", children: displayValue }),
|
|
316
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: "ml-2 h-4 w-4 shrink-0 text-muted-foreground" })
|
|
317
|
+
]
|
|
318
|
+
}
|
|
319
|
+
) }),
|
|
320
|
+
/* @__PURE__ */ jsx(
|
|
321
|
+
DropdownMenuContent,
|
|
322
|
+
{
|
|
323
|
+
align: "start",
|
|
324
|
+
sideOffset: 0,
|
|
325
|
+
className: cn("min-w-(--radix-dropdown-menu-trigger-width) z-101"),
|
|
326
|
+
children: options.map((opt) => /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: () => field.onChange(opt.value), children: opt.label }, opt.value))
|
|
327
|
+
}
|
|
328
|
+
)
|
|
329
|
+
] }) }),
|
|
330
|
+
/* @__PURE__ */ jsx(FormMessage, {})
|
|
331
|
+
] });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export { DescriptionField, EmailField, ExpiresAtField, FirstNameField, LastNameField, NameField, PasswordField, PermissionsField, RoleSelectField };
|
|
338
|
+
//# sourceMappingURL=chunk-2HJO2ICF.js.map
|
|
339
|
+
//# sourceMappingURL=chunk-2HJO2ICF.js.map
|