integrations keyed, and the Google guide's foreign labels marked (163 -> 96)
Applies the rule added to docs/localization.md in 91b9bec9: a label quoted from
another product's interface is an identifier, so the prose around it is translated
and the label itself is left in English and NOT keyed.
Gives that decision a name in the code. <Untranslated reason="foreign-ui-label">
states it at each of the ~30 call sites, where whoever next edits the sentence will
see it, and gives the scanner something exact to skip -- without which labels that
MUST stay English count as debt forever and the area can never be held at zero. That
is the same trap as counting new Error() messages. `reason` is a closed union so a
second excuse has to be argued for rather than smuggled in as free text.
The sentences needed rephrasing, not just splitting. "Go to the [New Project] page."
decomposes into "Go to the" + link + "page." -- and the Portuguese value of that
second key is "." A key that holds a full stop is the fragmentation smell, so the
English now puts the label at a clause boundary ("Open the project creation page:"
then the link), which reads naturally in both languages and needs no node-level
interpolation. Worth knowing for phase 3: sentences with an inline link are the one
shape this catalog format cannot express, and rephrasing is the cheap way out.
Scanner: the existing SCREAMING_CONSTANT rule now allows . and - so Google's
client-secret prefix example GOCSPX-... falls under it. Same rule, wider class.
This commit is contained in:
+5
-3
@@ -5,6 +5,7 @@ import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import { useTranslation } from '@/lib/i18n';
|
||||
|
||||
type ApifyConfigData = {
|
||||
apiToken: string;
|
||||
@@ -15,6 +16,7 @@ type ApifyStatus = {
|
||||
};
|
||||
|
||||
export const ApifyConfig = () => {
|
||||
const { t } = useTranslation();
|
||||
const client = useClient();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
@@ -38,10 +40,10 @@ export const ApifyConfig = () => {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
await client.put('/integrations/apify/config', { apiToken: apiToken.trim() });
|
||||
toast.success('Apify API token saved');
|
||||
toast.success(t('settings.apify.saved'));
|
||||
setStatus({ configured: !!apiToken.trim() });
|
||||
} catch {
|
||||
toast.error('Failed to save Apify API token');
|
||||
toast.error(t('settings.apify.saveFailed'));
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
@@ -64,7 +66,7 @@ export const ApifyConfig = () => {
|
||||
|
||||
<form onSubmit={handleSave} className="grid gap-5">
|
||||
<Label className="grid gap-2">
|
||||
<span className="text-duck-dark/70 dark:text-foreground/70">API Token</span>
|
||||
<span className="text-duck-dark/70 dark:text-foreground/70">{t('settings.apify.token')}</span>
|
||||
<Input
|
||||
className="h-11 bg-background/60 border-duck-dark/20 text-duck-dark placeholder:text-duck-dark/40"
|
||||
type="password"
|
||||
|
||||
+13
-11
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import { useTranslation } from '@/lib/i18n';
|
||||
|
||||
type GoogleStatus = {
|
||||
connected: boolean;
|
||||
@@ -11,6 +12,7 @@ type GoogleStatus = {
|
||||
};
|
||||
|
||||
export const GoogleAccount = () => {
|
||||
const { t } = useTranslation();
|
||||
const client = useClient();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [status, setStatus] = useState<GoogleStatus>({
|
||||
@@ -30,9 +32,9 @@ export const GoogleAccount = () => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const result = params.get('google');
|
||||
if (result === 'success') {
|
||||
toast.success('Google account connected');
|
||||
toast.success(t('settings.googleAccount.connectedToast'));
|
||||
} else if (result === 'error') {
|
||||
toast.error('Failed to connect Google account');
|
||||
toast.error(t('settings.googleAccount.connectFailed'));
|
||||
}
|
||||
if (result) {
|
||||
window.history.replaceState({}, '', window.location.pathname);
|
||||
@@ -51,9 +53,9 @@ export const GoogleAccount = () => {
|
||||
try {
|
||||
await client.delete('/integrations/google/connection');
|
||||
setStatus({ ...status, connected: false, email: null });
|
||||
toast.success('Google account disconnected');
|
||||
toast.success(t('settings.googleAccount.disconnectedToast'));
|
||||
} catch {
|
||||
toast.error('Failed to disconnect Google account');
|
||||
toast.error(t('settings.googleAccount.disconnectFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -63,17 +65,17 @@ export const GoogleAccount = () => {
|
||||
return (
|
||||
<div className="grid gap-4">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-duck-dark dark:text-foreground">Google Account</p>
|
||||
<p className="text-xs text-duck-dark/50 dark:text-foreground/50 mt-1">
|
||||
Connect your Google account for Calendar, Contacts, and other Google services.
|
||||
</p>
|
||||
<p className="text-sm font-medium text-duck-dark dark:text-foreground">{t('settings.googleAccount.title')}</p>
|
||||
<p className="text-xs text-duck-dark/50 dark:text-foreground/50 mt-1">{t('settings.googleAccount.hint')}</p>
|
||||
</div>
|
||||
{status.connected ? (
|
||||
<>
|
||||
<div className="flex items-center gap-3 rounded-lg border border-duck-dark/10 dark:border-foreground/10 p-4">
|
||||
<div className="h-2.5 w-2.5 rounded-full bg-success shrink-0" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-medium text-duck-dark dark:text-foreground">Connected</p>
|
||||
<p className="text-sm font-medium text-duck-dark dark:text-foreground">
|
||||
{t('settings.googleAccount.connected')}
|
||||
</p>
|
||||
<p className="text-xs text-duck-dark/50 dark:text-foreground/50 truncate">{status.email}</p>
|
||||
</div>
|
||||
{status.picture && (
|
||||
@@ -81,7 +83,7 @@ export const GoogleAccount = () => {
|
||||
)}
|
||||
</div>
|
||||
<Button type="button" variant="outline" onClick={handleDisconnect} className="w-full h-11 cursor-pointer">
|
||||
Disconnect
|
||||
{t('settings.googleAccount.disconnect')}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
@@ -90,7 +92,7 @@ export const GoogleAccount = () => {
|
||||
onClick={handleConnect}
|
||||
className="w-full h-11 bg-duck-yellow hover:bg-duck-yellow/90 text-duck-teal font-bold transition-all duration-200 hover:scale-105 cursor-pointer"
|
||||
>
|
||||
Connect Google Account
|
||||
{t('settings.googleAccount.connect')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
+89
-111
@@ -7,197 +7,175 @@ import { Label } from '@/components/ui/label';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '@/components/ui/collapsible';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import { useTranslation } from '@/lib/i18n';
|
||||
import { Untranslated } from '@/lib/Untranslated';
|
||||
|
||||
type GoogleOAuthSettings = {
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
};
|
||||
|
||||
// The scope strings are Google's own identifiers; only the explanation beside them is ours.
|
||||
const SCOPES = [
|
||||
{ scope: 'gmail.readonly', description: 'Read emails' },
|
||||
{ scope: 'calendar.readonly', description: 'Read calendar events' },
|
||||
];
|
||||
{ scope: 'gmail.readonly', descriptionKey: 'settings.google.scopeGmail' },
|
||||
{ scope: 'calendar.readonly', descriptionKey: 'settings.google.scopeCalendar' },
|
||||
] as const;
|
||||
|
||||
const SetupGuide = ({ redirectUri }: { redirectUri: string }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
/*
|
||||
Every quoted Google-console label below is wrapped in <Untranslated>. Per docs/localization.md they
|
||||
are identifiers, not prose: they exist to match what the reader sees on Google's screen, and Google
|
||||
localises by the reader's Google account language rather than ours.
|
||||
|
||||
The sentences are keyed WHOLE, each ending at the label or link it introduces. The first attempt
|
||||
split them around inline labels, which needs "Go to the" and "page." as separate keys — and the
|
||||
Portuguese value of the second one is "." That is the fragmentation smell, so the English was
|
||||
rephrased to put the label at a clause boundary, which reads naturally in both languages.
|
||||
*/
|
||||
const GoogleLink = ({ href, label }: { href: string; label: string }) => (
|
||||
<a href={href} target="_blank" rel="noopener noreferrer" className="text-duck-teal underline">
|
||||
<Untranslated reason="foreign-ui-label">{label}</Untranslated>
|
||||
</a>
|
||||
);
|
||||
|
||||
return (
|
||||
<Collapsible open={open} onOpenChange={setOpen}>
|
||||
<CollapsibleTrigger className="flex items-center gap-2 text-sm font-medium text-duck-teal cursor-pointer hover:underline w-full">
|
||||
<ChevronDown className={`h-3.5 w-3.5 transition-transform duration-200 ${open ? 'rotate-180' : ''}`} />
|
||||
Step-by-step setup guide
|
||||
{t('settings.google.guideToggle')}
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<ol className="mt-3 grid gap-4 text-sm text-duck-dark/70 dark:text-foreground/70 list-decimal list-outside pl-5">
|
||||
<li>
|
||||
<strong className="text-duck-dark dark:text-foreground">Create a Google Cloud project</strong>
|
||||
<strong className="text-duck-dark dark:text-foreground">{t('settings.google.step1.title')}</strong>
|
||||
<p className="mt-1">
|
||||
Go to the{' '}
|
||||
<a
|
||||
{t('settings.google.step1.body')}{' '}
|
||||
<GoogleLink
|
||||
href="https://console.cloud.google.com/projectcreate"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-duck-teal underline"
|
||||
>
|
||||
New Project
|
||||
</a>{' '}
|
||||
page. Give it a name (e.g. "Officer") and click <strong>Create</strong>.
|
||||
label={t('settings.google.step1.link')}
|
||||
/>{' '}
|
||||
→ <Untranslated reason="foreign-ui-label">Create</Untranslated>
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong className="text-duck-dark dark:text-foreground">Enable the APIs</strong>
|
||||
<strong className="text-duck-dark dark:text-foreground">{t('settings.google.step2.title')}</strong>
|
||||
<p className="mt-1">
|
||||
Go to{' '}
|
||||
<a
|
||||
{t('settings.google.step2.body')}{' '}
|
||||
<GoogleLink
|
||||
href="https://console.cloud.google.com/apis/library"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-duck-teal underline"
|
||||
>
|
||||
API Library
|
||||
</a>
|
||||
. Search for and enable each of these:
|
||||
label={t('settings.google.step2.link')}
|
||||
/>{' '}
|
||||
→ <Untranslated reason="foreign-ui-label">Enable</Untranslated>
|
||||
</p>
|
||||
<ul className="mt-1 list-disc list-outside pl-5 grid gap-0.5">
|
||||
<li>
|
||||
<strong>Gmail API</strong>
|
||||
<Untranslated reason="foreign-ui-label">Gmail API</Untranslated>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Google Calendar API</strong>
|
||||
<Untranslated reason="foreign-ui-label">Google Calendar API</Untranslated>
|
||||
</li>
|
||||
</ul>
|
||||
<p className="mt-1">
|
||||
Click each one, then click <strong>Enable</strong>.
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong className="text-duck-dark dark:text-foreground">Configure the OAuth consent screen</strong>
|
||||
<strong className="text-duck-dark dark:text-foreground">{t('settings.google.step3.title')}</strong>
|
||||
<p className="mt-1">
|
||||
Go to{' '}
|
||||
<a
|
||||
<GoogleLink
|
||||
href="https://console.cloud.google.com/auth/branding"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-duck-teal underline"
|
||||
>
|
||||
OAuth Branding
|
||||
</a>
|
||||
.
|
||||
label={t('settings.google.step3.link')}
|
||||
/>
|
||||
</p>
|
||||
<ul className="mt-1 list-disc list-outside pl-5 grid gap-0.5">
|
||||
<li>
|
||||
Set <strong>App name</strong> to your organization name or "Officer"
|
||||
{t('settings.google.step3.appName')} <Untranslated reason="foreign-ui-label">App name</Untranslated>
|
||||
</li>
|
||||
<li>
|
||||
Set <strong>User support email</strong> to your admin email
|
||||
{t('settings.google.step3.supportEmail')}{' '}
|
||||
<Untranslated reason="foreign-ui-label">User support email</Untranslated>
|
||||
</li>
|
||||
<li>
|
||||
Add your admin email under <strong>Developer contact information</strong>
|
||||
{t('settings.google.step3.devContact')}{' '}
|
||||
<Untranslated reason="foreign-ui-label">Developer contact information</Untranslated>
|
||||
</li>
|
||||
<li>
|
||||
Click <strong>Save</strong>
|
||||
{t('settings.google.step3.save')} <Untranslated reason="foreign-ui-label">Save</Untranslated>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong className="text-duck-dark dark:text-foreground">Set the audience</strong>
|
||||
<strong className="text-duck-dark dark:text-foreground">{t('settings.google.step4.title')}</strong>
|
||||
<p className="mt-1">
|
||||
Go to{' '}
|
||||
<a
|
||||
<GoogleLink
|
||||
href="https://console.cloud.google.com/auth/audience"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-duck-teal underline"
|
||||
>
|
||||
OAuth Audience
|
||||
</a>
|
||||
.
|
||||
label={t('settings.google.step4.link')}
|
||||
/>
|
||||
</p>
|
||||
<ul className="mt-1 list-disc list-outside pl-5 grid gap-0.5">
|
||||
<li>
|
||||
If your team uses Google Workspace, select <strong>Internal</strong> — no verification needed
|
||||
{t('settings.google.step4.internal')} <Untranslated reason="foreign-ui-label">Internal</Untranslated>
|
||||
</li>
|
||||
<li>
|
||||
Otherwise, select <strong>External</strong> and add your team's emails under <strong>Test users</strong>{' '}
|
||||
(required while the app is unverified; limit of 100 test users)
|
||||
{t('settings.google.step4.external')} <Untranslated reason="foreign-ui-label">External</Untranslated>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong className="text-duck-dark dark:text-foreground">Add scopes</strong>
|
||||
<strong className="text-duck-dark dark:text-foreground">{t('settings.google.step5.title')}</strong>
|
||||
<p className="mt-1">
|
||||
In the left sidebar, click{' '}
|
||||
<a
|
||||
href="https://console.cloud.google.com/auth/scopes"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-duck-teal underline"
|
||||
>
|
||||
Data Access
|
||||
</a>
|
||||
, then click <strong>Add or remove scopes</strong>. Search for and add:
|
||||
{t('settings.google.step5.body')}{' '}
|
||||
<GoogleLink href="https://console.cloud.google.com/auth/scopes" label={t('settings.google.step5.link')} />{' '}
|
||||
→ <Untranslated reason="foreign-ui-label">Add or remove scopes</Untranslated>
|
||||
</p>
|
||||
<ul className="mt-1 list-disc list-outside pl-5 grid gap-0.5">
|
||||
{SCOPES.map((s) => (
|
||||
<li key={s.scope}>
|
||||
<code className="text-xs bg-duck-dark/5 dark:bg-foreground/5 px-1.5 py-0.5 rounded">{s.scope}</code> —{' '}
|
||||
{s.description}
|
||||
{SCOPES.map((entry) => (
|
||||
<li key={entry.scope}>
|
||||
<code className="text-xs bg-duck-dark/5 dark:bg-foreground/5 px-1.5 py-0.5 rounded">
|
||||
{entry.scope}
|
||||
</code>{' '}
|
||||
— {t(entry.descriptionKey)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="mt-1">
|
||||
Click <strong>Update</strong>, then <strong>Save</strong>.
|
||||
</p>
|
||||
<p className="mt-2 text-xs text-duck-dark/50 dark:text-foreground/50">
|
||||
Note: <code className="bg-duck-dark/5 dark:bg-foreground/5 px-1 py-0.5 rounded">calendar.readonly</code>{' '}
|
||||
is classified as <strong>sensitive</strong> and{' '}
|
||||
<code className="bg-duck-dark/5 dark:bg-foreground/5 px-1 py-0.5 rounded">gmail.readonly</code> as{' '}
|
||||
<strong>restricted</strong> by Google. This is fine for Internal apps (Google Workspace) and External apps
|
||||
in testing mode. Publishing to production with restricted scopes requires Google verification.
|
||||
</p>
|
||||
<p className="mt-2 text-xs text-duck-dark/50 dark:text-foreground/50">{t('settings.google.step5.note')}</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong className="text-duck-dark dark:text-foreground">Create OAuth credentials</strong>
|
||||
<strong className="text-duck-dark dark:text-foreground">{t('settings.google.step6.title')}</strong>
|
||||
<p className="mt-1">
|
||||
In the left sidebar, click{' '}
|
||||
<a
|
||||
{t('settings.google.step6.body')}{' '}
|
||||
<GoogleLink
|
||||
href="https://console.cloud.google.com/auth/clients"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-duck-teal underline"
|
||||
>
|
||||
Clients
|
||||
</a>
|
||||
, then click <strong>Create OAuth client</strong>.
|
||||
label={t('settings.google.step6.link')}
|
||||
/>{' '}
|
||||
→ <Untranslated reason="foreign-ui-label">Create OAuth client</Untranslated>
|
||||
</p>
|
||||
<ul className="mt-1 list-disc list-outside pl-5 grid gap-0.5">
|
||||
<li>
|
||||
Application type: <strong>Web application</strong>
|
||||
{t('settings.google.step6.appType')}{' '}
|
||||
<Untranslated reason="foreign-ui-label">Web application</Untranslated>
|
||||
</li>
|
||||
<li>Name: anything (e.g. "Officer")</li>
|
||||
<li>{t('settings.google.step6.nameAnything')}</li>
|
||||
<li>
|
||||
Authorized redirect URIs: add{' '}
|
||||
{t('settings.google.step6.redirectUris')}{' '}
|
||||
<code className="text-xs bg-duck-dark/5 dark:bg-foreground/5 px-1.5 py-0.5 rounded break-all">
|
||||
{redirectUri}
|
||||
</code>
|
||||
</li>
|
||||
<li>
|
||||
Click <strong>Create</strong>
|
||||
{t('settings.google.step6.thenClick')} <Untranslated reason="foreign-ui-label">Create</Untranslated>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<strong className="text-duck-dark dark:text-foreground">Copy the credentials</strong>
|
||||
<p className="mt-1">
|
||||
A dialog will show your <strong>Client ID</strong> and <strong>Client Secret</strong>. Copy both and paste
|
||||
them into the fields below.
|
||||
</p>
|
||||
<strong className="text-duck-dark dark:text-foreground">{t('settings.google.step7.title')}</strong>
|
||||
<p className="mt-1">{t('settings.google.step7.body')}</p>
|
||||
</li>
|
||||
</ol>
|
||||
</CollapsibleContent>
|
||||
@@ -208,11 +186,12 @@ const SetupGuide = ({ redirectUri }: { redirectUri: string }) => {
|
||||
type VerifyStatus = { valid: boolean; error: string | null } | null;
|
||||
|
||||
const CredentialStatus = ({ status, isVerifying }: { status: VerifyStatus; isVerifying: boolean }) => {
|
||||
const { t } = useTranslation();
|
||||
if (isVerifying) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 rounded-lg border border-duck-dark/10 dark:border-foreground/10 p-3">
|
||||
<div className="h-2.5 w-2.5 rounded-full bg-duck-dark/20 dark:bg-foreground/20 animate-pulse shrink-0" />
|
||||
<span className="text-sm text-duck-dark/50 dark:text-foreground/50">Verifying credentials...</span>
|
||||
<span className="text-sm text-duck-dark/50 dark:text-foreground/50">{t('settings.google.verifying')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -223,13 +202,14 @@ const CredentialStatus = ({ status, isVerifying }: { status: VerifyStatus; isVer
|
||||
<div className="flex items-center gap-2 rounded-lg border border-duck-dark/10 dark:border-foreground/10 p-3">
|
||||
<div className={`h-2.5 w-2.5 rounded-full shrink-0 ${status.valid ? 'bg-success' : 'bg-red-500'}`} />
|
||||
<span className={`text-sm ${status.valid ? 'text-duck-dark dark:text-foreground' : 'text-red-500'}`}>
|
||||
{status.valid ? 'Credentials valid' : (status.error ?? 'Invalid credentials')}
|
||||
{status.valid ? t('settings.google.valid') : (status.error ?? t('settings.google.invalid'))}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const GoogleOAuthConfig = () => {
|
||||
const { t } = useTranslation();
|
||||
const client = useClient();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
@@ -243,7 +223,7 @@ export const GoogleOAuthConfig = () => {
|
||||
client
|
||||
.get<{ valid: boolean; error: string | null }>('/integrations/google/verify')
|
||||
.then(setVerifyStatus)
|
||||
.catch(() => setVerifyStatus({ valid: false, error: 'Verification request failed' }))
|
||||
.catch(() => setVerifyStatus({ valid: false, error: t('settings.google.verifyFailed') }))
|
||||
.finally(() => setIsVerifying(false));
|
||||
};
|
||||
|
||||
@@ -273,10 +253,10 @@ export const GoogleOAuthConfig = () => {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
await client.put('/integrations/google/config', { clientId: clientId.trim(), clientSecret: clientSecret.trim() });
|
||||
toast.success('Google OAuth configuration saved');
|
||||
toast.success(t('settings.google.saved'));
|
||||
verify();
|
||||
} catch {
|
||||
toast.error('Failed to save Google OAuth configuration');
|
||||
toast.error(t('settings.google.saveFailed'));
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
@@ -294,7 +274,7 @@ export const GoogleOAuthConfig = () => {
|
||||
|
||||
<form onSubmit={handleSave} className="border-t border-duck-dark/10 dark:border-foreground/10 pt-5 grid gap-5">
|
||||
<Label className="grid gap-2">
|
||||
<span className="text-duck-dark/70 dark:text-foreground/70">Client ID</span>
|
||||
<span className="text-duck-dark/70 dark:text-foreground/70">{t('settings.google.clientId')}</span>
|
||||
<Input
|
||||
className="h-11 bg-background/60 border-duck-dark/20 text-duck-dark placeholder:text-duck-dark/40"
|
||||
type="text"
|
||||
@@ -305,7 +285,7 @@ export const GoogleOAuthConfig = () => {
|
||||
</Label>
|
||||
|
||||
<Label className="grid gap-2">
|
||||
<span className="text-duck-dark/70 dark:text-foreground/70">Client Secret</span>
|
||||
<span className="text-duck-dark/70 dark:text-foreground/70">{t('settings.google.clientSecret')}</span>
|
||||
<Input
|
||||
className="h-11 bg-background/60 border-duck-dark/20 text-duck-dark placeholder:text-duck-dark/40"
|
||||
type="password"
|
||||
@@ -316,10 +296,8 @@ export const GoogleOAuthConfig = () => {
|
||||
</Label>
|
||||
|
||||
<Label className="grid gap-2">
|
||||
<span className="text-duck-dark/70 dark:text-foreground/70">Redirect URI</span>
|
||||
<p className="text-xs text-duck-dark/40 dark:text-foreground/40">
|
||||
Add this URI to your Google OAuth client's authorized redirect URIs
|
||||
</p>
|
||||
<span className="text-duck-dark/70 dark:text-foreground/70">{t('settings.google.redirectUri')}</span>
|
||||
<p className="text-xs text-duck-dark/40 dark:text-foreground/40">{t('settings.google.redirectUriHint')}</p>
|
||||
<Input
|
||||
className="h-11 bg-background/60 border-duck-dark/20 text-duck-dark/60"
|
||||
type="text"
|
||||
|
||||
Reference in New Issue
Block a user