feat: fix webapp build

pull/715/head
Travis Fischer 2025-06-24 22:49:24 -05:00
rodzic 425ce4793c
commit 71aa41e08d
5 zmienionych plików z 428 dodań i 406 usunięć

Wyświetl plik

@ -1,4 +1,5 @@
import { assert } from '@agentic/platform-core'
import { Suspense } from 'react'
import { OAuthSuccessCallback } from './oauth-success-callback'
@ -10,5 +11,9 @@ export default async function Page({
const { provider } = await params
assert(provider, 'Missing provider')
return <OAuthSuccessCallback provider={provider} />
return (
<Suspense>
<OAuthSuccessCallback provider={provider} />
</Suspense>
)
}

Wyświetl plik

@ -0,0 +1,183 @@
'use client'
import { sanitizeSearchParams } from '@agentic/platform-core'
import { isValidEmail, isValidPassword } from '@agentic/platform-validators'
import { useForm } from '@tanstack/react-form'
import { Loader2Icon } from 'lucide-react'
import { redirect, RedirectType } from 'next/navigation'
import { useCallback } from 'react'
import { z } from 'zod'
import {
useNextUrl,
useUnauthenticatedAgentic
} from '@/components/agentic-provider'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { GitHubIcon } from '@/icons/github'
import { toastError } from '@/lib/notifications'
import { cn } from '@/lib/utils'
export function LoginForm() {
const ctx = useUnauthenticatedAgentic()
const nextUrl = useNextUrl()
const onAuthWithGitHub = useCallback(async () => {
const redirectUri = `${globalThis.location.origin}/auth/github/success?${sanitizeSearchParams({ next: nextUrl }).toString()}`
const url = await ctx!.api.initAuthFlowWithGitHub({ redirectUri })
redirect(url, RedirectType.push)
}, [ctx, nextUrl])
const form = useForm({
defaultValues: {
email: '',
password: ''
},
validators: {
onChange: z.object({
email: z
.string()
.email()
.refine((email) => isValidEmail(email)),
password: z.string().refine((password) => isValidPassword(password))
})
},
onSubmit: async ({ value }) => {
try {
const res = await ctx!.api.signInWithPassword({
email: value.email,
password: value.password
})
console.log('login success', res)
} catch (err: any) {
void toastError(err, { label: 'Login error' })
return
}
return redirect(nextUrl || '/app', RedirectType.push)
}
})
return (
<section>
<div className='flex-col flex-1 items-center justify-center w-full max-w-xs'>
<form
className={cn('flex flex-col gap-6 w-full')}
onSubmit={(e) => {
e.preventDefault()
void form.handleSubmit()
}}
>
<div className='flex flex-col items-center gap-2 text-center'>
<h1 className='text-2xl font-bold'>Login to your account</h1>
</div>
<div className='grid gap-6'>
<form.Field
name='email'
children={(field) => (
<div className='grid gap-3'>
<Label htmlFor={field.name}>Email</Label>
<Input
id={field.name}
name={field.name}
type='email'
required
placeholder='Email'
autoComplete='email'
autoFocus={true}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e: any) => field.handleChange(e.target.value)}
/>
</div>
)}
/>
<form.Field
name='password'
children={(field) => (
<div className='grid gap-3'>
<div className='flex items-center'>
<Label htmlFor={field.name}>Password</Label>
{/* <a
href='/forgot-password'
className='ml-auto text-xs underline-offset-4 hover:underline'
tabIndex={-1}
>
Forgot your password?
</a> */}
</div>
<Input
id={field.name}
name={field.name}
type='password'
required
placeholder='Password'
// autoFocus={error?.type === 'invalid_password'}
autoComplete='current-password'
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
</div>
)}
/>
<form.Subscribe
selector={(state) => [
state.canSubmit,
state.isSubmitting,
state.isTouched
]}
children={([canSubmit, isSubmitting, isTouched]) => (
<Button
type='submit'
disabled={!(isTouched && canSubmit && ctx)}
className='w-full'
>
{isSubmitting && <Loader2Icon className='animate-spin' />}
<span>Login</span>
</Button>
)}
/>
<div className='after:border-border relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t'>
<span className='bg-background text-muted-foreground relative z-10 px-2'>
Or continue with
</span>
</div>
<Button
variant='outline'
className='w-full'
onClick={onAuthWithGitHub}
>
<GitHubIcon />
<span>Login with GitHub</span>
</Button>
</div>
<div className='text-center text-xs'>
Don&apos;t have an account?{' '}
<a
href={`/signup?${sanitizeSearchParams({ next: nextUrl }).toString()}`}
className='underline underline-offset-4'
>
Sign up
</a>
</div>
</form>
</div>
</section>
)
}

Wyświetl plik

@ -1,185 +1,11 @@
'use client'
import { Suspense } from 'react'
import { sanitizeSearchParams } from '@agentic/platform-core'
import { isValidEmail, isValidPassword } from '@agentic/platform-validators'
import { useForm } from '@tanstack/react-form'
import { Loader2Icon } from 'lucide-react'
import { redirect, RedirectType } from 'next/navigation'
import { useCallback } from 'react'
import { z } from 'zod'
import {
useNextUrl,
useUnauthenticatedAgentic
} from '@/components/agentic-provider'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { GitHubIcon } from '@/icons/github'
import { toastError } from '@/lib/notifications'
import { cn } from '@/lib/utils'
export default function LoginPage() {
const ctx = useUnauthenticatedAgentic()
const nextUrl = useNextUrl()
const onAuthWithGitHub = useCallback(async () => {
const redirectUri = `${globalThis.location.origin}/auth/github/success?${sanitizeSearchParams({ next: nextUrl }).toString()}`
const url = await ctx!.api.initAuthFlowWithGitHub({ redirectUri })
redirect(url, RedirectType.push)
}, [ctx, nextUrl])
const form = useForm({
defaultValues: {
email: '',
password: ''
},
validators: {
onChange: z.object({
email: z
.string()
.email()
.refine((email) => isValidEmail(email)),
password: z.string().refine((password) => isValidPassword(password))
})
},
onSubmit: async ({ value }) => {
try {
const res = await ctx!.api.signInWithPassword({
email: value.email,
password: value.password
})
console.log('login success', res)
} catch (err: any) {
void toastError(err, { label: 'Login error' })
return
}
return redirect(nextUrl || '/app', RedirectType.push)
}
})
import { LoginForm } from './login-form'
export default function Page() {
return (
<>
<section>
<div className='flex-col flex-1 items-center justify-center w-full max-w-xs'>
<form
className={cn('flex flex-col gap-6 w-full')}
onSubmit={(e) => {
e.preventDefault()
void form.handleSubmit()
}}
>
<div className='flex flex-col items-center gap-2 text-center'>
<h1 className='text-2xl font-bold'>Login to your account</h1>
</div>
<div className='grid gap-6'>
<form.Field
name='email'
children={(field) => (
<div className='grid gap-3'>
<Label htmlFor={field.name}>Email</Label>
<Input
id={field.name}
name={field.name}
type='email'
required
placeholder='Email'
autoComplete='email'
autoFocus={true}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e: any) => field.handleChange(e.target.value)}
/>
</div>
)}
/>
<form.Field
name='password'
children={(field) => (
<div className='grid gap-3'>
<div className='flex items-center'>
<Label htmlFor={field.name}>Password</Label>
{/* <a
href='/forgot-password'
className='ml-auto text-xs underline-offset-4 hover:underline'
tabIndex={-1}
>
Forgot your password?
</a> */}
</div>
<Input
id={field.name}
name={field.name}
type='password'
required
placeholder='Password'
// autoFocus={error?.type === 'invalid_password'}
autoComplete='current-password'
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
</div>
)}
/>
<form.Subscribe
selector={(state) => [
state.canSubmit,
state.isSubmitting,
state.isTouched
]}
children={([canSubmit, isSubmitting, isTouched]) => (
<Button
type='submit'
disabled={!(isTouched && canSubmit && ctx)}
className='w-full'
>
{isSubmitting && <Loader2Icon className='animate-spin' />}
<span>Login</span>
</Button>
)}
/>
<div className='after:border-border relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t'>
<span className='bg-background text-muted-foreground relative z-10 px-2'>
Or continue with
</span>
</div>
<Button
variant='outline'
className='w-full'
onClick={onAuthWithGitHub}
>
<GitHubIcon />
<span>Login with GitHub</span>
</Button>
</div>
<div className='text-center text-xs'>
Don&apos;t have an account?{' '}
<a
href={`/signup?${sanitizeSearchParams({ next: nextUrl }).toString()}`}
className='underline underline-offset-4'
>
Sign up
</a>
</div>
</form>
</div>
</section>
</>
<Suspense>
<LoginForm />
</Suspense>
)
}

Wyświetl plik

@ -1,230 +1,11 @@
'use client'
import { Suspense } from 'react'
import { sanitizeSearchParams } from '@agentic/platform-core'
import {
isValidEmail,
isValidPassword,
isValidUsername
} from '@agentic/platform-validators'
import { useForm } from '@tanstack/react-form'
import { Loader2Icon } from 'lucide-react'
import { redirect, RedirectType } from 'next/navigation'
import { useCallback } from 'react'
import { z } from 'zod'
import {
useNextUrl,
useUnauthenticatedAgentic
} from '@/components/agentic-provider'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { GitHubIcon } from '@/icons/github'
import { toastError } from '@/lib/notifications'
import { cn } from '@/lib/utils'
export default function SignupPage() {
// const [error] = useState<PasswordLoginError | undefined>(undefined)
const ctx = useUnauthenticatedAgentic()
const nextUrl = useNextUrl()
const onAuthWithGitHub = useCallback(async () => {
const redirectUri = `${globalThis.location.origin}/auth/github/success?${sanitizeSearchParams({ next: nextUrl }).toString()}`
const url = await ctx!.api.initAuthFlowWithGitHub({ redirectUri })
redirect(url, RedirectType.push)
}, [ctx, nextUrl])
const form = useForm({
defaultValues: {
email: '',
username: '',
password: '',
repeat: ''
},
validators: {
onChange: z.object({
email: z
.string()
.email()
.refine((email) => isValidEmail(email)),
username: z.string().refine((username) => isValidUsername(username)),
password: z.string().refine((password) => isValidPassword(password)),
repeat: z.string().refine((password) => isValidPassword(password))
})
},
onSubmit: async ({ value }) => {
try {
if (value.password !== value.repeat) {
void toastError('Passwords do not match', { label: 'signup error' })
return
}
const res = await ctx!.api.signUpWithPassword({
email: value.email,
username: value.username,
password: value.password
})
console.log('signup success', res)
} catch (err: any) {
void toastError(err, { label: 'signup error' })
return
}
return redirect(nextUrl || '/app', RedirectType.push)
}
})
import { SignupForm } from './signup-form'
export default function Page() {
return (
<>
<section>
<div className='flex-col flex-1 items-center justify-center w-full max-w-xs'>
<form
className={cn('flex flex-col gap-6 w-full')}
onSubmit={(e) => {
e.preventDefault()
void form.handleSubmit()
}}
>
<div className='flex flex-col items-center gap-2 text-center'>
<h1 className='text-2xl font-bold'>Create an account</h1>
</div>
<div className='grid gap-6'>
<form.Field
name='email'
children={(field) => (
<div className='grid gap-3'>
<Label htmlFor={field.name}>Email</Label>
<Input
id={field.name}
name={field.name}
type='email'
required
placeholder='Email'
autoComplete='email'
autoFocus={true}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e: any) => field.handleChange(e.target.value)}
/>
</div>
)}
/>
<form.Field
name='username'
children={(field) => (
<div className='grid gap-3'>
<Label htmlFor={field.name}>Username</Label>
<Input
id={field.name}
name={field.name}
type='text'
required
placeholder='Username'
autoComplete='username'
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e: any) => field.handleChange(e.target.value)}
/>
</div>
)}
/>
<form.Field
name='password'
children={(field) => (
<div className='grid gap-3'>
<Label htmlFor={field.name}>Password</Label>
<Input
id={field.name}
name={field.name}
type='password'
required
placeholder='Password'
// autoFocus={error?.type === 'invalid_password'}
autoComplete='new-password'
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
</div>
)}
/>
<form.Field
name='repeat'
children={(field) => (
<div className='grid gap-3'>
<Label htmlFor={field.name}>Repeat password</Label>
<Input
id={field.name}
name={field.name}
type='password'
required
placeholder='Password'
autoComplete='new-password'
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
</div>
)}
/>
<form.Subscribe
selector={(state) => [
state.canSubmit,
state.isSubmitting,
state.isTouched
]}
children={([canSubmit, isSubmitting, isTouched]) => (
<Button
type='submit'
disabled={!(isTouched && canSubmit && ctx)}
className='w-full'
>
{isSubmitting && <Loader2Icon className='animate-spin' />}
<span>Sign up</span>
</Button>
)}
/>
<div className='after:border-border relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t'>
<span className='bg-background text-muted-foreground relative z-10 px-2'>
Or continue with
</span>
</div>
<Button
variant='outline'
className='w-full'
onClick={onAuthWithGitHub}
>
<GitHubIcon />
<span>Sign up with GitHub</span>
</Button>
</div>
<div className='text-center text-xs'>
Already have an account?{' '}
<a
href={`/login?${sanitizeSearchParams({ next: nextUrl }).toString()}`}
className='underline underline-offset-4'
>
Login
</a>
</div>
</form>
</div>
</section>
</>
<Suspense>
<SignupForm />
</Suspense>
)
}

Wyświetl plik

@ -0,0 +1,227 @@
'use client'
import { sanitizeSearchParams } from '@agentic/platform-core'
import {
isValidEmail,
isValidPassword,
isValidUsername
} from '@agentic/platform-validators'
import { useForm } from '@tanstack/react-form'
import { Loader2Icon } from 'lucide-react'
import { redirect, RedirectType } from 'next/navigation'
import { useCallback } from 'react'
import { z } from 'zod'
import {
useNextUrl,
useUnauthenticatedAgentic
} from '@/components/agentic-provider'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { GitHubIcon } from '@/icons/github'
import { toastError } from '@/lib/notifications'
import { cn } from '@/lib/utils'
export function SignupForm() {
const ctx = useUnauthenticatedAgentic()
const nextUrl = useNextUrl()
const onAuthWithGitHub = useCallback(async () => {
const redirectUri = `${globalThis.location.origin}/auth/github/success?${sanitizeSearchParams({ next: nextUrl }).toString()}`
const url = await ctx!.api.initAuthFlowWithGitHub({ redirectUri })
redirect(url, RedirectType.push)
}, [ctx, nextUrl])
const form = useForm({
defaultValues: {
email: '',
username: '',
password: '',
repeat: ''
},
validators: {
onChange: z.object({
email: z
.string()
.email()
.refine((email) => isValidEmail(email)),
username: z.string().refine((username) => isValidUsername(username)),
password: z.string().refine((password) => isValidPassword(password)),
repeat: z.string().refine((password) => isValidPassword(password))
})
},
onSubmit: async ({ value }) => {
try {
if (value.password !== value.repeat) {
void toastError('Passwords do not match', { label: 'signup error' })
return
}
const res = await ctx!.api.signUpWithPassword({
email: value.email,
username: value.username,
password: value.password
})
console.log('signup success', res)
} catch (err: any) {
void toastError(err, { label: 'signup error' })
return
}
return redirect(nextUrl || '/app', RedirectType.push)
}
})
return (
<section>
<div className='flex-col flex-1 items-center justify-center w-full max-w-xs'>
<form
className={cn('flex flex-col gap-6 w-full')}
onSubmit={(e) => {
e.preventDefault()
void form.handleSubmit()
}}
>
<div className='flex flex-col items-center gap-2 text-center'>
<h1 className='text-2xl font-bold'>Create an account</h1>
</div>
<div className='grid gap-6'>
<form.Field
name='email'
children={(field) => (
<div className='grid gap-3'>
<Label htmlFor={field.name}>Email</Label>
<Input
id={field.name}
name={field.name}
type='email'
required
placeholder='Email'
autoComplete='email'
autoFocus={true}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e: any) => field.handleChange(e.target.value)}
/>
</div>
)}
/>
<form.Field
name='username'
children={(field) => (
<div className='grid gap-3'>
<Label htmlFor={field.name}>Username</Label>
<Input
id={field.name}
name={field.name}
type='text'
required
placeholder='Username'
autoComplete='username'
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e: any) => field.handleChange(e.target.value)}
/>
</div>
)}
/>
<form.Field
name='password'
children={(field) => (
<div className='grid gap-3'>
<Label htmlFor={field.name}>Password</Label>
<Input
id={field.name}
name={field.name}
type='password'
required
placeholder='Password'
// autoFocus={error?.type === 'invalid_password'}
autoComplete='new-password'
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
</div>
)}
/>
<form.Field
name='repeat'
children={(field) => (
<div className='grid gap-3'>
<Label htmlFor={field.name}>Repeat password</Label>
<Input
id={field.name}
name={field.name}
type='password'
required
placeholder='Password'
autoComplete='new-password'
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
</div>
)}
/>
<form.Subscribe
selector={(state) => [
state.canSubmit,
state.isSubmitting,
state.isTouched
]}
children={([canSubmit, isSubmitting, isTouched]) => (
<Button
type='submit'
disabled={!(isTouched && canSubmit && ctx)}
className='w-full'
>
{isSubmitting && <Loader2Icon className='animate-spin' />}
<span>Sign up</span>
</Button>
)}
/>
<div className='after:border-border relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t'>
<span className='bg-background text-muted-foreground relative z-10 px-2'>
Or continue with
</span>
</div>
<Button
variant='outline'
className='w-full'
onClick={onAuthWithGitHub}
>
<GitHubIcon />
<span>Sign up with GitHub</span>
</Button>
</div>
<div className='text-center text-xs'>
Already have an account?{' '}
<a
href={`/login?${sanitizeSearchParams({ next: nextUrl }).toString()}`}
className='underline underline-offset-4'
>
Login
</a>
</div>
</form>
</div>
</section>
)
}