Skip to content

Instantly share code, notes, and snippets.

@mmikhan
Created February 13, 2025 20:55
Show Gist options
  • Save mmikhan/1feca81c3ae4b7b2d5ec226c90873ede to your computer and use it in GitHub Desktop.
Save mmikhan/1feca81c3ae4b7b2d5ec226c90873ede to your computer and use it in GitHub Desktop.
Better Auth social providers with Secret Key component
"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