Instantly share code, notes, and snippets.
Created
February 13, 2025 20:55
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save mmikhan/1feca81c3ae4b7b2d5ec226c90873ede to your computer and use it in GitHub Desktop.
Better Auth social providers with Secret Key component
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use client"; | |
import { Button } from "@/components/ui/button"; | |
import { Checkbox } from "@/components/ui/checkbox"; | |
import { | |
Collapsible, | |
CollapsibleContent, | |
CollapsibleTrigger, | |
} from "@/components/ui/collapsible"; | |
import { | |
Form, | |
FormControl, | |
FormDescription, | |
FormField, | |
FormItem, | |
FormLabel, | |
FormMessage, | |
} from "@/components/ui/form"; | |
import { Input } from "@/components/ui/input"; | |
import { ToastAction } from "@/components/ui/toast"; | |
import { toast } from "@/hooks/use-toast"; | |
import { api } from "@/trpc/react"; | |
import { generateSecret } from "@/utils"; | |
import { | |
type AuthSettings, | |
authSettingsSchema, | |
SOCIAL_PROVIDERS, | |
} from "@/utils/schema/settings"; | |
import { zodResolver } from "@hookform/resolvers/zod"; | |
import { ChevronDown, Loader2 } from "lucide-react"; | |
import { useCallback, useEffect } from "react"; | |
import { useForm } from "react-hook-form"; | |
export function UpdateAuthSocialProviderForm() { | |
const utils = api.useUtils(); | |
const [settings] = api.settings.socialAuth.useSuspenseQuery(); | |
const update = api.settings.updateSocialAuth.useMutation({ | |
onSuccess: async () => { | |
toast({ | |
title: "Success", | |
description: | |
"Your social authentication settings have been saved successfully.", | |
}); | |
await utils.settings.socialAuth.invalidate(); | |
}, | |
onError: (error) => { | |
toast({ | |
title: "Uh oh! Something went wrong.", | |
description: | |
error.message || "Failed to update settings. Please try again.", | |
action: <ToastAction altText="Try again">Try again</ToastAction>, | |
variant: "destructive", | |
}); | |
}, | |
}); | |
const form = useForm<AuthSettings>({ | |
resolver: zodResolver(authSettingsSchema), | |
defaultValues: settings, | |
}); | |
useEffect(() => { | |
if (settings) { | |
form.reset(settings); | |
} | |
}, [settings, form]); | |
const onSubmit = (data: AuthSettings) => { | |
update.mutate(data); | |
}; | |
const enabledProviders = form.watch("enabledProviders") ?? []; | |
const handleGenerateSecret = useCallback(() => { | |
const newSecret = generateSecret(); | |
form.setValue("secret", newSecret, { | |
shouldDirty: true, | |
shouldValidate: true, | |
}); | |
}, [form]); | |
return ( | |
<Form {...form}> | |
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> | |
<h3 className="mb-4 text-lg font-medium">Authentication</h3> | |
<div className="space-y-4"> | |
<FormField | |
control={form.control} | |
name="secret" | |
render={({ field }) => ( | |
<FormItem> | |
<FormLabel>Secret Key</FormLabel> | |
<FormControl> | |
<Input {...field} type="password" /> | |
</FormControl> | |
<FormDescription> | |
Random value used by the library for encryption and generating | |
hashes. | |
</FormDescription> | |
<FormMessage /> | |
</FormItem> | |
)} | |
/> | |
<Button | |
type="button" | |
variant="outline" | |
size="sm" | |
onClick={handleGenerateSecret} | |
disabled={update.isPending} | |
> | |
{update.isPending ? ( | |
<> | |
<Loader2 className="animate-spin" /> | |
Generating... | |
</> | |
) : ( | |
"Generate new secret" | |
)} | |
</Button> | |
<div className="space-y-4"> | |
<FormField | |
control={form.control} | |
name="enabledProviders" | |
render={() => ( | |
<FormItem> | |
<FormLabel>Social Providers</FormLabel> | |
<div className="grid grid-cols-2 gap-4"> | |
{SOCIAL_PROVIDERS.map((provider) => ( | |
<FormField | |
key={provider} | |
control={form.control} | |
name="enabledProviders" | |
render={({ field }) => ( | |
<FormItem | |
key={provider} | |
className="flex items-start space-x-3 space-y-0 rounded-md border p-4" | |
> | |
<FormControl> | |
<Checkbox | |
checked={field.value?.includes(provider)} | |
onCheckedChange={(checked) => { | |
return checked | |
? field.onChange([...field.value, provider]) | |
: field.onChange( | |
field.value?.filter( | |
(v) => v !== provider, | |
), | |
); | |
}} | |
/> | |
</FormControl> | |
<FormLabel className="font-normal"> | |
{provider.charAt(0).toUpperCase() + | |
provider.slice(1)} | |
</FormLabel> | |
</FormItem> | |
)} | |
/> | |
))} | |
</div> | |
</FormItem> | |
)} | |
/> | |
</div> | |
{enabledProviders?.length > 0 && ( | |
<div className="space-y-4"> | |
{enabledProviders.map((provider) => ( | |
<Collapsible key={provider} className="space-y-2"> | |
<div className="space-y-2"> | |
<CollapsibleTrigger className="flex w-full items-center justify-between rounded-lg border px-4 py-2 font-medium hover:bg-muted"> | |
<span className="text-sm"> | |
{provider.charAt(0).toUpperCase() + provider.slice(1)}{" "} | |
Configuration | |
</span> | |
<ChevronDown className="h-4 w-4" /> | |
</CollapsibleTrigger> | |
<CollapsibleContent> | |
<div className="space-y-4 rounded-md border px-4 py-3"> | |
<FormField | |
control={form.control} | |
name={`providerCredentials.${provider}.clientId`} | |
defaultValue="" | |
render={({ field }) => ( | |
<FormItem> | |
<FormLabel>Client ID</FormLabel> | |
<FormControl> | |
<Input {...field} /> | |
</FormControl> | |
<FormMessage /> | |
</FormItem> | |
)} | |
/> | |
<FormField | |
control={form.control} | |
name={`providerCredentials.${provider}.clientSecret`} | |
defaultValue="" | |
render={({ field }) => ( | |
<FormItem> | |
<FormLabel>Client Secret</FormLabel> | |
<FormControl> | |
<Input type="password" {...field} /> | |
</FormControl> | |
<FormMessage /> | |
</FormItem> | |
)} | |
/> | |
</div> | |
</CollapsibleContent> | |
</div> | |
</Collapsible> | |
))} | |
</div> | |
)} | |
</div> | |
<Button | |
type="submit" | |
size="sm" | |
variant={"outline"} | |
disabled={ | |
update.isPending || | |
!form.formState.isValid || | |
!form.formState.isDirty | |
} | |
> | |
{update.isPending ? ( | |
<> | |
<Loader2 className="animate-spin" /> | |
Saving... | |
</> | |
) : ( | |
"Save changes" | |
)} | |
</Button> | |
</form> | |
</Form> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment