feat(auth): hide Google button when OAuth env is missing

- Export enabledSocialProviders from @ketopath/auth: ['google'] when
  GOOGLE_CLIENT_ID/SECRET are both set, [] otherwise
- sign-in and sign-up pages read enabledSocialProviders server-side and pass
  googleEnabled to their forms; the divider and button disappear when false
- No changes to the Better Auth instance — the Google provider is still
  conditionally registered, this just keeps the UI honest about it

docs/runbooks/google-oauth-setup.md walks through the Google Cloud Console
flow end-to-end (project, consent screen, credentials, redirect URIs, env
injection, verification, troubleshooting, prod considerations).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
lucianoandClaude Opus 4.7 committed 2026-04-29 13:55:04 +02:00
1 parent c27e807c43
commit 646b98cccf
7 files changed
+180 -48

No files matched your search

@@ -1,3 +1,4 @@
import { enabledSocialProviders } from '@ketopath/auth';
import { useTranslations } from 'next-intl';
import { setRequestLocale } from 'next-intl/server';
@@ -13,7 +14,7 @@ export default function SignInPage({ params: { locale } }: { params: { locale: s
<h1 className="text-3xl font-bold tracking-tight">{t('title')}</h1>
<p className="text-sm text-slate-600">{t('subtitle')}</p>
</header>
<SignInForm />
<SignInForm googleEnabled={enabledSocialProviders.includes('google')} />
</main>
);
}
@@ -7,7 +7,7 @@ import { useState, type FormEvent } from 'react';
import { authClient } from '@/lib/auth-client';
export function SignInForm() {
export function SignInForm({ googleEnabled }: { googleEnabled: boolean }) {
const t = useTranslations('Auth.SignIn');
const router = useRouter();
const [email, setEmail] = useState('');
@@ -82,23 +82,27 @@ export function SignInForm() {
{pending ? t('submitting') : t('submit')}
</button>
</form>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-slate-200" />
</div>
<div className="relative flex justify-center">
<span className="bg-white px-2 text-xs uppercase tracking-wider text-slate-500">
{t('or')}
</span>
</div>
</div>
<button
type="button"
onClick={handleGoogle}
className="w-full rounded-md border border-slate-300 bg-white px-5 py-2.5 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50"
>
{t('google')}
</button>
{googleEnabled ? (
<>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-slate-200" />
</div>
<div className="relative flex justify-center">
<span className="bg-white px-2 text-xs uppercase tracking-wider text-slate-500">
{t('or')}
</span>
</div>
</div>
<button
type="button"
onClick={handleGoogle}
className="w-full rounded-md border border-slate-300 bg-white px-5 py-2.5 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50"
>
{t('google')}
</button>
</>
) : null}
<p className="text-center text-sm text-slate-600">
{t.rich('noAccount', {
link: (chunks) => (
@@ -1,3 +1,4 @@
import { enabledSocialProviders } from '@ketopath/auth';
import { useTranslations } from 'next-intl';
import { setRequestLocale } from 'next-intl/server';
@@ -13,7 +14,7 @@ export default function SignUpPage({ params: { locale } }: { params: { locale: s
<h1 className="text-3xl font-bold tracking-tight">{t('title')}</h1>
<p className="text-sm text-slate-600">{t('subtitle')}</p>
</header>
<SignUpForm />
<SignUpForm googleEnabled={enabledSocialProviders.includes('google')} />
<p className="text-xs text-slate-500">{t('disclaimer')}</p>
</main>
);
@@ -7,7 +7,7 @@ import { useState, type FormEvent } from 'react';
import { authClient } from '@/lib/auth-client';
export function SignUpForm() {
export function SignUpForm({ googleEnabled }: { googleEnabled: boolean }) {
const t = useTranslations('Auth.SignUp');
const router = useRouter();
const [name, setName] = useState('');
@@ -95,23 +95,27 @@ export function SignUpForm() {
{pending ? t('submitting') : t('submit')}
</button>
</form>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-slate-200" />
</div>
<div className="relative flex justify-center">
<span className="bg-white px-2 text-xs uppercase tracking-wider text-slate-500">
{t('or')}
</span>
</div>
</div>
<button
type="button"
onClick={handleGoogle}
className="w-full rounded-md border border-slate-300 bg-white px-5 py-2.5 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50"
>
{t('google')}
</button>
{googleEnabled ? (
<>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-slate-200" />
</div>
<div className="relative flex justify-center">
<span className="bg-white px-2 text-xs uppercase tracking-wider text-slate-500">
{t('or')}
</span>
</div>
</div>
<button
type="button"
onClick={handleGoogle}
className="w-full rounded-md border border-slate-300 bg-white px-5 py-2.5 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50"
>
{t('google')}
</button>
</>
) : null}
<p className="text-center text-sm text-slate-600">
{t.rich('haveAccount', {
link: (chunks) => (