@fayz-ai/plugin-auth 0.1.1

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/src/forms.tsx ADDED
@@ -0,0 +1,252 @@
1
+ import * as React from 'react'
2
+ import { useAuth } from '@fayz-ai/auth'
3
+ import type { AuthProvider } from '@fayz-ai/core'
4
+
5
+ function providerLabel(provider: string): string {
6
+ return provider.charAt(0).toUpperCase() + provider.slice(1)
7
+ }
8
+
9
+ function fieldClass() {
10
+ return 'h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50'
11
+ }
12
+
13
+ function buttonClass(variant: 'primary' | 'outline' | 'ghost' = 'primary') {
14
+ if (variant === 'outline') {
15
+ return 'inline-flex h-10 w-full items-center justify-center rounded-md border border-input bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-muted disabled:pointer-events-none disabled:opacity-50'
16
+ }
17
+ if (variant === 'ghost') return 'text-sm font-medium text-primary hover:underline'
18
+ return 'inline-flex h-10 w-full items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-50'
19
+ }
20
+
21
+ export interface OAuthButtonsProps {
22
+ providers?: Exclude<AuthProvider, 'email'>[]
23
+ disabled?: boolean
24
+ onProviderClick?: (provider: AuthProvider) => void | Promise<void>
25
+ }
26
+
27
+ export function OAuthButtons({ providers = ['google'], disabled, onProviderClick }: OAuthButtonsProps) {
28
+ const { signInWithOAuth } = useAuth()
29
+ return (
30
+ <div className="space-y-2">
31
+ {providers.map((provider) => (
32
+ <button
33
+ key={provider}
34
+ type="button"
35
+ className={buttonClass('outline')}
36
+ disabled={disabled}
37
+ onClick={() => {
38
+ void (onProviderClick ? onProviderClick(provider) : signInWithOAuth(provider))
39
+ }}
40
+ >
41
+ Continue with {providerLabel(provider)}
42
+ </button>
43
+ ))}
44
+ </div>
45
+ )
46
+ }
47
+
48
+ export interface LoginFormProps {
49
+ onSuccess?: () => void
50
+ onForgotPassword?: () => void
51
+ showOAuth?: boolean
52
+ oauthProviders?: Exclude<AuthProvider, 'email'>[]
53
+ }
54
+
55
+ export function LoginForm({ onSuccess, onForgotPassword, showOAuth, oauthProviders }: LoginFormProps) {
56
+ const { signIn, error, isLoading } = useAuth()
57
+ const [email, setEmail] = React.useState('')
58
+ const [password, setPassword] = React.useState('')
59
+ const [busy, setBusy] = React.useState(false)
60
+ const disabled = busy || isLoading
61
+
62
+ async function submit(event: React.FormEvent) {
63
+ event.preventDefault()
64
+ setBusy(true)
65
+ try {
66
+ await signIn(email, password)
67
+ onSuccess?.()
68
+ } finally {
69
+ setBusy(false)
70
+ }
71
+ }
72
+
73
+ return (
74
+ <form onSubmit={submit} className="space-y-4">
75
+ <label className="block space-y-1.5 text-sm font-medium">
76
+ <span>Email</span>
77
+ <input className={fieldClass()} type="email" autoComplete="username" required value={email} onChange={(e) => setEmail(e.target.value)} disabled={disabled} />
78
+ </label>
79
+ <label className="block space-y-1.5 text-sm font-medium">
80
+ <span>Password</span>
81
+ <input className={fieldClass()} type="password" autoComplete="current-password" required value={password} onChange={(e) => setPassword(e.target.value)} disabled={disabled} />
82
+ </label>
83
+ {onForgotPassword && (
84
+ <div className="-mt-1 flex justify-end">
85
+ <button type="button" className={buttonClass('ghost')} onClick={onForgotPassword}>
86
+ Forgot password?
87
+ </button>
88
+ </div>
89
+ )}
90
+ {error && <p className="text-sm text-destructive">{error.message}</p>}
91
+ <button className={buttonClass()} type="submit" disabled={disabled}>
92
+ {disabled ? 'Signing in...' : 'Sign in'}
93
+ </button>
94
+ {showOAuth && <OAuthButtons providers={oauthProviders} disabled={disabled} />}
95
+ </form>
96
+ )
97
+ }
98
+
99
+ export interface SignupFormProps {
100
+ onSuccess?: () => void
101
+ }
102
+
103
+ export function SignupForm({ onSuccess }: SignupFormProps) {
104
+ const { signUp, error, isLoading } = useAuth()
105
+ const [fullName, setFullName] = React.useState('')
106
+ const [email, setEmail] = React.useState('')
107
+ const [password, setPassword] = React.useState('')
108
+ const [confirmPassword, setConfirmPassword] = React.useState('')
109
+ const [localError, setLocalError] = React.useState<string | null>(null)
110
+ const [busy, setBusy] = React.useState(false)
111
+ const disabled = busy || isLoading
112
+
113
+ async function submit(event: React.FormEvent) {
114
+ event.preventDefault()
115
+ setLocalError(null)
116
+ if (password.length < 8) {
117
+ setLocalError('Password must be at least 8 characters.')
118
+ return
119
+ }
120
+ if (password !== confirmPassword) {
121
+ setLocalError('Passwords do not match.')
122
+ return
123
+ }
124
+ setBusy(true)
125
+ try {
126
+ await signUp(email, password, fullName)
127
+ onSuccess?.()
128
+ } finally {
129
+ setBusy(false)
130
+ }
131
+ }
132
+
133
+ return (
134
+ <form onSubmit={submit} className="space-y-4">
135
+ <label className="block space-y-1.5 text-sm font-medium">
136
+ <span>Full name</span>
137
+ <input className={fieldClass()} required value={fullName} onChange={(e) => setFullName(e.target.value)} disabled={disabled} />
138
+ </label>
139
+ <label className="block space-y-1.5 text-sm font-medium">
140
+ <span>Email</span>
141
+ <input className={fieldClass()} type="email" autoComplete="username" required value={email} onChange={(e) => setEmail(e.target.value)} disabled={disabled} />
142
+ </label>
143
+ <label className="block space-y-1.5 text-sm font-medium">
144
+ <span>Password</span>
145
+ <input className={fieldClass()} type="password" autoComplete="new-password" required value={password} onChange={(e) => setPassword(e.target.value)} disabled={disabled} />
146
+ </label>
147
+ <label className="block space-y-1.5 text-sm font-medium">
148
+ <span>Confirm password</span>
149
+ <input className={fieldClass()} type="password" autoComplete="new-password" required value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} disabled={disabled} />
150
+ </label>
151
+ {(localError || error) && <p className="text-sm text-destructive">{localError ?? error?.message}</p>}
152
+ <button className={buttonClass()} type="submit" disabled={disabled}>
153
+ {disabled ? 'Creating account...' : 'Create account'}
154
+ </button>
155
+ </form>
156
+ )
157
+ }
158
+
159
+ export interface ForgotPasswordFormProps {
160
+ redirectTo?: string
161
+ onSent?: (email: string) => void
162
+ onBackToLogin?: () => void
163
+ }
164
+
165
+ export function ForgotPasswordForm({ redirectTo, onSent, onBackToLogin }: ForgotPasswordFormProps) {
166
+ const { resetPassword, error, isLoading } = useAuth()
167
+ const [email, setEmail] = React.useState('')
168
+ const [sent, setSent] = React.useState(false)
169
+ const [busy, setBusy] = React.useState(false)
170
+ const disabled = busy || isLoading
171
+
172
+ async function submit(event: React.FormEvent) {
173
+ event.preventDefault()
174
+ setBusy(true)
175
+ try {
176
+ await resetPassword(email, { redirectTo })
177
+ setSent(true)
178
+ onSent?.(email)
179
+ } finally {
180
+ setBusy(false)
181
+ }
182
+ }
183
+
184
+ return (
185
+ <form onSubmit={submit} className="space-y-4">
186
+ <label className="block space-y-1.5 text-sm font-medium">
187
+ <span>Email</span>
188
+ <input className={fieldClass()} type="email" autoComplete="username" required value={email} onChange={(e) => setEmail(e.target.value)} disabled={disabled} />
189
+ </label>
190
+ {error && <p className="text-sm text-destructive">{error.message}</p>}
191
+ {sent && <p className="text-sm text-emerald-600">Password reset instructions sent.</p>}
192
+ <button className={buttonClass()} type="submit" disabled={disabled}>
193
+ {disabled ? 'Sending...' : 'Send reset link'}
194
+ </button>
195
+ {onBackToLogin && (
196
+ <button type="button" className={buttonClass('ghost')} onClick={onBackToLogin}>
197
+ Back to login
198
+ </button>
199
+ )}
200
+ </form>
201
+ )
202
+ }
203
+
204
+ export interface ResetPasswordFormProps {
205
+ onSuccess?: () => void
206
+ }
207
+
208
+ export function ResetPasswordForm({ onSuccess }: ResetPasswordFormProps) {
209
+ const { updatePassword, error, isLoading } = useAuth()
210
+ const [password, setPassword] = React.useState('')
211
+ const [confirmPassword, setConfirmPassword] = React.useState('')
212
+ const [localError, setLocalError] = React.useState<string | null>(null)
213
+ const [busy, setBusy] = React.useState(false)
214
+ const disabled = busy || isLoading
215
+
216
+ async function submit(event: React.FormEvent) {
217
+ event.preventDefault()
218
+ setLocalError(null)
219
+ if (password.length < 8) {
220
+ setLocalError('Password must be at least 8 characters.')
221
+ return
222
+ }
223
+ if (password !== confirmPassword) {
224
+ setLocalError('Passwords do not match.')
225
+ return
226
+ }
227
+ setBusy(true)
228
+ try {
229
+ await updatePassword(password)
230
+ onSuccess?.()
231
+ } finally {
232
+ setBusy(false)
233
+ }
234
+ }
235
+
236
+ return (
237
+ <form onSubmit={submit} className="space-y-4">
238
+ <label className="block space-y-1.5 text-sm font-medium">
239
+ <span>New password</span>
240
+ <input className={fieldClass()} type="password" autoComplete="new-password" required value={password} onChange={(e) => setPassword(e.target.value)} disabled={disabled} />
241
+ </label>
242
+ <label className="block space-y-1.5 text-sm font-medium">
243
+ <span>Confirm password</span>
244
+ <input className={fieldClass()} type="password" autoComplete="new-password" required value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} disabled={disabled} />
245
+ </label>
246
+ {(localError || error) && <p className="text-sm text-destructive">{localError ?? error?.message}</p>}
247
+ <button className={buttonClass()} type="submit" disabled={disabled}>
248
+ {disabled ? 'Saving...' : 'Update password'}
249
+ </button>
250
+ </form>
251
+ )
252
+ }
package/src/index.ts ADDED
@@ -0,0 +1,49 @@
1
+ export {
2
+ createAuthPlugin,
3
+ createAuthRuntime,
4
+ resolveAuthAdapter,
5
+ } from './runtime'
6
+ export {
7
+ AuthGate,
8
+ LoginPage,
9
+ AuthCallbackPage,
10
+ ResetPasswordPage,
11
+ } from './pages'
12
+ export type {
13
+ AuthCallbackPageProps,
14
+ AuthGateProps,
15
+ LoginPageProps,
16
+ ResetPasswordPageProps,
17
+ } from './pages'
18
+ export {
19
+ LoginForm,
20
+ SignupForm,
21
+ ForgotPasswordForm,
22
+ ResetPasswordForm,
23
+ OAuthButtons,
24
+ } from './forms'
25
+ export type {
26
+ ForgotPasswordFormProps,
27
+ LoginFormProps,
28
+ OAuthButtonsProps,
29
+ ResetPasswordFormProps,
30
+ SignupFormProps,
31
+ } from './forms'
32
+ export {
33
+ createMockAuthAdapter,
34
+ createSupabaseAuthAdapter,
35
+ AuthProvider,
36
+ useAuth,
37
+ useAuthStore,
38
+ } from '@fayz-ai/auth'
39
+ export type {
40
+ AuthPluginOptions,
41
+ ResolvedAuthPlugin,
42
+ AuthRoutesConfig,
43
+ AuthOAuthConfig,
44
+ AuthSupabaseConfig,
45
+ AuthProviderKey,
46
+ AuthLayout,
47
+ AuthFormView,
48
+ } from './types'
49
+ export type { AuthAdapter, AuthSession, AuthUser, AuthProvider as OAuthProvider } from '@fayz-ai/core'
package/src/pages.tsx ADDED
@@ -0,0 +1,196 @@
1
+ import * as React from 'react'
2
+ import { useAuth } from '@fayz-ai/auth'
3
+ import type { AuthProvider } from '@fayz-ai/core'
4
+ import { ForgotPasswordForm, LoginForm, ResetPasswordForm, SignupForm } from './forms'
5
+ import type { AuthFormView, AuthLayout } from './types'
6
+
7
+ export interface LoginPageProps {
8
+ appName?: string
9
+ logo?: React.ReactNode
10
+ loginLogo?: React.ReactNode
11
+ layout?: AuthLayout
12
+ tagline?: string
13
+ description?: string
14
+ showOAuth?: boolean
15
+ oauthProviders?: Exclude<AuthProvider, 'email'>[]
16
+ resetPasswordUrl?: string
17
+ onSuccess?: () => void
18
+ }
19
+
20
+ export function LoginPage({
21
+ appName = 'App',
22
+ logo,
23
+ loginLogo,
24
+ layout = 'split',
25
+ tagline,
26
+ description,
27
+ showOAuth,
28
+ oauthProviders,
29
+ resetPasswordUrl,
30
+ onSuccess,
31
+ }: LoginPageProps) {
32
+ const [view, setView] = React.useState<AuthFormView>('login')
33
+ const brandLogo = loginLogo ?? logo
34
+ const heading = view === 'login' ? 'Welcome back' : view === 'signup' ? 'Create account' : 'Reset password'
35
+ const subtitle =
36
+ view === 'login'
37
+ ? 'Enter your credentials to continue'
38
+ : view === 'signup'
39
+ ? 'Create your account to get started'
40
+ : 'Enter your email and we will send reset instructions'
41
+ const form =
42
+ view === 'login' ? (
43
+ <LoginForm
44
+ onSuccess={onSuccess}
45
+ onForgotPassword={() => setView('recovery')}
46
+ showOAuth={showOAuth}
47
+ oauthProviders={oauthProviders}
48
+ />
49
+ ) : view === 'signup' ? (
50
+ <SignupForm onSuccess={onSuccess} />
51
+ ) : (
52
+ <ForgotPasswordForm redirectTo={resetPasswordUrl} onBackToLogin={() => setView('login')} />
53
+ )
54
+ const switcher =
55
+ view === 'login' ? (
56
+ <p className="text-center text-sm text-muted-foreground">
57
+ Do not have an account?{' '}
58
+ <button type="button" className="font-medium text-primary hover:underline" onClick={() => setView('signup')}>
59
+ Sign up
60
+ </button>
61
+ </p>
62
+ ) : (
63
+ <p className="text-center text-sm text-muted-foreground">
64
+ {view === 'signup' ? 'Already have an account?' : 'Remembered your password?'}{' '}
65
+ <button type="button" className="font-medium text-primary hover:underline" onClick={() => setView('login')}>
66
+ Sign in
67
+ </button>
68
+ </p>
69
+ )
70
+
71
+ if (layout === 'centered') {
72
+ return (
73
+ <div className="flex min-h-screen items-center justify-center bg-sidebar p-4">
74
+ <div className="w-full max-w-md rounded-2xl border border-border/50 bg-card p-8 shadow-2xl sm:p-10">
75
+ <div className="mb-8 text-center">
76
+ {brandLogo && <div className="mb-4 flex justify-center">{brandLogo}</div>}
77
+ <h1 className="text-2xl font-bold text-foreground">{heading}</h1>
78
+ <p className="mt-1 text-sm text-muted-foreground">{subtitle}</p>
79
+ </div>
80
+ <div className="space-y-6">
81
+ {form}
82
+ {switcher}
83
+ </div>
84
+ </div>
85
+ </div>
86
+ )
87
+ }
88
+
89
+ return (
90
+ <div className="flex min-h-screen bg-sidebar">
91
+ <div className="relative hidden w-1/2 overflow-hidden lg:block">
92
+ <div className="absolute inset-0 bg-gradient-to-br from-sidebar-accent/50 via-transparent to-sidebar/80" />
93
+ <div className="relative flex h-full flex-col justify-end p-12">
94
+ <div className="space-y-4">
95
+ <p className="text-sm font-medium text-sidebar-muted">{appName}</p>
96
+ <h2 className="text-3xl font-bold text-sidebar-foreground">{tagline ?? `Welcome to ${appName}`}</h2>
97
+ {description && <p className="max-w-md text-sm leading-relaxed text-sidebar-muted">{description}</p>}
98
+ </div>
99
+ </div>
100
+ </div>
101
+ <div className="flex flex-1 flex-col lg:p-3 lg:pl-3">
102
+ <div className="flex flex-1 flex-col justify-center bg-background px-6 py-12 lg:rounded-[1.25rem] lg:px-16">
103
+ <div className="mx-auto w-full max-w-sm">
104
+ {brandLogo && <div className="mb-8">{brandLogo}</div>}
105
+ <div className="mb-8">
106
+ <h1 className="text-2xl font-bold text-foreground">{heading}</h1>
107
+ <p className="mt-1 text-sm text-muted-foreground">{subtitle}</p>
108
+ </div>
109
+ <div className="space-y-6">
110
+ {form}
111
+ {switcher}
112
+ </div>
113
+ </div>
114
+ </div>
115
+ </div>
116
+ </div>
117
+ )
118
+ }
119
+
120
+ export interface AuthGateProps extends LoginPageProps {
121
+ requireAuth?: boolean
122
+ loadingFallback?: React.ReactNode
123
+ children: React.ReactNode
124
+ }
125
+
126
+ export function AuthGate({ requireAuth = true, loadingFallback, children, ...loginProps }: AuthGateProps) {
127
+ const { isAuthenticated, isLoading } = useAuth()
128
+ if (!requireAuth || isAuthenticated) return <>{children}</>
129
+ if (isLoading) {
130
+ return (
131
+ <>
132
+ {loadingFallback ?? (
133
+ <div className="flex min-h-screen items-center justify-center bg-background">
134
+ <span className="text-sm text-muted-foreground">Loading...</span>
135
+ </div>
136
+ )}
137
+ </>
138
+ )
139
+ }
140
+ return <LoginPage {...loginProps} />
141
+ }
142
+
143
+ export interface AuthCallbackPageProps {
144
+ onSuccess?: () => void
145
+ onError?: (error: Error) => void
146
+ }
147
+
148
+ export function AuthCallbackPage({ onSuccess, onError }: AuthCallbackPageProps) {
149
+ const { handleCallback } = useAuth()
150
+ const [error, setError] = React.useState<string | null>(null)
151
+
152
+ React.useEffect(() => {
153
+ let cancelled = false
154
+ handleCallback(typeof window === 'undefined' ? undefined : window.location.href)
155
+ .then(() => {
156
+ if (!cancelled) onSuccess?.()
157
+ })
158
+ .catch((err: unknown) => {
159
+ const errorObject = err instanceof Error ? err : new Error(String(err))
160
+ if (!cancelled) {
161
+ setError(errorObject.message)
162
+ onError?.(errorObject)
163
+ }
164
+ })
165
+ return () => {
166
+ cancelled = true
167
+ }
168
+ }, [handleCallback, onError, onSuccess])
169
+
170
+ return (
171
+ <div className="flex min-h-screen items-center justify-center bg-background p-6 text-center">
172
+ <div>
173
+ <h1 className="text-xl font-semibold text-foreground">{error ? 'Authentication failed' : 'Completing sign in...'}</h1>
174
+ {error && <p className="mt-2 text-sm text-destructive">{error}</p>}
175
+ </div>
176
+ </div>
177
+ )
178
+ }
179
+
180
+ export interface ResetPasswordPageProps {
181
+ onSuccess?: () => void
182
+ }
183
+
184
+ export function ResetPasswordPage({ onSuccess }: ResetPasswordPageProps) {
185
+ return (
186
+ <div className="flex min-h-screen items-center justify-center bg-background p-4">
187
+ <div className="w-full max-w-md rounded-2xl border bg-card p-8 shadow-xl">
188
+ <div className="mb-6">
189
+ <h1 className="text-2xl font-bold text-foreground">Update password</h1>
190
+ <p className="mt-1 text-sm text-muted-foreground">Choose a new password for your account.</p>
191
+ </div>
192
+ <ResetPasswordForm onSuccess={onSuccess} />
193
+ </div>
194
+ </div>
195
+ )
196
+ }
package/src/runtime.ts ADDED
@@ -0,0 +1,59 @@
1
+ import type { AuthAdapter } from '@fayz-ai/core'
2
+ import { createMockAuthAdapter, createSupabaseAuthAdapter } from '@fayz-ai/auth'
3
+ import type { AuthPluginOptions, ResolvedAuthPlugin } from './types'
4
+
5
+ function absoluteUrl(path: string): string | undefined {
6
+ if (typeof window === 'undefined') return undefined
7
+ if (/^https?:\/\//.test(path)) return path
8
+ return `${window.location.origin}${path.startsWith('/') ? path : `/${path}`}`
9
+ }
10
+
11
+ export function resolveAuthAdapter(options: AuthPluginOptions = {}): AuthAdapter {
12
+ const configured = options.adapter ?? options.provider
13
+ if (configured && typeof configured === 'object') return configured
14
+
15
+ const provider = configured ?? (options.supabase?.url ? 'supabase' : 'mock')
16
+ if (provider === 'supabase') {
17
+ const url = options.supabase?.url
18
+ const anonKey = options.supabase?.anonKey
19
+ if (!url || !anonKey) {
20
+ throw new Error('plugin-auth provider="supabase" requires supabase.url and supabase.anonKey.')
21
+ }
22
+ return createSupabaseAuthAdapter({
23
+ supabaseUrl: url,
24
+ supabaseAnonKey: anonKey,
25
+ supabaseClient: options.supabase?.client,
26
+ callbackUrl: options.supabase?.callbackUrl ?? absoluteUrl(options.routes?.callback ?? '/auth/callback'),
27
+ resetPasswordUrl:
28
+ options.supabase?.resetPasswordUrl ?? absoluteUrl(options.routes?.resetPassword ?? '/auth/reset-password'),
29
+ })
30
+ }
31
+
32
+ return createMockAuthAdapter()
33
+ }
34
+
35
+ export function createAuthRuntime(options: AuthPluginOptions = {}): ResolvedAuthPlugin {
36
+ return {
37
+ kind: 'auth',
38
+ adapter: resolveAuthAdapter(options),
39
+ requireAuth: options.requireAuth ?? true,
40
+ layout: options.layout ?? 'split',
41
+ logo: options.logo,
42
+ tagline: options.tagline,
43
+ description: options.description,
44
+ oauth: {
45
+ enabled: options.oauth?.enabled ?? false,
46
+ providers: options.oauth?.providers ?? ['google'],
47
+ },
48
+ routes: {
49
+ login: options.routes?.login ?? '/login',
50
+ callback: options.routes?.callback ?? '/auth/callback',
51
+ resetPassword: options.routes?.resetPassword ?? '/auth/reset-password',
52
+ afterSignIn: options.routes?.afterSignIn ?? '/',
53
+ },
54
+ }
55
+ }
56
+
57
+ export function createAuthPlugin(options: AuthPluginOptions = {}): ResolvedAuthPlugin {
58
+ return createAuthRuntime(options)
59
+ }
package/src/types.ts ADDED
@@ -0,0 +1,53 @@
1
+ import type React from 'react'
2
+ import type { AuthAdapter, AuthProvider } from '@fayz-ai/core'
3
+ import type { SupabaseAuthConfig } from '@fayz-ai/auth'
4
+
5
+ export type AuthProviderKey = 'supabase' | 'mock' | (string & {})
6
+ export type AuthLayout = 'split' | 'centered'
7
+
8
+ export interface AuthRoutesConfig {
9
+ login?: string
10
+ callback?: string
11
+ resetPassword?: string
12
+ afterSignIn?: string
13
+ }
14
+
15
+ export interface AuthOAuthConfig {
16
+ enabled?: boolean
17
+ providers?: Exclude<AuthProvider, 'email'>[]
18
+ }
19
+
20
+ export interface AuthSupabaseConfig {
21
+ url?: string
22
+ anonKey?: string
23
+ client?: SupabaseAuthConfig['supabaseClient']
24
+ callbackUrl?: string
25
+ resetPasswordUrl?: string
26
+ }
27
+
28
+ export interface AuthPluginOptions {
29
+ provider?: AuthProviderKey | AuthAdapter
30
+ adapter?: AuthProviderKey | AuthAdapter
31
+ supabase?: AuthSupabaseConfig
32
+ requireAuth?: boolean
33
+ layout?: AuthLayout
34
+ logo?: React.ReactNode
35
+ tagline?: string
36
+ description?: string
37
+ oauth?: AuthOAuthConfig
38
+ routes?: AuthRoutesConfig
39
+ }
40
+
41
+ export interface ResolvedAuthPlugin {
42
+ kind: 'auth'
43
+ adapter: AuthAdapter
44
+ requireAuth: boolean
45
+ layout: AuthLayout
46
+ logo?: React.ReactNode
47
+ tagline?: string
48
+ description?: string
49
+ oauth: Required<AuthOAuthConfig>
50
+ routes: Required<AuthRoutesConfig>
51
+ }
52
+
53
+ export type AuthFormView = 'login' | 'signup' | 'recovery'