@githat/nextjs 0.2.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/index.mjs ADDED
@@ -0,0 +1,627 @@
1
+ "use client";
2
+
3
+ // src/provider.tsx
4
+ import { createContext, useState, useEffect, useCallback, useMemo, useRef } from "react";
5
+
6
+ // src/config.ts
7
+ var DEFAULT_API_URL = "https://api.githat.io";
8
+ var TOKEN_KEYS = {
9
+ accessToken: "githat_access_token",
10
+ refreshToken: "githat_refresh_token",
11
+ user: "githat_user",
12
+ org: "githat_org"
13
+ };
14
+ function resolveConfig(config) {
15
+ return {
16
+ publishableKey: config.publishableKey,
17
+ apiUrl: config.apiUrl || DEFAULT_API_URL,
18
+ signInUrl: config.signInUrl || "/sign-in",
19
+ signUpUrl: config.signUpUrl || "/sign-up",
20
+ afterSignInUrl: config.afterSignInUrl || "/dashboard",
21
+ afterSignOutUrl: config.afterSignOutUrl || "/"
22
+ };
23
+ }
24
+
25
+ // src/client.ts
26
+ var _refreshPromise = null;
27
+ async function refreshTokens(apiUrl, appKey) {
28
+ const refreshToken = typeof window !== "undefined" ? localStorage.getItem(TOKEN_KEYS.refreshToken) : null;
29
+ if (!refreshToken) return false;
30
+ let orgId = null;
31
+ try {
32
+ const orgStr = localStorage.getItem(TOKEN_KEYS.org);
33
+ if (orgStr) orgId = JSON.parse(orgStr).id;
34
+ } catch {
35
+ }
36
+ try {
37
+ const res = await fetch(`${apiUrl}/auth/refresh`, {
38
+ method: "POST",
39
+ headers: {
40
+ "Content-Type": "application/json",
41
+ "X-GitHat-App-Key": appKey
42
+ },
43
+ body: JSON.stringify({ refreshToken, orgId })
44
+ });
45
+ if (!res.ok) return false;
46
+ const data = await res.json();
47
+ if (data.accessToken) localStorage.setItem(TOKEN_KEYS.accessToken, data.accessToken);
48
+ if (data.refreshToken) localStorage.setItem(TOKEN_KEYS.refreshToken, data.refreshToken);
49
+ if (data.org) localStorage.setItem(TOKEN_KEYS.org, JSON.stringify(data.org));
50
+ return true;
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+ function clearAuth() {
56
+ if (typeof window === "undefined") return;
57
+ Object.values(TOKEN_KEYS).forEach((key) => localStorage.removeItem(key));
58
+ window.dispatchEvent(new CustomEvent("githat:auth-changed", {
59
+ detail: { user: null, org: null, signedIn: false }
60
+ }));
61
+ }
62
+ function createClient(apiUrl, appKey) {
63
+ async function fetchApi(endpoint, options = {}) {
64
+ const url = `${apiUrl}${endpoint}`;
65
+ const token = typeof window !== "undefined" ? localStorage.getItem(TOKEN_KEYS.accessToken) : null;
66
+ const headers = {
67
+ "Content-Type": "application/json",
68
+ "X-GitHat-App-Key": appKey,
69
+ ...token && { Authorization: `Bearer ${token}` },
70
+ ...options.headers
71
+ };
72
+ const response = await fetch(url, { ...options, headers });
73
+ if (response.status === 401) {
74
+ if (!_refreshPromise) {
75
+ _refreshPromise = refreshTokens(apiUrl, appKey).finally(() => {
76
+ _refreshPromise = null;
77
+ });
78
+ }
79
+ const refreshed = await _refreshPromise;
80
+ if (refreshed) {
81
+ const newToken = localStorage.getItem(TOKEN_KEYS.accessToken);
82
+ const retryResponse = await fetch(url, {
83
+ ...options,
84
+ headers: {
85
+ ...headers,
86
+ ...newToken && { Authorization: `Bearer ${newToken}` }
87
+ }
88
+ });
89
+ const retryData = await retryResponse.json();
90
+ if (!retryResponse.ok) throw new Error(retryData.error || "Request failed");
91
+ return retryData;
92
+ }
93
+ clearAuth();
94
+ throw new Error("Session expired");
95
+ }
96
+ const data = await response.json();
97
+ if (!response.ok) throw new Error(data.error || "Request failed");
98
+ return data;
99
+ }
100
+ return { fetchApi, clearAuth };
101
+ }
102
+
103
+ // src/provider.tsx
104
+ import { jsx } from "react/jsx-runtime";
105
+ var GitHatContext = createContext(null);
106
+ function GitHatProvider({ config: rawConfig, children }) {
107
+ const config = useMemo(() => resolveConfig(rawConfig), [rawConfig]);
108
+ const clientRef = useRef(createClient(config.apiUrl, config.publishableKey));
109
+ const [user, setUser] = useState(null);
110
+ const [org, setOrg] = useState(null);
111
+ const [isSignedIn, setIsSignedIn] = useState(false);
112
+ const [isLoading, setIsLoading] = useState(true);
113
+ const [authError, setAuthError] = useState(null);
114
+ useEffect(() => {
115
+ const token = localStorage.getItem(TOKEN_KEYS.accessToken);
116
+ const storedUser = localStorage.getItem(TOKEN_KEYS.user);
117
+ if (token && storedUser) {
118
+ clientRef.current.fetchApi("/auth/me").then((data) => {
119
+ const u = data.user || JSON.parse(storedUser);
120
+ setUser(u);
121
+ const storedOrg = localStorage.getItem(TOKEN_KEYS.org);
122
+ setOrg(data.currentOrg || (storedOrg ? JSON.parse(storedOrg) : null));
123
+ setIsSignedIn(true);
124
+ setAuthError(null);
125
+ }).catch((err) => {
126
+ if (err.message === "Session expired") {
127
+ clientRef.current.clearAuth();
128
+ } else {
129
+ try {
130
+ setUser(JSON.parse(storedUser));
131
+ } catch {
132
+ }
133
+ setAuthError(err.message || "Failed to verify session");
134
+ }
135
+ }).finally(() => setIsLoading(false));
136
+ } else {
137
+ setIsLoading(false);
138
+ }
139
+ }, []);
140
+ useEffect(() => {
141
+ const handleAuthChanged = (e) => {
142
+ const detail = e.detail;
143
+ if (detail?.signedIn === false) {
144
+ setIsSignedIn(false);
145
+ setUser(null);
146
+ setOrg(null);
147
+ } else if (detail?.signedIn === true && detail?.user) {
148
+ setUser(detail.user);
149
+ setIsSignedIn(true);
150
+ if (detail.org) setOrg(detail.org);
151
+ }
152
+ };
153
+ window.addEventListener("githat:auth-changed", handleAuthChanged);
154
+ return () => window.removeEventListener("githat:auth-changed", handleAuthChanged);
155
+ }, []);
156
+ const signIn = useCallback(async (email, password) => {
157
+ const data = await clientRef.current.fetchApi("/auth/login", {
158
+ method: "POST",
159
+ body: JSON.stringify({ email, password })
160
+ });
161
+ localStorage.setItem(TOKEN_KEYS.accessToken, data.accessToken);
162
+ localStorage.setItem(TOKEN_KEYS.refreshToken, data.refreshToken);
163
+ localStorage.setItem(TOKEN_KEYS.user, JSON.stringify(data.user));
164
+ if (data.org) localStorage.setItem(TOKEN_KEYS.org, JSON.stringify(data.org));
165
+ setUser(data.user);
166
+ setOrg(data.org || null);
167
+ setIsSignedIn(true);
168
+ window.dispatchEvent(new CustomEvent("githat:auth-changed", {
169
+ detail: { user: data.user, org: data.org, signedIn: true }
170
+ }));
171
+ }, []);
172
+ const signUp = useCallback(async (signUpData) => {
173
+ const data = await clientRef.current.fetchApi("/auth/register", {
174
+ method: "POST",
175
+ body: JSON.stringify(signUpData)
176
+ });
177
+ localStorage.setItem(TOKEN_KEYS.accessToken, data.accessToken);
178
+ localStorage.setItem(TOKEN_KEYS.refreshToken, data.refreshToken);
179
+ localStorage.setItem(TOKEN_KEYS.user, JSON.stringify(data.user));
180
+ if (data.org) localStorage.setItem(TOKEN_KEYS.org, JSON.stringify(data.org));
181
+ setUser(data.user);
182
+ setOrg(data.org || null);
183
+ setIsSignedIn(true);
184
+ window.dispatchEvent(new CustomEvent("githat:auth-changed", {
185
+ detail: { user: data.user, org: data.org, signedIn: true }
186
+ }));
187
+ return { requiresVerification: !data.user.emailVerified, email: signUpData.email };
188
+ }, []);
189
+ const signOut = useCallback(async () => {
190
+ try {
191
+ await clientRef.current.fetchApi("/auth/logout", { method: "POST" });
192
+ } catch {
193
+ }
194
+ clientRef.current.clearAuth();
195
+ setIsSignedIn(false);
196
+ setUser(null);
197
+ setOrg(null);
198
+ if (typeof window !== "undefined" && config.afterSignOutUrl) {
199
+ window.location.href = config.afterSignOutUrl;
200
+ }
201
+ }, [config.afterSignOutUrl]);
202
+ const switchOrg = useCallback(async (orgId) => {
203
+ try {
204
+ const data = await clientRef.current.fetchApi(`/user/orgs/${orgId}/switch`, { method: "POST" });
205
+ if (data.accessToken) localStorage.setItem(TOKEN_KEYS.accessToken, data.accessToken);
206
+ if (data.refreshToken) localStorage.setItem(TOKEN_KEYS.refreshToken, data.refreshToken);
207
+ const orgData = data.org;
208
+ localStorage.setItem(TOKEN_KEYS.org, JSON.stringify(orgData));
209
+ setOrg(orgData);
210
+ window.dispatchEvent(new CustomEvent("githat:auth-changed", {
211
+ detail: { user, org: orgData, signedIn: true }
212
+ }));
213
+ } catch (e) {
214
+ console.error("Org switch failed:", e);
215
+ }
216
+ }, [user]);
217
+ const value = useMemo(() => ({
218
+ user,
219
+ org,
220
+ isSignedIn,
221
+ isLoading,
222
+ authError,
223
+ config,
224
+ signIn,
225
+ signUp,
226
+ signOut,
227
+ switchOrg
228
+ }), [user, org, isSignedIn, isLoading, authError, config, signIn, signUp, signOut, switchOrg]);
229
+ return /* @__PURE__ */ jsx(GitHatContext.Provider, { value, children });
230
+ }
231
+
232
+ // src/hooks.ts
233
+ import { useContext, useMemo as useMemo2 } from "react";
234
+ function useAuth() {
235
+ const ctx = useContext(GitHatContext);
236
+ if (!ctx) throw new Error("useAuth must be used within a <GitHatProvider>");
237
+ return ctx;
238
+ }
239
+ function useGitHat() {
240
+ const ctx = useAuth();
241
+ const client = useMemo2(
242
+ () => createClient(ctx.config.apiUrl, ctx.config.publishableKey),
243
+ [ctx.config.apiUrl, ctx.config.publishableKey]
244
+ );
245
+ return {
246
+ fetch: client.fetchApi,
247
+ getUserOrgs: () => client.fetchApi("/user/orgs"),
248
+ verifyMCP: (domain) => client.fetchApi(`/verify/mcp/${domain}`),
249
+ verifyAgent: (wallet) => client.fetchApi(`/verify/agent/${wallet}`)
250
+ };
251
+ }
252
+
253
+ // src/components/SignInForm.tsx
254
+ import { useState as useState2 } from "react";
255
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
256
+ function SignInForm({ onSuccess, signUpUrl, forgotPasswordUrl }) {
257
+ const { signIn, config } = useAuth();
258
+ const [email, setEmail] = useState2("");
259
+ const [password, setPassword] = useState2("");
260
+ const [error, setError] = useState2("");
261
+ const [loading, setLoading] = useState2(false);
262
+ const emailValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
263
+ const handleSubmit = async (e) => {
264
+ e.preventDefault();
265
+ if (!emailValid) {
266
+ setError("Please enter a valid email address");
267
+ return;
268
+ }
269
+ setError("");
270
+ setLoading(true);
271
+ try {
272
+ await signIn(email, password);
273
+ if (onSuccess) {
274
+ onSuccess();
275
+ } else if (typeof window !== "undefined") {
276
+ const params = new URLSearchParams(window.location.search);
277
+ window.location.href = params.get("redirect_url") || config.afterSignInUrl;
278
+ }
279
+ } catch (err) {
280
+ setError(err.message || "Sign in failed");
281
+ } finally {
282
+ setLoading(false);
283
+ }
284
+ };
285
+ return /* @__PURE__ */ jsxs("div", { className: "githat-form-container", children: [
286
+ /* @__PURE__ */ jsxs("div", { className: "githat-form-header", children: [
287
+ /* @__PURE__ */ jsx2("h2", { className: "githat-form-title", children: "Sign in" }),
288
+ /* @__PURE__ */ jsx2("p", { className: "githat-form-subtitle", children: "Welcome back to GitHat" })
289
+ ] }),
290
+ error && /* @__PURE__ */ jsx2("div", { className: "githat-alert githat-alert-error", role: "alert", "aria-live": "polite", children: error }),
291
+ /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "githat-form", "aria-label": "Sign in form", children: [
292
+ /* @__PURE__ */ jsxs("div", { className: "githat-field", children: [
293
+ /* @__PURE__ */ jsx2("label", { className: "githat-label", htmlFor: "githat-signin-email", children: "Email" }),
294
+ /* @__PURE__ */ jsx2(
295
+ "input",
296
+ {
297
+ id: "githat-signin-email",
298
+ className: "githat-input",
299
+ type: "email",
300
+ value: email,
301
+ onChange: (e) => setEmail(e.target.value),
302
+ placeholder: "you@example.com",
303
+ autoComplete: "email",
304
+ required: true
305
+ }
306
+ )
307
+ ] }),
308
+ /* @__PURE__ */ jsxs("div", { className: "githat-field", children: [
309
+ /* @__PURE__ */ jsx2("label", { className: "githat-label", htmlFor: "githat-signin-password", children: "Password" }),
310
+ /* @__PURE__ */ jsx2(
311
+ "input",
312
+ {
313
+ id: "githat-signin-password",
314
+ className: "githat-input",
315
+ type: "password",
316
+ value: password,
317
+ onChange: (e) => setPassword(e.target.value),
318
+ placeholder: "Enter your password",
319
+ autoComplete: "current-password",
320
+ required: true
321
+ }
322
+ )
323
+ ] }),
324
+ forgotPasswordUrl && /* @__PURE__ */ jsx2("a", { href: forgotPasswordUrl, className: "githat-link githat-forgot-link", children: "Forgot password?" }),
325
+ /* @__PURE__ */ jsx2(
326
+ "button",
327
+ {
328
+ type: "submit",
329
+ className: "githat-button githat-button-primary",
330
+ disabled: loading || !email || !password || email.length > 0 && !emailValid,
331
+ children: loading ? "Signing in..." : "Sign in"
332
+ }
333
+ )
334
+ ] }),
335
+ signUpUrl && /* @__PURE__ */ jsxs("p", { className: "githat-form-footer", children: [
336
+ "Don't have an account? ",
337
+ /* @__PURE__ */ jsx2("a", { href: signUpUrl, className: "githat-link", children: "Sign up" })
338
+ ] }),
339
+ /* @__PURE__ */ jsxs("p", { className: "githat-powered-by", children: [
340
+ "Secured by ",
341
+ /* @__PURE__ */ jsx2("strong", { children: "GitHat" })
342
+ ] })
343
+ ] });
344
+ }
345
+
346
+ // src/components/SignUpForm.tsx
347
+ import { useState as useState3 } from "react";
348
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
349
+ function SignUpForm({ onSuccess, signInUrl }) {
350
+ const { signUp, config } = useAuth();
351
+ const [name, setName] = useState3("");
352
+ const [email, setEmail] = useState3("");
353
+ const [password, setPassword] = useState3("");
354
+ const [error, setError] = useState3("");
355
+ const [loading, setLoading] = useState3(false);
356
+ const emailValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
357
+ const passwordValid = password.length >= 8 && /[A-Z]/.test(password) && /[a-z]/.test(password) && /\d/.test(password);
358
+ const handleSubmit = async (e) => {
359
+ e.preventDefault();
360
+ if (!emailValid) {
361
+ setError("Please enter a valid email address");
362
+ return;
363
+ }
364
+ if (!passwordValid) {
365
+ setError("Password must be 8+ characters with uppercase, lowercase, number, and special character");
366
+ return;
367
+ }
368
+ setError("");
369
+ setLoading(true);
370
+ try {
371
+ const result = await signUp({ email, password, name });
372
+ if (onSuccess) {
373
+ onSuccess(result);
374
+ } else if (typeof window !== "undefined") {
375
+ window.location.href = config.afterSignInUrl;
376
+ }
377
+ } catch (err) {
378
+ setError(err.message || "Sign up failed");
379
+ } finally {
380
+ setLoading(false);
381
+ }
382
+ };
383
+ return /* @__PURE__ */ jsxs2("div", { className: "githat-form-container", children: [
384
+ /* @__PURE__ */ jsxs2("div", { className: "githat-form-header", children: [
385
+ /* @__PURE__ */ jsx3("h2", { className: "githat-form-title", children: "Create an account" }),
386
+ /* @__PURE__ */ jsx3("p", { className: "githat-form-subtitle", children: "Get started with GitHat" })
387
+ ] }),
388
+ error && /* @__PURE__ */ jsx3("div", { className: "githat-alert githat-alert-error", role: "alert", "aria-live": "polite", children: error }),
389
+ /* @__PURE__ */ jsxs2("form", { onSubmit: handleSubmit, className: "githat-form", "aria-label": "Sign up form", children: [
390
+ /* @__PURE__ */ jsxs2("div", { className: "githat-field", children: [
391
+ /* @__PURE__ */ jsx3("label", { className: "githat-label", htmlFor: "githat-signup-name", children: "Full name" }),
392
+ /* @__PURE__ */ jsx3(
393
+ "input",
394
+ {
395
+ id: "githat-signup-name",
396
+ className: "githat-input",
397
+ type: "text",
398
+ value: name,
399
+ onChange: (e) => setName(e.target.value),
400
+ placeholder: "Your name",
401
+ autoComplete: "name",
402
+ required: true
403
+ }
404
+ )
405
+ ] }),
406
+ /* @__PURE__ */ jsxs2("div", { className: "githat-field", children: [
407
+ /* @__PURE__ */ jsx3("label", { className: "githat-label", htmlFor: "githat-signup-email", children: "Email" }),
408
+ /* @__PURE__ */ jsx3(
409
+ "input",
410
+ {
411
+ id: "githat-signup-email",
412
+ className: "githat-input",
413
+ type: "email",
414
+ value: email,
415
+ onChange: (e) => setEmail(e.target.value),
416
+ placeholder: "you@example.com",
417
+ autoComplete: "email",
418
+ required: true
419
+ }
420
+ )
421
+ ] }),
422
+ /* @__PURE__ */ jsxs2("div", { className: "githat-field", children: [
423
+ /* @__PURE__ */ jsx3("label", { className: "githat-label", htmlFor: "githat-signup-password", children: "Password" }),
424
+ /* @__PURE__ */ jsx3(
425
+ "input",
426
+ {
427
+ id: "githat-signup-password",
428
+ className: "githat-input",
429
+ type: "password",
430
+ value: password,
431
+ onChange: (e) => setPassword(e.target.value),
432
+ placeholder: "8+ characters",
433
+ autoComplete: "new-password",
434
+ required: true
435
+ }
436
+ ),
437
+ password && !passwordValid && /* @__PURE__ */ jsx3("p", { className: "githat-field-error", children: "Must be 8+ characters with uppercase, lowercase, and number" })
438
+ ] }),
439
+ /* @__PURE__ */ jsx3(
440
+ "button",
441
+ {
442
+ type: "submit",
443
+ className: "githat-button githat-button-primary",
444
+ disabled: loading || !email || !password || !name || !emailValid,
445
+ children: loading ? "Creating account..." : "Sign up"
446
+ }
447
+ )
448
+ ] }),
449
+ signInUrl && /* @__PURE__ */ jsxs2("p", { className: "githat-form-footer", children: [
450
+ "Already have an account? ",
451
+ /* @__PURE__ */ jsx3("a", { href: signInUrl, className: "githat-link", children: "Sign in" })
452
+ ] }),
453
+ /* @__PURE__ */ jsxs2("p", { className: "githat-powered-by", children: [
454
+ "Secured by ",
455
+ /* @__PURE__ */ jsx3("strong", { children: "GitHat" })
456
+ ] })
457
+ ] });
458
+ }
459
+
460
+ // src/components/SignInButton.tsx
461
+ import { useContext as useContext2 } from "react";
462
+ import { jsx as jsx4 } from "react/jsx-runtime";
463
+ function SignInButton({ className, children, href }) {
464
+ const ctx = useContext2(GitHatContext);
465
+ const url = href || ctx?.config.signInUrl || "/sign-in";
466
+ return /* @__PURE__ */ jsx4("a", { href: url, className: className || "githat-button githat-button-primary", "aria-label": "Sign in", children: children || "Sign in" });
467
+ }
468
+
469
+ // src/components/SignUpButton.tsx
470
+ import { useContext as useContext3 } from "react";
471
+ import { jsx as jsx5 } from "react/jsx-runtime";
472
+ function SignUpButton({ className, children, href }) {
473
+ const ctx = useContext3(GitHatContext);
474
+ const url = href || ctx?.config.signUpUrl || "/sign-up";
475
+ return /* @__PURE__ */ jsx5("a", { href: url, className: className || "githat-button githat-button-outline", "aria-label": "Sign up", children: children || "Sign up" });
476
+ }
477
+
478
+ // src/components/UserButton.tsx
479
+ import { useState as useState4, useRef as useRef2, useEffect as useEffect2 } from "react";
480
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
481
+ function UserButton() {
482
+ const { user, org, isSignedIn, signOut } = useAuth();
483
+ const [open, setOpen] = useState4(false);
484
+ const ref = useRef2(null);
485
+ useEffect2(() => {
486
+ const handleClickOutside = (e) => {
487
+ if (ref.current && !ref.current.contains(e.target)) setOpen(false);
488
+ };
489
+ document.addEventListener("mousedown", handleClickOutside);
490
+ return () => document.removeEventListener("mousedown", handleClickOutside);
491
+ }, []);
492
+ if (!isSignedIn || !user) return null;
493
+ const initials = user.name ? user.name.split(" ").map((n) => n[0]).join("").toUpperCase().slice(0, 2) : user.email[0].toUpperCase();
494
+ return /* @__PURE__ */ jsxs3("div", { className: "githat-user-button", ref, children: [
495
+ /* @__PURE__ */ jsx6("button", { className: "githat-avatar-trigger", onClick: () => setOpen(!open), "aria-label": "User menu", "aria-expanded": open, "aria-haspopup": "true", children: user.avatarUrl ? /* @__PURE__ */ jsx6("img", { src: user.avatarUrl, alt: user.name || "User avatar", className: "githat-avatar-img" }) : /* @__PURE__ */ jsx6("span", { className: "githat-avatar-initials", children: initials }) }),
496
+ open && /* @__PURE__ */ jsxs3("div", { className: "githat-dropdown", role: "menu", children: [
497
+ /* @__PURE__ */ jsxs3("div", { className: "githat-dropdown-header", children: [
498
+ /* @__PURE__ */ jsx6("p", { className: "githat-dropdown-name", children: user.name }),
499
+ /* @__PURE__ */ jsx6("p", { className: "githat-dropdown-email", children: user.email }),
500
+ org && /* @__PURE__ */ jsx6("p", { className: "githat-dropdown-org", children: org.name })
501
+ ] }),
502
+ /* @__PURE__ */ jsx6("div", { className: "githat-dropdown-divider" }),
503
+ /* @__PURE__ */ jsx6("button", { className: "githat-dropdown-item", role: "menuitem", onClick: () => {
504
+ signOut();
505
+ setOpen(false);
506
+ }, children: "Sign out" })
507
+ ] })
508
+ ] });
509
+ }
510
+
511
+ // src/components/OrgSwitcher.tsx
512
+ import { useState as useState5, useEffect as useEffect3, useRef as useRef3 } from "react";
513
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
514
+ function OrgSwitcher() {
515
+ const { org, isSignedIn, switchOrg } = useAuth();
516
+ const githat = useGitHat();
517
+ const [orgs, setOrgs] = useState5([]);
518
+ const [orgsLoading, setOrgsLoading] = useState5(false);
519
+ const [open, setOpen] = useState5(false);
520
+ const ref = useRef3(null);
521
+ useEffect3(() => {
522
+ if (isSignedIn) {
523
+ setOrgsLoading(true);
524
+ githat.getUserOrgs().then((data) => setOrgs(data.orgs || [])).catch(() => {
525
+ }).finally(() => setOrgsLoading(false));
526
+ }
527
+ }, [isSignedIn]);
528
+ useEffect3(() => {
529
+ const handleClickOutside = (e) => {
530
+ if (ref.current && !ref.current.contains(e.target)) setOpen(false);
531
+ };
532
+ document.addEventListener("mousedown", handleClickOutside);
533
+ return () => document.removeEventListener("mousedown", handleClickOutside);
534
+ }, []);
535
+ if (!isSignedIn || !org || orgs.length < 2 && !orgsLoading) return null;
536
+ return /* @__PURE__ */ jsxs4("div", { className: "githat-org-switcher", ref, children: [
537
+ /* @__PURE__ */ jsxs4("button", { className: "githat-org-trigger", onClick: () => setOpen(!open), "aria-label": "Switch organization", "aria-expanded": open, "aria-haspopup": "true", children: [
538
+ /* @__PURE__ */ jsx7("span", { className: "githat-org-name", children: org.name }),
539
+ /* @__PURE__ */ jsx7("span", { className: "githat-chevron", children: open ? "\u25B2" : "\u25BC" })
540
+ ] }),
541
+ open && /* @__PURE__ */ jsx7("div", { className: "githat-dropdown", role: "menu", children: orgsLoading ? /* @__PURE__ */ jsx7("div", { className: "githat-dropdown-item", "aria-busy": "true", children: "Loading..." }) : orgs.map((o) => /* @__PURE__ */ jsxs4(
542
+ "button",
543
+ {
544
+ className: `githat-dropdown-item ${o.id === org.id ? "githat-dropdown-item-active" : ""}`,
545
+ role: "menuitem",
546
+ "aria-current": o.id === org.id ? "true" : void 0,
547
+ onClick: () => {
548
+ switchOrg(o.id);
549
+ setOpen(false);
550
+ },
551
+ children: [
552
+ o.name,
553
+ o.id === org.id && /* @__PURE__ */ jsx7("span", { className: "githat-check", children: "\u2713" })
554
+ ]
555
+ },
556
+ o.id
557
+ )) })
558
+ ] });
559
+ }
560
+
561
+ // src/components/VerifiedBadge.tsx
562
+ import { useState as useState6, useEffect as useEffect4, useRef as useRef4 } from "react";
563
+ import { jsxs as jsxs5 } from "react/jsx-runtime";
564
+ var CACHE_TTL = 5 * 60 * 1e3;
565
+ var cache = /* @__PURE__ */ new Map();
566
+ function VerifiedBadge({ type, identifier, label }) {
567
+ const githat = useGitHat();
568
+ const [verified, setVerified] = useState6(null);
569
+ const mounted = useRef4(true);
570
+ useEffect4(() => {
571
+ mounted.current = true;
572
+ const key = `${type}:${identifier}`;
573
+ const cached = cache.get(key);
574
+ if (cached && Date.now() - cached.ts < CACHE_TTL) {
575
+ setVerified(cached.verified);
576
+ return;
577
+ }
578
+ const verify = type === "mcp" ? githat.verifyMCP : githat.verifyAgent;
579
+ verify(identifier).then((data) => {
580
+ if (mounted.current) {
581
+ setVerified(data.verified);
582
+ cache.set(key, { verified: data.verified, ts: Date.now() });
583
+ }
584
+ }).catch(() => {
585
+ if (mounted.current) setVerified(false);
586
+ });
587
+ return () => {
588
+ mounted.current = false;
589
+ };
590
+ }, [type, identifier]);
591
+ if (verified === null) return null;
592
+ return /* @__PURE__ */ jsxs5("span", { className: `githat-badge ${verified ? "githat-badge-verified" : "githat-badge-unverified"}`, children: [
593
+ verified ? "\u2713" : "\u2717",
594
+ " ",
595
+ label || (verified ? "Verified" : "Unverified")
596
+ ] });
597
+ }
598
+
599
+ // src/components/ProtectedRoute.tsx
600
+ import { Fragment, jsx as jsx8 } from "react/jsx-runtime";
601
+ function ProtectedRoute({ children, fallback }) {
602
+ const { isSignedIn, isLoading, config } = useAuth();
603
+ if (isLoading) {
604
+ return /* @__PURE__ */ jsx8(Fragment, { children: fallback || /* @__PURE__ */ jsx8("div", { className: "githat-loading", children: "Loading..." }) });
605
+ }
606
+ if (!isSignedIn) {
607
+ if (typeof window !== "undefined") {
608
+ window.location.href = config.signInUrl;
609
+ }
610
+ return null;
611
+ }
612
+ return /* @__PURE__ */ jsx8(Fragment, { children });
613
+ }
614
+ export {
615
+ GitHatProvider,
616
+ OrgSwitcher,
617
+ ProtectedRoute,
618
+ SignInButton,
619
+ SignInForm,
620
+ SignUpButton,
621
+ SignUpForm,
622
+ UserButton,
623
+ VerifiedBadge,
624
+ useAuth,
625
+ useGitHat
626
+ };
627
+ //# sourceMappingURL=index.mjs.map