| 1 | "use client"; |
| 2 | |
| 3 | import type * as React |
| 4 | import Image from "next/image |
| 5 | import { useState } |
| 6 | import { useTranslations } |
| 7 | import { useAuthSettings } |
| 8 | import { authClient } |
| 9 | |
| 10 | type AuthShellProps |
| 11 | title: string |
| 12 | subtitle: string |
| 13 | children: React |
| 14 | reverse?: boolean |
| 15 | }; |
| 16 | |
| 17 | function GithubIcon({ className |
| 18 | return ( |
| 19 | <svg className={className |
| 20 | <path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2z |
| 21 | </svg> |
| 22 | ); |
| 23 | } |
| 24 | |
| 25 | function DiscordIcon({ className |
| 26 | return ( |
| 27 | <svg className={className |
| 28 | <path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057c.003.02.015.04.03.052a19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z |
| 29 | </svg> |
| 30 | ); |
| 31 | } |
| 32 | |
| 33 | const PROVIDER_META: Record |
| 34 | github: { label: " |
| 35 | discord: { label: " |
| 36 | }; |
| 37 | |
| 38 | function Logo({ logoUrl |
| 39 | if (logoUrl) { |
| 40 | // eslint-disable-next-line @next/next/no-img-element |
| 41 | return <img src |
| 42 | } |
| 43 | return ( |
| 44 | <> |
| 45 | <Image src=" |
| 46 | <Image src=" |
| 47 | </> |
| 48 | ); |
| 49 | } |
| 50 | |
| 51 | export default function AuthShell |
| 52 | const t = |
| 53 | const { appName, |
| 54 | const [loadingProvider, |
| 55 | |
| 56 | async function signInWithProvider |
| 57 | setLoadingProvider(provider); |
| 58 | try { |
| 59 | await authClient.signIn |
| 60 | } finally { |
| 61 | setLoadingProvider(null); |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | return ( |
| 66 | <main className="min-h-svh bg-background lg:grid lg:grid-cols-2 |
| 67 | {/* Branding panel — desktop only */ |
| 68 | <div className={` |
| 69 | <Logo logoUrl={ |
| 70 | <div className={` |
| 71 | <p className="text-2xl font-semibold tracking-tight text-foreground |
| 72 | <p className="text-sm text-muted-foreground max-w-xs |
| 73 | {ogDescription || t |
| 74 | </p> |
| 75 | </div> |
| 76 | <p className="text-xs text-muted-foreground/60 |
| 77 | </div> |
| 78 | |
| 79 | {/* Form panel */} |
| 80 | <div className={` |
| 81 | <div className="flex w-full max-w-sm flex-col gap-6 |
| 82 | {/* Logo shown only on mobile */ |
| 83 | <div className="flex justify-center lg:hidden |
| 84 | <Logo logoUrl={ |
| 85 | </div> |
| 86 | |
| 87 | <div className="flex flex-col gap-1 text-center |
| 88 | <h1 className="text-xl font-semibold tracking-tight text-foreground |
| 89 | <p className="text-sm text-muted-foreground |
| 90 | </div> |
| 91 | |
| 92 | <div className="flex flex-col gap-4 |
| 93 | {socialProviders.length > |
| 94 | <> |
| 95 | <div className="flex flex-col gap-2 |
| 96 | {socialProviders.map(( |
| 97 | const meta = |
| 98 | if (!meta) return |
| 99 | const Icon = |
| 100 | return ( |
| 101 | <button |
| 102 | key={provider} |
| 103 | type="button" |
| 104 | disabled={!!loadingProvider} |
| 105 | onClick={() => |
| 106 | className={`flex w-full items-center justify-center gap-2 rounded-lg border border-border bg-background px-4 py-2 text-sm font-medium text-foreground transition-colors disabled:opacity-50 ${meta |
| 107 | > |
| 108 | <Icon className=" |
| 109 | {loadingProvider === provider |
| 110 | </button> |
| 111 | ); |
| 112 | })} |
| 113 | </div> |
| 114 | <div className="relative flex items-center gap-3 |
| 115 | <div className="flex-1 border-t border-border |
| 116 | <span className="text-xs text-muted-foreground |
| 117 | <div className="flex-1 border-t border-border |
| 118 | </div> |
| 119 | </> |
| 120 | )} |
| 121 | {children} |
| 122 | </div> |
| 123 | </div> |
| 124 | </div> |
| 125 | </main> |
| 126 | ); |
| 127 | } |