Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/api/src/controllers/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
API_URI,
AWS_SES_REGION,
DASHBOARD_URI,
DISABLE_SIGNUPS,
GITHUB_OAUTH_ENABLED,
GOOGLE_OAUTH_ENABLED,
LANDING_URI,
Expand Down Expand Up @@ -47,6 +48,9 @@ export class Config {
github: GITHUB_OAUTH_ENABLED,
google: GOOGLE_OAUTH_ENABLED,
},
signup: {
signupsDisabled: DISABLE_SIGNUPS,
},
email: {
trackingToggleEnabled: TRACKING_TOGGLE_ENABLED,
},
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/lib/hooks/useConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface ConfigResponse {
billing: {enabled: boolean};
storage: {s3Enabled: boolean};
authProviders: {github: boolean; google: boolean};
signup: {signupsDisabled: boolean};
email: {trackingToggleEnabled: boolean};
smtp: {
enabled: boolean;
Expand Down
15 changes: 9 additions & 6 deletions apps/web/src/pages/auth/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export default function Login() {
github: config?.features.authProviders.github ?? false,
google: config?.features.authProviders.google ?? false,
};
const signupsDisabled = config?.features.signup.signupsDisabled ?? false;

async function onSubmit(values: z.infer<typeof AuthenticationSchemas.login>) {
try {
Expand Down Expand Up @@ -295,12 +296,14 @@ export default function Login() {
)}
</Button>

<p className="text-center text-sm text-neutral-500">
Don&apos;t have an account?{' '}
<Link href="/auth/signup" className="text-neutral-900 underline underline-offset-4 hover:text-neutral-600 transition-colors">
Sign up
</Link>
</p>
{!signupsDisabled && (
<p className="text-center text-sm text-neutral-500">
Don&apos;t have an account?{' '}
<Link href="/auth/signup" className="text-neutral-900 underline underline-offset-4 hover:text-neutral-600 transition-colors">
Sign up
</Link>
</p>
)}
</div>
</form>
</Form>
Expand Down
281 changes: 149 additions & 132 deletions apps/web/src/pages/auth/signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export default function Signup() {
github: config?.features.authProviders.github ?? false,
google: config?.features.authProviders.google ?? false,
};
const signupsDisabled = config?.features.signup.signupsDisabled ?? false;

async function onSubmit(values: z.infer<typeof AuthenticationSchemas.signup>) {
try {
Expand Down Expand Up @@ -96,144 +97,160 @@ export default function Signup() {

<Card>
<CardContent className="p-0">
<Form {...form}>
<form
onSubmit={e => {
e.preventDefault();
void form.handleSubmit(onSubmit)(e);
}}
className="p-8"
>
<div className="flex flex-col gap-6">
<div className="flex flex-col gap-1.5">
<h1 className="text-2xl font-bold tracking-tight">Create an account</h1>
<p className="text-sm text-neutral-500">Start sending emails in minutes</p>
</div>
{signupsDisabled ? (
<div className="p-8 flex flex-col gap-4 text-center">
<h1 className="text-2xl font-bold tracking-tight">Signups are disabled</h1>
<p className="text-sm text-neutral-500">
New account registration is currently disabled on this instance. Please contact your administrator if you
believe this is a mistake.
</p>
<Link
href="/auth/login"
className="text-sm text-neutral-900 underline underline-offset-4 hover:text-neutral-600 transition-colors"
>
Back to log in
</Link>
</div>
) : (
<Form {...form}>
<form
onSubmit={e => {
e.preventDefault();
void form.handleSubmit(onSubmit)(e);
}}
className="p-8"
>
<div className="flex flex-col gap-6">
<div className="flex flex-col gap-1.5">
<h1 className="text-2xl font-bold tracking-tight">Create an account</h1>
<p className="text-sm text-neutral-500">Start sending emails in minutes</p>
</div>

{(oauthConfig.github || oauthConfig.google) && (
<>
<div className="grid gap-2">
{oauthConfig.google && (
<Button
type="button"
variant="outline"
className="w-full"
onClick={() => {
window.location.href = `${API_URI}/oauth/google/outbound`;
}}
>
<svg className="h-4 w-4" viewBox="0 0 24 24">
<path
fill="currentColor"
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
/>
<path
fill="currentColor"
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
/>
<path
fill="currentColor"
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
/>
<path
fill="currentColor"
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
/>
</svg>
Continue with Google
</Button>
)}
{oauthConfig.github && (
<Button
type="button"
variant="outline"
className="w-full"
onClick={() => {
window.location.href = `${API_URI}/oauth/github/outbound`;
}}
>
<svg className="h-4 w-4" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</svg>
Continue with GitHub
</Button>
)}
</div>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t border-neutral-200" />
{(oauthConfig.github || oauthConfig.google) && (
<>
<div className="grid gap-2">
{oauthConfig.google && (
<Button
type="button"
variant="outline"
className="w-full"
onClick={() => {
window.location.href = `${API_URI}/oauth/google/outbound`;
}}
>
<svg className="h-4 w-4" viewBox="0 0 24 24">
<path
fill="currentColor"
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
/>
<path
fill="currentColor"
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
/>
<path
fill="currentColor"
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
/>
<path
fill="currentColor"
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
/>
</svg>
Continue with Google
</Button>
)}
{oauthConfig.github && (
<Button
type="button"
variant="outline"
className="w-full"
onClick={() => {
window.location.href = `${API_URI}/oauth/github/outbound`;
}}
>
<svg className="h-4 w-4" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
</svg>
Continue with GitHub
</Button>
)}
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-white px-2 text-neutral-400 tracking-wider">or</span>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t border-neutral-200" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-white px-2 text-neutral-400 tracking-wider">or</span>
</div>
</div>
</div>
</>
)}

<div className="grid gap-4">
<FormField
control={form.control}
name="email"
render={({field}) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="you@example.com" autoFocus {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="password"
render={({field}) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input placeholder="At least 6 characters" type="password" {...field} />
</FormControl>
<FormMessage />
</FormItem>
</>
)}

<div className="grid gap-4">
<FormField
control={form.control}
name="email"
render={({field}) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="you@example.com" autoFocus {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="password"
render={({field}) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input placeholder="At least 6 characters" type="password" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>

<AnimatePresence>
{errorMessage && (
<motion.p
initial={{opacity: 0, y: -8}}
animate={{opacity: 1, y: 0}}
exit={{opacity: 0, y: -8}}
transition={{duration: 0.15}}
className="text-sm text-red-500"
>
{errorMessage}
</motion.p>
)}
/>
</div>
</AnimatePresence>

<AnimatePresence>
{errorMessage && (
<motion.p
initial={{opacity: 0, y: -8}}
animate={{opacity: 1, y: 0}}
exit={{opacity: 0, y: -8}}
transition={{duration: 0.15}}
className="text-sm text-red-500"
>
{errorMessage}
</motion.p>
)}
</AnimatePresence>
<Button type="submit" className="w-full" disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting ? (
<>
<IconSpinner size="sm" />
Creating account...
</>
) : (
'Create account'
)}
</Button>

<Button type="submit" className="w-full" disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting ? (
<>
<IconSpinner size="sm" />
Creating account...
</>
) : (
'Create account'
)}
</Button>

<p className="text-center text-sm text-neutral-500">
Already have an account?{' '}
<Link href="/auth/login" className="text-neutral-900 underline underline-offset-4 hover:text-neutral-600 transition-colors">
Log in
</Link>
</p>
</div>
</form>
</Form>
<p className="text-center text-sm text-neutral-500">
Already have an account?{' '}
<Link href="/auth/login" className="text-neutral-900 underline underline-offset-4 hover:text-neutral-600 transition-colors">
Log in
</Link>
</p>
</div>
</form>
</Form>
)}
</CardContent>
</Card>
</div>
Expand Down
Loading