authfyio-react 0.3.9

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.
Files changed (68) hide show
  1. package/README.md +83 -0
  2. package/dist/client.d.ts +128 -0
  3. package/dist/client.d.ts.map +1 -0
  4. package/dist/client.js +227 -0
  5. package/dist/components.d.ts +79 -0
  6. package/dist/components.d.ts.map +1 -0
  7. package/dist/components.js +111 -0
  8. package/dist/hooks-flow.d.ts +59 -0
  9. package/dist/hooks-flow.d.ts.map +1 -0
  10. package/dist/hooks-flow.js +133 -0
  11. package/dist/hooks.d.ts +113 -0
  12. package/dist/hooks.d.ts.map +1 -0
  13. package/dist/hooks.js +212 -0
  14. package/dist/index.d.ts +8 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +15 -0
  17. package/dist/jwt.d.ts +20 -0
  18. package/dist/jwt.d.ts.map +1 -0
  19. package/dist/jwt.js +30 -0
  20. package/dist/provider.d.ts +50 -0
  21. package/dist/provider.d.ts.map +1 -0
  22. package/dist/provider.js +91 -0
  23. package/dist/ui/CheckoutButton.d.ts +14 -0
  24. package/dist/ui/CheckoutButton.d.ts.map +1 -0
  25. package/dist/ui/CheckoutButton.js +28 -0
  26. package/dist/ui/Control.d.ts +17 -0
  27. package/dist/ui/Control.d.ts.map +1 -0
  28. package/dist/ui/Control.js +33 -0
  29. package/dist/ui/CreateOrganization.d.ts +19 -0
  30. package/dist/ui/CreateOrganization.d.ts.map +1 -0
  31. package/dist/ui/CreateOrganization.js +75 -0
  32. package/dist/ui/OrganizationList.d.ts +13 -0
  33. package/dist/ui/OrganizationList.d.ts.map +1 -0
  34. package/dist/ui/OrganizationList.js +47 -0
  35. package/dist/ui/OrganizationProfile.d.ts +12 -0
  36. package/dist/ui/OrganizationProfile.d.ts.map +1 -0
  37. package/dist/ui/OrganizationProfile.js +116 -0
  38. package/dist/ui/OrganizationSwitcher.d.ts +17 -0
  39. package/dist/ui/OrganizationSwitcher.d.ts.map +1 -0
  40. package/dist/ui/OrganizationSwitcher.js +59 -0
  41. package/dist/ui/PricingTable.d.ts +15 -0
  42. package/dist/ui/PricingTable.d.ts.map +1 -0
  43. package/dist/ui/PricingTable.js +74 -0
  44. package/dist/ui/SignIn.d.ts +23 -0
  45. package/dist/ui/SignIn.d.ts.map +1 -0
  46. package/dist/ui/SignIn.js +489 -0
  47. package/dist/ui/SignUp.d.ts +18 -0
  48. package/dist/ui/SignUp.d.ts.map +1 -0
  49. package/dist/ui/SignUp.js +153 -0
  50. package/dist/ui/UnstyledButtons.d.ts +24 -0
  51. package/dist/ui/UnstyledButtons.d.ts.map +1 -0
  52. package/dist/ui/UnstyledButtons.js +42 -0
  53. package/dist/ui/UserAvatar.d.ts +14 -0
  54. package/dist/ui/UserAvatar.d.ts.map +1 -0
  55. package/dist/ui/UserAvatar.js +52 -0
  56. package/dist/ui/UserButton.d.ts +15 -0
  57. package/dist/ui/UserButton.d.ts.map +1 -0
  58. package/dist/ui/UserButton.js +82 -0
  59. package/dist/ui/UserProfile.d.ts +19 -0
  60. package/dist/ui/UserProfile.d.ts.map +1 -0
  61. package/dist/ui/UserProfile.js +199 -0
  62. package/dist/ui/index.d.ts +14 -0
  63. package/dist/ui/index.d.ts.map +1 -0
  64. package/dist/ui/index.js +13 -0
  65. package/dist/ui/styles.d.ts +10 -0
  66. package/dist/ui/styles.d.ts.map +1 -0
  67. package/dist/ui/styles.js +291 -0
  68. package/package.json +46 -0
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # authfyio-react
2
+
3
+ React SDK for Authfyio. Provides a `<AuthfyioProvider />`, hooks for session state, and a background refresh loop that keeps the short-lived `__session` JWT valid without interrupting your UI.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install authfyio-react
9
+ ```
10
+
11
+ Requires React 18 or newer.
12
+
13
+ ## Quick start
14
+
15
+ ```tsx
16
+ // app.tsx
17
+ import { AuthfyioProvider } from 'authfyio-react';
18
+
19
+ export default function App({ children }: { children: React.ReactNode }) {
20
+ return (
21
+ <AuthfyioProvider baseUrl={process.env.REACT_APP_AF_API_URL!}>
22
+ {children}
23
+ </AuthfyioProvider>
24
+ );
25
+ }
26
+ ```
27
+
28
+ ```tsx
29
+ // any component
30
+ import { useSession, useUserId } from 'authfyio-react';
31
+
32
+ export function Avatar() {
33
+ const session = useSession();
34
+ const userId = useUserId();
35
+
36
+ if (session.status === 'signed_out') return <SignInButton />;
37
+ return <span>Signed in as {userId}</span>;
38
+ }
39
+ ```
40
+
41
+ ## API
42
+
43
+ ### `<AuthfyioProvider />`
44
+
45
+ | Prop | Type | Default | Description |
46
+ | --------------------- | --------- | ------- | ---------------------------------------------------------------------------- |
47
+ | `baseUrl` | `string` | — | Instance API base URL (e.g. `https://auth.example.com`). |
48
+ | `autoRefresh` | `boolean` | `true` | Runs the refresh loop in the background to keep `__session` fresh. |
49
+ | `refreshSkewSeconds` | `number` | `15` | How many seconds before JWT expiry to trigger a refresh. |
50
+
51
+ The provider reads the `__session` cookie on mount, decodes its claims, and exposes them to descendants. When `autoRefresh` is on, it schedules a refresh that fires `refreshSkewSeconds` before `exp` and repeats for the lifetime of the session.
52
+
53
+ ### Hooks
54
+
55
+ - `useSession()` — returns `{ status: 'signed_in', jwt, claims } | { status: 'signed_out' }`.
56
+ - `useUserId()` — returns the `sub` claim when signed in, otherwise `null`.
57
+ - `useAuthfyio()` — returns `{ client, session, refresh }`. `refresh()` lets you trigger a token refresh manually (e.g. after a password change).
58
+
59
+ ### Low-level client
60
+
61
+ `AuthfyioReactClient` is exposed if you want to skip the provider — useful for scripts or non-React roots. Most apps should not need it.
62
+
63
+ ## How the refresh loop works
64
+
65
+ Your instance issues two cookies:
66
+
67
+ - `__client` (httpOnly, long-lived) — held by the browser, used only for the refresh call.
68
+ - `__session` (readable by JS, ~60s) — the JWT your frontend sends to your backend.
69
+
70
+ The SDK decodes `__session.exp`, schedules `setTimeout(refresh, exp - skew)`, hits the instance's `/v1/auth/refresh` endpoint, and re-reads the new `__session` cookie. The httpOnly `__client` never touches JS memory. If the refresh fails (revoked, network error), state transitions to `signed_out` and children re-render.
71
+
72
+ ## Next.js + Server Components
73
+
74
+ This package is a **Client SDK** (`'use client'` under the hood). For App Router middleware and server-side session access, use `authfyio-nextjs`.
75
+
76
+ ## Error handling
77
+
78
+ - `useSession()` never throws — it simply reports `signed_out` when the cookie is missing or invalid.
79
+ - Manual `refresh()` can throw network errors; wrap it in a `try/catch` if you surface retry UI.
80
+
81
+ ## License
82
+
83
+ MIT
@@ -0,0 +1,128 @@
1
+ import { type SessionClaims } from './jwt.js';
2
+ export declare const PUBLISHABLE_KEY_HEADER = "x-authfyio-publishable-key";
3
+ export type AuthConfigSnapshot = {
4
+ signIn: {
5
+ emailPassword: boolean;
6
+ usernamePassword: boolean;
7
+ magicLink: boolean;
8
+ emailCode: boolean;
9
+ phoneOtp: boolean;
10
+ passkey: boolean;
11
+ };
12
+ signUp: {
13
+ enabled: boolean;
14
+ email: boolean;
15
+ emailRequired: boolean;
16
+ username: {
17
+ required: boolean;
18
+ minLength: number;
19
+ maxLength: number;
20
+ } | null;
21
+ password: boolean;
22
+ phone: boolean;
23
+ firstAndLastName: {
24
+ required: boolean;
25
+ } | null;
26
+ verifyAtSignUp: boolean;
27
+ };
28
+ socialProviders: string[];
29
+ web3Wallets: string[];
30
+ legal: {
31
+ termsOfServiceUrl: string | null;
32
+ privacyPolicyUrl: string | null;
33
+ requireConsent: boolean;
34
+ };
35
+ branding: {
36
+ primaryColor: string | null;
37
+ logoUrl: string | null;
38
+ appName: string | null;
39
+ removeBranding: boolean;
40
+ };
41
+ };
42
+ export type AuthfyioReactClientOptions = {
43
+ baseUrl: string;
44
+ /**
45
+ * `pk_live_…` / `pk_test_…` — identifies which environment to route
46
+ * to on api.authfyio.com. Required when calling the hosted SaaS so
47
+ * requests don't depend on container-level `AF_ENVIRONMENT_ID`.
48
+ */
49
+ publishableKey?: string;
50
+ };
51
+ /**
52
+ * Minimal browser-side client used by the React hooks. Mirrors the resource
53
+ * shapes defined in hooks.ts so the type-checker is happy. All methods use
54
+ * `credentials: 'include'` so the FAPI session cookie travels with the request.
55
+ */
56
+ export declare class AuthfyioReactClient {
57
+ readonly baseUrl: string;
58
+ readonly publishableKey: string | null;
59
+ constructor(opts: AuthfyioReactClientOptions);
60
+ private headers;
61
+ /**
62
+ * Public auth-method config for this instance — which methods, social
63
+ * providers and web3 wallets the dashboard has enabled. Safe to call
64
+ * unauthenticated.
65
+ */
66
+ fetchAuthConfig(): Promise<AuthConfigSnapshot | null>;
67
+ getSessionFromCookies(): {
68
+ jwt: string;
69
+ claims: SessionClaims;
70
+ } | null;
71
+ refresh(): Promise<{
72
+ ok: true;
73
+ jwt: string;
74
+ claims: SessionClaims;
75
+ } | {
76
+ ok: false;
77
+ }>;
78
+ fetchCurrentUser(): Promise<{
79
+ id: string;
80
+ email: string | null;
81
+ username: string | null;
82
+ firstName: string | null;
83
+ lastName: string | null;
84
+ publicMetadata: Record<string, unknown>;
85
+ createdAt: string;
86
+ } | null>;
87
+ fetchOrganization(orgId: string): Promise<{
88
+ id: string;
89
+ name: string;
90
+ slug: string;
91
+ } | null>;
92
+ fetchPlans(): Promise<Array<{
93
+ id: string;
94
+ name: string;
95
+ description: string | null;
96
+ monthlyPriceCents: number;
97
+ annualPriceCents: number | null;
98
+ }>>;
99
+ /**
100
+ * Returns the caller's effective plan + attached features, used by
101
+ * `useAuth().has({ plan })` / `has({ feature })` and `<Protect>` /
102
+ * `<Show when>`. Always served by the instance API; returns `null` for
103
+ * signed-out users.
104
+ */
105
+ fetchMyBilling(): Promise<{
106
+ plan: {
107
+ id: string;
108
+ key: string | null;
109
+ name: string;
110
+ } | null;
111
+ features: Array<{
112
+ key: string;
113
+ name: string;
114
+ description?: string;
115
+ }>;
116
+ status: string | null;
117
+ } | null>;
118
+ fetchSubscription(): Promise<{
119
+ id: string;
120
+ status: string;
121
+ planId: string;
122
+ currentPeriodEnd: string | null;
123
+ } | null>;
124
+ createCheckoutSession(planId: string, billingCycle?: 'monthly' | 'annual'): Promise<{
125
+ url: string;
126
+ }>;
127
+ }
128
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/client.js ADDED
@@ -0,0 +1,227 @@
1
+ import { decodeJwtPayload, getSessionJwtFromDocumentCookie } from './jwt.js';
2
+ export const PUBLISHABLE_KEY_HEADER = 'x-authfyio-publishable-key';
3
+ /**
4
+ * Minimal browser-side client used by the React hooks. Mirrors the resource
5
+ * shapes defined in hooks.ts so the type-checker is happy. All methods use
6
+ * `credentials: 'include'` so the FAPI session cookie travels with the request.
7
+ */
8
+ export class AuthfyioReactClient {
9
+ // Public so UI components (notably <SignIn>) can append the
10
+ // publishable key to OAuth /authorize anchors as a query string.
11
+ // Top-level browser navigations don't carry custom headers, so the
12
+ // header path used by fetch() requests doesn't apply here.
13
+ baseUrl;
14
+ publishableKey;
15
+ constructor(opts) {
16
+ this.baseUrl = opts.baseUrl.replace(/\/+$/, '');
17
+ this.publishableKey = opts.publishableKey ?? null;
18
+ }
19
+ headers(extra = {}) {
20
+ const out = { ...extra };
21
+ if (this.publishableKey)
22
+ out[PUBLISHABLE_KEY_HEADER] = this.publishableKey;
23
+ return out;
24
+ }
25
+ /**
26
+ * Public auth-method config for this instance — which methods, social
27
+ * providers and web3 wallets the dashboard has enabled. Safe to call
28
+ * unauthenticated.
29
+ */
30
+ async fetchAuthConfig() {
31
+ const res = await fetch(`${this.baseUrl}/v1/auth/config`, {
32
+ method: 'GET',
33
+ credentials: 'include',
34
+ headers: this.headers({ accept: 'application/json' }),
35
+ });
36
+ if (!res.ok)
37
+ return null;
38
+ const body = (await res.json());
39
+ if (!body || typeof body !== 'object')
40
+ return null;
41
+ const si = body.signIn ?? {};
42
+ const su = body.signUp ?? {};
43
+ return {
44
+ signIn: {
45
+ emailPassword: !!si.emailPassword,
46
+ usernamePassword: !!si.usernamePassword,
47
+ magicLink: !!si.magicLink,
48
+ emailCode: !!si.emailCode,
49
+ phoneOtp: !!si.phoneOtp,
50
+ passkey: !!si.passkey,
51
+ },
52
+ signUp: {
53
+ enabled: su.enabled !== false,
54
+ email: !!su.email,
55
+ emailRequired: !!su.emailRequired,
56
+ username: su.username
57
+ ? {
58
+ required: su.username.required === true,
59
+ minLength: Number(su.username.minLength ?? 4),
60
+ maxLength: Number(su.username.maxLength ?? 64),
61
+ }
62
+ : null,
63
+ password: !!su.password,
64
+ phone: !!su.phone,
65
+ firstAndLastName: su.firstAndLastName ? { required: su.firstAndLastName.required === true } : null,
66
+ verifyAtSignUp: !!su.verifyAtSignUp,
67
+ },
68
+ socialProviders: Array.isArray(body.socialProviders) ? body.socialProviders.map(String) : [],
69
+ web3Wallets: Array.isArray(body.web3Wallets) ? body.web3Wallets.map(String) : [],
70
+ legal: {
71
+ termsOfServiceUrl: typeof body.legal?.termsOfServiceUrl === 'string' ? body.legal.termsOfServiceUrl : null,
72
+ privacyPolicyUrl: typeof body.legal?.privacyPolicyUrl === 'string' ? body.legal.privacyPolicyUrl : null,
73
+ requireConsent: body.legal?.requireConsent === true,
74
+ },
75
+ branding: {
76
+ primaryColor: typeof body.branding?.primaryColor === 'string' ? body.branding.primaryColor : null,
77
+ logoUrl: typeof body.branding?.logoUrl === 'string' ? body.branding.logoUrl : null,
78
+ appName: typeof body.branding?.appName === 'string' ? body.branding.appName : null,
79
+ removeBranding: body.branding?.removeBranding === true,
80
+ },
81
+ };
82
+ }
83
+ getSessionFromCookies() {
84
+ const jwt = getSessionJwtFromDocumentCookie();
85
+ if (!jwt)
86
+ return null;
87
+ const claims = decodeJwtPayload(jwt);
88
+ return { jwt, claims };
89
+ }
90
+ async refresh() {
91
+ const res = await fetch(`${this.baseUrl}/v1/auth/sessions/refresh`, {
92
+ method: 'POST',
93
+ credentials: 'include',
94
+ headers: this.headers({ 'content-type': 'application/json' }),
95
+ body: JSON.stringify({}),
96
+ });
97
+ if (!res.ok)
98
+ return { ok: false };
99
+ const next = this.getSessionFromCookies();
100
+ if (!next)
101
+ return { ok: false };
102
+ return { ok: true, jwt: next.jwt, claims: next.claims };
103
+ }
104
+ async fetchCurrentUser() {
105
+ const res = await fetch(`${this.baseUrl}/v1/auth/sessions/current`, {
106
+ method: 'GET',
107
+ credentials: 'include',
108
+ headers: this.headers({ accept: 'application/json' }),
109
+ });
110
+ if (!res.ok)
111
+ return null;
112
+ const body = (await res.json());
113
+ if (!body?.user?.id)
114
+ return null;
115
+ const u = body.user;
116
+ return {
117
+ id: String(u.id),
118
+ email: u.email ?? null,
119
+ username: u.username ?? null,
120
+ firstName: u.firstName ?? null,
121
+ lastName: u.lastName ?? null,
122
+ publicMetadata: (u.publicMetadata ?? {}),
123
+ createdAt: String(u.createdAt ?? new Date().toISOString()),
124
+ };
125
+ }
126
+ async fetchOrganization(orgId) {
127
+ const res = await fetch(`${this.baseUrl}/v1/orgs/${encodeURIComponent(orgId)}`, {
128
+ method: 'GET',
129
+ credentials: 'include',
130
+ headers: this.headers({ accept: 'application/json' }),
131
+ });
132
+ if (!res.ok)
133
+ return null;
134
+ const body = (await res.json());
135
+ if (!body?.organization?.id)
136
+ return null;
137
+ const o = body.organization;
138
+ return { id: String(o.id), name: String(o.name), slug: String(o.slug) };
139
+ }
140
+ async fetchPlans() {
141
+ const res = await fetch(`${this.baseUrl}/v1/billing/plans`, {
142
+ method: 'GET',
143
+ credentials: 'include',
144
+ headers: this.headers({ accept: 'application/json' }),
145
+ });
146
+ if (!res.ok)
147
+ return [];
148
+ const body = (await res.json());
149
+ if (!Array.isArray(body?.plans))
150
+ return [];
151
+ return body.plans.map((p) => ({
152
+ id: String(p.id),
153
+ name: String(p.name),
154
+ description: p.description ?? null,
155
+ monthlyPriceCents: Number(p.monthlyPriceCents ?? 0),
156
+ annualPriceCents: p.annualPriceCents == null ? null : Number(p.annualPriceCents),
157
+ }));
158
+ }
159
+ /**
160
+ * Returns the caller's effective plan + attached features, used by
161
+ * `useAuth().has({ plan })` / `has({ feature })` and `<Protect>` /
162
+ * `<Show when>`. Always served by the instance API; returns `null` for
163
+ * signed-out users.
164
+ */
165
+ async fetchMyBilling() {
166
+ const res = await fetch(`${this.baseUrl}/v1/billing/me`, {
167
+ method: 'GET',
168
+ credentials: 'include',
169
+ headers: this.headers({ accept: 'application/json' }),
170
+ });
171
+ if (!res.ok)
172
+ return null;
173
+ const body = (await res.json());
174
+ if (!body)
175
+ return null;
176
+ return {
177
+ plan: body.plan
178
+ ? {
179
+ id: String(body.plan.id),
180
+ key: body.plan.key ?? null,
181
+ name: String(body.plan.name ?? ''),
182
+ }
183
+ : null,
184
+ features: Array.isArray(body.features)
185
+ ? body.features.map((f) => ({
186
+ key: String(f.key),
187
+ name: String(f.name ?? f.key),
188
+ description: f.description ?? undefined,
189
+ }))
190
+ : [],
191
+ status: body.status ?? null,
192
+ };
193
+ }
194
+ async fetchSubscription() {
195
+ const res = await fetch(`${this.baseUrl}/v1/billing/subscription`, {
196
+ method: 'GET',
197
+ credentials: 'include',
198
+ headers: this.headers({ accept: 'application/json' }),
199
+ });
200
+ if (!res.ok)
201
+ return null;
202
+ const body = (await res.json());
203
+ if (!body?.subscription?.id)
204
+ return null;
205
+ const s = body.subscription;
206
+ return {
207
+ id: String(s.id),
208
+ status: String(s.status),
209
+ planId: String(s.planId),
210
+ currentPeriodEnd: s.currentPeriodEnd ?? null,
211
+ };
212
+ }
213
+ async createCheckoutSession(planId, billingCycle = 'monthly') {
214
+ const res = await fetch(`${this.baseUrl}/v1/billing/checkout`, {
215
+ method: 'POST',
216
+ credentials: 'include',
217
+ headers: this.headers({ 'content-type': 'application/json', accept: 'application/json' }),
218
+ body: JSON.stringify({ planId, billingCycle }),
219
+ });
220
+ if (!res.ok)
221
+ throw new Error(`checkout_failed_${res.status}`);
222
+ const body = (await res.json());
223
+ if (!body?.url)
224
+ throw new Error('checkout_no_url');
225
+ return { url: body.url };
226
+ }
227
+ }
@@ -0,0 +1,79 @@
1
+ import React from 'react';
2
+ /**
3
+ * Renders its children only when there's an active session. Server-side
4
+ * it renders nothing until the provider hydrates, so wrap it in a client
5
+ * component tree (the AuthfyioProvider already is).
6
+ */
7
+ export declare function SignedIn({ children }: {
8
+ children: React.ReactNode;
9
+ }): React.ReactElement | null;
10
+ /**
11
+ * Renders its children only when the session is absent or expired.
12
+ */
13
+ export declare function SignedOut({ children }: {
14
+ children: React.ReactNode;
15
+ }): React.ReactElement | null;
16
+ export type ProtectProps = {
17
+ children: React.ReactNode;
18
+ /** Require an org role (matches the session's `org_role` claim). */
19
+ role?: string;
20
+ /** Require an active subscription to a plan by stable key (e.g. "pro"). */
21
+ plan?: string;
22
+ /** Require a specific feature attached to the user's active plan. */
23
+ feature?: string;
24
+ /** Fallback rendered when the user is signed out or fails the check. */
25
+ fallback?: React.ReactNode;
26
+ };
27
+ /**
28
+ * Renders `children` when the user is signed in AND satisfies every provided
29
+ * check (`role`, `plan`, `feature`). Otherwise renders `fallback`. Use this
30
+ * for component-level authorization inside larger unprotected pages.
31
+ */
32
+ export declare function Protect({ children, role, plan, feature, fallback, }: ProtectProps): React.ReactElement | null;
33
+ export type ShowProps = {
34
+ children: React.ReactNode;
35
+ /** Conditions; all must match (AND). */
36
+ when: {
37
+ plan?: string;
38
+ feature?: string;
39
+ role?: string;
40
+ };
41
+ /** Rendered when the check fails. */
42
+ fallback?: React.ReactNode;
43
+ };
44
+ /**
45
+ * customisable `<Show when={{plan,feature,role}}>`. Thin wrapper over
46
+ * `auth.has({...})`. Returns nothing while the check is loading so you
47
+ * don't flash the fallback state.
48
+ */
49
+ export declare function Show({ children, when, fallback }: ShowProps): React.ReactElement | null;
50
+ export type RedirectToSignInProps = {
51
+ /** Target path on your app that renders the sign-in form (default: '/sign-in'). */
52
+ signInUrl?: string;
53
+ /** Optional query parameter carrying the caller URL so you can return after sign-in. */
54
+ afterSignInParam?: string;
55
+ };
56
+ /**
57
+ * Client-side redirect to the sign-in page. Fire-and-forget: it navigates on
58
+ * mount via `window.location.replace` so it also works inside React trees
59
+ * without `next/navigation`. Prefer `<SignedOut>` + this for guarded UIs.
60
+ */
61
+ export declare function RedirectToSignIn({ signInUrl, afterSignInParam, }: RedirectToSignInProps): null;
62
+ export type RedirectToSignUpProps = {
63
+ signUpUrl?: string;
64
+ afterSignUpParam?: string;
65
+ };
66
+ export declare function RedirectToSignUp({ signUpUrl, afterSignUpParam, }: RedirectToSignUpProps): null;
67
+ export type SignOutButtonProps = {
68
+ children?: React.ReactNode;
69
+ /** Redirect target after sign-out completes. Defaults to '/'. */
70
+ redirectUrl?: string;
71
+ className?: string;
72
+ };
73
+ /**
74
+ * Convenience button — calls `POST /v1/auth/sign-out` on the instance API,
75
+ * then navigates to `redirectUrl`. Use inside `<SignedIn>` or check `isSignedIn`
76
+ * yourself. Uses the provider's configured baseUrl.
77
+ */
78
+ export declare function SignOutButton({ children, redirectUrl, className }: SignOutButtonProps): import("react/jsx-runtime").JSX.Element;
79
+ //# sourceMappingURL=components.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../src/components.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAoB,MAAM,OAAO,CAAC;AAKzC;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CAI/F;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CAIhG;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,EACtB,QAAQ,EACR,IAAI,EACJ,IAAI,EACJ,OAAO,EACP,QAAe,GAChB,EAAE,YAAY,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CAY1C;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,wCAAwC;IACxC,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzD,qCAAqC;IACrC,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAe,EAAE,EAAE,SAAS,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CAK9F;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,mFAAmF;IACnF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wFAAwF;IACxF,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,SAAsB,EACtB,gBAAiC,GAClC,EAAE,qBAAqB,GAAG,IAAI,CAS9B;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,EAC/B,SAAsB,EACtB,gBAAiC,GAClC,EAAE,qBAAqB,GAAG,IAAI,CAS9B;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,WAAiB,EAAE,SAAS,EAAE,EAAE,kBAAkB,2CAiB3F"}
@@ -0,0 +1,111 @@
1
+ 'use client';
2
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
3
+ import { useEffect } from 'react';
4
+ import { useSession, useAuthfyio } from './provider.js';
5
+ import { useAuth } from './hooks.js';
6
+ /**
7
+ * Renders its children only when there's an active session. Server-side
8
+ * it renders nothing until the provider hydrates, so wrap it in a client
9
+ * component tree (the AuthfyioProvider already is).
10
+ */
11
+ export function SignedIn({ children }) {
12
+ const s = useSession();
13
+ if (s.status !== 'signed_in')
14
+ return null;
15
+ return _jsx(_Fragment, { children: children });
16
+ }
17
+ /**
18
+ * Renders its children only when the session is absent or expired.
19
+ */
20
+ export function SignedOut({ children }) {
21
+ const s = useSession();
22
+ if (s.status === 'signed_in')
23
+ return null;
24
+ return _jsx(_Fragment, { children: children });
25
+ }
26
+ /**
27
+ * Renders `children` when the user is signed in AND satisfies every provided
28
+ * check (`role`, `plan`, `feature`). Otherwise renders `fallback`. Use this
29
+ * for component-level authorization inside larger unprotected pages.
30
+ */
31
+ export function Protect({ children, role, plan, feature, fallback = null, }) {
32
+ const auth = useAuth();
33
+ if (!auth.isSignedIn)
34
+ return _jsx(_Fragment, { children: fallback });
35
+ const checks = [];
36
+ if (role)
37
+ checks.push(auth.has({ role }));
38
+ if (plan)
39
+ checks.push(auth.has({ plan }));
40
+ if (feature)
41
+ checks.push(auth.has({ feature }));
42
+ // While any async check is still resolving (`undefined`), render nothing to
43
+ // avoid a flash of the fallback. Once all checks settle to booleans, render.
44
+ if (checks.some((c) => c === undefined))
45
+ return null;
46
+ if (checks.some((c) => c === false))
47
+ return _jsx(_Fragment, { children: fallback });
48
+ return _jsx(_Fragment, { children: children });
49
+ }
50
+ /**
51
+ * customisable `<Show when={{plan,feature,role}}>`. Thin wrapper over
52
+ * `auth.has({...})`. Returns nothing while the check is loading so you
53
+ * don't flash the fallback state.
54
+ */
55
+ export function Show({ children, when, fallback = null }) {
56
+ const auth = useAuth();
57
+ const result = auth.has(when);
58
+ if (result === undefined)
59
+ return null;
60
+ return result ? _jsx(_Fragment, { children: children }) : _jsx(_Fragment, { children: fallback });
61
+ }
62
+ /**
63
+ * Client-side redirect to the sign-in page. Fire-and-forget: it navigates on
64
+ * mount via `window.location.replace` so it also works inside React trees
65
+ * without `next/navigation`. Prefer `<SignedOut>` + this for guarded UIs.
66
+ */
67
+ export function RedirectToSignIn({ signInUrl = '/sign-in', afterSignInParam = 'redirect_url', }) {
68
+ useEffect(() => {
69
+ if (typeof window === 'undefined')
70
+ return;
71
+ const next = window.location.pathname + window.location.search;
72
+ const url = new URL(signInUrl, window.location.origin);
73
+ if (next && next !== '/')
74
+ url.searchParams.set(afterSignInParam, next);
75
+ window.location.replace(url.toString());
76
+ }, [signInUrl, afterSignInParam]);
77
+ return null;
78
+ }
79
+ export function RedirectToSignUp({ signUpUrl = '/sign-up', afterSignUpParam = 'redirect_url', }) {
80
+ useEffect(() => {
81
+ if (typeof window === 'undefined')
82
+ return;
83
+ const next = window.location.pathname + window.location.search;
84
+ const url = new URL(signUpUrl, window.location.origin);
85
+ if (next && next !== '/')
86
+ url.searchParams.set(afterSignUpParam, next);
87
+ window.location.replace(url.toString());
88
+ }, [signUpUrl, afterSignUpParam]);
89
+ return null;
90
+ }
91
+ /**
92
+ * Convenience button — calls `POST /v1/auth/sign-out` on the instance API,
93
+ * then navigates to `redirectUrl`. Use inside `<SignedIn>` or check `isSignedIn`
94
+ * yourself. Uses the provider's configured baseUrl.
95
+ */
96
+ export function SignOutButton({ children, redirectUrl = '/', className }) {
97
+ const { client } = useAuthfyio();
98
+ async function onClick() {
99
+ try {
100
+ await fetch(`${client.baseUrl ?? ''}/v1/auth/sign-out`, {
101
+ method: 'POST',
102
+ credentials: 'include',
103
+ });
104
+ }
105
+ finally {
106
+ if (typeof window !== 'undefined')
107
+ window.location.href = redirectUrl;
108
+ }
109
+ }
110
+ return (_jsx("button", { type: "button", onClick: onClick, className: className, children: children ?? 'Sign out' }));
111
+ }