@fayz-ai/plugin-auth 0.9.0 → 0.9.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/dist/{AmbassadorBubbles.d.ts → components/AmbassadorBubbles.d.ts} +1 -1
- package/dist/components/AmbassadorBubbles.d.ts.map +1 -0
- package/dist/components/forms.d.ts.map +1 -0
- package/dist/index.d.ts +9 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/{runtime.d.ts → lib/runtime.d.ts} +1 -1
- package/dist/lib/runtime.d.ts.map +1 -0
- package/dist/{pages.d.ts → views/pages.d.ts} +1 -1
- package/dist/views/pages.d.ts.map +1 -0
- package/dist/views/set-password.d.ts.map +1 -0
- package/dist/website/components/AuthModalProvider.d.ts.map +1 -0
- package/dist/website/components/PhoneAuthModal.d.ts.map +1 -0
- package/dist/website/index.d.ts +5 -5
- package/dist/website/index.d.ts.map +1 -1
- package/dist/website/index.js +1 -1
- package/dist/website/index.js.map +1 -1
- package/dist/website/lib/phone.d.ts.map +1 -0
- package/package.json +5 -8
- package/dist/AmbassadorBubbles.d.ts.map +0 -1
- package/dist/forms.d.ts.map +0 -1
- package/dist/pages.d.ts.map +0 -1
- package/dist/runtime.d.ts.map +0 -1
- package/dist/set-password.d.ts.map +0 -1
- package/dist/website/AuthModalProvider.d.ts.map +0 -1
- package/dist/website/PhoneAuthModal.d.ts.map +0 -1
- package/dist/website/phone.d.ts.map +0 -1
- package/src/AmbassadorBubbles.tsx +0 -61
- package/src/forms.tsx +0 -307
- package/src/index.ts +0 -56
- package/src/pages.tsx +0 -209
- package/src/runtime.ts +0 -67
- package/src/set-password.tsx +0 -229
- package/src/types.ts +0 -65
- package/src/website/AuthModalProvider.tsx +0 -48
- package/src/website/PhoneAuthModal.tsx +0 -178
- package/src/website/index.tsx +0 -17
- package/src/website/phone.ts +0 -12
- /package/dist/{forms.d.ts → components/forms.d.ts} +0 -0
- /package/dist/{set-password.d.ts → views/set-password.d.ts} +0 -0
- /package/dist/website/{AuthModalProvider.d.ts → components/AuthModalProvider.d.ts} +0 -0
- /package/dist/website/{PhoneAuthModal.d.ts → components/PhoneAuthModal.d.ts} +0 -0
- /package/dist/website/{phone.d.ts → lib/phone.d.ts} +0 -0
package/dist/pages.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../src/pages.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAIjD,OAAO,KAAK,EAAgB,UAAU,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAExE,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC3B,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;yFACqF;IACrF,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAA;IACpC,mFAAmF;IACnF,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,EAAE,CAAA;IACjD,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,wBAAgB,SAAS,CAAC,EACxB,OAAe,EACf,IAAI,EACJ,SAAS,EACT,MAAgB,EAChB,OAAO,EACP,WAAW,EACX,gBAAgB,EAChB,qBAAqB,EACrB,SAAS,EACT,cAAc,EACd,gBAAgB,EAChB,SAAS,GACV,EAAE,cAAc,qBAmGhB;AAED,MAAM,WAAW,aAAc,SAAQ,cAAc;IACnD,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,eAAe,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACjC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAED,wBAAgB,QAAQ,CAAC,EAAE,WAAkB,EAAE,eAAe,EAAE,QAAQ,EAAE,GAAG,UAAU,EAAE,EAAE,aAAa,qBAevG;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;CACjC;AAED,wBAAgB,gBAAgB,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,qBAAqB,qBA8B7E;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED;gFACgF;AAChF,wBAAgB,iBAAiB,CAAC,EAAE,SAAS,EAAE,EAAE,sBAAsB,qBAEtE"}
|
package/dist/runtime.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAEhD,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAQpE,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,iBAAsB,GAAG,WAAW,CA0B/E;AAED,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,iBAAsB,GAAG,kBAAkB,CAwBrF;AAED,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,iBAAsB,GAAG,kBAAkB,CAEpF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"set-password.d.ts","sourceRoot":"","sources":["../src/set-password.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAqB9B,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,QAAQ,CAAA;AAuBrD,MAAM,WAAW,iBAAiB;IAChC,yEAAyE;IACzE,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAA;IAChC,6EAA6E;IAC7E,OAAO,EAAE,OAAO,CAAA;IAChB,oDAAoD;IACpD,QAAQ,EAAE,OAAO,CAAA;IACjB,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,IAAI,CAAA;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,IAAI,iBAAiB,CAmBxD;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,iBAAiB,CAAA;IACzB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,yEAAyE;IACzE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,OAAe,EACf,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,SAAS,GACV,EAAE,oBAAoB,qBA+HtB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AuthModalProvider.d.ts","sourceRoot":"","sources":["../../src/website/AuthModalProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAC3E,OAAO,EAAkB,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAE5E,UAAU,qBAAqB;IAC7B,aAAa,EAAE,MAAM,IAAI,CAAA;IACzB,cAAc,EAAE,MAAM,IAAI,CAAA;IAC1B,eAAe,EAAE,OAAO,CAAA;CACzB;AAID,MAAM,WAAW,sBAAsB;IACrC,sEAAsE;IACtE,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,MAAM,CAAC,EAAE,oBAAoB,CAAA;IAC7B,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,SAAgB,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,sBAAsB,+BAa/F;AAED,wFAAwF;AACxF,wBAAgB,YAAY,IAAI,qBAAqB,CAQpD"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PhoneAuthModal.d.ts","sourceRoot":"","sources":["../../src/website/PhoneAuthModal.tsx"],"names":[],"mappings":"AAOA,MAAM,WAAW,oBAAoB;IACnC,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAID;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EAAE,OAAO,EAAE,MAAM,GACtB,EAAE;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,oBAAoB,CAAA;CAAE,+BAyJvE"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"phone.d.ts","sourceRoot":"","sources":["../../src/website/phone.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAG1D"}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import * as React from 'react'
|
|
2
|
-
import type { LoginAmbassador } from './types'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Social-proof "ambassador" strip for the split-login brand panel. Restores
|
|
6
|
-
* (modernized) the old login avatar cluster: a short row of small, softly
|
|
7
|
-
* overlapping circular portraits sitting just above the tagline, optionally
|
|
8
|
-
* followed by a microtext label ("+2 mil profissionais").
|
|
9
|
-
*
|
|
10
|
-
* Small (40px), subtle, no flashy motion — a very gentle idle float only, which
|
|
11
|
-
* fully disables under prefers-reduced-motion. Degrades to nothing when no
|
|
12
|
-
* usable images are supplied, so it is always safe to render. Purely
|
|
13
|
-
* presentational — never touches the auth flow.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const FLOAT_STYLES = `
|
|
17
|
-
@keyframes fayz-amb-drift {
|
|
18
|
-
0%, 100% { transform: translateY(0); }
|
|
19
|
-
50% { transform: translateY(-2px); }
|
|
20
|
-
}
|
|
21
|
-
@media (prefers-reduced-motion: reduce) {
|
|
22
|
-
.fayz-amb-avatar { animation: none !important; }
|
|
23
|
-
}
|
|
24
|
-
`
|
|
25
|
-
|
|
26
|
-
export interface AmbassadorBubblesProps {
|
|
27
|
-
ambassadors?: LoginAmbassador[]
|
|
28
|
-
/** Optional social-proof caption shown beside the avatars, e.g. "+2 mil profissionais". */
|
|
29
|
-
label?: string
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function AmbassadorBubbles({ ambassadors, label }: AmbassadorBubblesProps) {
|
|
33
|
-
const items = (ambassadors ?? []).filter((a) => a && typeof a.image === 'string' && a.image.trim() !== '')
|
|
34
|
-
if (items.length === 0) return null
|
|
35
|
-
const shown = items.slice(0, 5)
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<div className="flex items-center gap-3">
|
|
39
|
-
<style>{FLOAT_STYLES}</style>
|
|
40
|
-
<div className="flex -space-x-2">
|
|
41
|
-
{shown.map((ambassador, index) => (
|
|
42
|
-
<div
|
|
43
|
-
key={`${ambassador.name}-${index}`}
|
|
44
|
-
title={ambassador.role ? `${ambassador.name} · ${ambassador.role}` : ambassador.name}
|
|
45
|
-
className="fayz-amb-avatar relative h-10 w-10 shrink-0 overflow-hidden rounded-full border-2 border-sidebar bg-sidebar-accent shadow-md ring-1 ring-sidebar-foreground/10 transition-transform duration-200 hover:z-10 hover:scale-110"
|
|
46
|
-
style={{ animation: `fayz-amb-drift 4s ease-in-out ${index * 0.4}s infinite` }}
|
|
47
|
-
>
|
|
48
|
-
<img
|
|
49
|
-
src={ambassador.image}
|
|
50
|
-
alt={ambassador.name}
|
|
51
|
-
className="h-full w-full object-cover"
|
|
52
|
-
loading="lazy"
|
|
53
|
-
draggable={false}
|
|
54
|
-
/>
|
|
55
|
-
</div>
|
|
56
|
-
))}
|
|
57
|
-
</div>
|
|
58
|
-
{label ? <p className="text-xs font-medium text-sidebar-foreground/80">{label}</p> : null}
|
|
59
|
-
</div>
|
|
60
|
-
)
|
|
61
|
-
}
|
package/src/forms.tsx
DELETED
|
@@ -1,307 +0,0 @@
|
|
|
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
|
-
/** Seeds the field, so switching between login and recovery never asks for the
|
|
54
|
-
* e-mail the user already typed. */
|
|
55
|
-
initialEmail?: string
|
|
56
|
-
onEmailChange?: (email: string) => void
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function LoginForm({
|
|
60
|
-
onSuccess,
|
|
61
|
-
onForgotPassword,
|
|
62
|
-
showOAuth,
|
|
63
|
-
oauthProviders,
|
|
64
|
-
initialEmail = '',
|
|
65
|
-
onEmailChange,
|
|
66
|
-
}: LoginFormProps) {
|
|
67
|
-
const { signIn, error, isLoading } = useAuth()
|
|
68
|
-
const [email, setEmail] = React.useState(initialEmail)
|
|
69
|
-
const [password, setPassword] = React.useState('')
|
|
70
|
-
const [busy, setBusy] = React.useState(false)
|
|
71
|
-
const disabled = busy || isLoading
|
|
72
|
-
|
|
73
|
-
async function submit(event: React.FormEvent) {
|
|
74
|
-
event.preventDefault()
|
|
75
|
-
setBusy(true)
|
|
76
|
-
try {
|
|
77
|
-
await signIn(email, password)
|
|
78
|
-
onSuccess?.()
|
|
79
|
-
} finally {
|
|
80
|
-
setBusy(false)
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return (
|
|
85
|
-
<form onSubmit={submit} className="space-y-4">
|
|
86
|
-
<label className="block space-y-1.5 text-sm font-medium">
|
|
87
|
-
<span>Email</span>
|
|
88
|
-
<input
|
|
89
|
-
className={fieldClass()}
|
|
90
|
-
type="email"
|
|
91
|
-
autoComplete="username"
|
|
92
|
-
required
|
|
93
|
-
value={email}
|
|
94
|
-
onChange={(e) => {
|
|
95
|
-
setEmail(e.target.value)
|
|
96
|
-
onEmailChange?.(e.target.value)
|
|
97
|
-
}}
|
|
98
|
-
disabled={disabled}
|
|
99
|
-
/>
|
|
100
|
-
</label>
|
|
101
|
-
<label className="block space-y-1.5 text-sm font-medium">
|
|
102
|
-
<span>Password</span>
|
|
103
|
-
<input className={fieldClass()} type="password" autoComplete="current-password" required value={password} onChange={(e) => setPassword(e.target.value)} disabled={disabled} />
|
|
104
|
-
</label>
|
|
105
|
-
{onForgotPassword && (
|
|
106
|
-
<div className="-mt-1 flex justify-end">
|
|
107
|
-
<button type="button" className={buttonClass('ghost')} onClick={onForgotPassword}>
|
|
108
|
-
Forgot password?
|
|
109
|
-
</button>
|
|
110
|
-
</div>
|
|
111
|
-
)}
|
|
112
|
-
{error && <p className="text-sm text-destructive">{error.message}</p>}
|
|
113
|
-
<button className={buttonClass()} type="submit" disabled={disabled}>
|
|
114
|
-
{disabled ? 'Signing in...' : 'Sign in'}
|
|
115
|
-
</button>
|
|
116
|
-
{showOAuth && <OAuthButtons providers={oauthProviders} disabled={disabled} />}
|
|
117
|
-
</form>
|
|
118
|
-
)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export interface SignupFormProps {
|
|
122
|
-
onSuccess?: () => void
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function SignupForm({ onSuccess }: SignupFormProps) {
|
|
126
|
-
const { signUp, error, isLoading } = useAuth()
|
|
127
|
-
const [fullName, setFullName] = React.useState('')
|
|
128
|
-
const [email, setEmail] = React.useState('')
|
|
129
|
-
const [password, setPassword] = React.useState('')
|
|
130
|
-
const [confirmPassword, setConfirmPassword] = React.useState('')
|
|
131
|
-
const [localError, setLocalError] = React.useState<string | null>(null)
|
|
132
|
-
const [busy, setBusy] = React.useState(false)
|
|
133
|
-
const disabled = busy || isLoading
|
|
134
|
-
|
|
135
|
-
async function submit(event: React.FormEvent) {
|
|
136
|
-
event.preventDefault()
|
|
137
|
-
setLocalError(null)
|
|
138
|
-
if (password.length < 8) {
|
|
139
|
-
setLocalError('Password must be at least 8 characters.')
|
|
140
|
-
return
|
|
141
|
-
}
|
|
142
|
-
if (password !== confirmPassword) {
|
|
143
|
-
setLocalError('Passwords do not match.')
|
|
144
|
-
return
|
|
145
|
-
}
|
|
146
|
-
setBusy(true)
|
|
147
|
-
try {
|
|
148
|
-
await signUp(email, password, fullName)
|
|
149
|
-
onSuccess?.()
|
|
150
|
-
} finally {
|
|
151
|
-
setBusy(false)
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return (
|
|
156
|
-
<form onSubmit={submit} className="space-y-4">
|
|
157
|
-
<label className="block space-y-1.5 text-sm font-medium">
|
|
158
|
-
<span>Full name</span>
|
|
159
|
-
<input className={fieldClass()} required value={fullName} onChange={(e) => setFullName(e.target.value)} disabled={disabled} />
|
|
160
|
-
</label>
|
|
161
|
-
<label className="block space-y-1.5 text-sm font-medium">
|
|
162
|
-
<span>Email</span>
|
|
163
|
-
<input className={fieldClass()} type="email" autoComplete="username" required value={email} onChange={(e) => setEmail(e.target.value)} disabled={disabled} />
|
|
164
|
-
</label>
|
|
165
|
-
<label className="block space-y-1.5 text-sm font-medium">
|
|
166
|
-
<span>Password</span>
|
|
167
|
-
<input className={fieldClass()} type="password" autoComplete="new-password" required value={password} onChange={(e) => setPassword(e.target.value)} disabled={disabled} />
|
|
168
|
-
</label>
|
|
169
|
-
<label className="block space-y-1.5 text-sm font-medium">
|
|
170
|
-
<span>Confirm password</span>
|
|
171
|
-
<input className={fieldClass()} type="password" autoComplete="new-password" required value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} disabled={disabled} />
|
|
172
|
-
</label>
|
|
173
|
-
{(localError || error) && <p className="text-sm text-destructive">{localError ?? error?.message}</p>}
|
|
174
|
-
<button className={buttonClass()} type="submit" disabled={disabled}>
|
|
175
|
-
{disabled ? 'Creating account...' : 'Create account'}
|
|
176
|
-
</button>
|
|
177
|
-
</form>
|
|
178
|
-
)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export interface ForgotPasswordFormProps {
|
|
182
|
-
redirectTo?: string
|
|
183
|
-
onSent?: (email: string) => void
|
|
184
|
-
onBackToLogin?: () => void
|
|
185
|
-
/** Pre-filled with whatever the user already typed on the login form. */
|
|
186
|
-
initialEmail?: string
|
|
187
|
-
onEmailChange?: (email: string) => void
|
|
188
|
-
/** Copy overrides — the set-password screen reuses this form to hand out a
|
|
189
|
-
* fresh link, where "Send reset link" is the wrong label. */
|
|
190
|
-
emailLabel?: string
|
|
191
|
-
submitLabel?: string
|
|
192
|
-
sendingLabel?: string
|
|
193
|
-
sentLabel?: string
|
|
194
|
-
backLabel?: string
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
export function ForgotPasswordForm({
|
|
198
|
-
redirectTo,
|
|
199
|
-
onSent,
|
|
200
|
-
onBackToLogin,
|
|
201
|
-
initialEmail = '',
|
|
202
|
-
onEmailChange,
|
|
203
|
-
emailLabel = 'Email',
|
|
204
|
-
submitLabel = 'Send reset link',
|
|
205
|
-
sendingLabel = 'Sending...',
|
|
206
|
-
sentLabel = 'Password reset instructions sent.',
|
|
207
|
-
backLabel = 'Back to login',
|
|
208
|
-
}: ForgotPasswordFormProps) {
|
|
209
|
-
const { resetPassword, error, isLoading } = useAuth()
|
|
210
|
-
const [email, setEmail] = React.useState(initialEmail)
|
|
211
|
-
const [sent, setSent] = React.useState(false)
|
|
212
|
-
const [busy, setBusy] = React.useState(false)
|
|
213
|
-
const disabled = busy || isLoading
|
|
214
|
-
|
|
215
|
-
async function submit(event: React.FormEvent) {
|
|
216
|
-
event.preventDefault()
|
|
217
|
-
setBusy(true)
|
|
218
|
-
try {
|
|
219
|
-
await resetPassword(email, { redirectTo })
|
|
220
|
-
setSent(true)
|
|
221
|
-
onSent?.(email)
|
|
222
|
-
} finally {
|
|
223
|
-
setBusy(false)
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return (
|
|
228
|
-
<form onSubmit={submit} className="space-y-4">
|
|
229
|
-
<label className="block space-y-1.5 text-sm font-medium">
|
|
230
|
-
<span>{emailLabel}</span>
|
|
231
|
-
<input
|
|
232
|
-
className={fieldClass()}
|
|
233
|
-
type="email"
|
|
234
|
-
autoComplete="username"
|
|
235
|
-
required
|
|
236
|
-
autoFocus={!email}
|
|
237
|
-
value={email}
|
|
238
|
-
onChange={(e) => {
|
|
239
|
-
setEmail(e.target.value)
|
|
240
|
-
onEmailChange?.(e.target.value)
|
|
241
|
-
}}
|
|
242
|
-
disabled={disabled}
|
|
243
|
-
/>
|
|
244
|
-
</label>
|
|
245
|
-
{error && <p className="text-sm text-destructive">{error.message}</p>}
|
|
246
|
-
{sent && <p className="text-sm text-emerald-600">{sentLabel}</p>}
|
|
247
|
-
<button className={buttonClass()} type="submit" disabled={disabled}>
|
|
248
|
-
{disabled ? sendingLabel : submitLabel}
|
|
249
|
-
</button>
|
|
250
|
-
{onBackToLogin && (
|
|
251
|
-
<button type="button" className={buttonClass('ghost')} onClick={onBackToLogin}>
|
|
252
|
-
{backLabel}
|
|
253
|
-
</button>
|
|
254
|
-
)}
|
|
255
|
-
</form>
|
|
256
|
-
)
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
export interface ResetPasswordFormProps {
|
|
260
|
-
onSuccess?: () => void
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
export function ResetPasswordForm({ onSuccess }: ResetPasswordFormProps) {
|
|
264
|
-
const { updatePassword, error, isLoading } = useAuth()
|
|
265
|
-
const [password, setPassword] = React.useState('')
|
|
266
|
-
const [confirmPassword, setConfirmPassword] = React.useState('')
|
|
267
|
-
const [localError, setLocalError] = React.useState<string | null>(null)
|
|
268
|
-
const [busy, setBusy] = React.useState(false)
|
|
269
|
-
const disabled = busy || isLoading
|
|
270
|
-
|
|
271
|
-
async function submit(event: React.FormEvent) {
|
|
272
|
-
event.preventDefault()
|
|
273
|
-
setLocalError(null)
|
|
274
|
-
if (password.length < 8) {
|
|
275
|
-
setLocalError('Password must be at least 8 characters.')
|
|
276
|
-
return
|
|
277
|
-
}
|
|
278
|
-
if (password !== confirmPassword) {
|
|
279
|
-
setLocalError('Passwords do not match.')
|
|
280
|
-
return
|
|
281
|
-
}
|
|
282
|
-
setBusy(true)
|
|
283
|
-
try {
|
|
284
|
-
await updatePassword(password)
|
|
285
|
-
onSuccess?.()
|
|
286
|
-
} finally {
|
|
287
|
-
setBusy(false)
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
return (
|
|
292
|
-
<form onSubmit={submit} className="space-y-4">
|
|
293
|
-
<label className="block space-y-1.5 text-sm font-medium">
|
|
294
|
-
<span>New password</span>
|
|
295
|
-
<input className={fieldClass()} type="password" autoComplete="new-password" required value={password} onChange={(e) => setPassword(e.target.value)} disabled={disabled} />
|
|
296
|
-
</label>
|
|
297
|
-
<label className="block space-y-1.5 text-sm font-medium">
|
|
298
|
-
<span>Confirm password</span>
|
|
299
|
-
<input className={fieldClass()} type="password" autoComplete="new-password" required value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} disabled={disabled} />
|
|
300
|
-
</label>
|
|
301
|
-
{(localError || error) && <p className="text-sm text-destructive">{localError ?? error?.message}</p>}
|
|
302
|
-
<button className={buttonClass()} type="submit" disabled={disabled}>
|
|
303
|
-
{disabled ? 'Saving...' : 'Update password'}
|
|
304
|
-
</button>
|
|
305
|
-
</form>
|
|
306
|
-
)
|
|
307
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
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 { SetPasswordPage, usePasswordSetupGate } from './set-password'
|
|
19
|
-
export type { SetPasswordPageProps, SetPasswordReason, PasswordSetupGate } from './set-password'
|
|
20
|
-
export { AmbassadorBubbles } from './AmbassadorBubbles'
|
|
21
|
-
export type { AmbassadorBubblesProps } from './AmbassadorBubbles'
|
|
22
|
-
export {
|
|
23
|
-
LoginForm,
|
|
24
|
-
SignupForm,
|
|
25
|
-
ForgotPasswordForm,
|
|
26
|
-
ResetPasswordForm,
|
|
27
|
-
OAuthButtons,
|
|
28
|
-
} from './forms'
|
|
29
|
-
export type {
|
|
30
|
-
ForgotPasswordFormProps,
|
|
31
|
-
LoginFormProps,
|
|
32
|
-
OAuthButtonsProps,
|
|
33
|
-
ResetPasswordFormProps,
|
|
34
|
-
SignupFormProps,
|
|
35
|
-
} from './forms'
|
|
36
|
-
export {
|
|
37
|
-
createMockAuthAdapter,
|
|
38
|
-
createSupabaseAuthAdapter,
|
|
39
|
-
AuthProvider,
|
|
40
|
-
useAuth,
|
|
41
|
-
useAuthStore,
|
|
42
|
-
readAuthUrlIntent,
|
|
43
|
-
clearAuthUrlTokens,
|
|
44
|
-
} from '@fayz-ai/auth'
|
|
45
|
-
export type {
|
|
46
|
-
AuthPluginOptions,
|
|
47
|
-
ResolvedAuthPlugin,
|
|
48
|
-
AuthRoutesConfig,
|
|
49
|
-
AuthOAuthConfig,
|
|
50
|
-
AuthSupabaseConfig,
|
|
51
|
-
AuthProviderKey,
|
|
52
|
-
AuthLayout,
|
|
53
|
-
AuthFormView,
|
|
54
|
-
LoginAmbassador,
|
|
55
|
-
} from './types'
|
|
56
|
-
export type { AuthAdapter, AuthSession, AuthUser, AuthProvider as OAuthProvider } from '@fayz-ai/core'
|
package/src/pages.tsx
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
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, SignupForm } from './forms'
|
|
5
|
-
import { SetPasswordPage } from './set-password'
|
|
6
|
-
import { AmbassadorBubbles } from './AmbassadorBubbles'
|
|
7
|
-
import type { AuthFormView, AuthLayout, LoginAmbassador } from './types'
|
|
8
|
-
|
|
9
|
-
export interface LoginPageProps {
|
|
10
|
-
appName?: string
|
|
11
|
-
logo?: React.ReactNode
|
|
12
|
-
loginLogo?: React.ReactNode
|
|
13
|
-
layout?: AuthLayout
|
|
14
|
-
tagline?: string
|
|
15
|
-
description?: string
|
|
16
|
-
/** Product ambassadors shown as a small social-proof avatar strip above the
|
|
17
|
-
* tagline on the split-layout brand panel. Renders nothing when empty/imageless. */
|
|
18
|
-
loginAmbassadors?: LoginAmbassador[]
|
|
19
|
-
/** Optional microtext beside the ambassador strip, e.g. "+2 mil profissionais". */
|
|
20
|
-
loginAmbassadorsLabel?: string
|
|
21
|
-
showOAuth?: boolean
|
|
22
|
-
oauthProviders?: Exclude<AuthProvider, 'email'>[]
|
|
23
|
-
resetPasswordUrl?: string
|
|
24
|
-
onSuccess?: () => void
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function LoginPage({
|
|
28
|
-
appName = 'App',
|
|
29
|
-
logo,
|
|
30
|
-
loginLogo,
|
|
31
|
-
layout = 'split',
|
|
32
|
-
tagline,
|
|
33
|
-
description,
|
|
34
|
-
loginAmbassadors,
|
|
35
|
-
loginAmbassadorsLabel,
|
|
36
|
-
showOAuth,
|
|
37
|
-
oauthProviders,
|
|
38
|
-
resetPasswordUrl,
|
|
39
|
-
onSuccess,
|
|
40
|
-
}: LoginPageProps) {
|
|
41
|
-
const [view, setView] = React.useState<AuthFormView>('login')
|
|
42
|
-
// Lifted out of the forms so switching view carries the e-mail across: someone
|
|
43
|
-
// who typed it and then remembered they forgot the password should not have to
|
|
44
|
-
// type it again (and back on the login form it is still there).
|
|
45
|
-
const [email, setEmail] = React.useState('')
|
|
46
|
-
const brandLogo = loginLogo ?? logo
|
|
47
|
-
const heading = view === 'login' ? 'Welcome back' : view === 'signup' ? 'Create account' : 'Reset password'
|
|
48
|
-
const subtitle =
|
|
49
|
-
view === 'login'
|
|
50
|
-
? 'Enter your credentials to continue'
|
|
51
|
-
: view === 'signup'
|
|
52
|
-
? 'Create your account to get started'
|
|
53
|
-
: 'Enter your email and we will send reset instructions'
|
|
54
|
-
const form =
|
|
55
|
-
view === 'login' ? (
|
|
56
|
-
<LoginForm
|
|
57
|
-
onSuccess={onSuccess}
|
|
58
|
-
onForgotPassword={() => setView('recovery')}
|
|
59
|
-
showOAuth={showOAuth}
|
|
60
|
-
oauthProviders={oauthProviders}
|
|
61
|
-
initialEmail={email}
|
|
62
|
-
onEmailChange={setEmail}
|
|
63
|
-
/>
|
|
64
|
-
) : view === 'signup' ? (
|
|
65
|
-
<SignupForm onSuccess={onSuccess} />
|
|
66
|
-
) : (
|
|
67
|
-
<ForgotPasswordForm
|
|
68
|
-
redirectTo={resetPasswordUrl}
|
|
69
|
-
onBackToLogin={() => setView('login')}
|
|
70
|
-
initialEmail={email}
|
|
71
|
-
onEmailChange={setEmail}
|
|
72
|
-
/>
|
|
73
|
-
)
|
|
74
|
-
const switcher =
|
|
75
|
-
view === 'login' ? (
|
|
76
|
-
<p className="text-center text-sm text-muted-foreground">
|
|
77
|
-
Do not have an account?{' '}
|
|
78
|
-
<button type="button" className="font-medium text-primary hover:underline" onClick={() => setView('signup')}>
|
|
79
|
-
Sign up
|
|
80
|
-
</button>
|
|
81
|
-
</p>
|
|
82
|
-
) : (
|
|
83
|
-
<p className="text-center text-sm text-muted-foreground">
|
|
84
|
-
{view === 'signup' ? 'Already have an account?' : 'Remembered your password?'}{' '}
|
|
85
|
-
<button type="button" className="font-medium text-primary hover:underline" onClick={() => setView('login')}>
|
|
86
|
-
Sign in
|
|
87
|
-
</button>
|
|
88
|
-
</p>
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
if (layout === 'centered') {
|
|
92
|
-
return (
|
|
93
|
-
<div className="flex min-h-screen items-center justify-center bg-sidebar p-4">
|
|
94
|
-
<div className="w-full max-w-md rounded-2xl border border-border/50 bg-card p-8 shadow-2xl sm:p-10">
|
|
95
|
-
<div className="mb-8 text-center">
|
|
96
|
-
{brandLogo && <div className="mb-4 flex justify-center">{brandLogo}</div>}
|
|
97
|
-
<h1 className="text-2xl font-bold text-foreground">{heading}</h1>
|
|
98
|
-
<p className="mt-1 text-sm text-muted-foreground">{subtitle}</p>
|
|
99
|
-
</div>
|
|
100
|
-
<div className="space-y-6">
|
|
101
|
-
{form}
|
|
102
|
-
{switcher}
|
|
103
|
-
</div>
|
|
104
|
-
</div>
|
|
105
|
-
</div>
|
|
106
|
-
)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
<div className="flex min-h-screen bg-sidebar">
|
|
111
|
-
<div className="relative hidden w-1/2 overflow-hidden lg:block">
|
|
112
|
-
<div className="absolute inset-0 bg-gradient-to-br from-sidebar-accent/50 via-transparent to-sidebar/80" />
|
|
113
|
-
<div className="relative z-20 flex h-full flex-col justify-end p-12">
|
|
114
|
-
<div className="space-y-4">
|
|
115
|
-
<AmbassadorBubbles ambassadors={loginAmbassadors} label={loginAmbassadorsLabel} />
|
|
116
|
-
<p className="text-sm font-medium text-sidebar-muted">{appName}</p>
|
|
117
|
-
<h2 className="text-3xl font-bold text-sidebar-foreground">{tagline ?? `Welcome to ${appName}`}</h2>
|
|
118
|
-
{description && <p className="max-w-md text-sm leading-relaxed text-sidebar-muted">{description}</p>}
|
|
119
|
-
</div>
|
|
120
|
-
</div>
|
|
121
|
-
</div>
|
|
122
|
-
<div className="flex flex-1 flex-col lg:p-3 lg:pl-3">
|
|
123
|
-
<div className="flex flex-1 flex-col justify-center bg-background px-6 py-12 lg:rounded-[1.25rem] lg:px-16">
|
|
124
|
-
<div className="mx-auto w-full max-w-sm">
|
|
125
|
-
{brandLogo && <div className="mb-8">{brandLogo}</div>}
|
|
126
|
-
<div className="mb-8">
|
|
127
|
-
<h1 className="text-2xl font-bold text-foreground">{heading}</h1>
|
|
128
|
-
<p className="mt-1 text-sm text-muted-foreground">{subtitle}</p>
|
|
129
|
-
</div>
|
|
130
|
-
<div className="space-y-6">
|
|
131
|
-
{form}
|
|
132
|
-
{switcher}
|
|
133
|
-
</div>
|
|
134
|
-
</div>
|
|
135
|
-
</div>
|
|
136
|
-
</div>
|
|
137
|
-
</div>
|
|
138
|
-
)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export interface AuthGateProps extends LoginPageProps {
|
|
142
|
-
requireAuth?: boolean
|
|
143
|
-
loadingFallback?: React.ReactNode
|
|
144
|
-
children: React.ReactNode
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export function AuthGate({ requireAuth = true, loadingFallback, children, ...loginProps }: AuthGateProps) {
|
|
148
|
-
const { isAuthenticated, isLoading } = useAuth()
|
|
149
|
-
if (!requireAuth || isAuthenticated) return <>{children}</>
|
|
150
|
-
if (isLoading) {
|
|
151
|
-
return (
|
|
152
|
-
<>
|
|
153
|
-
{loadingFallback ?? (
|
|
154
|
-
<div className="flex min-h-screen items-center justify-center bg-background">
|
|
155
|
-
<span className="text-sm text-muted-foreground">Loading...</span>
|
|
156
|
-
</div>
|
|
157
|
-
)}
|
|
158
|
-
</>
|
|
159
|
-
)
|
|
160
|
-
}
|
|
161
|
-
return <LoginPage {...loginProps} />
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export interface AuthCallbackPageProps {
|
|
165
|
-
onSuccess?: () => void
|
|
166
|
-
onError?: (error: Error) => void
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export function AuthCallbackPage({ onSuccess, onError }: AuthCallbackPageProps) {
|
|
170
|
-
const { handleCallback } = useAuth()
|
|
171
|
-
const [error, setError] = React.useState<string | null>(null)
|
|
172
|
-
|
|
173
|
-
React.useEffect(() => {
|
|
174
|
-
let cancelled = false
|
|
175
|
-
handleCallback(typeof window === 'undefined' ? undefined : window.location.href)
|
|
176
|
-
.then(() => {
|
|
177
|
-
if (!cancelled) onSuccess?.()
|
|
178
|
-
})
|
|
179
|
-
.catch((err: unknown) => {
|
|
180
|
-
const errorObject = err instanceof Error ? err : new Error(String(err))
|
|
181
|
-
if (!cancelled) {
|
|
182
|
-
setError(errorObject.message)
|
|
183
|
-
onError?.(errorObject)
|
|
184
|
-
}
|
|
185
|
-
})
|
|
186
|
-
return () => {
|
|
187
|
-
cancelled = true
|
|
188
|
-
}
|
|
189
|
-
}, [handleCallback, onError, onSuccess])
|
|
190
|
-
|
|
191
|
-
return (
|
|
192
|
-
<div className="flex min-h-screen items-center justify-center bg-background p-6 text-center">
|
|
193
|
-
<div>
|
|
194
|
-
<h1 className="text-xl font-semibold text-foreground">{error ? 'Authentication failed' : 'Completing sign in...'}</h1>
|
|
195
|
-
{error && <p className="mt-2 text-sm text-destructive">{error}</p>}
|
|
196
|
-
</div>
|
|
197
|
-
</div>
|
|
198
|
-
)
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
export interface ResetPasswordPageProps {
|
|
202
|
-
onSuccess?: () => void
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/** Kept for apps that route `/auth/reset-password` themselves. The shell no
|
|
206
|
-
* longer needs a route: SetPasswordPage is mounted by usePasswordSetupGate. */
|
|
207
|
-
export function ResetPasswordPage({ onSuccess }: ResetPasswordPageProps) {
|
|
208
|
-
return <SetPasswordPage reason="recovery" onSuccess={onSuccess} />
|
|
209
|
-
}
|