@poly-x/next 0.1.0-alpha.2 → 0.1.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server.cjs +53 -23
- package/dist/server.d.cts +5 -0
- package/dist/server.d.mts +5 -0
- package/dist/server.mjs +53 -23
- package/dist/styles.css +379 -0
- package/package.json +9 -6
package/dist/server.cjs
CHANGED
|
@@ -36,6 +36,8 @@ function buildServerAuthClient(config) {
|
|
|
36
36
|
*/
|
|
37
37
|
const VERIFIER_COOKIE = "polyx_pkce";
|
|
38
38
|
const VERIFIER_MAX_AGE = 600;
|
|
39
|
+
/** poly-auth's own floor (Joi: min 6, max 128) — reject early rather than round-trip. */
|
|
40
|
+
const MIN_PASSWORD_LENGTH = 6;
|
|
39
41
|
function adapt(store) {
|
|
40
42
|
return {
|
|
41
43
|
get: (name) => store.get(name)?.value,
|
|
@@ -92,6 +94,7 @@ function createAuthHandlers(handlersConfig = {}) {
|
|
|
92
94
|
async authenticate(request) {
|
|
93
95
|
const config = resolveNextConfig(handlersConfig);
|
|
94
96
|
const redirectUri = `${new URL(request.url).origin}${callbackPath}`;
|
|
97
|
+
const client = buildServerAuthClient(config);
|
|
95
98
|
let payload;
|
|
96
99
|
try {
|
|
97
100
|
payload = await request.json();
|
|
@@ -103,36 +106,63 @@ function createAuthHandlers(handlersConfig = {}) {
|
|
|
103
106
|
if (!email || !password) return Response.json({ status: "invalid_request" }, { status: 400 });
|
|
104
107
|
const organizationCode = typeof payload.organizationCode === "string" ? payload.organizationCode : void 0;
|
|
105
108
|
const tenantId = typeof payload.tenantId === "string" ? payload.tenantId : void 0;
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
+
const newPassword = typeof payload.newPassword === "string" ? payload.newPassword : void 0;
|
|
110
|
+
const confirmPassword = typeof payload.confirmPassword === "string" ? payload.confirmPassword : void 0;
|
|
111
|
+
/** One authorize attempt, mapped to the wire response (sealing the session on success). */
|
|
112
|
+
async function attempt(withPassword) {
|
|
113
|
+
const { verifier, challenge } = await (0, _poly_x_core.generatePkce)();
|
|
114
|
+
const outcome = await client.authorizeWithPassword({
|
|
115
|
+
email,
|
|
116
|
+
password: withPassword,
|
|
117
|
+
redirectUri,
|
|
118
|
+
state: (0, _poly_x_core.randomState)(),
|
|
119
|
+
challenge,
|
|
120
|
+
organizationCode,
|
|
121
|
+
tenantId
|
|
122
|
+
});
|
|
123
|
+
switch (outcome.type) {
|
|
124
|
+
case "authorized":
|
|
125
|
+
await newSession(adapt(await (0, next_headers.cookies)()), config).establishFromCode(outcome.code, verifier, redirectUri);
|
|
126
|
+
return Response.json({
|
|
127
|
+
status: "success",
|
|
128
|
+
redirectTo: afterSignInPath
|
|
129
|
+
});
|
|
130
|
+
case "select_tenant": return Response.json({
|
|
131
|
+
status: "select_tenant",
|
|
132
|
+
tenants: outcome.tenants,
|
|
133
|
+
state: outcome.state
|
|
134
|
+
});
|
|
135
|
+
case "password_change_required": return Response.json({
|
|
136
|
+
status: "password_change_required",
|
|
137
|
+
userId: outcome.userId
|
|
138
|
+
}, { status: 403 });
|
|
139
|
+
case "no_access": return Response.json({ status: "no_access" }, { status: 403 });
|
|
140
|
+
default: return Response.json({ status: "invalid_credentials" }, { status: 401 });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (newPassword === void 0) return attempt(password);
|
|
144
|
+
if (newPassword.length < MIN_PASSWORD_LENGTH) return Response.json({
|
|
145
|
+
status: "weak_password",
|
|
146
|
+
minLength: MIN_PASSWORD_LENGTH
|
|
147
|
+
}, { status: 400 });
|
|
148
|
+
if (newPassword !== confirmPassword) return Response.json({ status: "password_mismatch" }, { status: 400 });
|
|
149
|
+
const { challenge } = await (0, _poly_x_core.generatePkce)();
|
|
150
|
+
const proof = await client.authorizeWithPassword({
|
|
109
151
|
email,
|
|
110
152
|
password,
|
|
111
153
|
redirectUri,
|
|
112
|
-
state,
|
|
154
|
+
state: (0, _poly_x_core.randomState)(),
|
|
113
155
|
challenge,
|
|
114
156
|
organizationCode,
|
|
115
157
|
tenantId
|
|
116
158
|
});
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
case "select_tenant": return Response.json({
|
|
125
|
-
status: "select_tenant",
|
|
126
|
-
tenants: outcome.tenants,
|
|
127
|
-
state: outcome.state
|
|
128
|
-
});
|
|
129
|
-
case "password_change_required": return Response.json({
|
|
130
|
-
status: "password_change_required",
|
|
131
|
-
userId: outcome.userId
|
|
132
|
-
}, { status: 403 });
|
|
133
|
-
case "no_access": return Response.json({ status: "no_access" }, { status: 403 });
|
|
134
|
-
default: return Response.json({ status: "invalid_credentials" }, { status: 401 });
|
|
135
|
-
}
|
|
159
|
+
if (proof.type !== "password_change_required") return Response.json({ status: "invalid_credentials" }, { status: 401 });
|
|
160
|
+
await client.setPassword({
|
|
161
|
+
userId: proof.userId,
|
|
162
|
+
newPassword,
|
|
163
|
+
confirmPassword
|
|
164
|
+
});
|
|
165
|
+
return attempt(newPassword);
|
|
136
166
|
},
|
|
137
167
|
async callback(request) {
|
|
138
168
|
const config = resolveNextConfig(handlersConfig);
|
package/dist/server.d.cts
CHANGED
package/dist/server.d.mts
CHANGED
package/dist/server.mjs
CHANGED
|
@@ -35,6 +35,8 @@ function buildServerAuthClient(config) {
|
|
|
35
35
|
*/
|
|
36
36
|
const VERIFIER_COOKIE = "polyx_pkce";
|
|
37
37
|
const VERIFIER_MAX_AGE = 600;
|
|
38
|
+
/** poly-auth's own floor (Joi: min 6, max 128) — reject early rather than round-trip. */
|
|
39
|
+
const MIN_PASSWORD_LENGTH = 6;
|
|
38
40
|
function adapt(store) {
|
|
39
41
|
return {
|
|
40
42
|
get: (name) => store.get(name)?.value,
|
|
@@ -91,6 +93,7 @@ function createAuthHandlers(handlersConfig = {}) {
|
|
|
91
93
|
async authenticate(request) {
|
|
92
94
|
const config = resolveNextConfig(handlersConfig);
|
|
93
95
|
const redirectUri = `${new URL(request.url).origin}${callbackPath}`;
|
|
96
|
+
const client = buildServerAuthClient(config);
|
|
94
97
|
let payload;
|
|
95
98
|
try {
|
|
96
99
|
payload = await request.json();
|
|
@@ -102,36 +105,63 @@ function createAuthHandlers(handlersConfig = {}) {
|
|
|
102
105
|
if (!email || !password) return Response.json({ status: "invalid_request" }, { status: 400 });
|
|
103
106
|
const organizationCode = typeof payload.organizationCode === "string" ? payload.organizationCode : void 0;
|
|
104
107
|
const tenantId = typeof payload.tenantId === "string" ? payload.tenantId : void 0;
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
+
const newPassword = typeof payload.newPassword === "string" ? payload.newPassword : void 0;
|
|
109
|
+
const confirmPassword = typeof payload.confirmPassword === "string" ? payload.confirmPassword : void 0;
|
|
110
|
+
/** One authorize attempt, mapped to the wire response (sealing the session on success). */
|
|
111
|
+
async function attempt(withPassword) {
|
|
112
|
+
const { verifier, challenge } = await generatePkce();
|
|
113
|
+
const outcome = await client.authorizeWithPassword({
|
|
114
|
+
email,
|
|
115
|
+
password: withPassword,
|
|
116
|
+
redirectUri,
|
|
117
|
+
state: randomState(),
|
|
118
|
+
challenge,
|
|
119
|
+
organizationCode,
|
|
120
|
+
tenantId
|
|
121
|
+
});
|
|
122
|
+
switch (outcome.type) {
|
|
123
|
+
case "authorized":
|
|
124
|
+
await newSession(adapt(await cookies()), config).establishFromCode(outcome.code, verifier, redirectUri);
|
|
125
|
+
return Response.json({
|
|
126
|
+
status: "success",
|
|
127
|
+
redirectTo: afterSignInPath
|
|
128
|
+
});
|
|
129
|
+
case "select_tenant": return Response.json({
|
|
130
|
+
status: "select_tenant",
|
|
131
|
+
tenants: outcome.tenants,
|
|
132
|
+
state: outcome.state
|
|
133
|
+
});
|
|
134
|
+
case "password_change_required": return Response.json({
|
|
135
|
+
status: "password_change_required",
|
|
136
|
+
userId: outcome.userId
|
|
137
|
+
}, { status: 403 });
|
|
138
|
+
case "no_access": return Response.json({ status: "no_access" }, { status: 403 });
|
|
139
|
+
default: return Response.json({ status: "invalid_credentials" }, { status: 401 });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (newPassword === void 0) return attempt(password);
|
|
143
|
+
if (newPassword.length < MIN_PASSWORD_LENGTH) return Response.json({
|
|
144
|
+
status: "weak_password",
|
|
145
|
+
minLength: MIN_PASSWORD_LENGTH
|
|
146
|
+
}, { status: 400 });
|
|
147
|
+
if (newPassword !== confirmPassword) return Response.json({ status: "password_mismatch" }, { status: 400 });
|
|
148
|
+
const { challenge } = await generatePkce();
|
|
149
|
+
const proof = await client.authorizeWithPassword({
|
|
108
150
|
email,
|
|
109
151
|
password,
|
|
110
152
|
redirectUri,
|
|
111
|
-
state,
|
|
153
|
+
state: randomState(),
|
|
112
154
|
challenge,
|
|
113
155
|
organizationCode,
|
|
114
156
|
tenantId
|
|
115
157
|
});
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
case "select_tenant": return Response.json({
|
|
124
|
-
status: "select_tenant",
|
|
125
|
-
tenants: outcome.tenants,
|
|
126
|
-
state: outcome.state
|
|
127
|
-
});
|
|
128
|
-
case "password_change_required": return Response.json({
|
|
129
|
-
status: "password_change_required",
|
|
130
|
-
userId: outcome.userId
|
|
131
|
-
}, { status: 403 });
|
|
132
|
-
case "no_access": return Response.json({ status: "no_access" }, { status: 403 });
|
|
133
|
-
default: return Response.json({ status: "invalid_credentials" }, { status: 401 });
|
|
134
|
-
}
|
|
158
|
+
if (proof.type !== "password_change_required") return Response.json({ status: "invalid_credentials" }, { status: 401 });
|
|
159
|
+
await client.setPassword({
|
|
160
|
+
userId: proof.userId,
|
|
161
|
+
newPassword,
|
|
162
|
+
confirmPassword
|
|
163
|
+
});
|
|
164
|
+
return attempt(newPassword);
|
|
135
165
|
},
|
|
136
166
|
async callback(request) {
|
|
137
167
|
const config = resolveNextConfig(handlersConfig);
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @poly-x/react — default component styles.
|
|
3
|
+
*
|
|
4
|
+
* Import once, near the root of your app:
|
|
5
|
+
* import "@poly-x/react/styles.css"; // or "@poly-x/next/styles.css"
|
|
6
|
+
*
|
|
7
|
+
* The SDK is a guest in your app: every rule is scoped under a `.polyx-*` class,
|
|
8
|
+
* there are no global element selectors, and nothing here leaks into your own
|
|
9
|
+
* markup. To theme it, override the custom properties below on any ancestor
|
|
10
|
+
* (`:root`, a wrapper div, …) — that is the supported theming surface. Dark mode
|
|
11
|
+
* follows the OS by default and defers to an explicit `.dark` / `[data-theme]`
|
|
12
|
+
* on a host ancestor when one is present.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
.polyx-signin {
|
|
16
|
+
/* Colors */
|
|
17
|
+
--polyx-brand: #4f46e5;
|
|
18
|
+
--polyx-brand-hover: #4338ca;
|
|
19
|
+
--polyx-brand-fg: #ffffff;
|
|
20
|
+
--polyx-bg: #ffffff;
|
|
21
|
+
--polyx-fg: #18181b;
|
|
22
|
+
--polyx-muted-fg: #71717a;
|
|
23
|
+
--polyx-border: #e4e4e7;
|
|
24
|
+
--polyx-input-bg: #ffffff;
|
|
25
|
+
--polyx-ring: #a5b4fc;
|
|
26
|
+
--polyx-danger: #dc2626;
|
|
27
|
+
--polyx-danger-bg: #fef2f2;
|
|
28
|
+
--polyx-page-bg: #fafafa;
|
|
29
|
+
|
|
30
|
+
/* Shape + type */
|
|
31
|
+
--polyx-radius: 0.75rem;
|
|
32
|
+
--polyx-radius-sm: 0.5rem;
|
|
33
|
+
--polyx-font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
34
|
+
--polyx-shadow: 0 1px 3px rgb(0 0 0 / 0.06), 0 10px 30px rgb(0 0 0 / 0.07);
|
|
35
|
+
|
|
36
|
+
font-family: var(--polyx-font);
|
|
37
|
+
color: var(--polyx-fg);
|
|
38
|
+
-webkit-font-smoothing: antialiased;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Scoped box model — applies only inside our own subtree. */
|
|
42
|
+
.polyx-signin,
|
|
43
|
+
.polyx-signin *,
|
|
44
|
+
.polyx-signin *::before,
|
|
45
|
+
.polyx-signin *::after {
|
|
46
|
+
box-sizing: border-box;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Dark: follow the OS… */
|
|
50
|
+
@media (prefers-color-scheme: dark) {
|
|
51
|
+
.polyx-signin {
|
|
52
|
+
--polyx-brand: #6366f1;
|
|
53
|
+
--polyx-brand-hover: #818cf8;
|
|
54
|
+
--polyx-bg: #18181b;
|
|
55
|
+
--polyx-fg: #fafafa;
|
|
56
|
+
--polyx-muted-fg: #a1a1aa;
|
|
57
|
+
--polyx-border: #3f3f46;
|
|
58
|
+
--polyx-input-bg: #27272a;
|
|
59
|
+
--polyx-ring: #4f46e5;
|
|
60
|
+
--polyx-danger: #f87171;
|
|
61
|
+
--polyx-danger-bg: #2a1616;
|
|
62
|
+
--polyx-page-bg: #09090b;
|
|
63
|
+
--polyx-shadow: 0 1px 3px rgb(0 0 0 / 0.4), 0 10px 30px rgb(0 0 0 / 0.35);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* …but an explicit host theme wins over the OS preference (`:where` keeps
|
|
68
|
+
specificity at 0 so these stay as easy to override as the base rule). */
|
|
69
|
+
:where(.dark, [data-theme="dark"]) .polyx-signin {
|
|
70
|
+
--polyx-brand: #6366f1;
|
|
71
|
+
--polyx-brand-hover: #818cf8;
|
|
72
|
+
--polyx-bg: #18181b;
|
|
73
|
+
--polyx-fg: #fafafa;
|
|
74
|
+
--polyx-muted-fg: #a1a1aa;
|
|
75
|
+
--polyx-border: #3f3f46;
|
|
76
|
+
--polyx-input-bg: #27272a;
|
|
77
|
+
--polyx-ring: #4f46e5;
|
|
78
|
+
--polyx-danger: #f87171;
|
|
79
|
+
--polyx-danger-bg: #2a1616;
|
|
80
|
+
--polyx-page-bg: #09090b;
|
|
81
|
+
--polyx-shadow: 0 1px 3px rgb(0 0 0 / 0.4), 0 10px 30px rgb(0 0 0 / 0.35);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
:where(.light, [data-theme="light"]) .polyx-signin {
|
|
85
|
+
--polyx-brand: #4f46e5;
|
|
86
|
+
--polyx-brand-hover: #4338ca;
|
|
87
|
+
--polyx-bg: #ffffff;
|
|
88
|
+
--polyx-fg: #18181b;
|
|
89
|
+
--polyx-muted-fg: #71717a;
|
|
90
|
+
--polyx-border: #e4e4e7;
|
|
91
|
+
--polyx-input-bg: #ffffff;
|
|
92
|
+
--polyx-ring: #a5b4fc;
|
|
93
|
+
--polyx-danger: #dc2626;
|
|
94
|
+
--polyx-danger-bg: #fef2f2;
|
|
95
|
+
--polyx-page-bg: #fafafa;
|
|
96
|
+
--polyx-shadow: 0 1px 3px rgb(0 0 0 / 0.06), 0 10px 30px rgb(0 0 0 / 0.07);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* --- Variants ------------------------------------------------------------ */
|
|
100
|
+
|
|
101
|
+
/* `card`: just the card — drop it anywhere in your own layout. */
|
|
102
|
+
.polyx-signin--card {
|
|
103
|
+
display: block;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.polyx-signin__card {
|
|
107
|
+
width: 100%;
|
|
108
|
+
max-width: 27rem;
|
|
109
|
+
margin-inline: auto;
|
|
110
|
+
padding: 2.25rem;
|
|
111
|
+
background: var(--polyx-bg);
|
|
112
|
+
border: 1px solid var(--polyx-border);
|
|
113
|
+
border-radius: var(--polyx-radius);
|
|
114
|
+
box-shadow: var(--polyx-shadow);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* `page`: owns the viewport. With brand content (a `logo`, a `tagline`, or a
|
|
119
|
+
* custom `panel`) it becomes a split layout — brand panel beside the form on
|
|
120
|
+
* wide screens, a slim brand band above it on narrow ones. With no brand content
|
|
121
|
+
* there's nothing to put in a panel, so it stays a single centered column.
|
|
122
|
+
*/
|
|
123
|
+
.polyx-signin--page {
|
|
124
|
+
display: grid;
|
|
125
|
+
grid-template-columns: 1fr;
|
|
126
|
+
min-height: 100dvh;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.polyx-signin--page.polyx-signin--split {
|
|
130
|
+
grid-template-rows: auto 1fr;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@media (min-width: 64rem) {
|
|
134
|
+
.polyx-signin--page.polyx-signin--split {
|
|
135
|
+
grid-template-columns: 1.1fr 1fr;
|
|
136
|
+
grid-template-rows: 1fr;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.polyx-signin__panel {
|
|
141
|
+
display: flex;
|
|
142
|
+
position: relative;
|
|
143
|
+
overflow: hidden;
|
|
144
|
+
align-items: center;
|
|
145
|
+
gap: 1rem;
|
|
146
|
+
padding: 1.25rem 1.5rem;
|
|
147
|
+
color: var(--polyx-brand-fg);
|
|
148
|
+
background: linear-gradient(145deg, var(--polyx-brand), color-mix(in srgb, var(--polyx-brand) 55%, #000));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@media (min-width: 64rem) {
|
|
152
|
+
.polyx-signin__panel {
|
|
153
|
+
flex-direction: column;
|
|
154
|
+
align-items: flex-start;
|
|
155
|
+
justify-content: space-between;
|
|
156
|
+
gap: 2rem;
|
|
157
|
+
padding: 3.5rem;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* Soft light-bloom so a bare brand panel still reads as designed, not as a flat fill. */
|
|
162
|
+
.polyx-signin__panel::after {
|
|
163
|
+
content: "";
|
|
164
|
+
position: absolute;
|
|
165
|
+
inset: 0;
|
|
166
|
+
pointer-events: none;
|
|
167
|
+
background:
|
|
168
|
+
radial-gradient(circle at 82% 12%, rgb(255 255 255 / 0.18), transparent 45%),
|
|
169
|
+
radial-gradient(circle at 12% 88%, rgb(255 255 255 / 0.1), transparent 42%);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.polyx-signin__panel-logo,
|
|
173
|
+
.polyx-signin__tagline {
|
|
174
|
+
position: relative;
|
|
175
|
+
z-index: 1;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* The tagline is the panel's headline — it only earns its space on wide screens. */
|
|
179
|
+
.polyx-signin__tagline {
|
|
180
|
+
display: none;
|
|
181
|
+
margin: 0;
|
|
182
|
+
max-width: 18ch;
|
|
183
|
+
font-size: 2rem;
|
|
184
|
+
font-weight: 600;
|
|
185
|
+
line-height: 1.2;
|
|
186
|
+
letter-spacing: -0.02em;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
@media (min-width: 64rem) {
|
|
190
|
+
.polyx-signin__tagline {
|
|
191
|
+
display: block;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.polyx-signin__main {
|
|
196
|
+
display: grid;
|
|
197
|
+
place-items: center;
|
|
198
|
+
padding: 2.5rem 1.5rem;
|
|
199
|
+
background: var(--polyx-page-bg);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* On the page variant the form isn't a floating card — it sits on the page itself. */
|
|
203
|
+
.polyx-signin--page .polyx-signin__card {
|
|
204
|
+
max-width: 25rem;
|
|
205
|
+
padding: 0;
|
|
206
|
+
background: transparent;
|
|
207
|
+
border: none;
|
|
208
|
+
box-shadow: none;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
/* --- Header -------------------------------------------------------------- */
|
|
213
|
+
|
|
214
|
+
.polyx-signin__header {
|
|
215
|
+
display: flex;
|
|
216
|
+
flex-direction: column;
|
|
217
|
+
gap: 0.375rem;
|
|
218
|
+
margin-bottom: 1.75rem;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.polyx-signin__logo {
|
|
222
|
+
display: flex;
|
|
223
|
+
margin-bottom: 0.75rem;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.polyx-signin__heading {
|
|
227
|
+
margin: 0;
|
|
228
|
+
font-size: 1.375rem;
|
|
229
|
+
font-weight: 600;
|
|
230
|
+
letter-spacing: -0.01em;
|
|
231
|
+
color: var(--polyx-fg);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.polyx-signin__subheading {
|
|
235
|
+
margin: 0;
|
|
236
|
+
font-size: 0.875rem;
|
|
237
|
+
color: var(--polyx-muted-fg);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* --- Form ---------------------------------------------------------------- */
|
|
241
|
+
|
|
242
|
+
.polyx-signin__form {
|
|
243
|
+
display: flex;
|
|
244
|
+
flex-direction: column;
|
|
245
|
+
gap: 1.125rem;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.polyx-signin__field {
|
|
249
|
+
display: flex;
|
|
250
|
+
flex-direction: column;
|
|
251
|
+
gap: 0.4375rem;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.polyx-signin__label {
|
|
255
|
+
font-size: 0.875rem;
|
|
256
|
+
font-weight: 500;
|
|
257
|
+
color: var(--polyx-fg);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.polyx-signin__input {
|
|
261
|
+
width: 100%;
|
|
262
|
+
height: 2.5rem;
|
|
263
|
+
padding: 0 0.75rem;
|
|
264
|
+
font: inherit;
|
|
265
|
+
font-size: 0.875rem;
|
|
266
|
+
color: var(--polyx-fg);
|
|
267
|
+
background: var(--polyx-input-bg);
|
|
268
|
+
border: 1px solid var(--polyx-border);
|
|
269
|
+
border-radius: var(--polyx-radius-sm);
|
|
270
|
+
outline: none;
|
|
271
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.polyx-signin__input::placeholder {
|
|
275
|
+
color: var(--polyx-muted-fg);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.polyx-signin__input:focus-visible {
|
|
279
|
+
border-color: var(--polyx-brand);
|
|
280
|
+
box-shadow: 0 0 0 3px var(--polyx-ring);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.polyx-signin__input:disabled {
|
|
284
|
+
opacity: 0.6;
|
|
285
|
+
cursor: not-allowed;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/* --- Buttons ------------------------------------------------------------- */
|
|
289
|
+
|
|
290
|
+
.polyx-signin__submit,
|
|
291
|
+
.polyx-signin__tenant {
|
|
292
|
+
display: inline-flex;
|
|
293
|
+
align-items: center;
|
|
294
|
+
justify-content: center;
|
|
295
|
+
width: 100%;
|
|
296
|
+
min-height: 2.5rem;
|
|
297
|
+
padding: 0 1rem;
|
|
298
|
+
font: inherit;
|
|
299
|
+
font-size: 0.875rem;
|
|
300
|
+
font-weight: 500;
|
|
301
|
+
color: var(--polyx-brand-fg);
|
|
302
|
+
background: var(--polyx-brand);
|
|
303
|
+
border: 1px solid transparent;
|
|
304
|
+
border-radius: var(--polyx-radius-sm);
|
|
305
|
+
cursor: pointer;
|
|
306
|
+
transition: background-color 0.15s ease;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.polyx-signin__submit:hover:not(:disabled),
|
|
310
|
+
.polyx-signin__tenant:hover:not(:disabled) {
|
|
311
|
+
background: var(--polyx-brand-hover);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.polyx-signin__submit:focus-visible,
|
|
315
|
+
.polyx-signin__tenant:focus-visible {
|
|
316
|
+
outline: none;
|
|
317
|
+
box-shadow: 0 0 0 3px var(--polyx-ring);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.polyx-signin__submit:disabled,
|
|
321
|
+
.polyx-signin__tenant:disabled {
|
|
322
|
+
opacity: 0.6;
|
|
323
|
+
cursor: not-allowed;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/* --- Error --------------------------------------------------------------- */
|
|
327
|
+
|
|
328
|
+
.polyx-signin__error {
|
|
329
|
+
margin: 0;
|
|
330
|
+
padding: 0.625rem 0.75rem;
|
|
331
|
+
font-size: 0.8125rem;
|
|
332
|
+
color: var(--polyx-danger);
|
|
333
|
+
background: var(--polyx-danger-bg);
|
|
334
|
+
border: 1px solid color-mix(in srgb, var(--polyx-danger) 30%, transparent);
|
|
335
|
+
border-radius: var(--polyx-radius-sm);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/* --- Footer -------------------------------------------------------------- */
|
|
339
|
+
|
|
340
|
+
.polyx-signin__footer {
|
|
341
|
+
display: flex;
|
|
342
|
+
align-items: center;
|
|
343
|
+
justify-content: center;
|
|
344
|
+
gap: 0.375rem;
|
|
345
|
+
margin-top: 1.5rem;
|
|
346
|
+
font-size: 0.75rem;
|
|
347
|
+
color: var(--polyx-muted-fg);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.polyx-signin__shield {
|
|
351
|
+
flex: none;
|
|
352
|
+
width: 0.875rem;
|
|
353
|
+
height: 0.875rem;
|
|
354
|
+
opacity: 0.85;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/* --- Workspace picker ---------------------------------------------------- */
|
|
358
|
+
|
|
359
|
+
.polyx-signin__tenants {
|
|
360
|
+
display: flex;
|
|
361
|
+
flex-direction: column;
|
|
362
|
+
gap: 0.625rem;
|
|
363
|
+
margin: 0;
|
|
364
|
+
padding: 0;
|
|
365
|
+
list-style: none;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/* The picker's buttons read as choices, not as the primary CTA. */
|
|
369
|
+
.polyx-signin__tenant {
|
|
370
|
+
justify-content: flex-start;
|
|
371
|
+
color: var(--polyx-fg);
|
|
372
|
+
background: var(--polyx-input-bg);
|
|
373
|
+
border-color: var(--polyx-border);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.polyx-signin__tenant:hover:not(:disabled) {
|
|
377
|
+
background: var(--polyx-input-bg);
|
|
378
|
+
border-color: var(--polyx-brand);
|
|
379
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poly-x/next",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.4",
|
|
4
4
|
"description": "PolyX SDK for Next.js App Router - components plus server helpers",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"sideEffects":
|
|
7
|
+
"sideEffects": [
|
|
8
|
+
"**/*.css"
|
|
9
|
+
],
|
|
8
10
|
"files": [
|
|
9
11
|
"dist"
|
|
10
12
|
],
|
|
@@ -38,14 +40,15 @@
|
|
|
38
40
|
"types": "./dist/proxy.d.cts",
|
|
39
41
|
"default": "./dist/proxy.cjs"
|
|
40
42
|
}
|
|
41
|
-
}
|
|
43
|
+
},
|
|
44
|
+
"./styles.css": "./dist/styles.css"
|
|
42
45
|
},
|
|
43
46
|
"main": "./dist/index.cjs",
|
|
44
47
|
"module": "./dist/index.mjs",
|
|
45
48
|
"types": "./dist/index.d.cts",
|
|
46
49
|
"dependencies": {
|
|
47
|
-
"@poly-x/core": "0.1.0-alpha.
|
|
48
|
-
"@poly-x/react": "0.1.0-alpha.
|
|
50
|
+
"@poly-x/core": "0.1.0-alpha.4",
|
|
51
|
+
"@poly-x/react": "0.1.0-alpha.4"
|
|
49
52
|
},
|
|
50
53
|
"peerDependencies": {
|
|
51
54
|
"next": ">=14",
|
|
@@ -68,7 +71,7 @@
|
|
|
68
71
|
"react-dom": "^19.2.7"
|
|
69
72
|
},
|
|
70
73
|
"scripts": {
|
|
71
|
-
"build": "tsdown",
|
|
74
|
+
"build": "tsdown && node scripts/copy-styles.mjs",
|
|
72
75
|
"test": "vitest run",
|
|
73
76
|
"lint": "eslint src"
|
|
74
77
|
}
|