@drawnagency/primitives 0.1.33 → 0.1.35
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/auth/types.d.ts +2 -0
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/components/shell/EditorLoginForm.d.ts.map +1 -1
- package/dist/components/shell/SetPasswordForm.d.ts +6 -0
- package/dist/components/shell/SetPasswordForm.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/auth/types.ts +2 -0
- package/src/components/sections/SplitContent/SplitContent.tsx +1 -1
- package/src/components/shell/EditorLoginForm.tsx +125 -2
- package/src/components/shell/SetPasswordForm.tsx +100 -0
package/dist/auth/types.d.ts
CHANGED
|
@@ -49,6 +49,8 @@ export interface AuthProvider {
|
|
|
49
49
|
getPasswordEnabled(): Promise<boolean>;
|
|
50
50
|
setPasswordEnabled?(enabled: boolean): Promise<void>;
|
|
51
51
|
handleCallback?(request: Request, ctx: AuthContext): Promise<Response>;
|
|
52
|
+
updatePassword?(password: string, ctx: AuthContext): Promise<void>;
|
|
53
|
+
resetPassword?(email: string): Promise<void>;
|
|
52
54
|
}
|
|
53
55
|
export interface CookieLike {
|
|
54
56
|
get(name: string): {
|
package/dist/auth/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EditorLoginForm.d.ts","sourceRoot":"","sources":["../../../src/components/shell/EditorLoginForm.tsx"],"names":[],"mappings":"AAKA,UAAU,KAAK;IACb,YAAY,EAAE;QACZ,KAAK,EAAE,OAAO,CAAC;QACf,aAAa,EAAE,OAAO,CAAC;QACvB,YAAY,EAAE,OAAO,CAAC;QACtB,cAAc,EAAE,OAAO,CAAC;KACzB,CAAC;IACF,cAAc,CAAC,EAAE,CAAC,QAAQ,GAAG,QAAQ,CAAC,EAAE,CAAC;IACzC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,wBAAgB,eAAe,CAAC,EAAE,YAAY,EAAE,cAAmB,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"EditorLoginForm.d.ts","sourceRoot":"","sources":["../../../src/components/shell/EditorLoginForm.tsx"],"names":[],"mappings":"AAKA,UAAU,KAAK;IACb,YAAY,EAAE;QACZ,KAAK,EAAE,OAAO,CAAC;QACf,aAAa,EAAE,OAAO,CAAC;QACvB,YAAY,EAAE,OAAO,CAAC;QACtB,cAAc,EAAE,OAAO,CAAC;KACzB,CAAC;IACF,cAAc,CAAC,EAAE,CAAC,QAAQ,GAAG,QAAQ,CAAC,EAAE,CAAC;IACzC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,wBAAgB,eAAe,CAAC,EAAE,YAAY,EAAE,cAAmB,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,KAAK,2CA0O9F"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SetPasswordForm.d.ts","sourceRoot":"","sources":["../../../src/components/shell/SetPasswordForm.tsx"],"names":[],"mappings":"AAIA,UAAU,KAAK;IACb,MAAM,CAAC,EAAE,QAAQ,GAAG,UAAU,GAAG,IAAI,CAAC;CACvC;AAED,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,EAAE,KAAK,2CA2FhD"}
|
package/package.json
CHANGED
package/src/auth/types.ts
CHANGED
|
@@ -47,6 +47,8 @@ export interface AuthProvider {
|
|
|
47
47
|
setPasswordEnabled?(enabled: boolean): Promise<void>;
|
|
48
48
|
|
|
49
49
|
handleCallback?(request: Request, ctx: AuthContext): Promise<Response>;
|
|
50
|
+
updatePassword?(password: string, ctx: AuthContext): Promise<void>;
|
|
51
|
+
resetPassword?(email: string): Promise<void>;
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
export interface CookieLike {
|
|
@@ -32,7 +32,7 @@ export default function SplitContent({ imageId, src, srcset, alt, body, border,
|
|
|
32
32
|
const handleBodyChange = (html: string) => {
|
|
33
33
|
onChange?.({
|
|
34
34
|
type: "split_content",
|
|
35
|
-
content: { imageId, body: html },
|
|
35
|
+
content: { imageId, body: html, src, srcset, alt },
|
|
36
36
|
options: { border, imagePosition },
|
|
37
37
|
});
|
|
38
38
|
};
|
|
@@ -20,6 +20,8 @@ export function EditorLoginForm({ capabilities, oauthProviders = [], next, serve
|
|
|
20
20
|
const [password, setPassword] = useState("");
|
|
21
21
|
const [error, setError] = useState<string | null>(serverError ?? null);
|
|
22
22
|
const [loading, setLoading] = useState(false);
|
|
23
|
+
const [mode, setMode] = useState<"sign-in" | "forgot">("sign-in");
|
|
24
|
+
const [resetSent, setResetSent] = useState(false);
|
|
23
25
|
|
|
24
26
|
async function handlePasswordSubmit(e: FormEvent) {
|
|
25
27
|
e.preventDefault();
|
|
@@ -46,8 +48,36 @@ export function EditorLoginForm({ capabilities, oauthProviders = [], next, serve
|
|
|
46
48
|
|
|
47
49
|
const data = await response.json().catch(() => ({}));
|
|
48
50
|
window.location.assign(safeRedirect(data.redirectTo, "/edit"));
|
|
49
|
-
} catch {
|
|
50
|
-
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error("sign-in request failed:", err);
|
|
53
|
+
setError("Unable to reach the server. Check your connection and try again.");
|
|
54
|
+
} finally {
|
|
55
|
+
setLoading(false);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function handleResetSubmit(e: FormEvent) {
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
setLoading(true);
|
|
62
|
+
setError(null);
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const response = await fetch("/api/auth/reset-password", {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: { "Content-Type": "application/json" },
|
|
68
|
+
body: JSON.stringify({ email }),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
const data = await response.json().catch(() => ({}));
|
|
73
|
+
setError(data.error || "Failed to send reset link");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
setResetSent(true);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.error("reset-password request failed:", err);
|
|
80
|
+
setError("Unable to reach the server. Check your connection and try again.");
|
|
51
81
|
} finally {
|
|
52
82
|
setLoading(false);
|
|
53
83
|
}
|
|
@@ -57,6 +87,87 @@ export function EditorLoginForm({ capabilities, oauthProviders = [], next, serve
|
|
|
57
87
|
window.location.href = `/api/auth/oauth?provider=${provider}`;
|
|
58
88
|
}
|
|
59
89
|
|
|
90
|
+
function switchToForgot() {
|
|
91
|
+
setMode("forgot");
|
|
92
|
+
setError(null);
|
|
93
|
+
setResetSent(false);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function switchToSignIn() {
|
|
97
|
+
setMode("sign-in");
|
|
98
|
+
setError(null);
|
|
99
|
+
setResetSent(false);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (mode === "forgot") {
|
|
103
|
+
return (
|
|
104
|
+
<div className="mx-auto max-w-sm space-y-6">
|
|
105
|
+
<div className="space-y-2 text-center">
|
|
106
|
+
<h2 className="text-lg font-semibold text-base-contrast">Reset your password</h2>
|
|
107
|
+
<p className="text-sm text-base-contrast-light">
|
|
108
|
+
Enter your email and we'll send you a link to reset your password.
|
|
109
|
+
</p>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
{resetSent ? (
|
|
113
|
+
<div className="space-y-4">
|
|
114
|
+
<p className="text-center text-sm text-base-contrast-light">
|
|
115
|
+
If an account exists for <strong className="text-base-contrast">{email}</strong>, you'll receive
|
|
116
|
+
a password reset link shortly.
|
|
117
|
+
</p>
|
|
118
|
+
<Button
|
|
119
|
+
type="button"
|
|
120
|
+
variant="secondary"
|
|
121
|
+
size="md"
|
|
122
|
+
onClick={switchToSignIn}
|
|
123
|
+
className="w-full"
|
|
124
|
+
>
|
|
125
|
+
Back to sign in
|
|
126
|
+
</Button>
|
|
127
|
+
</div>
|
|
128
|
+
) : (
|
|
129
|
+
<form onSubmit={handleResetSubmit} className="space-y-4">
|
|
130
|
+
<div>
|
|
131
|
+
<label htmlFor="reset-email" className="mb-1.5 block text-sm font-medium text-base-contrast">
|
|
132
|
+
Email
|
|
133
|
+
</label>
|
|
134
|
+
<input
|
|
135
|
+
id="reset-email"
|
|
136
|
+
type="email"
|
|
137
|
+
value={email}
|
|
138
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
139
|
+
required
|
|
140
|
+
autoComplete="email"
|
|
141
|
+
className="w-full rounded border border-base-200 bg-base px-3 py-2 text-sm text-base-contrast focus:border-base-contrast focus:outline-none focus:ring-1 focus:ring-base-contrast"
|
|
142
|
+
/>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
{error && <p className="text-sm text-red-600">{error}</p>}
|
|
146
|
+
|
|
147
|
+
<Button
|
|
148
|
+
type="submit"
|
|
149
|
+
variant="primary"
|
|
150
|
+
size="md"
|
|
151
|
+
isLoading={loading}
|
|
152
|
+
loadingLabel="Sending..."
|
|
153
|
+
className="w-full"
|
|
154
|
+
>
|
|
155
|
+
Send reset link
|
|
156
|
+
</Button>
|
|
157
|
+
|
|
158
|
+
<button
|
|
159
|
+
type="button"
|
|
160
|
+
onClick={switchToSignIn}
|
|
161
|
+
className="block w-full text-center text-sm text-base-contrast-light hover:text-base-contrast"
|
|
162
|
+
>
|
|
163
|
+
Back to sign in
|
|
164
|
+
</button>
|
|
165
|
+
</form>
|
|
166
|
+
)}
|
|
167
|
+
</div>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
60
171
|
return (
|
|
61
172
|
<div className="mx-auto max-w-sm space-y-6">
|
|
62
173
|
{capabilities.oauth && oauthProviders.length > 0 && (
|
|
@@ -109,6 +220,18 @@ export function EditorLoginForm({ capabilities, oauthProviders = [], next, serve
|
|
|
109
220
|
autoComplete="current-password"
|
|
110
221
|
/>
|
|
111
222
|
|
|
223
|
+
{capabilities.emailPassword && (
|
|
224
|
+
<div className="flex justify-end">
|
|
225
|
+
<button
|
|
226
|
+
type="button"
|
|
227
|
+
onClick={switchToForgot}
|
|
228
|
+
className="text-sm text-base-contrast-light hover:text-base-contrast"
|
|
229
|
+
>
|
|
230
|
+
Forgot password?
|
|
231
|
+
</button>
|
|
232
|
+
</div>
|
|
233
|
+
)}
|
|
234
|
+
|
|
112
235
|
{error && (
|
|
113
236
|
<p className="text-sm text-red-600">{error}</p>
|
|
114
237
|
)}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { useState, type FormEvent } from "react";
|
|
2
|
+
import { PasswordInput } from "../shared/PasswordInput";
|
|
3
|
+
import { Button } from "../shared/Button";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
reason?: "invite" | "recovery" | null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function SetPasswordForm({ reason }: Props) {
|
|
10
|
+
const [password, setPassword] = useState("");
|
|
11
|
+
const [confirm, setConfirm] = useState("");
|
|
12
|
+
const [error, setError] = useState<string | null>(null);
|
|
13
|
+
const [loading, setLoading] = useState(false);
|
|
14
|
+
|
|
15
|
+
const title = reason === "recovery" ? "Reset your password" : "Set your password";
|
|
16
|
+
const buttonLabel = reason === "recovery" ? "Reset Password" : "Set Password";
|
|
17
|
+
|
|
18
|
+
async function handleSubmit(e: FormEvent) {
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
setError(null);
|
|
21
|
+
|
|
22
|
+
if (password.length < 8) {
|
|
23
|
+
setError("Password must be at least 8 characters");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (password !== confirm) {
|
|
27
|
+
setError("Passwords do not match");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
setLoading(true);
|
|
32
|
+
try {
|
|
33
|
+
const response = await fetch("/api/auth/set-password", {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: { "Content-Type": "application/json" },
|
|
36
|
+
body: JSON.stringify({ password }),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
if (response.status === 401) {
|
|
41
|
+
setError("Your session has expired. Please request a new link.");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const data = await response.json().catch(() => ({}));
|
|
45
|
+
setError(data.error || "Failed to set password");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
window.location.assign("/edit");
|
|
50
|
+
} catch (err) {
|
|
51
|
+
console.error("set-password request failed:", err);
|
|
52
|
+
setError("Unable to reach the server. Check your connection and try again.");
|
|
53
|
+
} finally {
|
|
54
|
+
setLoading(false);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div className="mx-auto max-w-sm space-y-6">
|
|
60
|
+
<div className="space-y-2 text-center">
|
|
61
|
+
<h1 className="text-2xl font-bold text-base-contrast">{title}</h1>
|
|
62
|
+
<p className="text-sm text-base-contrast-light">
|
|
63
|
+
{reason === "invite"
|
|
64
|
+
? "Create a password to complete your account setup."
|
|
65
|
+
: "Enter a new password for your account."}
|
|
66
|
+
</p>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
70
|
+
<PasswordInput
|
|
71
|
+
label="New password"
|
|
72
|
+
value={password}
|
|
73
|
+
onChange={setPassword}
|
|
74
|
+
autoComplete="new-password"
|
|
75
|
+
showToggle
|
|
76
|
+
/>
|
|
77
|
+
|
|
78
|
+
<PasswordInput
|
|
79
|
+
label="Confirm password"
|
|
80
|
+
value={confirm}
|
|
81
|
+
onChange={setConfirm}
|
|
82
|
+
autoComplete="new-password"
|
|
83
|
+
/>
|
|
84
|
+
|
|
85
|
+
{error && <p className="text-sm text-red-600">{error}</p>}
|
|
86
|
+
|
|
87
|
+
<Button
|
|
88
|
+
type="submit"
|
|
89
|
+
variant="primary"
|
|
90
|
+
size="md"
|
|
91
|
+
isLoading={loading}
|
|
92
|
+
loadingLabel="Saving..."
|
|
93
|
+
className="w-full"
|
|
94
|
+
>
|
|
95
|
+
{buttonLabel}
|
|
96
|
+
</Button>
|
|
97
|
+
</form>
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
}
|