@brokr/sdk 1.0.0
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.js +175 -0
- package/dist/auth.mjs +151 -0
- package/dist/brokr_runtime.py +333 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +568 -0
- package/dist/index.mjs +558 -0
- package/dist/management.js +92 -0
- package/dist/management.mjs +66 -0
- package/dist/react.js +264 -0
- package/dist/react.mjs +225 -0
- package/dist/runtime.js +512 -0
- package/dist/runtime.mjs +501 -0
- package/dist/src/auth.d.ts +70 -0
- package/dist/src/auth.d.ts.map +1 -0
- package/dist/src/management.d.ts +68 -0
- package/dist/src/management.d.ts.map +1 -0
- package/dist/src/react/auth.d.ts +50 -0
- package/dist/src/react/auth.d.ts.map +1 -0
- package/dist/src/runtime.d.ts +217 -0
- package/dist/src/runtime.d.ts.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types.d.ts +135 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +79 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// src/management.ts
|
|
2
|
+
async function trpcRequest(config, procedure, input) {
|
|
3
|
+
const url = new URL(`${config.apiUrl}/api/trpc/${procedure}`);
|
|
4
|
+
const response = await fetch(url.toString(), {
|
|
5
|
+
method: "POST",
|
|
6
|
+
headers: {
|
|
7
|
+
"Content-Type": "application/json",
|
|
8
|
+
"Authorization": `Bearer ${config.accessToken}`,
|
|
9
|
+
...config.headers
|
|
10
|
+
},
|
|
11
|
+
body: JSON.stringify(input),
|
|
12
|
+
signal: config.timeout ? AbortSignal.timeout(config.timeout) : void 0
|
|
13
|
+
});
|
|
14
|
+
if (!response.ok) {
|
|
15
|
+
const error = await response.json().catch(() => ({ message: "Request failed" }));
|
|
16
|
+
throw new Error(error.message || `HTTP ${response.status}`);
|
|
17
|
+
}
|
|
18
|
+
const result = await response.json();
|
|
19
|
+
return result.data;
|
|
20
|
+
}
|
|
21
|
+
var BrokrClient = class {
|
|
22
|
+
constructor(config) {
|
|
23
|
+
this.config = config;
|
|
24
|
+
}
|
|
25
|
+
/** Create a new stack with default providers. */
|
|
26
|
+
async create(input) {
|
|
27
|
+
return trpcRequest(this.config, "v1.stack.create", input);
|
|
28
|
+
}
|
|
29
|
+
/** List all stacks for the current user. */
|
|
30
|
+
async listStacks() {
|
|
31
|
+
return trpcRequest(this.config, "v1.stack.list", {});
|
|
32
|
+
}
|
|
33
|
+
/** Get a specific stack by ID. */
|
|
34
|
+
async getStack(id) {
|
|
35
|
+
return trpcRequest(this.config, "v1.stack.get", { id });
|
|
36
|
+
}
|
|
37
|
+
/** Update a stack. */
|
|
38
|
+
async updateStack(id, input) {
|
|
39
|
+
return trpcRequest(this.config, "v1.stack.update", { id, ...input });
|
|
40
|
+
}
|
|
41
|
+
/** Delete a stack. */
|
|
42
|
+
async deleteStack(id) {
|
|
43
|
+
return trpcRequest(this.config, "v1.stack.delete", { id });
|
|
44
|
+
}
|
|
45
|
+
/** Add a provider to a stack. */
|
|
46
|
+
async add(provider, input) {
|
|
47
|
+
return trpcRequest(this.config, "v1.provider.add", { provider, ...input });
|
|
48
|
+
}
|
|
49
|
+
/** Refresh the access token. */
|
|
50
|
+
async refreshToken(refreshToken) {
|
|
51
|
+
return trpcRequest(this.config, "v1.auth.refresh", { refreshToken });
|
|
52
|
+
}
|
|
53
|
+
/** Update the access token. */
|
|
54
|
+
setAccessToken(accessToken) {
|
|
55
|
+
this.config.accessToken = accessToken;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
function createBrokrClient(config) {
|
|
59
|
+
return new BrokrClient(config);
|
|
60
|
+
}
|
|
61
|
+
var create = createBrokrClient;
|
|
62
|
+
export {
|
|
63
|
+
BrokrClient,
|
|
64
|
+
create,
|
|
65
|
+
createBrokrClient as default
|
|
66
|
+
};
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/react/auth.tsx
|
|
32
|
+
var auth_exports = {};
|
|
33
|
+
__export(auth_exports, {
|
|
34
|
+
BrokrAuth: () => BrokrAuth,
|
|
35
|
+
SignIn: () => SignIn,
|
|
36
|
+
SignUp: () => SignUp,
|
|
37
|
+
UserButton: () => UserButton,
|
|
38
|
+
useAuth: () => useAuth,
|
|
39
|
+
useUser: () => useUser
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(auth_exports);
|
|
42
|
+
var import_react = __toESM(require("react"));
|
|
43
|
+
function mapSessionUser(raw) {
|
|
44
|
+
return {
|
|
45
|
+
id: raw.id,
|
|
46
|
+
email: raw.email,
|
|
47
|
+
name: raw.name ?? null,
|
|
48
|
+
avatarUrl: raw.image ?? null,
|
|
49
|
+
emailVerified: raw.emailVerified ?? false
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
var AuthContext = (0, import_react.createContext)(null);
|
|
53
|
+
function BrokrAuth({ children }) {
|
|
54
|
+
const [user, setUser] = (0, import_react.useState)(null);
|
|
55
|
+
const [isLoaded, setIsLoaded] = (0, import_react.useState)(false);
|
|
56
|
+
const [isSigningOut, setIsSigningOut] = (0, import_react.useState)(false);
|
|
57
|
+
(0, import_react.useEffect)(() => {
|
|
58
|
+
let cancelled = false;
|
|
59
|
+
async function loadSession() {
|
|
60
|
+
try {
|
|
61
|
+
const res = await fetch("/api/auth/get-session", { credentials: "include" });
|
|
62
|
+
if (res.ok) {
|
|
63
|
+
const data = await res.json();
|
|
64
|
+
if (!cancelled && data?.user) {
|
|
65
|
+
setUser(mapSessionUser(data.user));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
} finally {
|
|
70
|
+
if (!cancelled) setIsLoaded(true);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
loadSession();
|
|
74
|
+
return () => {
|
|
75
|
+
cancelled = true;
|
|
76
|
+
};
|
|
77
|
+
}, []);
|
|
78
|
+
const reloadSession = (0, import_react.useCallback)(async () => {
|
|
79
|
+
const res = await fetch("/api/auth/get-session", { credentials: "include" });
|
|
80
|
+
if (res.ok) {
|
|
81
|
+
const data = await res.json();
|
|
82
|
+
if (data?.user) {
|
|
83
|
+
setUser(mapSessionUser(data.user));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}, []);
|
|
87
|
+
const signIn = (0, import_react.useCallback)(async (email, password) => {
|
|
88
|
+
const res = await fetch("/api/auth/sign-in/email", {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: { "Content-Type": "application/json" },
|
|
91
|
+
credentials: "include",
|
|
92
|
+
body: JSON.stringify({ email, password })
|
|
93
|
+
});
|
|
94
|
+
if (!res.ok) {
|
|
95
|
+
const data = await res.json().catch(() => ({}));
|
|
96
|
+
throw new Error(data.message ?? "Sign in failed");
|
|
97
|
+
}
|
|
98
|
+
await reloadSession();
|
|
99
|
+
}, [reloadSession]);
|
|
100
|
+
const signUp = (0, import_react.useCallback)(async (email, password, name) => {
|
|
101
|
+
const res = await fetch("/api/auth/sign-up/email", {
|
|
102
|
+
method: "POST",
|
|
103
|
+
headers: { "Content-Type": "application/json" },
|
|
104
|
+
credentials: "include",
|
|
105
|
+
body: JSON.stringify({ email, password, name: name ?? "" })
|
|
106
|
+
});
|
|
107
|
+
if (!res.ok) {
|
|
108
|
+
const data = await res.json().catch(() => ({}));
|
|
109
|
+
throw new Error(data.message ?? "Sign up failed");
|
|
110
|
+
}
|
|
111
|
+
await reloadSession();
|
|
112
|
+
}, [reloadSession]);
|
|
113
|
+
const signOut = (0, import_react.useCallback)(async () => {
|
|
114
|
+
setIsSigningOut(true);
|
|
115
|
+
try {
|
|
116
|
+
await fetch("/api/auth/sign-out", { method: "POST", credentials: "include" });
|
|
117
|
+
setUser(null);
|
|
118
|
+
} finally {
|
|
119
|
+
setIsSigningOut(false);
|
|
120
|
+
}
|
|
121
|
+
}, []);
|
|
122
|
+
const value = (0, import_react.useMemo)(
|
|
123
|
+
() => ({ user, isLoaded, signIn, signUp, signOut, isSigningOut }),
|
|
124
|
+
[user, isLoaded, signIn, signUp, signOut, isSigningOut]
|
|
125
|
+
);
|
|
126
|
+
return /* @__PURE__ */ import_react.default.createElement(AuthContext.Provider, { value }, children);
|
|
127
|
+
}
|
|
128
|
+
function useUser() {
|
|
129
|
+
const ctx = (0, import_react.useContext)(AuthContext);
|
|
130
|
+
if (!ctx) throw new Error("useUser must be used within <BrokrAuth>");
|
|
131
|
+
return { user: ctx.user, isLoaded: ctx.isLoaded };
|
|
132
|
+
}
|
|
133
|
+
function useAuth() {
|
|
134
|
+
const ctx = (0, import_react.useContext)(AuthContext);
|
|
135
|
+
if (!ctx) throw new Error("useAuth must be used within <BrokrAuth>");
|
|
136
|
+
return { signIn: ctx.signIn, signUp: ctx.signUp, signOut: ctx.signOut, isSigningOut: ctx.isSigningOut };
|
|
137
|
+
}
|
|
138
|
+
function SignIn({
|
|
139
|
+
redirectTo,
|
|
140
|
+
children
|
|
141
|
+
}) {
|
|
142
|
+
const { signIn } = useAuth();
|
|
143
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
144
|
+
const [pending, setPending] = (0, import_react.useState)(false);
|
|
145
|
+
const action = (0, import_react.useCallback)(async (formData) => {
|
|
146
|
+
setPending(true);
|
|
147
|
+
setError(null);
|
|
148
|
+
try {
|
|
149
|
+
const email = formData.get("email");
|
|
150
|
+
const password = formData.get("password");
|
|
151
|
+
await signIn(email, password);
|
|
152
|
+
if (redirectTo && typeof window !== "undefined") {
|
|
153
|
+
window.location.href = redirectTo;
|
|
154
|
+
}
|
|
155
|
+
} catch (err) {
|
|
156
|
+
setError(err instanceof Error ? err.message : "Sign in failed");
|
|
157
|
+
} finally {
|
|
158
|
+
setPending(false);
|
|
159
|
+
}
|
|
160
|
+
}, [signIn, redirectTo]);
|
|
161
|
+
if (children) return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, children({ action, error, pending }));
|
|
162
|
+
return /* @__PURE__ */ import_react.default.createElement("form", { onSubmit: async (e) => {
|
|
163
|
+
e.preventDefault();
|
|
164
|
+
await action(new FormData(e.currentTarget));
|
|
165
|
+
} }, /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "12px", maxWidth: "320px" } }, /* @__PURE__ */ import_react.default.createElement("input", { name: "email", type: "email", placeholder: "Email", required: true, style: { padding: "8px 12px", borderRadius: "6px", border: "1px solid #333" } }), /* @__PURE__ */ import_react.default.createElement("input", { name: "password", type: "password", placeholder: "Password", required: true, style: { padding: "8px 12px", borderRadius: "6px", border: "1px solid #333" } }), error && /* @__PURE__ */ import_react.default.createElement("p", { style: { color: "#ef4444", fontSize: "14px", margin: 0 } }, error), /* @__PURE__ */ import_react.default.createElement("button", { type: "submit", disabled: pending, style: { padding: "8px 16px", borderRadius: "6px", background: "#fff", color: "#000", border: "none", cursor: pending ? "wait" : "pointer" } }, pending ? "Signing in..." : "Sign in")));
|
|
166
|
+
}
|
|
167
|
+
function SignUp({
|
|
168
|
+
redirectTo,
|
|
169
|
+
children
|
|
170
|
+
}) {
|
|
171
|
+
const { signUp } = useAuth();
|
|
172
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
173
|
+
const [pending, setPending] = (0, import_react.useState)(false);
|
|
174
|
+
const action = (0, import_react.useCallback)(async (formData) => {
|
|
175
|
+
setPending(true);
|
|
176
|
+
setError(null);
|
|
177
|
+
try {
|
|
178
|
+
const email = formData.get("email");
|
|
179
|
+
const password = formData.get("password");
|
|
180
|
+
const name = formData.get("name");
|
|
181
|
+
await signUp(email, password, name);
|
|
182
|
+
if (redirectTo && typeof window !== "undefined") {
|
|
183
|
+
window.location.href = redirectTo;
|
|
184
|
+
}
|
|
185
|
+
} catch (err) {
|
|
186
|
+
setError(err instanceof Error ? err.message : "Sign up failed");
|
|
187
|
+
} finally {
|
|
188
|
+
setPending(false);
|
|
189
|
+
}
|
|
190
|
+
}, [signUp, redirectTo]);
|
|
191
|
+
if (children) return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, children({ action, error, pending }));
|
|
192
|
+
return /* @__PURE__ */ import_react.default.createElement("form", { onSubmit: async (e) => {
|
|
193
|
+
e.preventDefault();
|
|
194
|
+
await action(new FormData(e.currentTarget));
|
|
195
|
+
} }, /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "12px", maxWidth: "320px" } }, /* @__PURE__ */ import_react.default.createElement("input", { name: "name", type: "text", placeholder: "Name", style: { padding: "8px 12px", borderRadius: "6px", border: "1px solid #333" } }), /* @__PURE__ */ import_react.default.createElement("input", { name: "email", type: "email", placeholder: "Email", required: true, style: { padding: "8px 12px", borderRadius: "6px", border: "1px solid #333" } }), /* @__PURE__ */ import_react.default.createElement("input", { name: "password", type: "password", placeholder: "Password", required: true, minLength: 8, style: { padding: "8px 12px", borderRadius: "6px", border: "1px solid #333" } }), error && /* @__PURE__ */ import_react.default.createElement("p", { style: { color: "#ef4444", fontSize: "14px", margin: 0 } }, error), /* @__PURE__ */ import_react.default.createElement("button", { type: "submit", disabled: pending, style: { padding: "8px 16px", borderRadius: "6px", background: "#fff", color: "#000", border: "none", cursor: pending ? "wait" : "pointer" } }, pending ? "Creating account..." : "Sign up")));
|
|
196
|
+
}
|
|
197
|
+
function UserButton() {
|
|
198
|
+
const { user, isLoaded } = useUser();
|
|
199
|
+
const { signOut, isSigningOut } = useAuth();
|
|
200
|
+
const [open, setOpen] = (0, import_react.useState)(false);
|
|
201
|
+
const handleToggle = (0, import_react.useCallback)(() => setOpen((prev) => !prev), []);
|
|
202
|
+
const handleSignOut = (0, import_react.useCallback)(async () => {
|
|
203
|
+
await signOut();
|
|
204
|
+
setOpen(false);
|
|
205
|
+
}, [signOut]);
|
|
206
|
+
if (!isLoaded || !user) return null;
|
|
207
|
+
const initials = (user.name ?? user.email).slice(0, 2).toUpperCase();
|
|
208
|
+
return /* @__PURE__ */ import_react.default.createElement("div", { style: { position: "relative", display: "inline-block" } }, /* @__PURE__ */ import_react.default.createElement(
|
|
209
|
+
"button",
|
|
210
|
+
{
|
|
211
|
+
onClick: handleToggle,
|
|
212
|
+
style: {
|
|
213
|
+
width: "36px",
|
|
214
|
+
height: "36px",
|
|
215
|
+
borderRadius: "50%",
|
|
216
|
+
background: "#333",
|
|
217
|
+
color: "#fff",
|
|
218
|
+
border: "none",
|
|
219
|
+
cursor: "pointer",
|
|
220
|
+
fontSize: "14px",
|
|
221
|
+
fontWeight: 600,
|
|
222
|
+
overflow: "hidden"
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
user.avatarUrl ? /* @__PURE__ */ import_react.default.createElement("img", { src: user.avatarUrl, alt: "", style: { width: "100%", height: "100%", objectFit: "cover" } }) : initials
|
|
226
|
+
), open && /* @__PURE__ */ import_react.default.createElement("div", { style: {
|
|
227
|
+
position: "absolute",
|
|
228
|
+
top: "42px",
|
|
229
|
+
right: 0,
|
|
230
|
+
minWidth: "200px",
|
|
231
|
+
background: "#1a1a1a",
|
|
232
|
+
border: "1px solid #333",
|
|
233
|
+
borderRadius: "8px",
|
|
234
|
+
padding: "8px 0",
|
|
235
|
+
zIndex: 50
|
|
236
|
+
} }, /* @__PURE__ */ import_react.default.createElement("div", { style: { padding: "8px 16px", borderBottom: "1px solid #333" } }, /* @__PURE__ */ import_react.default.createElement("p", { style: { margin: 0, fontSize: "14px", fontWeight: 500, color: "#fff" } }, user.name ?? "User"), /* @__PURE__ */ import_react.default.createElement("p", { style: { margin: 0, fontSize: "12px", color: "#888" } }, user.email)), /* @__PURE__ */ import_react.default.createElement(
|
|
237
|
+
"button",
|
|
238
|
+
{
|
|
239
|
+
onClick: handleSignOut,
|
|
240
|
+
disabled: isSigningOut,
|
|
241
|
+
style: {
|
|
242
|
+
display: "block",
|
|
243
|
+
width: "100%",
|
|
244
|
+
padding: "8px 16px",
|
|
245
|
+
background: "none",
|
|
246
|
+
border: "none",
|
|
247
|
+
color: "#ef4444",
|
|
248
|
+
textAlign: "left",
|
|
249
|
+
cursor: "pointer",
|
|
250
|
+
fontSize: "14px"
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
isSigningOut ? "Signing out..." : "Sign out"
|
|
254
|
+
)));
|
|
255
|
+
}
|
|
256
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
257
|
+
0 && (module.exports = {
|
|
258
|
+
BrokrAuth,
|
|
259
|
+
SignIn,
|
|
260
|
+
SignUp,
|
|
261
|
+
UserButton,
|
|
262
|
+
useAuth,
|
|
263
|
+
useUser
|
|
264
|
+
});
|
package/dist/react.mjs
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/react/auth.tsx
|
|
4
|
+
import React, { createContext, useContext, useCallback, useMemo, useState, useEffect } from "react";
|
|
5
|
+
function mapSessionUser(raw) {
|
|
6
|
+
return {
|
|
7
|
+
id: raw.id,
|
|
8
|
+
email: raw.email,
|
|
9
|
+
name: raw.name ?? null,
|
|
10
|
+
avatarUrl: raw.image ?? null,
|
|
11
|
+
emailVerified: raw.emailVerified ?? false
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
var AuthContext = createContext(null);
|
|
15
|
+
function BrokrAuth({ children }) {
|
|
16
|
+
const [user, setUser] = useState(null);
|
|
17
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
18
|
+
const [isSigningOut, setIsSigningOut] = useState(false);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
let cancelled = false;
|
|
21
|
+
async function loadSession() {
|
|
22
|
+
try {
|
|
23
|
+
const res = await fetch("/api/auth/get-session", { credentials: "include" });
|
|
24
|
+
if (res.ok) {
|
|
25
|
+
const data = await res.json();
|
|
26
|
+
if (!cancelled && data?.user) {
|
|
27
|
+
setUser(mapSessionUser(data.user));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
} finally {
|
|
32
|
+
if (!cancelled) setIsLoaded(true);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
loadSession();
|
|
36
|
+
return () => {
|
|
37
|
+
cancelled = true;
|
|
38
|
+
};
|
|
39
|
+
}, []);
|
|
40
|
+
const reloadSession = useCallback(async () => {
|
|
41
|
+
const res = await fetch("/api/auth/get-session", { credentials: "include" });
|
|
42
|
+
if (res.ok) {
|
|
43
|
+
const data = await res.json();
|
|
44
|
+
if (data?.user) {
|
|
45
|
+
setUser(mapSessionUser(data.user));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}, []);
|
|
49
|
+
const signIn = useCallback(async (email, password) => {
|
|
50
|
+
const res = await fetch("/api/auth/sign-in/email", {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: { "Content-Type": "application/json" },
|
|
53
|
+
credentials: "include",
|
|
54
|
+
body: JSON.stringify({ email, password })
|
|
55
|
+
});
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
const data = await res.json().catch(() => ({}));
|
|
58
|
+
throw new Error(data.message ?? "Sign in failed");
|
|
59
|
+
}
|
|
60
|
+
await reloadSession();
|
|
61
|
+
}, [reloadSession]);
|
|
62
|
+
const signUp = useCallback(async (email, password, name) => {
|
|
63
|
+
const res = await fetch("/api/auth/sign-up/email", {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: { "Content-Type": "application/json" },
|
|
66
|
+
credentials: "include",
|
|
67
|
+
body: JSON.stringify({ email, password, name: name ?? "" })
|
|
68
|
+
});
|
|
69
|
+
if (!res.ok) {
|
|
70
|
+
const data = await res.json().catch(() => ({}));
|
|
71
|
+
throw new Error(data.message ?? "Sign up failed");
|
|
72
|
+
}
|
|
73
|
+
await reloadSession();
|
|
74
|
+
}, [reloadSession]);
|
|
75
|
+
const signOut = useCallback(async () => {
|
|
76
|
+
setIsSigningOut(true);
|
|
77
|
+
try {
|
|
78
|
+
await fetch("/api/auth/sign-out", { method: "POST", credentials: "include" });
|
|
79
|
+
setUser(null);
|
|
80
|
+
} finally {
|
|
81
|
+
setIsSigningOut(false);
|
|
82
|
+
}
|
|
83
|
+
}, []);
|
|
84
|
+
const value = useMemo(
|
|
85
|
+
() => ({ user, isLoaded, signIn, signUp, signOut, isSigningOut }),
|
|
86
|
+
[user, isLoaded, signIn, signUp, signOut, isSigningOut]
|
|
87
|
+
);
|
|
88
|
+
return /* @__PURE__ */ React.createElement(AuthContext.Provider, { value }, children);
|
|
89
|
+
}
|
|
90
|
+
function useUser() {
|
|
91
|
+
const ctx = useContext(AuthContext);
|
|
92
|
+
if (!ctx) throw new Error("useUser must be used within <BrokrAuth>");
|
|
93
|
+
return { user: ctx.user, isLoaded: ctx.isLoaded };
|
|
94
|
+
}
|
|
95
|
+
function useAuth() {
|
|
96
|
+
const ctx = useContext(AuthContext);
|
|
97
|
+
if (!ctx) throw new Error("useAuth must be used within <BrokrAuth>");
|
|
98
|
+
return { signIn: ctx.signIn, signUp: ctx.signUp, signOut: ctx.signOut, isSigningOut: ctx.isSigningOut };
|
|
99
|
+
}
|
|
100
|
+
function SignIn({
|
|
101
|
+
redirectTo,
|
|
102
|
+
children
|
|
103
|
+
}) {
|
|
104
|
+
const { signIn } = useAuth();
|
|
105
|
+
const [error, setError] = useState(null);
|
|
106
|
+
const [pending, setPending] = useState(false);
|
|
107
|
+
const action = useCallback(async (formData) => {
|
|
108
|
+
setPending(true);
|
|
109
|
+
setError(null);
|
|
110
|
+
try {
|
|
111
|
+
const email = formData.get("email");
|
|
112
|
+
const password = formData.get("password");
|
|
113
|
+
await signIn(email, password);
|
|
114
|
+
if (redirectTo && typeof window !== "undefined") {
|
|
115
|
+
window.location.href = redirectTo;
|
|
116
|
+
}
|
|
117
|
+
} catch (err) {
|
|
118
|
+
setError(err instanceof Error ? err.message : "Sign in failed");
|
|
119
|
+
} finally {
|
|
120
|
+
setPending(false);
|
|
121
|
+
}
|
|
122
|
+
}, [signIn, redirectTo]);
|
|
123
|
+
if (children) return /* @__PURE__ */ React.createElement(React.Fragment, null, children({ action, error, pending }));
|
|
124
|
+
return /* @__PURE__ */ React.createElement("form", { onSubmit: async (e) => {
|
|
125
|
+
e.preventDefault();
|
|
126
|
+
await action(new FormData(e.currentTarget));
|
|
127
|
+
} }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "12px", maxWidth: "320px" } }, /* @__PURE__ */ React.createElement("input", { name: "email", type: "email", placeholder: "Email", required: true, style: { padding: "8px 12px", borderRadius: "6px", border: "1px solid #333" } }), /* @__PURE__ */ React.createElement("input", { name: "password", type: "password", placeholder: "Password", required: true, style: { padding: "8px 12px", borderRadius: "6px", border: "1px solid #333" } }), error && /* @__PURE__ */ React.createElement("p", { style: { color: "#ef4444", fontSize: "14px", margin: 0 } }, error), /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: pending, style: { padding: "8px 16px", borderRadius: "6px", background: "#fff", color: "#000", border: "none", cursor: pending ? "wait" : "pointer" } }, pending ? "Signing in..." : "Sign in")));
|
|
128
|
+
}
|
|
129
|
+
function SignUp({
|
|
130
|
+
redirectTo,
|
|
131
|
+
children
|
|
132
|
+
}) {
|
|
133
|
+
const { signUp } = useAuth();
|
|
134
|
+
const [error, setError] = useState(null);
|
|
135
|
+
const [pending, setPending] = useState(false);
|
|
136
|
+
const action = useCallback(async (formData) => {
|
|
137
|
+
setPending(true);
|
|
138
|
+
setError(null);
|
|
139
|
+
try {
|
|
140
|
+
const email = formData.get("email");
|
|
141
|
+
const password = formData.get("password");
|
|
142
|
+
const name = formData.get("name");
|
|
143
|
+
await signUp(email, password, name);
|
|
144
|
+
if (redirectTo && typeof window !== "undefined") {
|
|
145
|
+
window.location.href = redirectTo;
|
|
146
|
+
}
|
|
147
|
+
} catch (err) {
|
|
148
|
+
setError(err instanceof Error ? err.message : "Sign up failed");
|
|
149
|
+
} finally {
|
|
150
|
+
setPending(false);
|
|
151
|
+
}
|
|
152
|
+
}, [signUp, redirectTo]);
|
|
153
|
+
if (children) return /* @__PURE__ */ React.createElement(React.Fragment, null, children({ action, error, pending }));
|
|
154
|
+
return /* @__PURE__ */ React.createElement("form", { onSubmit: async (e) => {
|
|
155
|
+
e.preventDefault();
|
|
156
|
+
await action(new FormData(e.currentTarget));
|
|
157
|
+
} }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: "12px", maxWidth: "320px" } }, /* @__PURE__ */ React.createElement("input", { name: "name", type: "text", placeholder: "Name", style: { padding: "8px 12px", borderRadius: "6px", border: "1px solid #333" } }), /* @__PURE__ */ React.createElement("input", { name: "email", type: "email", placeholder: "Email", required: true, style: { padding: "8px 12px", borderRadius: "6px", border: "1px solid #333" } }), /* @__PURE__ */ React.createElement("input", { name: "password", type: "password", placeholder: "Password", required: true, minLength: 8, style: { padding: "8px 12px", borderRadius: "6px", border: "1px solid #333" } }), error && /* @__PURE__ */ React.createElement("p", { style: { color: "#ef4444", fontSize: "14px", margin: 0 } }, error), /* @__PURE__ */ React.createElement("button", { type: "submit", disabled: pending, style: { padding: "8px 16px", borderRadius: "6px", background: "#fff", color: "#000", border: "none", cursor: pending ? "wait" : "pointer" } }, pending ? "Creating account..." : "Sign up")));
|
|
158
|
+
}
|
|
159
|
+
function UserButton() {
|
|
160
|
+
const { user, isLoaded } = useUser();
|
|
161
|
+
const { signOut, isSigningOut } = useAuth();
|
|
162
|
+
const [open, setOpen] = useState(false);
|
|
163
|
+
const handleToggle = useCallback(() => setOpen((prev) => !prev), []);
|
|
164
|
+
const handleSignOut = useCallback(async () => {
|
|
165
|
+
await signOut();
|
|
166
|
+
setOpen(false);
|
|
167
|
+
}, [signOut]);
|
|
168
|
+
if (!isLoaded || !user) return null;
|
|
169
|
+
const initials = (user.name ?? user.email).slice(0, 2).toUpperCase();
|
|
170
|
+
return /* @__PURE__ */ React.createElement("div", { style: { position: "relative", display: "inline-block" } }, /* @__PURE__ */ React.createElement(
|
|
171
|
+
"button",
|
|
172
|
+
{
|
|
173
|
+
onClick: handleToggle,
|
|
174
|
+
style: {
|
|
175
|
+
width: "36px",
|
|
176
|
+
height: "36px",
|
|
177
|
+
borderRadius: "50%",
|
|
178
|
+
background: "#333",
|
|
179
|
+
color: "#fff",
|
|
180
|
+
border: "none",
|
|
181
|
+
cursor: "pointer",
|
|
182
|
+
fontSize: "14px",
|
|
183
|
+
fontWeight: 600,
|
|
184
|
+
overflow: "hidden"
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
user.avatarUrl ? /* @__PURE__ */ React.createElement("img", { src: user.avatarUrl, alt: "", style: { width: "100%", height: "100%", objectFit: "cover" } }) : initials
|
|
188
|
+
), open && /* @__PURE__ */ React.createElement("div", { style: {
|
|
189
|
+
position: "absolute",
|
|
190
|
+
top: "42px",
|
|
191
|
+
right: 0,
|
|
192
|
+
minWidth: "200px",
|
|
193
|
+
background: "#1a1a1a",
|
|
194
|
+
border: "1px solid #333",
|
|
195
|
+
borderRadius: "8px",
|
|
196
|
+
padding: "8px 0",
|
|
197
|
+
zIndex: 50
|
|
198
|
+
} }, /* @__PURE__ */ React.createElement("div", { style: { padding: "8px 16px", borderBottom: "1px solid #333" } }, /* @__PURE__ */ React.createElement("p", { style: { margin: 0, fontSize: "14px", fontWeight: 500, color: "#fff" } }, user.name ?? "User"), /* @__PURE__ */ React.createElement("p", { style: { margin: 0, fontSize: "12px", color: "#888" } }, user.email)), /* @__PURE__ */ React.createElement(
|
|
199
|
+
"button",
|
|
200
|
+
{
|
|
201
|
+
onClick: handleSignOut,
|
|
202
|
+
disabled: isSigningOut,
|
|
203
|
+
style: {
|
|
204
|
+
display: "block",
|
|
205
|
+
width: "100%",
|
|
206
|
+
padding: "8px 16px",
|
|
207
|
+
background: "none",
|
|
208
|
+
border: "none",
|
|
209
|
+
color: "#ef4444",
|
|
210
|
+
textAlign: "left",
|
|
211
|
+
cursor: "pointer",
|
|
212
|
+
fontSize: "14px"
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
isSigningOut ? "Signing out..." : "Sign out"
|
|
216
|
+
)));
|
|
217
|
+
}
|
|
218
|
+
export {
|
|
219
|
+
BrokrAuth,
|
|
220
|
+
SignIn,
|
|
221
|
+
SignUp,
|
|
222
|
+
UserButton,
|
|
223
|
+
useAuth,
|
|
224
|
+
useUser
|
|
225
|
+
};
|