@replicated/portal-components 0.0.1 → 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.
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "0.0.1",
3
- "generatedAt": "2025-02-09T00:00:00.000Z",
2
+ "version": "0.0.2",
3
+ "generatedAt": "2025-11-18T13:16:42Z",
4
4
  "components": [
5
5
  {
6
6
  "name": "Button",
@@ -154,7 +154,7 @@
154
154
  "form",
155
155
  "card"
156
156
  ],
157
- "description": "Bordered authentication form with centered branding, email capture, and CTA button built for initial portal logins.",
157
+ "description": "Bordered authentication flow with centered branding, email capture, and CTA button built for initial portal logins. After a successful submission the component transitions to a verification UI showing the 12-digit code field. Pair with the `initiateLogin` server action exported from `@replicated/portal-components/actions`.",
158
158
  "props": [
159
159
  {
160
160
  "name": "logo",
@@ -215,6 +215,19 @@
215
215
  "type": "(email: string) => Promise<void> | void",
216
216
  "required": false,
217
217
  "description": "Callback invoked when the form submits. Receives the current email string."
218
+ },
219
+ {
220
+ "name": "onVerifyCode",
221
+ "type": "(code: string, redirectPath: string) => Promise<void | { success: true } | { success: false; message?: string }>",
222
+ "required": false,
223
+ "description": "Called when the user submits the 12-digit code in the verification step."
224
+ },
225
+ {
226
+ "name": "redirectPath",
227
+ "type": "string",
228
+ "required": false,
229
+ "defaultValue": "\"/\"",
230
+ "description": "Relative path to navigate to once verification succeeds."
218
231
  }
219
232
  ],
220
233
  "composition": {
@@ -237,6 +250,18 @@
237
250
  "description": "Override the logo with a vendor-specific mark.",
238
251
  "scenario": "White-labeled portal for an ISV.",
239
252
  "code": "<Login logo={<VendorLogo />} title=\"Acme Deployment Portal\" description=\"Access licenses, releases, and support\" />"
253
+ },
254
+ {
255
+ "id": "verification-step",
256
+ "description": "The component handles the verification step automatically after onContinue resolves.",
257
+ "scenario": "Customer enters their code from email to finish authentication.",
258
+ "code": "<Login onContinue={continueWithEmail} />"
259
+ },
260
+ {
261
+ "id": "server-action",
262
+ "description": "Invoke the provided server action to trigger passwordless email flows.",
263
+ "scenario": "Next.js App Router login route wired to Replicated APIs.",
264
+ "code": "'use server';\nimport { initiateLogin } from \"@replicated/portal-components/actions\";\n\nexport async function continueWithEmail(email: string) {\n await initiateLogin.run({ email }, {\n vendorId: \"demo-vendor\",\n licenseId: \"demo-license\",\n userId: \"anonymous\"\n });\n}\n\n<Login onContinue={continueWithEmail} />"
240
265
  }
241
266
  ],
242
267
  "behavior": {
@@ -1,7 +1,7 @@
1
1
  # Component Registry
2
2
 
3
- - Version: 0.0.1
4
- - Generated: 2025-02-09T00:00:00.000Z
3
+ - Version: 0.0.2
4
+ - Generated: 2025-11-18T13:16:42Z
5
5
 
6
6
  ## Button
7
7
 
@@ -45,7 +45,7 @@ Theme-aware CTA for triggering portal actions with optional icons and loading fe
45
45
 
46
46
  ## Login
47
47
 
48
- Bordered authentication form with centered branding, email capture, and CTA button built for initial portal logins.
48
+ Bordered authentication flow with centered branding, email capture, and CTA button built for initial portal logins. After a successful submission the component transitions to a verification UI showing the 12-digit code field. Pair with the `initiateLogin` server action exported from `@replicated/portal-components/actions`.
49
49
 
50
50
  **Category:** pattern
51
51
  **Stability:** experimental
@@ -61,6 +61,8 @@ Bordered authentication form with centered branding, email capture, and CTA butt
61
61
  | initialEmail | `string` | No | Prefills the input when the user already provided their email. |
62
62
  | isSubmitting | `boolean` | No | When true the form disables interactions and shows the button spinner. |
63
63
  | onContinue | `(email: string) => Promise<void> \| void` | No | Callback invoked when the form submits. Receives the current email string. |
64
+ | onVerifyCode | `(code: string, redirectPath: string) => Promise<void \| { success: true } \| { success: false; message?: string }>` | No | Called when the user submits the 12-digit code in the verification step. |
65
+ | redirectPath | `string` | No | Relative path to navigate to once verification succeeds. |
64
66
 
65
67
  **Examples**
66
68
 
@@ -75,3 +77,26 @@ Bordered authentication form with centered branding, email capture, and CTA butt
75
77
  ```tsx
76
78
  <Login logo={<VendorLogo />} title="Acme Deployment Portal" description="Access licenses, releases, and support" />
77
79
  ```
80
+
81
+ **The component handles the verification step automatically after onContinue resolves.**
82
+
83
+ ```tsx
84
+ <Login onContinue={continueWithEmail} />
85
+ ```
86
+
87
+ **Invoke the provided server action to trigger passwordless email flows.**
88
+
89
+ ```tsx
90
+ 'use server';
91
+ import { initiateLogin } from "@replicated/portal-components/actions";
92
+
93
+ export async function continueWithEmail(email: string) {
94
+ await initiateLogin.run({ email }, {
95
+ vendorId: "demo-vendor",
96
+ licenseId: "demo-license",
97
+ userId: "anonymous"
98
+ });
99
+ }
100
+
101
+ <Login onContinue={continueWithEmail} />
102
+ ```
@@ -16,8 +16,33 @@ interface PortalServerActionDefinition<Input, Output> {
16
16
  description: string;
17
17
  visibility: PortalActionVisibility;
18
18
  tags: string[];
19
- run: (input: Input, context: PortalActionContext) => Promise<Output>;
19
+ run: (input: Input, context?: PortalActionContext) => Promise<Output>;
20
20
  }
21
21
  declare const defineServerAction: <Input, Output>(definition: PortalServerActionDefinition<Input, Output>) => PortalServerActionDefinition<Input, Output>;
22
+ interface InitiateLoginInput {
23
+ email: string;
24
+ }
25
+ interface InitiateLoginResult {
26
+ status: "ok";
27
+ requestedAt: string;
28
+ message: string;
29
+ }
30
+ /**
31
+ * Reference server action for initiating the passwordless login flow.
32
+ * Real portals should replace the simulated delay with a call to their auth API.
33
+ */
34
+ declare const initiateLogin: PortalServerActionDefinition<InitiateLoginInput, InitiateLoginResult>;
35
+ interface VerifyMagicLinkInput {
36
+ nonce: string;
37
+ }
38
+ interface VerifyMagicLinkResult {
39
+ token: string;
40
+ raw: unknown;
41
+ }
42
+ interface VerifyMagicLinkError {
43
+ code: "invalid_code" | "unknown";
44
+ message: string;
45
+ }
46
+ declare const verifyMagicLink: PortalServerActionDefinition<VerifyMagicLinkInput, VerifyMagicLinkResult>;
22
47
 
23
- export { type PortalActionContext, type PortalActionVisibility, type PortalServerActionDefinition, defineServerAction };
48
+ export { type InitiateLoginInput, type InitiateLoginResult, type PortalActionContext, type PortalActionVisibility, type PortalServerActionDefinition, type VerifyMagicLinkError, type VerifyMagicLinkInput, type VerifyMagicLinkResult, defineServerAction, initiateLogin, verifyMagicLink };
@@ -16,8 +16,33 @@ interface PortalServerActionDefinition<Input, Output> {
16
16
  description: string;
17
17
  visibility: PortalActionVisibility;
18
18
  tags: string[];
19
- run: (input: Input, context: PortalActionContext) => Promise<Output>;
19
+ run: (input: Input, context?: PortalActionContext) => Promise<Output>;
20
20
  }
21
21
  declare const defineServerAction: <Input, Output>(definition: PortalServerActionDefinition<Input, Output>) => PortalServerActionDefinition<Input, Output>;
22
+ interface InitiateLoginInput {
23
+ email: string;
24
+ }
25
+ interface InitiateLoginResult {
26
+ status: "ok";
27
+ requestedAt: string;
28
+ message: string;
29
+ }
30
+ /**
31
+ * Reference server action for initiating the passwordless login flow.
32
+ * Real portals should replace the simulated delay with a call to their auth API.
33
+ */
34
+ declare const initiateLogin: PortalServerActionDefinition<InitiateLoginInput, InitiateLoginResult>;
35
+ interface VerifyMagicLinkInput {
36
+ nonce: string;
37
+ }
38
+ interface VerifyMagicLinkResult {
39
+ token: string;
40
+ raw: unknown;
41
+ }
42
+ interface VerifyMagicLinkError {
43
+ code: "invalid_code" | "unknown";
44
+ message: string;
45
+ }
46
+ declare const verifyMagicLink: PortalServerActionDefinition<VerifyMagicLinkInput, VerifyMagicLinkResult>;
22
47
 
23
- export { type PortalActionContext, type PortalActionVisibility, type PortalServerActionDefinition, defineServerAction };
48
+ export { type InitiateLoginInput, type InitiateLoginResult, type PortalActionContext, type PortalActionVisibility, type PortalServerActionDefinition, type VerifyMagicLinkError, type VerifyMagicLinkInput, type VerifyMagicLinkResult, defineServerAction, initiateLogin, verifyMagicLink };
@@ -7,7 +7,97 @@
7
7
 
8
8
  // src/actions/index.ts
9
9
  var defineServerAction = (definition) => definition;
10
+ var initiateLogin = defineServerAction({
11
+ id: "auth/initiate-login",
12
+ description: "Begins the passwordless login flow by dispatching a magic link email.",
13
+ visibility: "customer",
14
+ tags: ["auth", "login", "session"],
15
+ async run(input) {
16
+ const origin = process.env.REPLICATED_APP_ORIGIN ?? "https://replicated.app";
17
+ const endpoint = `${origin.replace(/\/+$/, "")}/v3/login/magic-link`;
18
+ const appSlug = process.env.PORTAL_APP_SLUG;
19
+ if (!appSlug) {
20
+ throw new Error("PORTAL_APP_SLUG is not configured");
21
+ }
22
+ const portalOrigin = process.env.PORTAL_ORIGIN ?? "https://enterprise.replicated.com";
23
+ const redirectUri = `${portalOrigin.replace(/\/+$/, "")}/${appSlug}/login`;
24
+ if (process.env.NODE_ENV !== "production") {
25
+ console.debug(
26
+ "[portal-components] initiating login via %s for app %s redirecting to %s",
27
+ endpoint,
28
+ appSlug,
29
+ redirectUri
30
+ );
31
+ }
32
+ const response = await fetch(endpoint, {
33
+ method: "POST",
34
+ headers: {
35
+ "content-type": "application/json"
36
+ },
37
+ body: JSON.stringify({
38
+ app_slug: appSlug,
39
+ email_address: input.email,
40
+ redirect_uri: redirectUri
41
+ })
42
+ });
43
+ if (!response.ok) {
44
+ throw new Error(
45
+ `Magic link request failed (${response.status} ${response.statusText})`
46
+ );
47
+ }
48
+ return {
49
+ status: "ok",
50
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
51
+ message: `Magic link requested for ${input.email}`
52
+ };
53
+ }
54
+ });
55
+ var verifyMagicLink = defineServerAction({
56
+ id: "auth/verify-magic-link",
57
+ description: "Verifies the 12-digit code provided via email and returns a JWT.",
58
+ visibility: "customer",
59
+ tags: ["auth", "login", "verify"],
60
+ async run({ nonce }) {
61
+ const origin = process.env.REPLICATED_APP_ORIGIN ?? "https://replicated.app";
62
+ const endpoint = `${origin.replace(/\/+$/, "")}/v3/login/magic-link/verify`;
63
+ if (process.env.NODE_ENV !== "production") {
64
+ console.debug(
65
+ "[portal-components] verifying magic link via %s",
66
+ endpoint
67
+ );
68
+ }
69
+ const response = await fetch(endpoint, {
70
+ method: "POST",
71
+ headers: {
72
+ "content-type": "application/json"
73
+ },
74
+ body: JSON.stringify({ nonce })
75
+ });
76
+ if (!response.ok) {
77
+ if (response.status === 401) {
78
+ const error2 = {
79
+ code: "invalid_code",
80
+ message: "Incorrect code, check your email and try again."
81
+ };
82
+ throw error2;
83
+ }
84
+ const error = {
85
+ code: "unknown",
86
+ message: `Magic link verification failed (${response.status} ${response.statusText})`
87
+ };
88
+ throw error;
89
+ }
90
+ const payload = await response.json();
91
+ const token = payload?.token ?? payload?.jwt ?? payload?.access_token;
92
+ if (typeof token !== "string") {
93
+ throw new Error("Magic link verification succeeded but no token returned");
94
+ }
95
+ return { token, raw: payload };
96
+ }
97
+ });
10
98
 
11
99
  exports.defineServerAction = defineServerAction;
100
+ exports.initiateLogin = initiateLogin;
101
+ exports.verifyMagicLink = verifyMagicLink;
12
102
  //# sourceMappingURL=index.js.map
13
103
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/actions/index.ts"],"names":[],"mappings":";;;;;;;;AAwBO,IAAM,kBAAA,GAAqB,CAChC,UAAA,KACG","file":"index.js","sourcesContent":["/**\n * Light-weight type helpers for defining Server Actions that align with the\n * enterprise portal guardrails. The component library does not implement\n * specific actions, but it exports helpers so downstream portals can describe\n * their actions with consistent metadata.\n */\n\nexport type PortalActionVisibility = \"vendor\" | \"customer\";\n\nexport interface PortalActionContext {\n vendorId: string;\n licenseId: string;\n userId: string;\n signal?: AbortSignal;\n}\n\nexport interface PortalServerActionDefinition<Input, Output> {\n id: string;\n description: string;\n visibility: PortalActionVisibility;\n tags: string[];\n run: (input: Input, context: PortalActionContext) => Promise<Output>;\n}\n\nexport const defineServerAction = <Input, Output>(\n definition: PortalServerActionDefinition<Input, Output>\n) => definition;\n"]}
1
+ {"version":3,"sources":["../../src/actions/index.ts"],"names":["error"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Light-weight type helpers for defining Server Actions that align with the\n * enterprise portal guardrails. The component library does not implement\n * specific actions, but it exports helpers so downstream portals can describe\n * their actions with consistent metadata.\n */\n\nexport type PortalActionVisibility = \"vendor\" | \"customer\";\n\nexport interface PortalActionContext {\n vendorId: string;\n licenseId: string;\n userId: string;\n signal?: AbortSignal;\n}\n\nexport interface PortalServerActionDefinition<Input, Output> {\n id: string;\n description: string;\n visibility: PortalActionVisibility;\n tags: string[];\n run: (input: Input, context?: PortalActionContext) => Promise<Output>;\n}\n\nexport const defineServerAction = <Input, Output>(\n definition: PortalServerActionDefinition<Input, Output>\n) => definition;\n\nexport interface InitiateLoginInput {\n email: string;\n}\n\nexport interface InitiateLoginResult {\n status: \"ok\";\n requestedAt: string;\n message: string;\n}\n\n/**\n * Reference server action for initiating the passwordless login flow.\n * Real portals should replace the simulated delay with a call to their auth API.\n */\nexport const initiateLogin = defineServerAction<\n InitiateLoginInput,\n InitiateLoginResult\n>({\n id: \"auth/initiate-login\",\n description:\n \"Begins the passwordless login flow by dispatching a magic link email.\",\n visibility: \"customer\",\n tags: [\"auth\", \"login\", \"session\"],\n async run(input) {\n const origin =\n process.env.REPLICATED_APP_ORIGIN ?? \"https://replicated.app\";\n const endpoint = `${origin.replace(/\\/+$/, \"\")}/v3/login/magic-link`;\n const appSlug = process.env.PORTAL_APP_SLUG;\n if (!appSlug) {\n throw new Error(\"PORTAL_APP_SLUG is not configured\");\n }\n const portalOrigin =\n process.env.PORTAL_ORIGIN ?? \"https://enterprise.replicated.com\";\n const redirectUri = `${portalOrigin.replace(/\\/+$/, \"\")}/${appSlug}/login`;\n\n if (process.env.NODE_ENV !== \"production\") {\n console.debug(\n \"[portal-components] initiating login via %s for app %s redirecting to %s\",\n endpoint,\n appSlug,\n redirectUri\n );\n }\n\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\"\n },\n body: JSON.stringify({\n app_slug: appSlug,\n email_address: input.email,\n redirect_uri: redirectUri\n })\n });\n\n if (!response.ok) {\n throw new Error(\n `Magic link request failed (${response.status} ${response.statusText})`\n );\n }\n\n return {\n status: \"ok\",\n requestedAt: new Date().toISOString(),\n message: `Magic link requested for ${input.email}`\n };\n }\n});\n\nexport interface VerifyMagicLinkInput {\n nonce: string;\n}\n\nexport interface VerifyMagicLinkResult {\n token: string;\n raw: unknown;\n}\n\nexport interface VerifyMagicLinkError {\n code: \"invalid_code\" | \"unknown\";\n message: string;\n}\n\nexport const verifyMagicLink = defineServerAction<\n VerifyMagicLinkInput,\n VerifyMagicLinkResult\n>({\n id: \"auth/verify-magic-link\",\n description: \"Verifies the 12-digit code provided via email and returns a JWT.\",\n visibility: \"customer\",\n tags: [\"auth\", \"login\", \"verify\"],\n async run({ nonce }) {\n const origin =\n process.env.REPLICATED_APP_ORIGIN ?? \"https://replicated.app\";\n const endpoint = `${origin.replace(/\\/+$/, \"\")}/v3/login/magic-link/verify`;\n\n if (process.env.NODE_ENV !== \"production\") {\n console.debug(\n \"[portal-components] verifying magic link via %s\",\n endpoint\n );\n }\n\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\"\n },\n body: JSON.stringify({ nonce })\n });\n\n if (!response.ok) {\n if (response.status === 401) {\n const error: VerifyMagicLinkError = {\n code: \"invalid_code\",\n message: \"Incorrect code, check your email and try again.\"\n };\n throw error;\n }\n const error: VerifyMagicLinkError = {\n code: \"unknown\",\n message: `Magic link verification failed (${response.status} ${response.statusText})`\n };\n throw error;\n }\n\n const payload = await response.json();\n const token = payload?.token ?? payload?.jwt ?? payload?.access_token;\n if (typeof token !== \"string\") {\n throw new Error(\"Magic link verification succeeded but no token returned\");\n }\n\n return { token, raw: payload };\n }\n});\n"]}
@@ -5,7 +5,95 @@
5
5
 
6
6
  // src/actions/index.ts
7
7
  var defineServerAction = (definition) => definition;
8
+ var initiateLogin = defineServerAction({
9
+ id: "auth/initiate-login",
10
+ description: "Begins the passwordless login flow by dispatching a magic link email.",
11
+ visibility: "customer",
12
+ tags: ["auth", "login", "session"],
13
+ async run(input) {
14
+ const origin = process.env.REPLICATED_APP_ORIGIN ?? "https://replicated.app";
15
+ const endpoint = `${origin.replace(/\/+$/, "")}/v3/login/magic-link`;
16
+ const appSlug = process.env.PORTAL_APP_SLUG;
17
+ if (!appSlug) {
18
+ throw new Error("PORTAL_APP_SLUG is not configured");
19
+ }
20
+ const portalOrigin = process.env.PORTAL_ORIGIN ?? "https://enterprise.replicated.com";
21
+ const redirectUri = `${portalOrigin.replace(/\/+$/, "")}/${appSlug}/login`;
22
+ if (process.env.NODE_ENV !== "production") {
23
+ console.debug(
24
+ "[portal-components] initiating login via %s for app %s redirecting to %s",
25
+ endpoint,
26
+ appSlug,
27
+ redirectUri
28
+ );
29
+ }
30
+ const response = await fetch(endpoint, {
31
+ method: "POST",
32
+ headers: {
33
+ "content-type": "application/json"
34
+ },
35
+ body: JSON.stringify({
36
+ app_slug: appSlug,
37
+ email_address: input.email,
38
+ redirect_uri: redirectUri
39
+ })
40
+ });
41
+ if (!response.ok) {
42
+ throw new Error(
43
+ `Magic link request failed (${response.status} ${response.statusText})`
44
+ );
45
+ }
46
+ return {
47
+ status: "ok",
48
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
49
+ message: `Magic link requested for ${input.email}`
50
+ };
51
+ }
52
+ });
53
+ var verifyMagicLink = defineServerAction({
54
+ id: "auth/verify-magic-link",
55
+ description: "Verifies the 12-digit code provided via email and returns a JWT.",
56
+ visibility: "customer",
57
+ tags: ["auth", "login", "verify"],
58
+ async run({ nonce }) {
59
+ const origin = process.env.REPLICATED_APP_ORIGIN ?? "https://replicated.app";
60
+ const endpoint = `${origin.replace(/\/+$/, "")}/v3/login/magic-link/verify`;
61
+ if (process.env.NODE_ENV !== "production") {
62
+ console.debug(
63
+ "[portal-components] verifying magic link via %s",
64
+ endpoint
65
+ );
66
+ }
67
+ const response = await fetch(endpoint, {
68
+ method: "POST",
69
+ headers: {
70
+ "content-type": "application/json"
71
+ },
72
+ body: JSON.stringify({ nonce })
73
+ });
74
+ if (!response.ok) {
75
+ if (response.status === 401) {
76
+ const error2 = {
77
+ code: "invalid_code",
78
+ message: "Incorrect code, check your email and try again."
79
+ };
80
+ throw error2;
81
+ }
82
+ const error = {
83
+ code: "unknown",
84
+ message: `Magic link verification failed (${response.status} ${response.statusText})`
85
+ };
86
+ throw error;
87
+ }
88
+ const payload = await response.json();
89
+ const token = payload?.token ?? payload?.jwt ?? payload?.access_token;
90
+ if (typeof token !== "string") {
91
+ throw new Error("Magic link verification succeeded but no token returned");
92
+ }
93
+ return { token, raw: payload };
94
+ }
95
+ });
8
96
 
9
- export { defineServerAction };
97
+ export { defineServerAction, initiateLogin, verifyMagicLink };
10
98
  //# sourceMappingURL=index.js.map
11
99
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/index.ts"],"names":[],"mappings":";;;;;;AAwBO,IAAM,kBAAA,GAAqB,CAChC,UAAA,KACG","file":"index.js","sourcesContent":["/**\n * Light-weight type helpers for defining Server Actions that align with the\n * enterprise portal guardrails. The component library does not implement\n * specific actions, but it exports helpers so downstream portals can describe\n * their actions with consistent metadata.\n */\n\nexport type PortalActionVisibility = \"vendor\" | \"customer\";\n\nexport interface PortalActionContext {\n vendorId: string;\n licenseId: string;\n userId: string;\n signal?: AbortSignal;\n}\n\nexport interface PortalServerActionDefinition<Input, Output> {\n id: string;\n description: string;\n visibility: PortalActionVisibility;\n tags: string[];\n run: (input: Input, context: PortalActionContext) => Promise<Output>;\n}\n\nexport const defineServerAction = <Input, Output>(\n definition: PortalServerActionDefinition<Input, Output>\n) => definition;\n"]}
1
+ {"version":3,"sources":["../../../src/actions/index.ts"],"names":["error"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Light-weight type helpers for defining Server Actions that align with the\n * enterprise portal guardrails. The component library does not implement\n * specific actions, but it exports helpers so downstream portals can describe\n * their actions with consistent metadata.\n */\n\nexport type PortalActionVisibility = \"vendor\" | \"customer\";\n\nexport interface PortalActionContext {\n vendorId: string;\n licenseId: string;\n userId: string;\n signal?: AbortSignal;\n}\n\nexport interface PortalServerActionDefinition<Input, Output> {\n id: string;\n description: string;\n visibility: PortalActionVisibility;\n tags: string[];\n run: (input: Input, context?: PortalActionContext) => Promise<Output>;\n}\n\nexport const defineServerAction = <Input, Output>(\n definition: PortalServerActionDefinition<Input, Output>\n) => definition;\n\nexport interface InitiateLoginInput {\n email: string;\n}\n\nexport interface InitiateLoginResult {\n status: \"ok\";\n requestedAt: string;\n message: string;\n}\n\n/**\n * Reference server action for initiating the passwordless login flow.\n * Real portals should replace the simulated delay with a call to their auth API.\n */\nexport const initiateLogin = defineServerAction<\n InitiateLoginInput,\n InitiateLoginResult\n>({\n id: \"auth/initiate-login\",\n description:\n \"Begins the passwordless login flow by dispatching a magic link email.\",\n visibility: \"customer\",\n tags: [\"auth\", \"login\", \"session\"],\n async run(input) {\n const origin =\n process.env.REPLICATED_APP_ORIGIN ?? \"https://replicated.app\";\n const endpoint = `${origin.replace(/\\/+$/, \"\")}/v3/login/magic-link`;\n const appSlug = process.env.PORTAL_APP_SLUG;\n if (!appSlug) {\n throw new Error(\"PORTAL_APP_SLUG is not configured\");\n }\n const portalOrigin =\n process.env.PORTAL_ORIGIN ?? \"https://enterprise.replicated.com\";\n const redirectUri = `${portalOrigin.replace(/\\/+$/, \"\")}/${appSlug}/login`;\n\n if (process.env.NODE_ENV !== \"production\") {\n console.debug(\n \"[portal-components] initiating login via %s for app %s redirecting to %s\",\n endpoint,\n appSlug,\n redirectUri\n );\n }\n\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\"\n },\n body: JSON.stringify({\n app_slug: appSlug,\n email_address: input.email,\n redirect_uri: redirectUri\n })\n });\n\n if (!response.ok) {\n throw new Error(\n `Magic link request failed (${response.status} ${response.statusText})`\n );\n }\n\n return {\n status: \"ok\",\n requestedAt: new Date().toISOString(),\n message: `Magic link requested for ${input.email}`\n };\n }\n});\n\nexport interface VerifyMagicLinkInput {\n nonce: string;\n}\n\nexport interface VerifyMagicLinkResult {\n token: string;\n raw: unknown;\n}\n\nexport interface VerifyMagicLinkError {\n code: \"invalid_code\" | \"unknown\";\n message: string;\n}\n\nexport const verifyMagicLink = defineServerAction<\n VerifyMagicLinkInput,\n VerifyMagicLinkResult\n>({\n id: \"auth/verify-magic-link\",\n description: \"Verifies the 12-digit code provided via email and returns a JWT.\",\n visibility: \"customer\",\n tags: [\"auth\", \"login\", \"verify\"],\n async run({ nonce }) {\n const origin =\n process.env.REPLICATED_APP_ORIGIN ?? \"https://replicated.app\";\n const endpoint = `${origin.replace(/\\/+$/, \"\")}/v3/login/magic-link/verify`;\n\n if (process.env.NODE_ENV !== \"production\") {\n console.debug(\n \"[portal-components] verifying magic link via %s\",\n endpoint\n );\n }\n\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\"\n },\n body: JSON.stringify({ nonce })\n });\n\n if (!response.ok) {\n if (response.status === 401) {\n const error: VerifyMagicLinkError = {\n code: \"invalid_code\",\n message: \"Incorrect code, check your email and try again.\"\n };\n throw error;\n }\n const error: VerifyMagicLinkError = {\n code: \"unknown\",\n message: `Magic link verification failed (${response.status} ${response.statusText})`\n };\n throw error;\n }\n\n const payload = await response.json();\n const token = payload?.token ?? payload?.jwt ?? payload?.access_token;\n if (typeof token !== \"string\") {\n throw new Error(\"Magic link verification succeeded but no token returned\");\n }\n\n return { token, raw: payload };\n }\n});\n"]}
package/dist/esm/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { forwardRef, useState } from 'react';
1
+ import { forwardRef, useState, useMemo } from 'react';
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
3
 
4
4
  /**
@@ -8,7 +8,7 @@ import { jsxs, jsx } from 'react/jsx-runtime';
8
8
 
9
9
  // package.json
10
10
  var package_default = {
11
- version: "0.0.1"};
11
+ version: "0.0.2"};
12
12
 
13
13
  // src/tokens/index.ts
14
14
  var baseTokens = {
@@ -133,24 +133,67 @@ var Login = forwardRef(
133
133
  initialEmail = "",
134
134
  isSubmitting = false,
135
135
  onContinue,
136
+ onVerifyCode,
137
+ redirectPath = "/",
136
138
  className,
137
139
  ...props
138
140
  }, ref) => {
141
+ const [mode, setMode] = useState("email");
139
142
  const [email, setEmail] = useState(initialEmail);
143
+ const [submittedEmail, setSubmittedEmail] = useState(initialEmail);
144
+ const [verificationCode, setVerificationCode] = useState("");
145
+ const [verifying, setVerifying] = useState(false);
140
146
  const [pending, setPending] = useState(false);
147
+ const [verificationError, setVerificationError] = useState(
148
+ null
149
+ );
141
150
  const handleSubmit = async (event) => {
142
151
  event.preventDefault();
143
- if (!onContinue) {
152
+ if (!onContinue || mode !== "email") {
144
153
  return;
145
154
  }
146
155
  try {
147
156
  setPending(true);
148
157
  await onContinue(email);
158
+ setSubmittedEmail(email);
159
+ setVerificationCode("");
160
+ setVerificationError(null);
161
+ setMode("verify");
149
162
  } finally {
150
163
  setPending(false);
151
164
  }
152
165
  };
153
- const disabled = isSubmitting || pending;
166
+ const disabled = mode === "email" && (isSubmitting || pending);
167
+ const verificationMessage = useMemo(() => {
168
+ if (!submittedEmail) {
169
+ return null;
170
+ }
171
+ return `If there is an account for ${submittedEmail}, an email will be sent with a login code.`;
172
+ }, [submittedEmail]);
173
+ const handleVerify = async () => {
174
+ if (!onVerifyCode || !verificationCode) {
175
+ return;
176
+ }
177
+ try {
178
+ setVerificationError(null);
179
+ setVerifying(true);
180
+ const result = await onVerifyCode(verificationCode, redirectPath);
181
+ if (result && typeof result === "object" && "success" in result && result.success === false) {
182
+ const message = result.message ?? "Unable to verify code. Try again.";
183
+ setVerificationError(message);
184
+ return;
185
+ }
186
+ setVerificationCode("");
187
+ } catch (error) {
188
+ if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
189
+ setVerificationError(error.message);
190
+ } else {
191
+ setVerificationError("Unable to verify code. Try again.");
192
+ }
193
+ } finally {
194
+ setVerifying(false);
195
+ }
196
+ };
154
197
  return /* @__PURE__ */ jsxs(
155
198
  "form",
156
199
  {
@@ -171,7 +214,7 @@ var Login = forwardRef(
171
214
  /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-white/70", children: description })
172
215
  ] })
173
216
  ] }),
174
- /* @__PURE__ */ jsxs("div", { className: "mt-8 space-y-3", children: [
217
+ mode === "email" ? /* @__PURE__ */ jsxs("div", { className: "mt-8 space-y-3", children: [
175
218
  /* @__PURE__ */ jsx(
176
219
  "label",
177
220
  {
@@ -206,6 +249,44 @@ var Login = forwardRef(
206
249
  children: ctaLabel
207
250
  }
208
251
  )
252
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "mt-8 space-y-4", children: [
253
+ /* @__PURE__ */ jsxs("div", { children: [
254
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold uppercase tracking-[0.35em] text-white/65", children: "Verification Code" }),
255
+ verificationMessage ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-white/70", children: verificationMessage }) : null,
256
+ verificationError ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm font-medium text-rose-300", children: verificationError }) : null
257
+ ] }),
258
+ /* @__PURE__ */ jsx(
259
+ "label",
260
+ {
261
+ htmlFor: "login-code",
262
+ className: "text-sm font-medium text-white/70",
263
+ children: "One-time code"
264
+ }
265
+ ),
266
+ /* @__PURE__ */ jsx(
267
+ "input",
268
+ {
269
+ id: "login-code",
270
+ inputMode: "numeric",
271
+ maxLength: 12,
272
+ placeholder: "Enter 12 digit code",
273
+ className: "w-full rounded-2xl border border-white/15 bg-white/5 px-5 py-4 text-base text-white placeholder:text-white/40 focus:border-white/40 focus:outline-none focus:ring-2 focus:ring-white/25",
274
+ value: verificationCode,
275
+ onChange: (event) => setVerificationCode(event.target.value)
276
+ }
277
+ ),
278
+ /* @__PURE__ */ jsx(
279
+ Button,
280
+ {
281
+ type: "button",
282
+ size: "lg",
283
+ className: "w-full justify-center",
284
+ disabled: verificationCode.length !== 12 || verifying,
285
+ isLoading: verifying,
286
+ onClick: handleVerify,
287
+ children: "Verify code"
288
+ }
289
+ )
209
290
  ] })
210
291
  ]
211
292
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../package.json","../../src/tokens/index.ts","../../src/components/button.tsx","../../src/components/login.tsx","../../src/actions/index.ts","../../src/index.ts"],"names":["forwardRef","jsxs","jsx"],"mappings":"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","file":"index.js","sourcesContent":["{\n \"name\": \"@replicated/portal-components\",\n \"version\": \"0.0.1\",\n \"description\": \"Opinionated component library for Replicated enterprise portals\",\n \"license\": \"Apache-2.0\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/replicatedhq/enterprise-portal-components.git\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/replicatedhq/enterprise-portal-components/issues\"\n },\n \"homepage\": \"https://github.com/replicatedhq/enterprise-portal-components#readme\",\n \"main\": \"dist/index.js\",\n \"module\": \"dist/esm/index.js\",\n \"types\": \"dist/index.d.ts\",\n \"style\": \"dist/styles.css\",\n \"files\": [\n \"dist\",\n \"components/metadata\"\n ],\n \"sideEffects\": [\n \"*.css\",\n \"*.scss\"\n ],\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/esm/index.js\",\n \"require\": \"./dist/index.js\"\n },\n \"./tokens\": {\n \"types\": \"./dist/tokens/index.d.ts\",\n \"import\": \"./dist/esm/tokens/index.js\",\n \"require\": \"./dist/tokens/index.js\"\n },\n \"./actions\": {\n \"types\": \"./dist/actions/index.d.ts\",\n \"import\": \"./dist/esm/actions/index.js\",\n \"require\": \"./dist/actions/index.js\"\n },\n \"./styles.css\": \"./dist/styles.css\",\n \"./metadata/registry.json\": \"./components/metadata/registry.json\"\n },\n \"scripts\": {\n \"clean\": \"rimraf dist\",\n \"build\": \"npm run clean && npm run registry:generate && npm run build:css && tsup\",\n \"build:css\": \"tailwindcss -i ./src/styles.css -o ./dist/styles.css --config tailwind.config.ts\",\n \"dev\": \"tsup --watch\",\n \"typecheck\": \"tsc --noEmit\",\n \"registry:generate\": \"tsx scripts/generate-registry.ts\",\n \"size\": \"size-limit\",\n \"release\": \"changeset\"\n },\n \"peerDependencies\": {\n \"react\": \"^19.0.0\",\n \"react-dom\": \"^19.0.0\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.27.8\",\n \"@size-limit/preset-big-lib\": \"^9.0.0\",\n \"@types/node\": \"^22.10.1\",\n \"@types/react\": \"^19.0.4\",\n \"@types/react-dom\": \"^19.0.3\",\n \"autoprefixer\": \"^10.4.20\",\n \"postcss\": \"^8.4.49\",\n \"react\": \"19.0.0\",\n \"react-dom\": \"19.0.0\",\n \"rimraf\": \"^6.0.1\",\n \"size-limit\": \"^9.0.0\",\n \"tailwindcss\": \"^3.4.17\",\n \"tslib\": \"^2.7.0\",\n \"tsup\": \"^8.3.5\",\n \"tsx\": \"^4.19.2\",\n \"typescript\": \"^5.6.3\"\n },\n \"engines\": {\n \"node\": \">=20\"\n },\n \"packageManager\": \"npm@10\",\n \"size-limit\": [\n {\n \"name\": \"core entry\",\n \"path\": \"dist/esm/index.js\",\n \"import\": \"{ Login }\",\n \"limit\": \"35 KB\",\n \"gzip\": true\n }\n ]\n}\n","type DeepPartial<T> = {\n [Key in keyof T]?: T[Key] extends Record<string, unknown>\n ? DeepPartial<T[Key]>\n : T[Key];\n};\n\nexport interface PortalThemeTokens {\n colors: {\n background: string;\n foreground: string;\n surfaceMuted: string;\n primary: string;\n onPrimary: string;\n secondary: string;\n onSecondary: string;\n success: string;\n warning: string;\n danger: string;\n };\n radii: {\n lg: string;\n md: string;\n sm: string;\n };\n spacing: Record<\"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\", string>;\n typography: {\n fontFamily: string;\n monoFamily: string;\n lineHeight: Record<\"tight\" | \"normal\" | \"relaxed\", string>;\n };\n shadows: {\n focus: string;\n overlay: string;\n };\n}\n\nconst baseTokens: PortalThemeTokens = {\n colors: {\n background: \"#0b0d12\",\n foreground: \"#f4f6fb\",\n surfaceMuted: \"#1b1f2a\",\n primary: \"#0d6efd\",\n onPrimary: \"#ffffff\",\n secondary: \"#6c757d\",\n onSecondary: \"#ffffff\",\n success: \"#22c55e\",\n warning: \"#fbbf24\",\n danger: \"#ef4444\"\n },\n radii: {\n lg: \"16px\",\n md: \"10px\",\n sm: \"6px\"\n },\n spacing: {\n xs: \"0.25rem\",\n sm: \"0.5rem\",\n md: \"0.75rem\",\n lg: \"1rem\",\n xl: \"1.5rem\"\n },\n typography: {\n fontFamily:\n \"Inter, 'SF Pro Display', 'Segoe UI', system-ui, -apple-system, sans-serif\",\n monoFamily: \"'JetBrains Mono', 'SFMono-Regular', Consolas, monospace\",\n lineHeight: {\n tight: \"1.2\",\n normal: \"1.5\",\n relaxed: \"1.75\"\n }\n },\n shadows: {\n focus: \"0 0 0 3px color-mix(in srgb, #0d6efd 35%, transparent)\",\n overlay: \"0px 20px 55px rgba(5, 7, 11, 0.5)\"\n }\n};\n\nconst mergeTokens = (\n base: PortalThemeTokens,\n overrides?: DeepPartial<PortalThemeTokens>\n): PortalThemeTokens => {\n if (!overrides) {\n return base;\n }\n\n const clone = structuredClone(base);\n\n const apply = (target: Record<string, any>, source: Record<string, any>) => {\n Object.entries(source).forEach(([key, value]) => {\n if (\n value &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n typeof target[key] === \"object\"\n ) {\n apply(target[key], value);\n return;\n }\n\n target[key] = value;\n });\n };\n\n apply(clone as Record<string, any>, overrides as Record<string, any>);\n return clone;\n};\n\nexport const portalThemeTokens: PortalThemeTokens =\n mergeTokens(baseTokens);\n\nexport const createPortalTheme = (\n overrides?: DeepPartial<PortalThemeTokens>\n) => mergeTokens(baseTokens, overrides);\n\nexport type { DeepPartial as PortalThemeOverrides };\n","import {\n forwardRef,\n type ComponentPropsWithoutRef,\n type ReactNode\n} from \"react\";\n\nconst buttonVariants = [\"primary\", \"secondary\", \"ghost\", \"destructive\"] as const;\nconst buttonSizes = [\"sm\", \"md\", \"lg\"] as const;\n\nconst variantStyles: Record<ButtonVariant, string> = {\n primary:\n \"bg-primary text-primary-foreground hover:bg-primary/90 focus-visible:ring-primary\",\n secondary:\n \"bg-secondary/20 text-secondary-foreground hover:bg-secondary/30 focus-visible:ring-secondary\",\n ghost:\n \"bg-transparent text-primary hover:bg-primary/10 focus-visible:ring-primary/60\",\n destructive:\n \"bg-danger text-white hover:bg-danger/90 focus-visible:ring-danger\"\n};\n\nconst sizeStyles: Record<ButtonSize, string> = {\n sm: \"h-8 px-3 text-sm\",\n md: \"h-10 px-4 text-sm\",\n lg: \"h-12 px-6 text-base\"\n};\n\nconst inlineFlexBase =\n \"inline-flex items-center justify-center gap-2 rounded-md font-medium tracking-tight transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-60\";\n\nconst Spinner = () => (\n <span className=\"inline-flex h-3.5 w-3.5 animate-spin items-center justify-center\">\n <span className=\"h-3 w-3 rounded-full border-2 border-transparent border-t-current\" />\n </span>\n);\n\nexport type ButtonVariant = (typeof buttonVariants)[number];\nexport type ButtonSize = (typeof buttonSizes)[number];\n\nexport interface ButtonProps extends ComponentPropsWithoutRef<\"button\"> {\n variant?: ButtonVariant;\n size?: ButtonSize;\n isLoading?: boolean;\n leadingIcon?: ReactNode;\n trailingIcon?: ReactNode;\n}\n\nconst composeClassName = (\n ...values: Array<string | undefined | false>\n): string => values.filter(Boolean).join(\" \");\n\n/**\n * Button is the primary interactive primitive for triggering portal actions.\n * It is theme aware via CSS variables generated from portal tokens.\n */\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n (\n {\n variant = \"primary\",\n size = \"md\",\n type = \"button\",\n isLoading = false,\n leadingIcon,\n trailingIcon,\n disabled,\n className,\n children,\n ...props\n },\n ref\n ) => {\n const computedLeading = isLoading ? <Spinner /> : leadingIcon;\n const computedDisabled = disabled ?? isLoading;\n\n return (\n <button\n ref={ref}\n type={type}\n className={composeClassName(\n inlineFlexBase,\n variantStyles[variant],\n sizeStyles[size],\n className\n )}\n aria-busy={isLoading || undefined}\n disabled={computedDisabled}\n {...props}\n >\n {computedLeading ? (\n <span aria-hidden=\"true\" className=\"inline-flex\">\n {computedLeading}\n </span>\n ) : null}\n <span className=\"flex-1 whitespace-nowrap\">{children}</span>\n {trailingIcon ? (\n <span aria-hidden=\"true\" className=\"inline-flex\">\n {trailingIcon}\n </span>\n ) : null}\n </button>\n );\n }\n);\n\nButton.displayName = \"Button\";\n","'use client';\n\nimport {\n forwardRef,\n useState,\n type ComponentPropsWithoutRef,\n type FormEvent,\n type ReactNode\n} from \"react\";\n\nimport { Button } from \"./button\";\n\nexport interface LoginProps\n extends Omit<ComponentPropsWithoutRef<\"form\">, \"onSubmit\" | \"children\"> {\n logo?: ReactNode;\n title?: string;\n description?: string;\n label?: string;\n placeholder?: string;\n ctaLabel?: string;\n initialEmail?: string;\n isSubmitting?: boolean;\n onContinue?: (email: string) => Promise<void> | void;\n}\n\n/**\n * Login renders a compact form card tailored for portal authentication flows.\n * The component keeps styling self-contained so it can be dropped into the\n * local Next.js template without additional wrappers.\n */\nexport const Login = forwardRef<HTMLFormElement, LoginProps>(\n (\n {\n logo,\n title = \"Enterprise Factory Installation Portal\",\n description = \"Sign in to manage your enterprise installation\",\n label = \"Work email address\",\n placeholder = \"you@company.com\",\n ctaLabel = \"Continue with email →\",\n initialEmail = \"\",\n isSubmitting = false,\n onContinue,\n className,\n ...props\n },\n ref\n ) => {\n const [email, setEmail] = useState(initialEmail);\n const [pending, setPending] = useState(false);\n\n const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n if (!onContinue) {\n return;\n }\n\n try {\n setPending(true);\n await onContinue(email);\n } finally {\n setPending(false);\n }\n };\n\n const disabled = isSubmitting || pending;\n\n return (\n <form\n ref={ref}\n className={[\n \"w-full max-w-md rounded-3xl border border-white/15 bg-slate-950/80 p-10 shadow-[0_25px_70px_rgba(5,7,11,0.65)] backdrop-blur\",\n \"text-white transition-shadow\",\n className\n ]\n .filter(Boolean)\n .join(\" \")}\n onSubmit={handleSubmit}\n {...props}\n >\n <div className=\"flex flex-col items-center gap-4 text-center\">\n {logo ?? (\n <div className=\"flex h-16 w-16 items-center justify-center rounded-2xl border border-white/20 bg-white/10 text-lg font-semibold leading-tight\">\n EP\n </div>\n )}\n <div>\n <p className=\"text-base font-semibold uppercase tracking-[0.35em] text-white/65\">\n Enterprise Portal\n </p>\n <h1 className=\"mt-3 text-3xl font-semibold tracking-tight\">\n {title}\n </h1>\n <p className=\"mt-2 text-sm text-white/70\">{description}</p>\n </div>\n </div>\n <div className=\"mt-8 space-y-3\">\n <label\n htmlFor=\"login-email\"\n className=\"text-sm font-medium text-white/70\"\n >\n {label}\n </label>\n <input\n id=\"login-email\"\n type=\"email\"\n inputMode=\"email\"\n autoComplete=\"email\"\n placeholder={placeholder}\n value={email}\n onChange={(event) => setEmail(event.target.value)}\n className=\"w-full rounded-2xl border border-white/15 bg-white/5 px-5 py-4 text-base text-white placeholder:text-white/40 focus:border-white/40 focus:outline-none focus:ring-2 focus:ring-white/25\"\n disabled={disabled}\n required\n />\n <Button\n type=\"submit\"\n size=\"lg\"\n className=\"w-full justify-center\"\n disabled={disabled}\n isLoading={disabled}\n >\n {ctaLabel}\n </Button>\n </div>\n </form>\n );\n }\n);\n\nLogin.displayName = \"Login\";\n","/**\n * Light-weight type helpers for defining Server Actions that align with the\n * enterprise portal guardrails. The component library does not implement\n * specific actions, but it exports helpers so downstream portals can describe\n * their actions with consistent metadata.\n */\n\nexport type PortalActionVisibility = \"vendor\" | \"customer\";\n\nexport interface PortalActionContext {\n vendorId: string;\n licenseId: string;\n userId: string;\n signal?: AbortSignal;\n}\n\nexport interface PortalServerActionDefinition<Input, Output> {\n id: string;\n description: string;\n visibility: PortalActionVisibility;\n tags: string[];\n run: (input: Input, context: PortalActionContext) => Promise<Output>;\n}\n\nexport const defineServerAction = <Input, Output>(\n definition: PortalServerActionDefinition<Input, Output>\n) => definition;\n","import packageJson from \"../package.json\";\n\nexport const portalComponentsVersion = packageJson.version;\n\nexport type {\n ComponentAccessibilityMetadata,\n ComponentBehaviorMetadata,\n ComponentCategory,\n ComponentRegistryDocument,\n ComponentRegistryEntry,\n ComponentExampleMetadata,\n ComponentPerformanceMetadata,\n ComponentPropMetadata\n} from \"./types/metadata\";\n\nexport {\n createPortalTheme,\n portalThemeTokens\n} from \"./tokens\";\nexport type { PortalThemeTokens, PortalThemeOverrides } from \"./tokens\";\n\nexport * from \"./components\";\n\nexport {\n defineServerAction\n} from \"./actions\";\nexport type {\n PortalActionContext,\n PortalActionVisibility,\n PortalServerActionDefinition\n} from \"./actions\";\n"]}
1
+ {"version":3,"sources":["../../package.json","../../src/tokens/index.ts","../../src/components/button.tsx","../../src/components/login.tsx","../../src/actions/index.ts","../../src/index.ts"],"names":["forwardRef","jsxs","jsx"],"mappings":"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","file":"index.js","sourcesContent":["{\n \"name\": \"@replicated/portal-components\",\n \"version\": \"0.0.2\",\n \"description\": \"Opinionated component library for Replicated enterprise portals\",\n \"license\": \"Apache-2.0\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/replicatedhq/enterprise-portal-components.git\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/replicatedhq/enterprise-portal-components/issues\"\n },\n \"homepage\": \"https://github.com/replicatedhq/enterprise-portal-components#readme\",\n \"main\": \"dist/index.js\",\n \"module\": \"dist/esm/index.js\",\n \"types\": \"dist/index.d.ts\",\n \"style\": \"dist/styles.css\",\n \"files\": [\n \"dist\",\n \"components/metadata\"\n ],\n \"sideEffects\": [\n \"*.css\",\n \"*.scss\"\n ],\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/esm/index.js\",\n \"require\": \"./dist/index.js\"\n },\n \"./tokens\": {\n \"types\": \"./dist/tokens/index.d.ts\",\n \"import\": \"./dist/esm/tokens/index.js\",\n \"require\": \"./dist/tokens/index.js\"\n },\n \"./actions\": {\n \"types\": \"./dist/actions/index.d.ts\",\n \"import\": \"./dist/esm/actions/index.js\",\n \"require\": \"./dist/actions/index.js\"\n },\n \"./styles.css\": \"./dist/styles.css\",\n \"./metadata/registry.json\": \"./components/metadata/registry.json\"\n },\n \"scripts\": {\n \"clean\": \"rimraf dist\",\n \"build\": \"npm run clean && npm run registry:generate && npm run build:css && tsup\",\n \"build:css\": \"tailwindcss -i ./src/styles.css -o ./dist/styles.css --config tailwind.config.ts\",\n \"dev\": \"tsup --watch\",\n \"typecheck\": \"tsc --noEmit\",\n \"registry:generate\": \"tsx scripts/generate-registry.ts\",\n \"size\": \"size-limit\",\n \"release\": \"changeset\"\n },\n \"peerDependencies\": {\n \"react\": \"^19.0.0\",\n \"react-dom\": \"^19.0.0\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.27.8\",\n \"@size-limit/preset-big-lib\": \"^9.0.0\",\n \"@types/node\": \"^22.10.1\",\n \"@types/react\": \"^19.0.4\",\n \"@types/react-dom\": \"^19.0.3\",\n \"autoprefixer\": \"^10.4.20\",\n \"postcss\": \"^8.4.49\",\n \"react\": \"19.0.0\",\n \"react-dom\": \"19.0.0\",\n \"rimraf\": \"^6.0.1\",\n \"size-limit\": \"^9.0.0\",\n \"tailwindcss\": \"^3.4.17\",\n \"tslib\": \"^2.7.0\",\n \"tsup\": \"^8.3.5\",\n \"tsx\": \"^4.19.2\",\n \"typescript\": \"^5.6.3\"\n },\n \"engines\": {\n \"node\": \">=20\"\n },\n \"packageManager\": \"npm@10\",\n \"size-limit\": [\n {\n \"name\": \"core entry\",\n \"path\": \"dist/esm/index.js\",\n \"import\": \"{ Login }\",\n \"limit\": \"35 KB\",\n \"gzip\": true\n }\n ]\n}\n","type DeepPartial<T> = {\n [Key in keyof T]?: T[Key] extends Record<string, unknown>\n ? DeepPartial<T[Key]>\n : T[Key];\n};\n\nexport interface PortalThemeTokens {\n colors: {\n background: string;\n foreground: string;\n surfaceMuted: string;\n primary: string;\n onPrimary: string;\n secondary: string;\n onSecondary: string;\n success: string;\n warning: string;\n danger: string;\n };\n radii: {\n lg: string;\n md: string;\n sm: string;\n };\n spacing: Record<\"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\", string>;\n typography: {\n fontFamily: string;\n monoFamily: string;\n lineHeight: Record<\"tight\" | \"normal\" | \"relaxed\", string>;\n };\n shadows: {\n focus: string;\n overlay: string;\n };\n}\n\nconst baseTokens: PortalThemeTokens = {\n colors: {\n background: \"#0b0d12\",\n foreground: \"#f4f6fb\",\n surfaceMuted: \"#1b1f2a\",\n primary: \"#0d6efd\",\n onPrimary: \"#ffffff\",\n secondary: \"#6c757d\",\n onSecondary: \"#ffffff\",\n success: \"#22c55e\",\n warning: \"#fbbf24\",\n danger: \"#ef4444\"\n },\n radii: {\n lg: \"16px\",\n md: \"10px\",\n sm: \"6px\"\n },\n spacing: {\n xs: \"0.25rem\",\n sm: \"0.5rem\",\n md: \"0.75rem\",\n lg: \"1rem\",\n xl: \"1.5rem\"\n },\n typography: {\n fontFamily:\n \"Inter, 'SF Pro Display', 'Segoe UI', system-ui, -apple-system, sans-serif\",\n monoFamily: \"'JetBrains Mono', 'SFMono-Regular', Consolas, monospace\",\n lineHeight: {\n tight: \"1.2\",\n normal: \"1.5\",\n relaxed: \"1.75\"\n }\n },\n shadows: {\n focus: \"0 0 0 3px color-mix(in srgb, #0d6efd 35%, transparent)\",\n overlay: \"0px 20px 55px rgba(5, 7, 11, 0.5)\"\n }\n};\n\nconst mergeTokens = (\n base: PortalThemeTokens,\n overrides?: DeepPartial<PortalThemeTokens>\n): PortalThemeTokens => {\n if (!overrides) {\n return base;\n }\n\n const clone = structuredClone(base);\n\n const apply = (target: Record<string, any>, source: Record<string, any>) => {\n Object.entries(source).forEach(([key, value]) => {\n if (\n value &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n typeof target[key] === \"object\"\n ) {\n apply(target[key], value);\n return;\n }\n\n target[key] = value;\n });\n };\n\n apply(clone as Record<string, any>, overrides as Record<string, any>);\n return clone;\n};\n\nexport const portalThemeTokens: PortalThemeTokens =\n mergeTokens(baseTokens);\n\nexport const createPortalTheme = (\n overrides?: DeepPartial<PortalThemeTokens>\n) => mergeTokens(baseTokens, overrides);\n\nexport type { DeepPartial as PortalThemeOverrides };\n","import {\n forwardRef,\n type ComponentPropsWithoutRef,\n type ReactNode\n} from \"react\";\n\nconst buttonVariants = [\"primary\", \"secondary\", \"ghost\", \"destructive\"] as const;\nconst buttonSizes = [\"sm\", \"md\", \"lg\"] as const;\n\nconst variantStyles: Record<ButtonVariant, string> = {\n primary:\n \"bg-primary text-primary-foreground hover:bg-primary/90 focus-visible:ring-primary\",\n secondary:\n \"bg-secondary/20 text-secondary-foreground hover:bg-secondary/30 focus-visible:ring-secondary\",\n ghost:\n \"bg-transparent text-primary hover:bg-primary/10 focus-visible:ring-primary/60\",\n destructive:\n \"bg-danger text-white hover:bg-danger/90 focus-visible:ring-danger\"\n};\n\nconst sizeStyles: Record<ButtonSize, string> = {\n sm: \"h-8 px-3 text-sm\",\n md: \"h-10 px-4 text-sm\",\n lg: \"h-12 px-6 text-base\"\n};\n\nconst inlineFlexBase =\n \"inline-flex items-center justify-center gap-2 rounded-md font-medium tracking-tight transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-60\";\n\nconst Spinner = () => (\n <span className=\"inline-flex h-3.5 w-3.5 animate-spin items-center justify-center\">\n <span className=\"h-3 w-3 rounded-full border-2 border-transparent border-t-current\" />\n </span>\n);\n\nexport type ButtonVariant = (typeof buttonVariants)[number];\nexport type ButtonSize = (typeof buttonSizes)[number];\n\nexport interface ButtonProps extends ComponentPropsWithoutRef<\"button\"> {\n variant?: ButtonVariant;\n size?: ButtonSize;\n isLoading?: boolean;\n leadingIcon?: ReactNode;\n trailingIcon?: ReactNode;\n}\n\nconst composeClassName = (\n ...values: Array<string | undefined | false>\n): string => values.filter(Boolean).join(\" \");\n\n/**\n * Button is the primary interactive primitive for triggering portal actions.\n * It is theme aware via CSS variables generated from portal tokens.\n */\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n (\n {\n variant = \"primary\",\n size = \"md\",\n type = \"button\",\n isLoading = false,\n leadingIcon,\n trailingIcon,\n disabled,\n className,\n children,\n ...props\n },\n ref\n ) => {\n const computedLeading = isLoading ? <Spinner /> : leadingIcon;\n const computedDisabled = disabled ?? isLoading;\n\n return (\n <button\n ref={ref}\n type={type}\n className={composeClassName(\n inlineFlexBase,\n variantStyles[variant],\n sizeStyles[size],\n className\n )}\n aria-busy={isLoading || undefined}\n disabled={computedDisabled}\n {...props}\n >\n {computedLeading ? (\n <span aria-hidden=\"true\" className=\"inline-flex\">\n {computedLeading}\n </span>\n ) : null}\n <span className=\"flex-1 whitespace-nowrap\">{children}</span>\n {trailingIcon ? (\n <span aria-hidden=\"true\" className=\"inline-flex\">\n {trailingIcon}\n </span>\n ) : null}\n </button>\n );\n }\n);\n\nButton.displayName = \"Button\";\n","'use client';\n\nimport {\n forwardRef,\n useMemo,\n useState,\n type ComponentPropsWithoutRef,\n type FormEvent,\n type ReactNode\n} from \"react\";\n\nimport { Button } from \"./button\";\n\nexport interface LoginProps\n extends Omit<ComponentPropsWithoutRef<\"form\">, \"onSubmit\" | \"children\"> {\n logo?: ReactNode;\n title?: string;\n description?: string;\n label?: string;\n placeholder?: string;\n ctaLabel?: string;\n initialEmail?: string;\n isSubmitting?: boolean;\n onContinue?: (email: string) => Promise<void> | void;\n onVerifyCode?: (\n code: string,\n redirectPath: string\n ) => Promise<\n | void\n | { success: true }\n | { success: false; message?: string }\n >;\n redirectPath?: string;\n}\n\n/**\n * Login renders a compact form card tailored for portal authentication flows.\n * The component keeps styling self-contained so it can be dropped into the\n * local Next.js template without additional wrappers.\n */\nexport const Login = forwardRef<HTMLFormElement, LoginProps>(\n (\n {\n logo,\n title = \"Enterprise Factory Installation Portal\",\n description = \"Sign in to manage your enterprise installation\",\n label = \"Work email address\",\n placeholder = \"you@company.com\",\n ctaLabel = \"Continue with email →\",\n initialEmail = \"\",\n isSubmitting = false,\n onContinue,\n onVerifyCode,\n redirectPath = \"/\",\n className,\n ...props\n },\n ref\n ) => {\n const [mode, setMode] = useState<\"email\" | \"verify\">(\"email\");\n const [email, setEmail] = useState(initialEmail);\n const [submittedEmail, setSubmittedEmail] = useState(initialEmail);\n const [verificationCode, setVerificationCode] = useState(\"\");\n const [verifying, setVerifying] = useState(false);\n const [pending, setPending] = useState(false);\n const [verificationError, setVerificationError] = useState<string | null>(\n null\n );\n\n const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n if (!onContinue || mode !== \"email\") {\n return;\n }\n\n try {\n setPending(true);\n await onContinue(email);\n setSubmittedEmail(email);\n setVerificationCode(\"\");\n setVerificationError(null);\n setMode(\"verify\");\n } finally {\n setPending(false);\n }\n };\n\n const disabled = mode === \"email\" && (isSubmitting || pending);\n const verificationMessage = useMemo(() => {\n if (!submittedEmail) {\n return null;\n }\n return `If there is an account for ${submittedEmail}, an email will be sent with a login code.`;\n }, [submittedEmail]);\n\n const handleVerify = async () => {\n if (!onVerifyCode || !verificationCode) {\n return;\n }\n try {\n setVerificationError(null);\n setVerifying(true);\n const result = await onVerifyCode(verificationCode, redirectPath);\n if (\n result &&\n typeof result === \"object\" &&\n \"success\" in result &&\n (result as { success?: unknown }).success === false\n ) {\n const message =\n (result as { message?: string }).message ??\n \"Unable to verify code. Try again.\";\n setVerificationError(message);\n return;\n }\n setVerificationCode(\"\");\n } catch (error) {\n if (\n typeof error === \"object\" &&\n error !== null &&\n \"message\" in error &&\n typeof (error as { message?: unknown }).message === \"string\"\n ) {\n setVerificationError((error as { message: string }).message);\n } else {\n setVerificationError(\"Unable to verify code. Try again.\");\n }\n } finally {\n setVerifying(false);\n }\n };\n\n return (\n <form\n ref={ref}\n className={[\n \"w-full max-w-md rounded-3xl border border-white/15 bg-slate-950/80 p-10 shadow-[0_25px_70px_rgba(5,7,11,0.65)] backdrop-blur\",\n \"text-white transition-shadow\",\n className\n ]\n .filter(Boolean)\n .join(\" \")}\n onSubmit={handleSubmit}\n {...props}\n >\n <div className=\"flex flex-col items-center gap-4 text-center\">\n {logo ?? (\n <div className=\"flex h-16 w-16 items-center justify-center rounded-2xl border border-white/20 bg-white/10 text-lg font-semibold leading-tight\">\n EP\n </div>\n )}\n <div>\n <p className=\"text-base font-semibold uppercase tracking-[0.35em] text-white/65\">\n Enterprise Portal\n </p>\n <h1 className=\"mt-3 text-3xl font-semibold tracking-tight\">\n {title}\n </h1>\n <p className=\"mt-2 text-sm text-white/70\">{description}</p>\n </div>\n </div>\n {mode === \"email\" ? (\n <div className=\"mt-8 space-y-3\">\n <label\n htmlFor=\"login-email\"\n className=\"text-sm font-medium text-white/70\"\n >\n {label}\n </label>\n <input\n id=\"login-email\"\n type=\"email\"\n inputMode=\"email\"\n autoComplete=\"email\"\n placeholder={placeholder}\n value={email}\n onChange={(event) => setEmail(event.target.value)}\n className=\"w-full rounded-2xl border border-white/15 bg-white/5 px-5 py-4 text-base text-white placeholder:text-white/40 focus:border-white/40 focus:outline-none focus:ring-2 focus:ring-white/25\"\n disabled={disabled}\n required\n />\n <Button\n type=\"submit\"\n size=\"lg\"\n className=\"w-full justify-center\"\n disabled={disabled}\n isLoading={disabled}\n >\n {ctaLabel}\n </Button>\n </div>\n ) : (\n <div className=\"mt-8 space-y-4\">\n <div>\n <p className=\"text-sm font-semibold uppercase tracking-[0.35em] text-white/65\">\n Verification Code\n </p>\n {verificationMessage ? (\n <p className=\"mt-2 text-sm text-white/70\">{verificationMessage}</p>\n ) : null}\n {verificationError ? (\n <p className=\"mt-2 text-sm font-medium text-rose-300\">\n {verificationError}\n </p>\n ) : null}\n </div>\n <label\n htmlFor=\"login-code\"\n className=\"text-sm font-medium text-white/70\"\n >\n One-time code\n </label>\n <input\n id=\"login-code\"\n inputMode=\"numeric\"\n maxLength={12}\n placeholder=\"Enter 12 digit code\"\n className=\"w-full rounded-2xl border border-white/15 bg-white/5 px-5 py-4 text-base text-white placeholder:text-white/40 focus:border-white/40 focus:outline-none focus:ring-2 focus:ring-white/25\"\n value={verificationCode}\n onChange={(event) => setVerificationCode(event.target.value)}\n />\n <Button\n type=\"button\"\n size=\"lg\"\n className=\"w-full justify-center\"\n disabled={verificationCode.length !== 12 || verifying}\n isLoading={verifying}\n onClick={handleVerify}\n >\n Verify code\n </Button>\n </div>\n )}\n </form>\n );\n }\n);\n\nLogin.displayName = \"Login\";\n","/**\n * Light-weight type helpers for defining Server Actions that align with the\n * enterprise portal guardrails. The component library does not implement\n * specific actions, but it exports helpers so downstream portals can describe\n * their actions with consistent metadata.\n */\n\nexport type PortalActionVisibility = \"vendor\" | \"customer\";\n\nexport interface PortalActionContext {\n vendorId: string;\n licenseId: string;\n userId: string;\n signal?: AbortSignal;\n}\n\nexport interface PortalServerActionDefinition<Input, Output> {\n id: string;\n description: string;\n visibility: PortalActionVisibility;\n tags: string[];\n run: (input: Input, context?: PortalActionContext) => Promise<Output>;\n}\n\nexport const defineServerAction = <Input, Output>(\n definition: PortalServerActionDefinition<Input, Output>\n) => definition;\n\nexport interface InitiateLoginInput {\n email: string;\n}\n\nexport interface InitiateLoginResult {\n status: \"ok\";\n requestedAt: string;\n message: string;\n}\n\n/**\n * Reference server action for initiating the passwordless login flow.\n * Real portals should replace the simulated delay with a call to their auth API.\n */\nexport const initiateLogin = defineServerAction<\n InitiateLoginInput,\n InitiateLoginResult\n>({\n id: \"auth/initiate-login\",\n description:\n \"Begins the passwordless login flow by dispatching a magic link email.\",\n visibility: \"customer\",\n tags: [\"auth\", \"login\", \"session\"],\n async run(input) {\n const origin =\n process.env.REPLICATED_APP_ORIGIN ?? \"https://replicated.app\";\n const endpoint = `${origin.replace(/\\/+$/, \"\")}/v3/login/magic-link`;\n const appSlug = process.env.PORTAL_APP_SLUG;\n if (!appSlug) {\n throw new Error(\"PORTAL_APP_SLUG is not configured\");\n }\n const portalOrigin =\n process.env.PORTAL_ORIGIN ?? \"https://enterprise.replicated.com\";\n const redirectUri = `${portalOrigin.replace(/\\/+$/, \"\")}/${appSlug}/login`;\n\n if (process.env.NODE_ENV !== \"production\") {\n console.debug(\n \"[portal-components] initiating login via %s for app %s redirecting to %s\",\n endpoint,\n appSlug,\n redirectUri\n );\n }\n\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\"\n },\n body: JSON.stringify({\n app_slug: appSlug,\n email_address: input.email,\n redirect_uri: redirectUri\n })\n });\n\n if (!response.ok) {\n throw new Error(\n `Magic link request failed (${response.status} ${response.statusText})`\n );\n }\n\n return {\n status: \"ok\",\n requestedAt: new Date().toISOString(),\n message: `Magic link requested for ${input.email}`\n };\n }\n});\n\nexport interface VerifyMagicLinkInput {\n nonce: string;\n}\n\nexport interface VerifyMagicLinkResult {\n token: string;\n raw: unknown;\n}\n\nexport interface VerifyMagicLinkError {\n code: \"invalid_code\" | \"unknown\";\n message: string;\n}\n\nexport const verifyMagicLink = defineServerAction<\n VerifyMagicLinkInput,\n VerifyMagicLinkResult\n>({\n id: \"auth/verify-magic-link\",\n description: \"Verifies the 12-digit code provided via email and returns a JWT.\",\n visibility: \"customer\",\n tags: [\"auth\", \"login\", \"verify\"],\n async run({ nonce }) {\n const origin =\n process.env.REPLICATED_APP_ORIGIN ?? \"https://replicated.app\";\n const endpoint = `${origin.replace(/\\/+$/, \"\")}/v3/login/magic-link/verify`;\n\n if (process.env.NODE_ENV !== \"production\") {\n console.debug(\n \"[portal-components] verifying magic link via %s\",\n endpoint\n );\n }\n\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\"\n },\n body: JSON.stringify({ nonce })\n });\n\n if (!response.ok) {\n if (response.status === 401) {\n const error: VerifyMagicLinkError = {\n code: \"invalid_code\",\n message: \"Incorrect code, check your email and try again.\"\n };\n throw error;\n }\n const error: VerifyMagicLinkError = {\n code: \"unknown\",\n message: `Magic link verification failed (${response.status} ${response.statusText})`\n };\n throw error;\n }\n\n const payload = await response.json();\n const token = payload?.token ?? payload?.jwt ?? payload?.access_token;\n if (typeof token !== \"string\") {\n throw new Error(\"Magic link verification succeeded but no token returned\");\n }\n\n return { token, raw: payload };\n }\n});\n","import packageJson from \"../package.json\";\n\nexport const portalComponentsVersion = packageJson.version;\n\nexport type {\n ComponentAccessibilityMetadata,\n ComponentBehaviorMetadata,\n ComponentCategory,\n ComponentRegistryDocument,\n ComponentRegistryEntry,\n ComponentExampleMetadata,\n ComponentPerformanceMetadata,\n ComponentPropMetadata\n} from \"./types/metadata\";\n\nexport {\n createPortalTheme,\n portalThemeTokens\n} from \"./tokens\";\nexport type { PortalThemeTokens, PortalThemeOverrides } from \"./tokens\";\n\nexport * from \"./components\";\n\nexport {\n defineServerAction\n} from \"./actions\";\nexport type {\n PortalActionContext,\n PortalActionVisibility,\n PortalServerActionDefinition\n} from \"./actions\";\n"]}
package/dist/index.d.mts CHANGED
@@ -109,6 +109,13 @@ interface LoginProps extends Omit<ComponentPropsWithoutRef<"form">, "onSubmit" |
109
109
  initialEmail?: string;
110
110
  isSubmitting?: boolean;
111
111
  onContinue?: (email: string) => Promise<void> | void;
112
+ onVerifyCode?: (code: string, redirectPath: string) => Promise<void | {
113
+ success: true;
114
+ } | {
115
+ success: false;
116
+ message?: string;
117
+ }>;
118
+ redirectPath?: string;
112
119
  }
113
120
  /**
114
121
  * Login renders a compact form card tailored for portal authentication flows.
package/dist/index.d.ts CHANGED
@@ -109,6 +109,13 @@ interface LoginProps extends Omit<ComponentPropsWithoutRef<"form">, "onSubmit" |
109
109
  initialEmail?: string;
110
110
  isSubmitting?: boolean;
111
111
  onContinue?: (email: string) => Promise<void> | void;
112
+ onVerifyCode?: (code: string, redirectPath: string) => Promise<void | {
113
+ success: true;
114
+ } | {
115
+ success: false;
116
+ message?: string;
117
+ }>;
118
+ redirectPath?: string;
112
119
  }
113
120
  /**
114
121
  * Login renders a compact form card tailored for portal authentication flows.
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ var jsxRuntime = require('react/jsx-runtime');
10
10
 
11
11
  // package.json
12
12
  var package_default = {
13
- version: "0.0.1"};
13
+ version: "0.0.2"};
14
14
 
15
15
  // src/tokens/index.ts
16
16
  var baseTokens = {
@@ -135,24 +135,67 @@ var Login = react.forwardRef(
135
135
  initialEmail = "",
136
136
  isSubmitting = false,
137
137
  onContinue,
138
+ onVerifyCode,
139
+ redirectPath = "/",
138
140
  className,
139
141
  ...props
140
142
  }, ref) => {
143
+ const [mode, setMode] = react.useState("email");
141
144
  const [email, setEmail] = react.useState(initialEmail);
145
+ const [submittedEmail, setSubmittedEmail] = react.useState(initialEmail);
146
+ const [verificationCode, setVerificationCode] = react.useState("");
147
+ const [verifying, setVerifying] = react.useState(false);
142
148
  const [pending, setPending] = react.useState(false);
149
+ const [verificationError, setVerificationError] = react.useState(
150
+ null
151
+ );
143
152
  const handleSubmit = async (event) => {
144
153
  event.preventDefault();
145
- if (!onContinue) {
154
+ if (!onContinue || mode !== "email") {
146
155
  return;
147
156
  }
148
157
  try {
149
158
  setPending(true);
150
159
  await onContinue(email);
160
+ setSubmittedEmail(email);
161
+ setVerificationCode("");
162
+ setVerificationError(null);
163
+ setMode("verify");
151
164
  } finally {
152
165
  setPending(false);
153
166
  }
154
167
  };
155
- const disabled = isSubmitting || pending;
168
+ const disabled = mode === "email" && (isSubmitting || pending);
169
+ const verificationMessage = react.useMemo(() => {
170
+ if (!submittedEmail) {
171
+ return null;
172
+ }
173
+ return `If there is an account for ${submittedEmail}, an email will be sent with a login code.`;
174
+ }, [submittedEmail]);
175
+ const handleVerify = async () => {
176
+ if (!onVerifyCode || !verificationCode) {
177
+ return;
178
+ }
179
+ try {
180
+ setVerificationError(null);
181
+ setVerifying(true);
182
+ const result = await onVerifyCode(verificationCode, redirectPath);
183
+ if (result && typeof result === "object" && "success" in result && result.success === false) {
184
+ const message = result.message ?? "Unable to verify code. Try again.";
185
+ setVerificationError(message);
186
+ return;
187
+ }
188
+ setVerificationCode("");
189
+ } catch (error) {
190
+ if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
191
+ setVerificationError(error.message);
192
+ } else {
193
+ setVerificationError("Unable to verify code. Try again.");
194
+ }
195
+ } finally {
196
+ setVerifying(false);
197
+ }
198
+ };
156
199
  return /* @__PURE__ */ jsxRuntime.jsxs(
157
200
  "form",
158
201
  {
@@ -173,7 +216,7 @@ var Login = react.forwardRef(
173
216
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm text-white/70", children: description })
174
217
  ] })
175
218
  ] }),
176
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-8 space-y-3", children: [
219
+ mode === "email" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-8 space-y-3", children: [
177
220
  /* @__PURE__ */ jsxRuntime.jsx(
178
221
  "label",
179
222
  {
@@ -208,6 +251,44 @@ var Login = react.forwardRef(
208
251
  children: ctaLabel
209
252
  }
210
253
  )
254
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-8 space-y-4", children: [
255
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
256
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold uppercase tracking-[0.35em] text-white/65", children: "Verification Code" }),
257
+ verificationMessage ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm text-white/70", children: verificationMessage }) : null,
258
+ verificationError ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm font-medium text-rose-300", children: verificationError }) : null
259
+ ] }),
260
+ /* @__PURE__ */ jsxRuntime.jsx(
261
+ "label",
262
+ {
263
+ htmlFor: "login-code",
264
+ className: "text-sm font-medium text-white/70",
265
+ children: "One-time code"
266
+ }
267
+ ),
268
+ /* @__PURE__ */ jsxRuntime.jsx(
269
+ "input",
270
+ {
271
+ id: "login-code",
272
+ inputMode: "numeric",
273
+ maxLength: 12,
274
+ placeholder: "Enter 12 digit code",
275
+ className: "w-full rounded-2xl border border-white/15 bg-white/5 px-5 py-4 text-base text-white placeholder:text-white/40 focus:border-white/40 focus:outline-none focus:ring-2 focus:ring-white/25",
276
+ value: verificationCode,
277
+ onChange: (event) => setVerificationCode(event.target.value)
278
+ }
279
+ ),
280
+ /* @__PURE__ */ jsxRuntime.jsx(
281
+ Button,
282
+ {
283
+ type: "button",
284
+ size: "lg",
285
+ className: "w-full justify-center",
286
+ disabled: verificationCode.length !== 12 || verifying,
287
+ isLoading: verifying,
288
+ onClick: handleVerify,
289
+ children: "Verify code"
290
+ }
291
+ )
211
292
  ] })
212
293
  ]
213
294
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../package.json","../src/tokens/index.ts","../src/components/button.tsx","../src/components/login.tsx","../src/actions/index.ts","../src/index.ts"],"names":["jsx","forwardRef","jsxs","useState"],"mappings":"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","file":"index.js","sourcesContent":["{\n \"name\": \"@replicated/portal-components\",\n \"version\": \"0.0.1\",\n \"description\": \"Opinionated component library for Replicated enterprise portals\",\n \"license\": \"Apache-2.0\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/replicatedhq/enterprise-portal-components.git\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/replicatedhq/enterprise-portal-components/issues\"\n },\n \"homepage\": \"https://github.com/replicatedhq/enterprise-portal-components#readme\",\n \"main\": \"dist/index.js\",\n \"module\": \"dist/esm/index.js\",\n \"types\": \"dist/index.d.ts\",\n \"style\": \"dist/styles.css\",\n \"files\": [\n \"dist\",\n \"components/metadata\"\n ],\n \"sideEffects\": [\n \"*.css\",\n \"*.scss\"\n ],\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/esm/index.js\",\n \"require\": \"./dist/index.js\"\n },\n \"./tokens\": {\n \"types\": \"./dist/tokens/index.d.ts\",\n \"import\": \"./dist/esm/tokens/index.js\",\n \"require\": \"./dist/tokens/index.js\"\n },\n \"./actions\": {\n \"types\": \"./dist/actions/index.d.ts\",\n \"import\": \"./dist/esm/actions/index.js\",\n \"require\": \"./dist/actions/index.js\"\n },\n \"./styles.css\": \"./dist/styles.css\",\n \"./metadata/registry.json\": \"./components/metadata/registry.json\"\n },\n \"scripts\": {\n \"clean\": \"rimraf dist\",\n \"build\": \"npm run clean && npm run registry:generate && npm run build:css && tsup\",\n \"build:css\": \"tailwindcss -i ./src/styles.css -o ./dist/styles.css --config tailwind.config.ts\",\n \"dev\": \"tsup --watch\",\n \"typecheck\": \"tsc --noEmit\",\n \"registry:generate\": \"tsx scripts/generate-registry.ts\",\n \"size\": \"size-limit\",\n \"release\": \"changeset\"\n },\n \"peerDependencies\": {\n \"react\": \"^19.0.0\",\n \"react-dom\": \"^19.0.0\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.27.8\",\n \"@size-limit/preset-big-lib\": \"^9.0.0\",\n \"@types/node\": \"^22.10.1\",\n \"@types/react\": \"^19.0.4\",\n \"@types/react-dom\": \"^19.0.3\",\n \"autoprefixer\": \"^10.4.20\",\n \"postcss\": \"^8.4.49\",\n \"react\": \"19.0.0\",\n \"react-dom\": \"19.0.0\",\n \"rimraf\": \"^6.0.1\",\n \"size-limit\": \"^9.0.0\",\n \"tailwindcss\": \"^3.4.17\",\n \"tslib\": \"^2.7.0\",\n \"tsup\": \"^8.3.5\",\n \"tsx\": \"^4.19.2\",\n \"typescript\": \"^5.6.3\"\n },\n \"engines\": {\n \"node\": \">=20\"\n },\n \"packageManager\": \"npm@10\",\n \"size-limit\": [\n {\n \"name\": \"core entry\",\n \"path\": \"dist/esm/index.js\",\n \"import\": \"{ Login }\",\n \"limit\": \"35 KB\",\n \"gzip\": true\n }\n ]\n}\n","type DeepPartial<T> = {\n [Key in keyof T]?: T[Key] extends Record<string, unknown>\n ? DeepPartial<T[Key]>\n : T[Key];\n};\n\nexport interface PortalThemeTokens {\n colors: {\n background: string;\n foreground: string;\n surfaceMuted: string;\n primary: string;\n onPrimary: string;\n secondary: string;\n onSecondary: string;\n success: string;\n warning: string;\n danger: string;\n };\n radii: {\n lg: string;\n md: string;\n sm: string;\n };\n spacing: Record<\"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\", string>;\n typography: {\n fontFamily: string;\n monoFamily: string;\n lineHeight: Record<\"tight\" | \"normal\" | \"relaxed\", string>;\n };\n shadows: {\n focus: string;\n overlay: string;\n };\n}\n\nconst baseTokens: PortalThemeTokens = {\n colors: {\n background: \"#0b0d12\",\n foreground: \"#f4f6fb\",\n surfaceMuted: \"#1b1f2a\",\n primary: \"#0d6efd\",\n onPrimary: \"#ffffff\",\n secondary: \"#6c757d\",\n onSecondary: \"#ffffff\",\n success: \"#22c55e\",\n warning: \"#fbbf24\",\n danger: \"#ef4444\"\n },\n radii: {\n lg: \"16px\",\n md: \"10px\",\n sm: \"6px\"\n },\n spacing: {\n xs: \"0.25rem\",\n sm: \"0.5rem\",\n md: \"0.75rem\",\n lg: \"1rem\",\n xl: \"1.5rem\"\n },\n typography: {\n fontFamily:\n \"Inter, 'SF Pro Display', 'Segoe UI', system-ui, -apple-system, sans-serif\",\n monoFamily: \"'JetBrains Mono', 'SFMono-Regular', Consolas, monospace\",\n lineHeight: {\n tight: \"1.2\",\n normal: \"1.5\",\n relaxed: \"1.75\"\n }\n },\n shadows: {\n focus: \"0 0 0 3px color-mix(in srgb, #0d6efd 35%, transparent)\",\n overlay: \"0px 20px 55px rgba(5, 7, 11, 0.5)\"\n }\n};\n\nconst mergeTokens = (\n base: PortalThemeTokens,\n overrides?: DeepPartial<PortalThemeTokens>\n): PortalThemeTokens => {\n if (!overrides) {\n return base;\n }\n\n const clone = structuredClone(base);\n\n const apply = (target: Record<string, any>, source: Record<string, any>) => {\n Object.entries(source).forEach(([key, value]) => {\n if (\n value &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n typeof target[key] === \"object\"\n ) {\n apply(target[key], value);\n return;\n }\n\n target[key] = value;\n });\n };\n\n apply(clone as Record<string, any>, overrides as Record<string, any>);\n return clone;\n};\n\nexport const portalThemeTokens: PortalThemeTokens =\n mergeTokens(baseTokens);\n\nexport const createPortalTheme = (\n overrides?: DeepPartial<PortalThemeTokens>\n) => mergeTokens(baseTokens, overrides);\n\nexport type { DeepPartial as PortalThemeOverrides };\n","import {\n forwardRef,\n type ComponentPropsWithoutRef,\n type ReactNode\n} from \"react\";\n\nconst buttonVariants = [\"primary\", \"secondary\", \"ghost\", \"destructive\"] as const;\nconst buttonSizes = [\"sm\", \"md\", \"lg\"] as const;\n\nconst variantStyles: Record<ButtonVariant, string> = {\n primary:\n \"bg-primary text-primary-foreground hover:bg-primary/90 focus-visible:ring-primary\",\n secondary:\n \"bg-secondary/20 text-secondary-foreground hover:bg-secondary/30 focus-visible:ring-secondary\",\n ghost:\n \"bg-transparent text-primary hover:bg-primary/10 focus-visible:ring-primary/60\",\n destructive:\n \"bg-danger text-white hover:bg-danger/90 focus-visible:ring-danger\"\n};\n\nconst sizeStyles: Record<ButtonSize, string> = {\n sm: \"h-8 px-3 text-sm\",\n md: \"h-10 px-4 text-sm\",\n lg: \"h-12 px-6 text-base\"\n};\n\nconst inlineFlexBase =\n \"inline-flex items-center justify-center gap-2 rounded-md font-medium tracking-tight transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-60\";\n\nconst Spinner = () => (\n <span className=\"inline-flex h-3.5 w-3.5 animate-spin items-center justify-center\">\n <span className=\"h-3 w-3 rounded-full border-2 border-transparent border-t-current\" />\n </span>\n);\n\nexport type ButtonVariant = (typeof buttonVariants)[number];\nexport type ButtonSize = (typeof buttonSizes)[number];\n\nexport interface ButtonProps extends ComponentPropsWithoutRef<\"button\"> {\n variant?: ButtonVariant;\n size?: ButtonSize;\n isLoading?: boolean;\n leadingIcon?: ReactNode;\n trailingIcon?: ReactNode;\n}\n\nconst composeClassName = (\n ...values: Array<string | undefined | false>\n): string => values.filter(Boolean).join(\" \");\n\n/**\n * Button is the primary interactive primitive for triggering portal actions.\n * It is theme aware via CSS variables generated from portal tokens.\n */\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n (\n {\n variant = \"primary\",\n size = \"md\",\n type = \"button\",\n isLoading = false,\n leadingIcon,\n trailingIcon,\n disabled,\n className,\n children,\n ...props\n },\n ref\n ) => {\n const computedLeading = isLoading ? <Spinner /> : leadingIcon;\n const computedDisabled = disabled ?? isLoading;\n\n return (\n <button\n ref={ref}\n type={type}\n className={composeClassName(\n inlineFlexBase,\n variantStyles[variant],\n sizeStyles[size],\n className\n )}\n aria-busy={isLoading || undefined}\n disabled={computedDisabled}\n {...props}\n >\n {computedLeading ? (\n <span aria-hidden=\"true\" className=\"inline-flex\">\n {computedLeading}\n </span>\n ) : null}\n <span className=\"flex-1 whitespace-nowrap\">{children}</span>\n {trailingIcon ? (\n <span aria-hidden=\"true\" className=\"inline-flex\">\n {trailingIcon}\n </span>\n ) : null}\n </button>\n );\n }\n);\n\nButton.displayName = \"Button\";\n","'use client';\n\nimport {\n forwardRef,\n useState,\n type ComponentPropsWithoutRef,\n type FormEvent,\n type ReactNode\n} from \"react\";\n\nimport { Button } from \"./button\";\n\nexport interface LoginProps\n extends Omit<ComponentPropsWithoutRef<\"form\">, \"onSubmit\" | \"children\"> {\n logo?: ReactNode;\n title?: string;\n description?: string;\n label?: string;\n placeholder?: string;\n ctaLabel?: string;\n initialEmail?: string;\n isSubmitting?: boolean;\n onContinue?: (email: string) => Promise<void> | void;\n}\n\n/**\n * Login renders a compact form card tailored for portal authentication flows.\n * The component keeps styling self-contained so it can be dropped into the\n * local Next.js template without additional wrappers.\n */\nexport const Login = forwardRef<HTMLFormElement, LoginProps>(\n (\n {\n logo,\n title = \"Enterprise Factory Installation Portal\",\n description = \"Sign in to manage your enterprise installation\",\n label = \"Work email address\",\n placeholder = \"you@company.com\",\n ctaLabel = \"Continue with email →\",\n initialEmail = \"\",\n isSubmitting = false,\n onContinue,\n className,\n ...props\n },\n ref\n ) => {\n const [email, setEmail] = useState(initialEmail);\n const [pending, setPending] = useState(false);\n\n const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n if (!onContinue) {\n return;\n }\n\n try {\n setPending(true);\n await onContinue(email);\n } finally {\n setPending(false);\n }\n };\n\n const disabled = isSubmitting || pending;\n\n return (\n <form\n ref={ref}\n className={[\n \"w-full max-w-md rounded-3xl border border-white/15 bg-slate-950/80 p-10 shadow-[0_25px_70px_rgba(5,7,11,0.65)] backdrop-blur\",\n \"text-white transition-shadow\",\n className\n ]\n .filter(Boolean)\n .join(\" \")}\n onSubmit={handleSubmit}\n {...props}\n >\n <div className=\"flex flex-col items-center gap-4 text-center\">\n {logo ?? (\n <div className=\"flex h-16 w-16 items-center justify-center rounded-2xl border border-white/20 bg-white/10 text-lg font-semibold leading-tight\">\n EP\n </div>\n )}\n <div>\n <p className=\"text-base font-semibold uppercase tracking-[0.35em] text-white/65\">\n Enterprise Portal\n </p>\n <h1 className=\"mt-3 text-3xl font-semibold tracking-tight\">\n {title}\n </h1>\n <p className=\"mt-2 text-sm text-white/70\">{description}</p>\n </div>\n </div>\n <div className=\"mt-8 space-y-3\">\n <label\n htmlFor=\"login-email\"\n className=\"text-sm font-medium text-white/70\"\n >\n {label}\n </label>\n <input\n id=\"login-email\"\n type=\"email\"\n inputMode=\"email\"\n autoComplete=\"email\"\n placeholder={placeholder}\n value={email}\n onChange={(event) => setEmail(event.target.value)}\n className=\"w-full rounded-2xl border border-white/15 bg-white/5 px-5 py-4 text-base text-white placeholder:text-white/40 focus:border-white/40 focus:outline-none focus:ring-2 focus:ring-white/25\"\n disabled={disabled}\n required\n />\n <Button\n type=\"submit\"\n size=\"lg\"\n className=\"w-full justify-center\"\n disabled={disabled}\n isLoading={disabled}\n >\n {ctaLabel}\n </Button>\n </div>\n </form>\n );\n }\n);\n\nLogin.displayName = \"Login\";\n","/**\n * Light-weight type helpers for defining Server Actions that align with the\n * enterprise portal guardrails. The component library does not implement\n * specific actions, but it exports helpers so downstream portals can describe\n * their actions with consistent metadata.\n */\n\nexport type PortalActionVisibility = \"vendor\" | \"customer\";\n\nexport interface PortalActionContext {\n vendorId: string;\n licenseId: string;\n userId: string;\n signal?: AbortSignal;\n}\n\nexport interface PortalServerActionDefinition<Input, Output> {\n id: string;\n description: string;\n visibility: PortalActionVisibility;\n tags: string[];\n run: (input: Input, context: PortalActionContext) => Promise<Output>;\n}\n\nexport const defineServerAction = <Input, Output>(\n definition: PortalServerActionDefinition<Input, Output>\n) => definition;\n","import packageJson from \"../package.json\";\n\nexport const portalComponentsVersion = packageJson.version;\n\nexport type {\n ComponentAccessibilityMetadata,\n ComponentBehaviorMetadata,\n ComponentCategory,\n ComponentRegistryDocument,\n ComponentRegistryEntry,\n ComponentExampleMetadata,\n ComponentPerformanceMetadata,\n ComponentPropMetadata\n} from \"./types/metadata\";\n\nexport {\n createPortalTheme,\n portalThemeTokens\n} from \"./tokens\";\nexport type { PortalThemeTokens, PortalThemeOverrides } from \"./tokens\";\n\nexport * from \"./components\";\n\nexport {\n defineServerAction\n} from \"./actions\";\nexport type {\n PortalActionContext,\n PortalActionVisibility,\n PortalServerActionDefinition\n} from \"./actions\";\n"]}
1
+ {"version":3,"sources":["../package.json","../src/tokens/index.ts","../src/components/button.tsx","../src/components/login.tsx","../src/actions/index.ts","../src/index.ts"],"names":["jsx","forwardRef","jsxs","useState","useMemo"],"mappings":";;;;;;;;;;;AAAA,IAAA,eAAA,GAAA;AAAA,EAEE,OAAA,EAAW,OAuFb,CAAA;;;ACrDA,IAAM,UAAA,GAAgC;AAAA,EACpC,MAAA,EAAQ;AAAA,IACN,UAAA,EAAY,SAAA;AAAA,IACZ,UAAA,EAAY,SAAA;AAAA,IACZ,YAAA,EAAc,SAAA;AAAA,IACd,OAAA,EAAS,SAAA;AAAA,IACT,SAAA,EAAW,SAAA;AAAA,IACX,SAAA,EAAW,SAAA;AAAA,IACX,WAAA,EAAa,SAAA;AAAA,IACb,OAAA,EAAS,SAAA;AAAA,IACT,OAAA,EAAS,SAAA;AAAA,IACT,MAAA,EAAQ;AAAA,GACV;AAAA,EACA,KAAA,EAAO;AAAA,IACL,EAAA,EAAI,MAAA;AAAA,IACJ,EAAA,EAAI,MAAA;AAAA,IACJ,EAAA,EAAI;AAAA,GACN;AAAA,EACA,OAAA,EAAS;AAAA,IACP,EAAA,EAAI,SAAA;AAAA,IACJ,EAAA,EAAI,QAAA;AAAA,IACJ,EAAA,EAAI,SAAA;AAAA,IACJ,EAAA,EAAI,MAAA;AAAA,IACJ,EAAA,EAAI;AAAA,GACN;AAAA,EACA,UAAA,EAAY;AAAA,IACV,UAAA,EACE,2EAAA;AAAA,IACF,UAAA,EAAY,yDAAA;AAAA,IACZ,UAAA,EAAY;AAAA,MACV,KAAA,EAAO,KAAA;AAAA,MACP,MAAA,EAAQ,KAAA;AAAA,MACR,OAAA,EAAS;AAAA;AACX,GACF;AAAA,EACA,OAAA,EAAS;AAAA,IACP,KAAA,EAAO,wDAAA;AAAA,IACP,OAAA,EAAS;AAAA;AAEb,CAAA;AAEA,IAAM,WAAA,GAAc,CAClB,IAAA,EACA,SAAA,KACsB;AACtB,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,KAAA,GAAQ,gBAAgB,IAAI,CAAA;AAElC,EAAA,MAAM,KAAA,GAAQ,CAAC,MAAA,EAA6B,MAAA,KAAgC;AAC1E,IAAA,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,CAAE,OAAA,CAAQ,CAAC,CAAC,GAAA,EAAK,KAAK,CAAA,KAAM;AAC/C,MAAA,IACE,KAAA,IACA,OAAO,KAAA,KAAU,QAAA,IACjB,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,IACpB,OAAO,MAAA,CAAO,GAAG,MAAM,QAAA,EACvB;AACA,QAAA,KAAA,CAAM,MAAA,CAAO,GAAG,CAAA,EAAG,KAAK,CAAA;AACxB,QAAA;AAAA,MACF;AAEA,MAAA,MAAA,CAAO,GAAG,CAAA,GAAI,KAAA;AAAA,IAChB,CAAC,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,KAAA,CAAM,OAA8B,SAAgC,CAAA;AACpE,EAAA,OAAO,KAAA;AACT,CAAA;AAEO,IAAM,iBAAA,GACX,YAAY,UAAU;AAEjB,IAAM,iBAAA,GAAoB,CAC/B,SAAA,KACG,WAAA,CAAY,YAAY,SAAS;ACvGtC,IAAM,aAAA,GAA+C;AAAA,EACnD,OAAA,EACE,mFAAA;AAAA,EACF,SAAA,EACE,8FAAA;AAAA,EACF,KAAA,EACE,+EAAA;AAAA,EACF,WAAA,EACE;AACJ,CAAA;AAEA,IAAM,UAAA,GAAyC;AAAA,EAC7C,EAAA,EAAI,kBAAA;AAAA,EACJ,EAAA,EAAI,mBAAA;AAAA,EACJ,EAAA,EAAI;AACN,CAAA;AAEA,IAAM,cAAA,GACJ,oOAAA;AAEF,IAAM,OAAA,GAAU,sBACdA,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,oEACd,QAAA,kBAAAA,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,mEAAA,EAAoE,CAAA,EACtF,CAAA;AAcF,IAAM,gBAAA,GAAmB,IACpB,MAAA,KACQ,MAAA,CAAO,OAAO,OAAO,CAAA,CAAE,KAAK,GAAG,CAAA;AAMrC,IAAM,MAAA,GAASC,gBAAA;AAAA,EACpB,CACE;AAAA,IACE,OAAA,GAAU,SAAA;AAAA,IACV,IAAA,GAAO,IAAA;AAAA,IACP,IAAA,GAAO,QAAA;AAAA,IACP,SAAA,GAAY,KAAA;AAAA,IACZ,WAAA;AAAA,IACA,YAAA;AAAA,IACA,QAAA;AAAA,IACA,SAAA;AAAA,IACA,QAAA;AAAA,IACA,GAAG;AAAA,KAEL,GAAA,KACG;AACH,IAAA,MAAM,eAAA,GAAkB,SAAA,mBAAYD,cAAA,CAAC,OAAA,EAAA,EAAQ,CAAA,GAAK,WAAA;AAClD,IAAA,MAAM,mBAAmB,QAAA,IAAY,SAAA;AAErC,IAAA,uBACEE,eAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,IAAA;AAAA,QACA,SAAA,EAAW,gBAAA;AAAA,UACT,cAAA;AAAA,UACA,cAAc,OAAO,CAAA;AAAA,UACrB,WAAW,IAAI,CAAA;AAAA,UACf;AAAA,SACF;AAAA,QACA,aAAW,SAAA,IAAa,MAAA;AAAA,QACxB,QAAA,EAAU,gBAAA;AAAA,QACT,GAAG,KAAA;AAAA,QAEH,QAAA,EAAA;AAAA,UAAA,eAAA,kCACE,MAAA,EAAA,EAAK,aAAA,EAAY,QAAO,SAAA,EAAU,aAAA,EAChC,2BACH,CAAA,GACE,IAAA;AAAA,0BACJF,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,0BAAA,EAA4B,QAAA,EAAS,CAAA;AAAA,UACpD,YAAA,kCACE,MAAA,EAAA,EAAK,aAAA,EAAY,QAAO,SAAA,EAAU,aAAA,EAChC,wBACH,CAAA,GACE;AAAA;AAAA;AAAA,KACN;AAAA,EAEJ;AACF;AAEA,MAAA,CAAO,WAAA,GAAc,QAAA;AC/Dd,IAAM,KAAA,GAAQC,gBAAAA;AAAA,EACnB,CACE;AAAA,IACE,IAAA;AAAA,IACA,KAAA,GAAQ,wCAAA;AAAA,IACR,WAAA,GAAc,gDAAA;AAAA,IACd,KAAA,GAAQ,oBAAA;AAAA,IACR,WAAA,GAAc,iBAAA;AAAA,IACd,QAAA,GAAW,4BAAA;AAAA,IACX,YAAA,GAAe,EAAA;AAAA,IACf,YAAA,GAAe,KAAA;AAAA,IACf,UAAA;AAAA,IACA,YAAA;AAAA,IACA,YAAA,GAAe,GAAA;AAAA,IACf,SAAA;AAAA,IACA,GAAG;AAAA,KAEL,GAAA,KACG;AACH,IAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAIE,eAA6B,OAAO,CAAA;AAC5D,IAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,eAAS,YAAY,CAAA;AAC/C,IAAA,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA,GAAIA,eAAS,YAAY,CAAA;AACjE,IAAA,MAAM,CAAC,gBAAA,EAAkB,mBAAmB,CAAA,GAAIA,eAAS,EAAE,CAAA;AAC3D,IAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAIA,eAAS,KAAK,CAAA;AAChD,IAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIA,eAAS,KAAK,CAAA;AAC5C,IAAA,MAAM,CAAC,iBAAA,EAAmB,oBAAoB,CAAA,GAAIA,cAAA;AAAA,MAChD;AAAA,KACF;AAEA,IAAA,MAAM,YAAA,GAAe,OAAO,KAAA,KAAsC;AAChE,MAAA,KAAA,CAAM,cAAA,EAAe;AACrB,MAAA,IAAI,CAAC,UAAA,IAAc,IAAA,KAAS,OAAA,EAAS;AACnC,QAAA;AAAA,MACF;AAEA,MAAA,IAAI;AACF,QAAA,UAAA,CAAW,IAAI,CAAA;AACf,QAAA,MAAM,WAAW,KAAK,CAAA;AACtB,QAAA,iBAAA,CAAkB,KAAK,CAAA;AACvB,QAAA,mBAAA,CAAoB,EAAE,CAAA;AACtB,QAAA,oBAAA,CAAqB,IAAI,CAAA;AACzB,QAAA,OAAA,CAAQ,QAAQ,CAAA;AAAA,MAClB,CAAA,SAAE;AACA,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA,MAClB;AAAA,IACF,CAAA;AAEA,IAAA,MAAM,QAAA,GAAW,IAAA,KAAS,OAAA,KAAY,YAAA,IAAgB,OAAA,CAAA;AACtD,IAAA,MAAM,mBAAA,GAAsBC,cAAQ,MAAM;AACxC,MAAA,IAAI,CAAC,cAAA,EAAgB;AACnB,QAAA,OAAO,IAAA;AAAA,MACT;AACA,MAAA,OAAO,8BAA8B,cAAc,CAAA,0CAAA,CAAA;AAAA,IACrD,CAAA,EAAG,CAAC,cAAc,CAAC,CAAA;AAEnB,IAAA,MAAM,eAAe,YAAY;AAC/B,MAAA,IAAI,CAAC,YAAA,IAAgB,CAAC,gBAAA,EAAkB;AACtC,QAAA;AAAA,MACF;AACA,MAAA,IAAI;AACF,QAAA,oBAAA,CAAqB,IAAI,CAAA;AACzB,QAAA,YAAA,CAAa,IAAI,CAAA;AACjB,QAAA,MAAM,MAAA,GAAS,MAAM,YAAA,CAAa,gBAAA,EAAkB,YAAY,CAAA;AAChE,QAAA,IACE,MAAA,IACA,OAAO,MAAA,KAAW,QAAA,IAClB,aAAa,MAAA,IACZ,MAAA,CAAiC,YAAY,KAAA,EAC9C;AACA,UAAA,MAAM,OAAA,GACH,OAAgC,OAAA,IACjC,mCAAA;AACF,UAAA,oBAAA,CAAqB,OAAO,CAAA;AAC5B,UAAA;AAAA,QACF;AACA,QAAA,mBAAA,CAAoB,EAAE,CAAA;AAAA,MACxB,SAAS,KAAA,EAAO;AACd,QAAA,IACE,OAAO,KAAA,KAAU,QAAA,IACjB,KAAA,KAAU,IAAA,IACV,aAAa,KAAA,IACb,OAAQ,KAAA,CAAgC,OAAA,KAAY,QAAA,EACpD;AACA,UAAA,oBAAA,CAAsB,MAA8B,OAAO,CAAA;AAAA,QAC7D,CAAA,MAAO;AACL,UAAA,oBAAA,CAAqB,mCAAmC,CAAA;AAAA,QAC1D;AAAA,MACF,CAAA,SAAE;AACA,QAAA,YAAA,CAAa,KAAK,CAAA;AAAA,MACpB;AAAA,IACF,CAAA;AAEA,IAAA,uBACEF,eAAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAW;AAAA,UACT,8HAAA;AAAA,UACA,8BAAA;AAAA,UACA;AAAA,SACF,CACG,MAAA,CAAO,OAAO,CAAA,CACd,KAAK,GAAG,CAAA;AAAA,QACX,QAAA,EAAU,YAAA;AAAA,QACT,GAAG,KAAA;AAAA,QAEJ,QAAA,EAAA;AAAA,0BAAAA,eAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,8CAAA,EACZ,QAAA,EAAA;AAAA,YAAA,IAAA,oBACCF,cAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,iIAAgI,QAAA,EAAA,IAAA,EAE/I,CAAA;AAAA,4BAEFE,gBAAC,KAAA,EAAA,EACC,QAAA,EAAA;AAAA,8BAAAF,cAAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,mEAAA,EAAoE,QAAA,EAAA,mBAAA,EAEjF,CAAA;AAAA,8BACAA,cAAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,8CACX,QAAA,EAAA,KAAA,EACH,CAAA;AAAA,8BACAA,cAAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,8BAA8B,QAAA,EAAA,WAAA,EAAY;AAAA,aAAA,EACzD;AAAA,WAAA,EACF,CAAA;AAAA,UACC,SAAS,OAAA,mBACRE,eAAAA,CAAC,KAAA,EAAA,EAAI,WAAU,gBAAA,EACb,QAAA,EAAA;AAAA,4BAAAF,cAAAA;AAAA,cAAC,OAAA;AAAA,cAAA;AAAA,gBACC,OAAA,EAAQ,aAAA;AAAA,gBACR,SAAA,EAAU,mCAAA;AAAA,gBAET,QAAA,EAAA;AAAA;AAAA,aACH;AAAA,4BACAA,cAAAA;AAAA,cAAC,OAAA;AAAA,cAAA;AAAA,gBACC,EAAA,EAAG,aAAA;AAAA,gBACH,IAAA,EAAK,OAAA;AAAA,gBACL,SAAA,EAAU,OAAA;AAAA,gBACV,YAAA,EAAa,OAAA;AAAA,gBACb,WAAA;AAAA,gBACA,KAAA,EAAO,KAAA;AAAA,gBACP,UAAU,CAAC,KAAA,KAAU,QAAA,CAAS,KAAA,CAAM,OAAO,KAAK,CAAA;AAAA,gBAChD,SAAA,EAAU,yLAAA;AAAA,gBACV,QAAA;AAAA,gBACA,QAAA,EAAQ;AAAA;AAAA,aACV;AAAA,4BACAA,cAAAA;AAAA,cAAC,MAAA;AAAA,cAAA;AAAA,gBACC,IAAA,EAAK,QAAA;AAAA,gBACL,IAAA,EAAK,IAAA;AAAA,gBACL,SAAA,EAAU,uBAAA;AAAA,gBACV,QAAA;AAAA,gBACA,SAAA,EAAW,QAAA;AAAA,gBAEV,QAAA,EAAA;AAAA;AAAA;AACH,WAAA,EACF,CAAA,mBAEAE,eAAAA,CAAC,KAAA,EAAA,EAAI,WAAU,gBAAA,EACb,QAAA,EAAA;AAAA,4BAAAA,gBAAC,KAAA,EAAA,EACC,QAAA,EAAA;AAAA,8BAAAF,cAAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,iEAAA,EAAkE,QAAA,EAAA,mBAAA,EAE/E,CAAA;AAAA,cACC,sCACCA,cAAAA,CAAC,OAAE,SAAA,EAAU,4BAAA,EAA8B,+BAAoB,CAAA,GAC7D,IAAA;AAAA,cACH,oCACCA,cAAAA,CAAC,OAAE,SAAA,EAAU,wCAAA,EACV,6BACH,CAAA,GACE;AAAA,aAAA,EACN,CAAA;AAAA,4BACAA,cAAAA;AAAA,cAAC,OAAA;AAAA,cAAA;AAAA,gBACC,OAAA,EAAQ,YAAA;AAAA,gBACR,SAAA,EAAU,mCAAA;AAAA,gBACX,QAAA,EAAA;AAAA;AAAA,aAED;AAAA,4BACAA,cAAAA;AAAA,cAAC,OAAA;AAAA,cAAA;AAAA,gBACC,EAAA,EAAG,YAAA;AAAA,gBACH,SAAA,EAAU,SAAA;AAAA,gBACV,SAAA,EAAW,EAAA;AAAA,gBACX,WAAA,EAAY,qBAAA;AAAA,gBACZ,SAAA,EAAU,yLAAA;AAAA,gBACV,KAAA,EAAO,gBAAA;AAAA,gBACP,UAAU,CAAC,KAAA,KAAU,mBAAA,CAAoB,KAAA,CAAM,OAAO,KAAK;AAAA;AAAA,aAC7D;AAAA,4BACAA,cAAAA;AAAA,cAAC,MAAA;AAAA,cAAA;AAAA,gBACC,IAAA,EAAK,QAAA;AAAA,gBACL,IAAA,EAAK,IAAA;AAAA,gBACL,SAAA,EAAU,uBAAA;AAAA,gBACV,QAAA,EAAU,gBAAA,CAAiB,MAAA,KAAW,EAAA,IAAM,SAAA;AAAA,gBAC5C,SAAA,EAAW,SAAA;AAAA,gBACX,OAAA,EAAS,YAAA;AAAA,gBACV,QAAA,EAAA;AAAA;AAAA;AAED,WAAA,EACF;AAAA;AAAA;AAAA,KAEJ;AAAA,EAEJ;AACF;AAEA,KAAA,CAAM,WAAA,GAAc,OAAA;;;ACtNb,IAAM,kBAAA,GAAqB,CAChC,UAAA,KACG;;;ACxBE,IAAM,0BAA0B,eAAA,CAAY","file":"index.js","sourcesContent":["{\n \"name\": \"@replicated/portal-components\",\n \"version\": \"0.0.2\",\n \"description\": \"Opinionated component library for Replicated enterprise portals\",\n \"license\": \"Apache-2.0\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/replicatedhq/enterprise-portal-components.git\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/replicatedhq/enterprise-portal-components/issues\"\n },\n \"homepage\": \"https://github.com/replicatedhq/enterprise-portal-components#readme\",\n \"main\": \"dist/index.js\",\n \"module\": \"dist/esm/index.js\",\n \"types\": \"dist/index.d.ts\",\n \"style\": \"dist/styles.css\",\n \"files\": [\n \"dist\",\n \"components/metadata\"\n ],\n \"sideEffects\": [\n \"*.css\",\n \"*.scss\"\n ],\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/esm/index.js\",\n \"require\": \"./dist/index.js\"\n },\n \"./tokens\": {\n \"types\": \"./dist/tokens/index.d.ts\",\n \"import\": \"./dist/esm/tokens/index.js\",\n \"require\": \"./dist/tokens/index.js\"\n },\n \"./actions\": {\n \"types\": \"./dist/actions/index.d.ts\",\n \"import\": \"./dist/esm/actions/index.js\",\n \"require\": \"./dist/actions/index.js\"\n },\n \"./styles.css\": \"./dist/styles.css\",\n \"./metadata/registry.json\": \"./components/metadata/registry.json\"\n },\n \"scripts\": {\n \"clean\": \"rimraf dist\",\n \"build\": \"npm run clean && npm run registry:generate && npm run build:css && tsup\",\n \"build:css\": \"tailwindcss -i ./src/styles.css -o ./dist/styles.css --config tailwind.config.ts\",\n \"dev\": \"tsup --watch\",\n \"typecheck\": \"tsc --noEmit\",\n \"registry:generate\": \"tsx scripts/generate-registry.ts\",\n \"size\": \"size-limit\",\n \"release\": \"changeset\"\n },\n \"peerDependencies\": {\n \"react\": \"^19.0.0\",\n \"react-dom\": \"^19.0.0\"\n },\n \"devDependencies\": {\n \"@changesets/cli\": \"^2.27.8\",\n \"@size-limit/preset-big-lib\": \"^9.0.0\",\n \"@types/node\": \"^22.10.1\",\n \"@types/react\": \"^19.0.4\",\n \"@types/react-dom\": \"^19.0.3\",\n \"autoprefixer\": \"^10.4.20\",\n \"postcss\": \"^8.4.49\",\n \"react\": \"19.0.0\",\n \"react-dom\": \"19.0.0\",\n \"rimraf\": \"^6.0.1\",\n \"size-limit\": \"^9.0.0\",\n \"tailwindcss\": \"^3.4.17\",\n \"tslib\": \"^2.7.0\",\n \"tsup\": \"^8.3.5\",\n \"tsx\": \"^4.19.2\",\n \"typescript\": \"^5.6.3\"\n },\n \"engines\": {\n \"node\": \">=20\"\n },\n \"packageManager\": \"npm@10\",\n \"size-limit\": [\n {\n \"name\": \"core entry\",\n \"path\": \"dist/esm/index.js\",\n \"import\": \"{ Login }\",\n \"limit\": \"35 KB\",\n \"gzip\": true\n }\n ]\n}\n","type DeepPartial<T> = {\n [Key in keyof T]?: T[Key] extends Record<string, unknown>\n ? DeepPartial<T[Key]>\n : T[Key];\n};\n\nexport interface PortalThemeTokens {\n colors: {\n background: string;\n foreground: string;\n surfaceMuted: string;\n primary: string;\n onPrimary: string;\n secondary: string;\n onSecondary: string;\n success: string;\n warning: string;\n danger: string;\n };\n radii: {\n lg: string;\n md: string;\n sm: string;\n };\n spacing: Record<\"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\", string>;\n typography: {\n fontFamily: string;\n monoFamily: string;\n lineHeight: Record<\"tight\" | \"normal\" | \"relaxed\", string>;\n };\n shadows: {\n focus: string;\n overlay: string;\n };\n}\n\nconst baseTokens: PortalThemeTokens = {\n colors: {\n background: \"#0b0d12\",\n foreground: \"#f4f6fb\",\n surfaceMuted: \"#1b1f2a\",\n primary: \"#0d6efd\",\n onPrimary: \"#ffffff\",\n secondary: \"#6c757d\",\n onSecondary: \"#ffffff\",\n success: \"#22c55e\",\n warning: \"#fbbf24\",\n danger: \"#ef4444\"\n },\n radii: {\n lg: \"16px\",\n md: \"10px\",\n sm: \"6px\"\n },\n spacing: {\n xs: \"0.25rem\",\n sm: \"0.5rem\",\n md: \"0.75rem\",\n lg: \"1rem\",\n xl: \"1.5rem\"\n },\n typography: {\n fontFamily:\n \"Inter, 'SF Pro Display', 'Segoe UI', system-ui, -apple-system, sans-serif\",\n monoFamily: \"'JetBrains Mono', 'SFMono-Regular', Consolas, monospace\",\n lineHeight: {\n tight: \"1.2\",\n normal: \"1.5\",\n relaxed: \"1.75\"\n }\n },\n shadows: {\n focus: \"0 0 0 3px color-mix(in srgb, #0d6efd 35%, transparent)\",\n overlay: \"0px 20px 55px rgba(5, 7, 11, 0.5)\"\n }\n};\n\nconst mergeTokens = (\n base: PortalThemeTokens,\n overrides?: DeepPartial<PortalThemeTokens>\n): PortalThemeTokens => {\n if (!overrides) {\n return base;\n }\n\n const clone = structuredClone(base);\n\n const apply = (target: Record<string, any>, source: Record<string, any>) => {\n Object.entries(source).forEach(([key, value]) => {\n if (\n value &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n typeof target[key] === \"object\"\n ) {\n apply(target[key], value);\n return;\n }\n\n target[key] = value;\n });\n };\n\n apply(clone as Record<string, any>, overrides as Record<string, any>);\n return clone;\n};\n\nexport const portalThemeTokens: PortalThemeTokens =\n mergeTokens(baseTokens);\n\nexport const createPortalTheme = (\n overrides?: DeepPartial<PortalThemeTokens>\n) => mergeTokens(baseTokens, overrides);\n\nexport type { DeepPartial as PortalThemeOverrides };\n","import {\n forwardRef,\n type ComponentPropsWithoutRef,\n type ReactNode\n} from \"react\";\n\nconst buttonVariants = [\"primary\", \"secondary\", \"ghost\", \"destructive\"] as const;\nconst buttonSizes = [\"sm\", \"md\", \"lg\"] as const;\n\nconst variantStyles: Record<ButtonVariant, string> = {\n primary:\n \"bg-primary text-primary-foreground hover:bg-primary/90 focus-visible:ring-primary\",\n secondary:\n \"bg-secondary/20 text-secondary-foreground hover:bg-secondary/30 focus-visible:ring-secondary\",\n ghost:\n \"bg-transparent text-primary hover:bg-primary/10 focus-visible:ring-primary/60\",\n destructive:\n \"bg-danger text-white hover:bg-danger/90 focus-visible:ring-danger\"\n};\n\nconst sizeStyles: Record<ButtonSize, string> = {\n sm: \"h-8 px-3 text-sm\",\n md: \"h-10 px-4 text-sm\",\n lg: \"h-12 px-6 text-base\"\n};\n\nconst inlineFlexBase =\n \"inline-flex items-center justify-center gap-2 rounded-md font-medium tracking-tight transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-60\";\n\nconst Spinner = () => (\n <span className=\"inline-flex h-3.5 w-3.5 animate-spin items-center justify-center\">\n <span className=\"h-3 w-3 rounded-full border-2 border-transparent border-t-current\" />\n </span>\n);\n\nexport type ButtonVariant = (typeof buttonVariants)[number];\nexport type ButtonSize = (typeof buttonSizes)[number];\n\nexport interface ButtonProps extends ComponentPropsWithoutRef<\"button\"> {\n variant?: ButtonVariant;\n size?: ButtonSize;\n isLoading?: boolean;\n leadingIcon?: ReactNode;\n trailingIcon?: ReactNode;\n}\n\nconst composeClassName = (\n ...values: Array<string | undefined | false>\n): string => values.filter(Boolean).join(\" \");\n\n/**\n * Button is the primary interactive primitive for triggering portal actions.\n * It is theme aware via CSS variables generated from portal tokens.\n */\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n (\n {\n variant = \"primary\",\n size = \"md\",\n type = \"button\",\n isLoading = false,\n leadingIcon,\n trailingIcon,\n disabled,\n className,\n children,\n ...props\n },\n ref\n ) => {\n const computedLeading = isLoading ? <Spinner /> : leadingIcon;\n const computedDisabled = disabled ?? isLoading;\n\n return (\n <button\n ref={ref}\n type={type}\n className={composeClassName(\n inlineFlexBase,\n variantStyles[variant],\n sizeStyles[size],\n className\n )}\n aria-busy={isLoading || undefined}\n disabled={computedDisabled}\n {...props}\n >\n {computedLeading ? (\n <span aria-hidden=\"true\" className=\"inline-flex\">\n {computedLeading}\n </span>\n ) : null}\n <span className=\"flex-1 whitespace-nowrap\">{children}</span>\n {trailingIcon ? (\n <span aria-hidden=\"true\" className=\"inline-flex\">\n {trailingIcon}\n </span>\n ) : null}\n </button>\n );\n }\n);\n\nButton.displayName = \"Button\";\n","'use client';\n\nimport {\n forwardRef,\n useMemo,\n useState,\n type ComponentPropsWithoutRef,\n type FormEvent,\n type ReactNode\n} from \"react\";\n\nimport { Button } from \"./button\";\n\nexport interface LoginProps\n extends Omit<ComponentPropsWithoutRef<\"form\">, \"onSubmit\" | \"children\"> {\n logo?: ReactNode;\n title?: string;\n description?: string;\n label?: string;\n placeholder?: string;\n ctaLabel?: string;\n initialEmail?: string;\n isSubmitting?: boolean;\n onContinue?: (email: string) => Promise<void> | void;\n onVerifyCode?: (\n code: string,\n redirectPath: string\n ) => Promise<\n | void\n | { success: true }\n | { success: false; message?: string }\n >;\n redirectPath?: string;\n}\n\n/**\n * Login renders a compact form card tailored for portal authentication flows.\n * The component keeps styling self-contained so it can be dropped into the\n * local Next.js template without additional wrappers.\n */\nexport const Login = forwardRef<HTMLFormElement, LoginProps>(\n (\n {\n logo,\n title = \"Enterprise Factory Installation Portal\",\n description = \"Sign in to manage your enterprise installation\",\n label = \"Work email address\",\n placeholder = \"you@company.com\",\n ctaLabel = \"Continue with email →\",\n initialEmail = \"\",\n isSubmitting = false,\n onContinue,\n onVerifyCode,\n redirectPath = \"/\",\n className,\n ...props\n },\n ref\n ) => {\n const [mode, setMode] = useState<\"email\" | \"verify\">(\"email\");\n const [email, setEmail] = useState(initialEmail);\n const [submittedEmail, setSubmittedEmail] = useState(initialEmail);\n const [verificationCode, setVerificationCode] = useState(\"\");\n const [verifying, setVerifying] = useState(false);\n const [pending, setPending] = useState(false);\n const [verificationError, setVerificationError] = useState<string | null>(\n null\n );\n\n const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n if (!onContinue || mode !== \"email\") {\n return;\n }\n\n try {\n setPending(true);\n await onContinue(email);\n setSubmittedEmail(email);\n setVerificationCode(\"\");\n setVerificationError(null);\n setMode(\"verify\");\n } finally {\n setPending(false);\n }\n };\n\n const disabled = mode === \"email\" && (isSubmitting || pending);\n const verificationMessage = useMemo(() => {\n if (!submittedEmail) {\n return null;\n }\n return `If there is an account for ${submittedEmail}, an email will be sent with a login code.`;\n }, [submittedEmail]);\n\n const handleVerify = async () => {\n if (!onVerifyCode || !verificationCode) {\n return;\n }\n try {\n setVerificationError(null);\n setVerifying(true);\n const result = await onVerifyCode(verificationCode, redirectPath);\n if (\n result &&\n typeof result === \"object\" &&\n \"success\" in result &&\n (result as { success?: unknown }).success === false\n ) {\n const message =\n (result as { message?: string }).message ??\n \"Unable to verify code. Try again.\";\n setVerificationError(message);\n return;\n }\n setVerificationCode(\"\");\n } catch (error) {\n if (\n typeof error === \"object\" &&\n error !== null &&\n \"message\" in error &&\n typeof (error as { message?: unknown }).message === \"string\"\n ) {\n setVerificationError((error as { message: string }).message);\n } else {\n setVerificationError(\"Unable to verify code. Try again.\");\n }\n } finally {\n setVerifying(false);\n }\n };\n\n return (\n <form\n ref={ref}\n className={[\n \"w-full max-w-md rounded-3xl border border-white/15 bg-slate-950/80 p-10 shadow-[0_25px_70px_rgba(5,7,11,0.65)] backdrop-blur\",\n \"text-white transition-shadow\",\n className\n ]\n .filter(Boolean)\n .join(\" \")}\n onSubmit={handleSubmit}\n {...props}\n >\n <div className=\"flex flex-col items-center gap-4 text-center\">\n {logo ?? (\n <div className=\"flex h-16 w-16 items-center justify-center rounded-2xl border border-white/20 bg-white/10 text-lg font-semibold leading-tight\">\n EP\n </div>\n )}\n <div>\n <p className=\"text-base font-semibold uppercase tracking-[0.35em] text-white/65\">\n Enterprise Portal\n </p>\n <h1 className=\"mt-3 text-3xl font-semibold tracking-tight\">\n {title}\n </h1>\n <p className=\"mt-2 text-sm text-white/70\">{description}</p>\n </div>\n </div>\n {mode === \"email\" ? (\n <div className=\"mt-8 space-y-3\">\n <label\n htmlFor=\"login-email\"\n className=\"text-sm font-medium text-white/70\"\n >\n {label}\n </label>\n <input\n id=\"login-email\"\n type=\"email\"\n inputMode=\"email\"\n autoComplete=\"email\"\n placeholder={placeholder}\n value={email}\n onChange={(event) => setEmail(event.target.value)}\n className=\"w-full rounded-2xl border border-white/15 bg-white/5 px-5 py-4 text-base text-white placeholder:text-white/40 focus:border-white/40 focus:outline-none focus:ring-2 focus:ring-white/25\"\n disabled={disabled}\n required\n />\n <Button\n type=\"submit\"\n size=\"lg\"\n className=\"w-full justify-center\"\n disabled={disabled}\n isLoading={disabled}\n >\n {ctaLabel}\n </Button>\n </div>\n ) : (\n <div className=\"mt-8 space-y-4\">\n <div>\n <p className=\"text-sm font-semibold uppercase tracking-[0.35em] text-white/65\">\n Verification Code\n </p>\n {verificationMessage ? (\n <p className=\"mt-2 text-sm text-white/70\">{verificationMessage}</p>\n ) : null}\n {verificationError ? (\n <p className=\"mt-2 text-sm font-medium text-rose-300\">\n {verificationError}\n </p>\n ) : null}\n </div>\n <label\n htmlFor=\"login-code\"\n className=\"text-sm font-medium text-white/70\"\n >\n One-time code\n </label>\n <input\n id=\"login-code\"\n inputMode=\"numeric\"\n maxLength={12}\n placeholder=\"Enter 12 digit code\"\n className=\"w-full rounded-2xl border border-white/15 bg-white/5 px-5 py-4 text-base text-white placeholder:text-white/40 focus:border-white/40 focus:outline-none focus:ring-2 focus:ring-white/25\"\n value={verificationCode}\n onChange={(event) => setVerificationCode(event.target.value)}\n />\n <Button\n type=\"button\"\n size=\"lg\"\n className=\"w-full justify-center\"\n disabled={verificationCode.length !== 12 || verifying}\n isLoading={verifying}\n onClick={handleVerify}\n >\n Verify code\n </Button>\n </div>\n )}\n </form>\n );\n }\n);\n\nLogin.displayName = \"Login\";\n","/**\n * Light-weight type helpers for defining Server Actions that align with the\n * enterprise portal guardrails. The component library does not implement\n * specific actions, but it exports helpers so downstream portals can describe\n * their actions with consistent metadata.\n */\n\nexport type PortalActionVisibility = \"vendor\" | \"customer\";\n\nexport interface PortalActionContext {\n vendorId: string;\n licenseId: string;\n userId: string;\n signal?: AbortSignal;\n}\n\nexport interface PortalServerActionDefinition<Input, Output> {\n id: string;\n description: string;\n visibility: PortalActionVisibility;\n tags: string[];\n run: (input: Input, context?: PortalActionContext) => Promise<Output>;\n}\n\nexport const defineServerAction = <Input, Output>(\n definition: PortalServerActionDefinition<Input, Output>\n) => definition;\n\nexport interface InitiateLoginInput {\n email: string;\n}\n\nexport interface InitiateLoginResult {\n status: \"ok\";\n requestedAt: string;\n message: string;\n}\n\n/**\n * Reference server action for initiating the passwordless login flow.\n * Real portals should replace the simulated delay with a call to their auth API.\n */\nexport const initiateLogin = defineServerAction<\n InitiateLoginInput,\n InitiateLoginResult\n>({\n id: \"auth/initiate-login\",\n description:\n \"Begins the passwordless login flow by dispatching a magic link email.\",\n visibility: \"customer\",\n tags: [\"auth\", \"login\", \"session\"],\n async run(input) {\n const origin =\n process.env.REPLICATED_APP_ORIGIN ?? \"https://replicated.app\";\n const endpoint = `${origin.replace(/\\/+$/, \"\")}/v3/login/magic-link`;\n const appSlug = process.env.PORTAL_APP_SLUG;\n if (!appSlug) {\n throw new Error(\"PORTAL_APP_SLUG is not configured\");\n }\n const portalOrigin =\n process.env.PORTAL_ORIGIN ?? \"https://enterprise.replicated.com\";\n const redirectUri = `${portalOrigin.replace(/\\/+$/, \"\")}/${appSlug}/login`;\n\n if (process.env.NODE_ENV !== \"production\") {\n console.debug(\n \"[portal-components] initiating login via %s for app %s redirecting to %s\",\n endpoint,\n appSlug,\n redirectUri\n );\n }\n\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\"\n },\n body: JSON.stringify({\n app_slug: appSlug,\n email_address: input.email,\n redirect_uri: redirectUri\n })\n });\n\n if (!response.ok) {\n throw new Error(\n `Magic link request failed (${response.status} ${response.statusText})`\n );\n }\n\n return {\n status: \"ok\",\n requestedAt: new Date().toISOString(),\n message: `Magic link requested for ${input.email}`\n };\n }\n});\n\nexport interface VerifyMagicLinkInput {\n nonce: string;\n}\n\nexport interface VerifyMagicLinkResult {\n token: string;\n raw: unknown;\n}\n\nexport interface VerifyMagicLinkError {\n code: \"invalid_code\" | \"unknown\";\n message: string;\n}\n\nexport const verifyMagicLink = defineServerAction<\n VerifyMagicLinkInput,\n VerifyMagicLinkResult\n>({\n id: \"auth/verify-magic-link\",\n description: \"Verifies the 12-digit code provided via email and returns a JWT.\",\n visibility: \"customer\",\n tags: [\"auth\", \"login\", \"verify\"],\n async run({ nonce }) {\n const origin =\n process.env.REPLICATED_APP_ORIGIN ?? \"https://replicated.app\";\n const endpoint = `${origin.replace(/\\/+$/, \"\")}/v3/login/magic-link/verify`;\n\n if (process.env.NODE_ENV !== \"production\") {\n console.debug(\n \"[portal-components] verifying magic link via %s\",\n endpoint\n );\n }\n\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\"\n },\n body: JSON.stringify({ nonce })\n });\n\n if (!response.ok) {\n if (response.status === 401) {\n const error: VerifyMagicLinkError = {\n code: \"invalid_code\",\n message: \"Incorrect code, check your email and try again.\"\n };\n throw error;\n }\n const error: VerifyMagicLinkError = {\n code: \"unknown\",\n message: `Magic link verification failed (${response.status} ${response.statusText})`\n };\n throw error;\n }\n\n const payload = await response.json();\n const token = payload?.token ?? payload?.jwt ?? payload?.access_token;\n if (typeof token !== \"string\") {\n throw new Error(\"Magic link verification succeeded but no token returned\");\n }\n\n return { token, raw: payload };\n }\n});\n","import packageJson from \"../package.json\";\n\nexport const portalComponentsVersion = packageJson.version;\n\nexport type {\n ComponentAccessibilityMetadata,\n ComponentBehaviorMetadata,\n ComponentCategory,\n ComponentRegistryDocument,\n ComponentRegistryEntry,\n ComponentExampleMetadata,\n ComponentPerformanceMetadata,\n ComponentPropMetadata\n} from \"./types/metadata\";\n\nexport {\n createPortalTheme,\n portalThemeTokens\n} from \"./tokens\";\nexport type { PortalThemeTokens, PortalThemeOverrides } from \"./tokens\";\n\nexport * from \"./components\";\n\nexport {\n defineServerAction\n} from \"./actions\";\nexport type {\n PortalActionContext,\n PortalActionVisibility,\n PortalServerActionDefinition\n} from \"./actions\";\n"]}
package/dist/styles.css CHANGED
@@ -658,6 +658,12 @@ video {
658
658
  margin-bottom: calc(0.75rem * var(--tw-space-y-reverse));
659
659
  }
660
660
 
661
+ .space-y-4 > :not([hidden]) ~ :not([hidden]) {
662
+ --tw-space-y-reverse: 0;
663
+ margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
664
+ margin-bottom: calc(1rem * var(--tw-space-y-reverse));
665
+ }
666
+
661
667
  .whitespace-nowrap {
662
668
  white-space: nowrap;
663
669
  }
@@ -811,6 +817,11 @@ video {
811
817
  color: var(--portal-color-on-primary);
812
818
  }
813
819
 
820
+ .text-rose-300 {
821
+ --tw-text-opacity: 1;
822
+ color: rgb(253 164 175 / var(--tw-text-opacity, 1));
823
+ }
824
+
814
825
  .text-secondary-foreground {
815
826
  color: var(--portal-color-on-secondary);
816
827
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@replicated/portal-components",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Opinionated component library for Replicated enterprise portals",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {