@carlonicora/nextjs-jsonapi 1.24.3 → 1.25.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/dist/{BlockNoteEditor-OFSTXGZX.js → BlockNoteEditor-7WAWEZVW.js} +13 -13
- package/dist/{BlockNoteEditor-OFSTXGZX.js.map → BlockNoteEditor-7WAWEZVW.js.map} +1 -1
- package/dist/{BlockNoteEditor-TJNLCNIP.mjs → BlockNoteEditor-UNVKGZ2G.mjs} +3 -3
- package/dist/billing/index.js +342 -342
- package/dist/billing/index.mjs +2 -2
- package/dist/{chunk-H5JZ5E7M.mjs → chunk-6BDOZDZ3.mjs} +1247 -54
- package/dist/chunk-6BDOZDZ3.mjs.map +1 -0
- package/dist/{chunk-EJALOG7L.js → chunk-JI6BDV7L.js} +1598 -405
- package/dist/chunk-JI6BDV7L.js.map +1 -0
- package/dist/{chunk-5U4NJJOF.mjs → chunk-LNBT2YPZ.mjs} +289 -2
- package/dist/chunk-LNBT2YPZ.mjs.map +1 -0
- package/dist/{chunk-NQVPCNRS.js → chunk-O3LLMGP7.js} +290 -3
- package/dist/chunk-O3LLMGP7.js.map +1 -0
- package/dist/client/index.d.mts +96 -1
- package/dist/client/index.d.ts +96 -1
- package/dist/client/index.js +9 -3
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +8 -2
- package/dist/components/index.d.mts +225 -1
- package/dist/components/index.d.ts +225 -1
- package/dist/components/index.js +25 -3
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +24 -2
- package/dist/contexts/index.js +3 -3
- package/dist/contexts/index.mjs +2 -2
- package/dist/core/index.d.mts +108 -1
- package/dist/core/index.d.ts +108 -1
- package/dist/core/index.js +14 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +13 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +14 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13 -1
- package/dist/oauth.interface-DsZ5ecSX.d.mts +119 -0
- package/dist/oauth.interface-vL7za9Bz.d.ts +119 -0
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +1 -1
- package/package.json +3 -2
- package/src/client/index.ts +1 -0
- package/src/components/index.ts +1 -0
- package/src/core/index.ts +3 -0
- package/src/core/registry/ModuleRegistry.ts +2 -0
- package/src/features/index.ts +1 -0
- package/src/features/oauth/atoms/index.ts +1 -0
- package/src/features/oauth/atoms/oauth.atoms.ts +131 -0
- package/src/features/oauth/components/OAuthClientCard.tsx +105 -0
- package/src/features/oauth/components/OAuthClientDetail.tsx +269 -0
- package/src/features/oauth/components/OAuthClientForm.tsx +212 -0
- package/src/features/oauth/components/OAuthClientList.tsx +127 -0
- package/src/features/oauth/components/OAuthClientSecretDisplay.tsx +127 -0
- package/src/features/oauth/components/OAuthRedirectUriInput.tsx +152 -0
- package/src/features/oauth/components/OAuthScopeSelector.tsx +123 -0
- package/src/features/oauth/components/consent/OAuthConsentActions.tsx +41 -0
- package/src/features/oauth/components/consent/OAuthConsentHeader.tsx +51 -0
- package/src/features/oauth/components/consent/OAuthConsentScreen.tsx +142 -0
- package/src/features/oauth/components/consent/OAuthScopeList.tsx +72 -0
- package/src/features/oauth/components/consent/index.ts +4 -0
- package/src/features/oauth/components/index.ts +8 -0
- package/src/features/oauth/data/index.ts +2 -0
- package/src/features/oauth/data/oauth.service.ts +191 -0
- package/src/features/oauth/data/oauth.ts +87 -0
- package/src/features/oauth/hooks/index.ts +3 -0
- package/src/features/oauth/hooks/useOAuthClient.ts +161 -0
- package/src/features/oauth/hooks/useOAuthClients.ts +111 -0
- package/src/features/oauth/hooks/useOAuthConsent.ts +125 -0
- package/src/features/oauth/index.ts +6 -0
- package/src/features/oauth/interfaces/index.ts +1 -0
- package/src/features/oauth/interfaces/oauth.interface.ts +175 -0
- package/src/features/oauth/oauth.module.ts +9 -0
- package/dist/chunk-5U4NJJOF.mjs.map +0 -1
- package/dist/chunk-EJALOG7L.js.map +0 -1
- package/dist/chunk-H5JZ5E7M.mjs.map +0 -1
- package/dist/chunk-NQVPCNRS.js.map +0 -1
- /package/dist/{BlockNoteEditor-TJNLCNIP.mjs.map → BlockNoteEditor-UNVKGZ2G.mjs.map} +0 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { A as ApiDataInterface } from './ApiDataInterface-DPP8s46n.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* OAuth client application interface
|
|
5
|
+
* Represents a registered OAuth application that can request access tokens
|
|
6
|
+
*/
|
|
7
|
+
interface OAuthClientInterface extends ApiDataInterface {
|
|
8
|
+
/** The public client identifier (UUID format) */
|
|
9
|
+
get clientId(): string;
|
|
10
|
+
/** Human-readable application name */
|
|
11
|
+
get name(): string;
|
|
12
|
+
/** Optional description of the application */
|
|
13
|
+
get description(): string | undefined;
|
|
14
|
+
/** Array of allowed redirect URIs (exact match validation) */
|
|
15
|
+
get redirectUris(): string[];
|
|
16
|
+
/** Array of scopes this client can request */
|
|
17
|
+
get allowedScopes(): string[];
|
|
18
|
+
/** Supported grant types (authorization_code, client_credentials, refresh_token) */
|
|
19
|
+
get allowedGrantTypes(): string[];
|
|
20
|
+
/** True for server-side apps (can keep secret secure), false for mobile/desktop apps */
|
|
21
|
+
get isConfidential(): boolean;
|
|
22
|
+
/** Whether the client is currently active */
|
|
23
|
+
get isActive(): boolean;
|
|
24
|
+
/** When the client was created */
|
|
25
|
+
get createdAt(): Date;
|
|
26
|
+
/** When the client was last updated */
|
|
27
|
+
get updatedAt(): Date;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Input type for OAuth client CRUD operations
|
|
31
|
+
*/
|
|
32
|
+
type OAuthClientInput = {
|
|
33
|
+
id?: string;
|
|
34
|
+
name?: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
redirectUris?: string[];
|
|
37
|
+
allowedScopes?: string[];
|
|
38
|
+
allowedGrantTypes?: string[];
|
|
39
|
+
isConfidential?: boolean;
|
|
40
|
+
isActive?: boolean;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Request body for creating a new OAuth client
|
|
44
|
+
*/
|
|
45
|
+
interface OAuthClientCreateRequest {
|
|
46
|
+
/** Required: Human-readable application name */
|
|
47
|
+
name: string;
|
|
48
|
+
/** Optional: Description of the application */
|
|
49
|
+
description?: string;
|
|
50
|
+
/** Required: At least one redirect URI */
|
|
51
|
+
redirectUris: string[];
|
|
52
|
+
/** Required: Array of scopes the client needs */
|
|
53
|
+
allowedScopes: string[];
|
|
54
|
+
/** Optional: Grant types (defaults to authorization_code + refresh_token) */
|
|
55
|
+
allowedGrantTypes?: string[];
|
|
56
|
+
/** Required: Whether this is a confidential client */
|
|
57
|
+
isConfidential: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Response when creating a client (includes one-time secret)
|
|
61
|
+
*/
|
|
62
|
+
interface OAuthClientCreateResponse {
|
|
63
|
+
client: OAuthClientInterface;
|
|
64
|
+
/** Only returned on creation - must be saved immediately */
|
|
65
|
+
clientSecret?: string;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Parameters for the OAuth authorization consent flow
|
|
69
|
+
* Passed via URL query parameters to the consent page
|
|
70
|
+
*/
|
|
71
|
+
interface OAuthConsentRequest {
|
|
72
|
+
/** The client_id requesting authorization */
|
|
73
|
+
clientId: string;
|
|
74
|
+
/** Where to redirect after authorization */
|
|
75
|
+
redirectUri: string;
|
|
76
|
+
/** Space-separated list of requested scopes */
|
|
77
|
+
scope: string;
|
|
78
|
+
/** CSRF protection token (passed back on redirect) */
|
|
79
|
+
state?: string;
|
|
80
|
+
/** PKCE code challenge (required for public clients) */
|
|
81
|
+
codeChallenge?: string;
|
|
82
|
+
/** PKCE method: 'S256' (recommended) or 'plain' */
|
|
83
|
+
codeChallengeMethod?: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Scope information for display in consent screen
|
|
87
|
+
*/
|
|
88
|
+
interface OAuthScopeInfo {
|
|
89
|
+
/** The scope identifier (e.g., 'photographs:read') */
|
|
90
|
+
scope: string;
|
|
91
|
+
/** Human-readable scope name */
|
|
92
|
+
name: string;
|
|
93
|
+
/** Description of what this scope allows */
|
|
94
|
+
description: string;
|
|
95
|
+
/** Optional icon identifier */
|
|
96
|
+
icon?: string;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Client info returned for consent screen display
|
|
100
|
+
*/
|
|
101
|
+
interface OAuthConsentInfo {
|
|
102
|
+
client: OAuthClientInterface;
|
|
103
|
+
scopes: OAuthScopeInfo[];
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Default scope display configuration
|
|
107
|
+
* Maps scope identifiers to human-readable info
|
|
108
|
+
*/
|
|
109
|
+
declare const OAUTH_SCOPE_DISPLAY: Record<string, OAuthScopeInfo>;
|
|
110
|
+
/**
|
|
111
|
+
* Available scopes list for the scope selector
|
|
112
|
+
*/
|
|
113
|
+
declare const AVAILABLE_OAUTH_SCOPES: OAuthScopeInfo[];
|
|
114
|
+
/**
|
|
115
|
+
* Default grant types for new clients
|
|
116
|
+
*/
|
|
117
|
+
declare const DEFAULT_GRANT_TYPES: string[];
|
|
118
|
+
|
|
119
|
+
export { AVAILABLE_OAUTH_SCOPES as A, DEFAULT_GRANT_TYPES as D, type OAuthClientInterface as O, type OAuthClientInput as a, type OAuthClientCreateRequest as b, type OAuthClientCreateResponse as c, type OAuthConsentRequest as d, type OAuthScopeInfo as e, type OAuthConsentInfo as f, OAUTH_SCOPE_DISPLAY as g };
|
package/dist/server/index.js
CHANGED
|
@@ -15,7 +15,7 @@ var _chunk3ZPK4QOBjs = require('../chunk-3ZPK4QOB.js');
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
var
|
|
18
|
+
var _chunkO3LLMGP7js = require('../chunk-O3LLMGP7.js');
|
|
19
19
|
require('../chunk-LXKSUWAV.js');
|
|
20
20
|
require('../chunk-IBS6NI7D.js');
|
|
21
21
|
|
|
@@ -93,7 +93,7 @@ var ServerSession = class {
|
|
|
93
93
|
if (!rawModules) return false;
|
|
94
94
|
const modules = JSON.parse(_pako2.default.ungzip(Buffer.from(rawModules, "base64"), { to: "string" }));
|
|
95
95
|
const selectedModule = modules.find((module) => module.id === params.module.moduleId);
|
|
96
|
-
return
|
|
96
|
+
return _chunkO3LLMGP7js.checkPermissionsFromServer.call(void 0, {
|
|
97
97
|
module: params.module,
|
|
98
98
|
action: params.action,
|
|
99
99
|
data: params.data,
|
|
@@ -303,5 +303,5 @@ _chunk7QVYU63Ejs.__name.call(void 0, ServerJsonApiDelete, "ServerJsonApiDelete")
|
|
|
303
303
|
|
|
304
304
|
|
|
305
305
|
|
|
306
|
-
exports.ServerAuthService =
|
|
306
|
+
exports.ServerAuthService = _chunkO3LLMGP7js.AuthService; exports.ServerCompanyService = _chunkO3LLMGP7js.CompanyService; exports.ServerContentService = _chunkO3LLMGP7js.ContentService; exports.ServerFeatureService = _chunkO3LLMGP7js.FeatureService; exports.ServerJsonApiDelete = ServerJsonApiDelete; exports.ServerJsonApiGet = ServerJsonApiGet; exports.ServerJsonApiPatch = ServerJsonApiPatch; exports.ServerJsonApiPost = ServerJsonApiPost; exports.ServerJsonApiPut = ServerJsonApiPut; exports.ServerNotificationService = _chunkO3LLMGP7js.NotificationService; exports.ServerPushService = _chunkO3LLMGP7js.PushService; exports.ServerRoleService = _chunkO3LLMGP7js.RoleService; exports.ServerS3Service = _chunkO3LLMGP7js.S3Service; exports.ServerSession = ServerSession; exports.ServerUserService = _chunkO3LLMGP7js.UserService; exports.configureServerJsonApi = configureServerJsonApi; exports.getServerApiUrl = getServerApiUrl; exports.getServerAppUrl = getServerAppUrl; exports.getServerToken = _chunkYUO55Q5Ajs.getServerToken; exports.getServerTrackablePages = getServerTrackablePages; exports.invalidateCacheTag = invalidateCacheTag; exports.invalidateCacheTags = invalidateCacheTags; exports.serverRequest = _chunk3ZPK4QOBjs.serverRequest;
|
|
307
307
|
//# sourceMappingURL=index.js.map
|
package/dist/server/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carlonicora/nextjs-jsonapi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.25.1",
|
|
4
4
|
"description": "Next.js JSON:API client with server/client support and caching",
|
|
5
5
|
"author": "Carlo Nicora",
|
|
6
6
|
"license": "GPL-3.0-or-later",
|
|
@@ -101,7 +101,6 @@
|
|
|
101
101
|
},
|
|
102
102
|
"dependencies": {
|
|
103
103
|
"@base-ui/react": "^1.0.0",
|
|
104
|
-
"class-variance-authority": "^0.7.1",
|
|
105
104
|
"@blocknote/core": "^0.45.0",
|
|
106
105
|
"@blocknote/react": "^0.45.0",
|
|
107
106
|
"@blocknote/shadcn": "^0.45.0",
|
|
@@ -111,6 +110,7 @@
|
|
|
111
110
|
"@hookform/resolvers": "^5.2.2",
|
|
112
111
|
"@radix-ui/react-label": "^2.1.8",
|
|
113
112
|
"@radix-ui/react-slot": "^1.2.4",
|
|
113
|
+
"class-variance-authority": "^0.7.1",
|
|
114
114
|
"clsx": "^2.1.1",
|
|
115
115
|
"cmdk": "^1.1.1",
|
|
116
116
|
"commander": "^14.0.2",
|
|
@@ -120,6 +120,7 @@
|
|
|
120
120
|
"embla-carousel-react": "^8.6.0",
|
|
121
121
|
"input-otp": "^1.4.2",
|
|
122
122
|
"jotai": "^2.16.1",
|
|
123
|
+
"jotai-family": "^1.0.1",
|
|
123
124
|
"lucide-react": "^0.562.0",
|
|
124
125
|
"next-themes": "^0.4.6",
|
|
125
126
|
"pako": "^2.1.0",
|
package/src/client/index.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { registerTableGenerator } from "../hooks";
|
|
|
24
24
|
export * from "../features/content/hooks";
|
|
25
25
|
export * from "../features/role/hooks";
|
|
26
26
|
export * from "../features/user/hooks";
|
|
27
|
+
export * from "../features/oauth/hooks";
|
|
27
28
|
|
|
28
29
|
registerTableGenerator("roles", useRoleTableStructure);
|
|
29
30
|
registerTableGenerator("users", useUserTableStructure);
|
package/src/components/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ export * from "../features/feature/components";
|
|
|
17
17
|
export * from "../features/notification/components";
|
|
18
18
|
export * from "../features/role/components";
|
|
19
19
|
export * from "../features/user/components";
|
|
20
|
+
export * from "../features/oauth/components";
|
|
20
21
|
|
|
21
22
|
// shadcn/ui components (merged from /shadcnui entry point)
|
|
22
23
|
export * from "../shadcnui";
|
package/src/core/index.ts
CHANGED
|
@@ -58,3 +58,6 @@ export * from "../features/search";
|
|
|
58
58
|
export * from "../features/user/author.module";
|
|
59
59
|
export * from "../features/user/data";
|
|
60
60
|
export * from "../features/user/user.module";
|
|
61
|
+
export * from "../features/oauth/oauth.module";
|
|
62
|
+
export * from "../features/oauth/data";
|
|
63
|
+
export * from "../features/oauth/interfaces";
|
|
@@ -26,6 +26,8 @@ export interface FoundationModuleDefinitions {
|
|
|
26
26
|
StripeProduct: ModuleWithPermissions;
|
|
27
27
|
StripePrice: ModuleWithPermissions;
|
|
28
28
|
StripeUsage: ModuleWithPermissions;
|
|
29
|
+
// OAuth modules
|
|
30
|
+
OAuth: ModuleWithPermissions;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
// App-specific modules - apps will augment this interface ONLY
|
package/src/features/index.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./oauth.atoms";
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { atom } from "jotai";
|
|
2
|
+
import { atomFamily } from "jotai-family";
|
|
3
|
+
import { OAuthClientInterface } from "../interfaces/oauth.interface";
|
|
4
|
+
|
|
5
|
+
// ==========================================
|
|
6
|
+
// OAUTH CLIENTS STATE
|
|
7
|
+
// ==========================================
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Primary store for OAuth clients
|
|
11
|
+
* Populated by useOAuthClients hook
|
|
12
|
+
*/
|
|
13
|
+
export const oauthClientsAtom = atom<OAuthClientInterface[]>([]);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Loading state for OAuth clients list
|
|
17
|
+
*/
|
|
18
|
+
export const oauthClientsLoadingAtom = atom<boolean>(false);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Error state for OAuth client operations
|
|
22
|
+
*/
|
|
23
|
+
export const oauthClientsErrorAtom = atom<Error | null>(null);
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Derived atom family for getting a single client by ID
|
|
27
|
+
* Usage: const client = useAtomValue(oauthClientByIdAtom(clientId))
|
|
28
|
+
*
|
|
29
|
+
* @param clientId - The client ID (not the internal id, but the clientId field)
|
|
30
|
+
* @returns The client if found, undefined otherwise
|
|
31
|
+
*/
|
|
32
|
+
export const oauthClientByIdAtom = atomFamily((clientId: string) =>
|
|
33
|
+
atom((get) => {
|
|
34
|
+
const clients = get(oauthClientsAtom);
|
|
35
|
+
return clients.find((c) => c.clientId === clientId || c.id === clientId);
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// ==========================================
|
|
40
|
+
// ONE-TIME SECRET DISPLAY STATE
|
|
41
|
+
// ==========================================
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Stores the client secret for one-time display
|
|
45
|
+
* Set after createClient or regenerateSecret, cleared after user acknowledges
|
|
46
|
+
*/
|
|
47
|
+
export const oauthNewClientSecretAtom = atom<string | null>(null);
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The client ID associated with the new secret
|
|
51
|
+
* Used to know which client the secret belongs to
|
|
52
|
+
*/
|
|
53
|
+
export const oauthNewClientIdAtom = atom<string | null>(null);
|
|
54
|
+
|
|
55
|
+
// ==========================================
|
|
56
|
+
// CONSENT FLOW STATE
|
|
57
|
+
// ==========================================
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Loading state for consent screen data fetch
|
|
61
|
+
*/
|
|
62
|
+
export const oauthConsentLoadingAtom = atom<boolean>(false);
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Error state for consent flow
|
|
66
|
+
*/
|
|
67
|
+
export const oauthConsentErrorAtom = atom<Error | null>(null);
|
|
68
|
+
|
|
69
|
+
// ==========================================
|
|
70
|
+
// WRITE ATOMS (for actions)
|
|
71
|
+
// ==========================================
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Write atom to set a new client secret and associated client ID
|
|
75
|
+
*/
|
|
76
|
+
export const setNewClientSecretAtom = atom(null, (get, set, value: { clientId: string; secret: string } | null) => {
|
|
77
|
+
if (value === null) {
|
|
78
|
+
set(oauthNewClientSecretAtom, null);
|
|
79
|
+
set(oauthNewClientIdAtom, null);
|
|
80
|
+
} else {
|
|
81
|
+
set(oauthNewClientSecretAtom, value.secret);
|
|
82
|
+
set(oauthNewClientIdAtom, value.clientId);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Write atom to clear the new client secret (after user acknowledges)
|
|
88
|
+
*/
|
|
89
|
+
export const clearNewClientSecretAtom = atom(null, (get, set) => {
|
|
90
|
+
set(oauthNewClientSecretAtom, null);
|
|
91
|
+
set(oauthNewClientIdAtom, null);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Write atom to update the clients list
|
|
96
|
+
*/
|
|
97
|
+
export const setOAuthClientsAtom = atom(null, (get, set, clients: OAuthClientInterface[]) => {
|
|
98
|
+
set(oauthClientsAtom, clients);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Write atom to add a client to the list
|
|
103
|
+
*/
|
|
104
|
+
export const addOAuthClientAtom = atom(null, (get, set, client: OAuthClientInterface) => {
|
|
105
|
+
const clients = get(oauthClientsAtom);
|
|
106
|
+
set(oauthClientsAtom, [...clients, client]);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Write atom to update a client in the list
|
|
111
|
+
*/
|
|
112
|
+
export const updateOAuthClientAtom = atom(null, (get, set, updatedClient: OAuthClientInterface) => {
|
|
113
|
+
const clients = get(oauthClientsAtom);
|
|
114
|
+
const index = clients.findIndex((c) => c.id === updatedClient.id || c.clientId === updatedClient.clientId);
|
|
115
|
+
if (index !== -1) {
|
|
116
|
+
const newClients = [...clients];
|
|
117
|
+
newClients[index] = updatedClient;
|
|
118
|
+
set(oauthClientsAtom, newClients);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Write atom to remove a client from the list
|
|
124
|
+
*/
|
|
125
|
+
export const removeOAuthClientAtom = atom(null, (get, set, clientId: string) => {
|
|
126
|
+
const clients = get(oauthClientsAtom);
|
|
127
|
+
set(
|
|
128
|
+
oauthClientsAtom,
|
|
129
|
+
clients.filter((c) => c.id !== clientId && c.clientId !== clientId),
|
|
130
|
+
);
|
|
131
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { formatDistanceToNow } from "date-fns";
|
|
4
|
+
import { Key, MoreVertical, Pencil, Trash2 } from "lucide-react";
|
|
5
|
+
import {
|
|
6
|
+
Badge,
|
|
7
|
+
Button,
|
|
8
|
+
Card,
|
|
9
|
+
CardContent,
|
|
10
|
+
CardDescription,
|
|
11
|
+
CardHeader,
|
|
12
|
+
CardTitle,
|
|
13
|
+
DropdownMenu,
|
|
14
|
+
DropdownMenuContent,
|
|
15
|
+
DropdownMenuItem,
|
|
16
|
+
DropdownMenuTrigger,
|
|
17
|
+
} from "../../../shadcnui";
|
|
18
|
+
import { OAuthClientInterface } from "../interfaces/oauth.interface";
|
|
19
|
+
|
|
20
|
+
export interface OAuthClientCardProps {
|
|
21
|
+
/** The OAuth client to display */
|
|
22
|
+
client: OAuthClientInterface;
|
|
23
|
+
/** Called when card is clicked */
|
|
24
|
+
onClick?: () => void;
|
|
25
|
+
/** Called when edit is clicked */
|
|
26
|
+
onEdit?: () => void;
|
|
27
|
+
/** Called when delete is clicked */
|
|
28
|
+
onDelete?: () => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Card component for displaying an OAuth client in a list
|
|
33
|
+
*/
|
|
34
|
+
export function OAuthClientCard({
|
|
35
|
+
client,
|
|
36
|
+
onClick,
|
|
37
|
+
onEdit,
|
|
38
|
+
onDelete,
|
|
39
|
+
}: OAuthClientCardProps) {
|
|
40
|
+
// Truncate client ID for display
|
|
41
|
+
const truncatedId = client.clientId.length > 12
|
|
42
|
+
? `${client.clientId.slice(0, 8)}...${client.clientId.slice(-4)}`
|
|
43
|
+
: client.clientId;
|
|
44
|
+
|
|
45
|
+
const createdAgo = client.createdAt
|
|
46
|
+
? formatDistanceToNow(new Date(client.createdAt), { addSuffix: true })
|
|
47
|
+
: "Unknown";
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Card
|
|
51
|
+
className={`cursor-pointer transition-colors hover:bg-accent/50 ${!client.isActive ? "opacity-60" : ""}`}
|
|
52
|
+
onClick={onClick}
|
|
53
|
+
>
|
|
54
|
+
<CardHeader className="pb-2">
|
|
55
|
+
<div className="flex items-start justify-between">
|
|
56
|
+
<div className="flex items-center gap-2">
|
|
57
|
+
<Key className="h-5 w-5 text-muted-foreground" />
|
|
58
|
+
<CardTitle className="text-lg">{client.name}</CardTitle>
|
|
59
|
+
</div>
|
|
60
|
+
<div className="flex items-center gap-2">
|
|
61
|
+
<Badge variant={client.isActive ? "default" : "secondary"}>
|
|
62
|
+
{client.isActive ? "Active" : "Inactive"}
|
|
63
|
+
</Badge>
|
|
64
|
+
{(onEdit || onDelete) && (
|
|
65
|
+
<DropdownMenu>
|
|
66
|
+
<DropdownMenuTrigger onClick={(e) => e.stopPropagation()}>
|
|
67
|
+
<Button render={<div />} nativeButton={false} variant="ghost" size="icon" className="h-8 w-8">
|
|
68
|
+
<MoreVertical className="h-4 w-4" />
|
|
69
|
+
</Button>
|
|
70
|
+
</DropdownMenuTrigger>
|
|
71
|
+
<DropdownMenuContent align="end">
|
|
72
|
+
{onEdit && (
|
|
73
|
+
<DropdownMenuItem onClick={(e) => { e.stopPropagation(); onEdit(); }}>
|
|
74
|
+
<Pencil className="h-4 w-4 mr-2" />
|
|
75
|
+
Edit
|
|
76
|
+
</DropdownMenuItem>
|
|
77
|
+
)}
|
|
78
|
+
{onDelete && (
|
|
79
|
+
<DropdownMenuItem
|
|
80
|
+
onClick={(e) => { e.stopPropagation(); onDelete(); }}
|
|
81
|
+
className="text-destructive"
|
|
82
|
+
>
|
|
83
|
+
<Trash2 className="h-4 w-4 mr-2" />
|
|
84
|
+
Delete
|
|
85
|
+
</DropdownMenuItem>
|
|
86
|
+
)}
|
|
87
|
+
</DropdownMenuContent>
|
|
88
|
+
</DropdownMenu>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
{client.description && (
|
|
93
|
+
<CardDescription className="line-clamp-2">{client.description}</CardDescription>
|
|
94
|
+
)}
|
|
95
|
+
</CardHeader>
|
|
96
|
+
<CardContent>
|
|
97
|
+
<div className="flex flex-wrap gap-x-4 gap-y-1 text-sm text-muted-foreground">
|
|
98
|
+
<span className="font-mono">{truncatedId}</span>
|
|
99
|
+
<span>Created {createdAgo}</span>
|
|
100
|
+
<span>{client.isConfidential ? "Confidential" : "Public"}</span>
|
|
101
|
+
</div>
|
|
102
|
+
</CardContent>
|
|
103
|
+
</Card>
|
|
104
|
+
);
|
|
105
|
+
}
|