Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions apps/web/src/components/domain/captcha-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@ export function CaptchaForm({
const [saving, setSaving] = useState(false);
const [removing, setRemoving] = useState(false);
const [error, setError] = useState<string | null>(null);
const [saved, setSaved] = useState(false);

const configured = Boolean(initial.siteKey) && initial.hasSecret;
// Reflect the live state, not the initial server snapshot, so the "Active"
// badge and Remove button appear immediately after the first save.
const configured = Boolean(siteKey.trim()) && hasSecret;

async function save() {
setError(null);
setSaved(false);
setSaving(true);
try {
const res = await fetch(`/api/guilds/${guildId}/captcha`, {
Expand All @@ -41,6 +45,7 @@ export function CaptchaForm({
if (!res.ok) throw new Error(data?.error ?? t.errSave);
setHasSecret(true);
setSecret("");
setSaved(true);
} catch (err) {
setError(err instanceof Error ? err.message : t.errSave);
} finally {
Expand All @@ -50,6 +55,7 @@ export function CaptchaForm({

async function remove() {
setError(null);
setSaved(false);
setRemoving(true);
try {
const res = await fetch(`/api/guilds/${guildId}/captcha`, { method: "DELETE" });
Expand Down Expand Up @@ -102,13 +108,21 @@ export function CaptchaForm({
<Input
type="password"
value={secret}
onChange={(e) => setSecret(e.target.value)}
onChange={(e) => {
setSecret(e.target.value);
setSaved(false);
}}
placeholder={hasSecret ? t.secretPlaceholderSet : t.secretPlaceholderNew}
autoComplete="off"
/>
</Field>

{error && <p className="text-sm text-destructive">{error}</p>}
{saved && !error && (
<p className="rounded-md border border-primary/30 bg-primary/10 px-3 py-2 text-sm text-primary">
{t.saved}
</p>
)}

<div className="flex items-center gap-2">
<Button type="button" onClick={save} disabled={saving || !siteKey.trim()}>
Expand Down
18 changes: 16 additions & 2 deletions apps/web/src/components/domain/oauth-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,17 @@ export function OAuthForm({
const [saving, setSaving] = useState(false);
const [removing, setRemoving] = useState(false);
const [error, setError] = useState<string | null>(null);
const [saved, setSaved] = useState(false);
const [copied, setCopied] = useState(false);

const redirectUri = customDomain ? `https://${customDomain}/api/auth/discord/callback` : "";
const configured = Boolean(initial.clientId) && initial.hasSecret;
// Reflect the live state, not the initial server snapshot, so the "Active"
// badge and Remove button appear immediately after the first save.
const configured = Boolean(clientId.trim()) && hasSecret;

async function save() {
setError(null);
setSaved(false);
setSaving(true);
try {
const res = await fetch(`/api/guilds/${guildId}/oauth`, {
Expand All @@ -43,6 +47,7 @@ export function OAuthForm({
if (!res.ok) throw new Error(data?.error ?? t.errSave);
setHasSecret(true);
setClientSecret("");
setSaved(true);
} catch (err) {
setError(err instanceof Error ? err.message : t.errSave);
} finally {
Expand All @@ -52,6 +57,7 @@ export function OAuthForm({

async function remove() {
setError(null);
setSaved(false);
setRemoving(true);
try {
const res = await fetch(`/api/guilds/${guildId}/oauth`, { method: "DELETE" });
Expand Down Expand Up @@ -128,13 +134,21 @@ export function OAuthForm({
<Input
type="password"
value={clientSecret}
onChange={(e) => setClientSecret(e.target.value)}
onChange={(e) => {
setClientSecret(e.target.value);
setSaved(false);
}}
placeholder={hasSecret ? t.secretPlaceholderSet : t.secretPlaceholderNew}
autoComplete="off"
/>
</Field>

{error && <p className="text-sm text-destructive">{error}</p>}
{saved && !error && (
<p className="rounded-md border border-primary/30 bg-primary/10 px-3 py-2 text-sm text-primary">
{t.saved}
</p>
)}

<div className="flex items-center gap-2">
<Button type="button" onClick={save} disabled={saving || !clientId.trim()}>
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/i18n/dictionaries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ const en = {
secretPlaceholderNew: "Client secret",
copy: "Copy", copied: "Copied!",
active: "Active", save: "Save", saving: "Saving…", remove: "Remove", removing: "Removing…",
saved: "Saved. Your secret is stored securely and stays hidden — the field is blank on purpose. Leave it empty next time to keep it.",
errSave: "Could not save the credentials.", errAction: "Action failed.",
},
captcha: {
Expand All @@ -351,6 +352,7 @@ const en = {
secretPlaceholderSet: "•••••••• (unchanged — leave blank to keep)",
secretPlaceholderNew: "Secret key",
active: "Active", save: "Save", saving: "Saving…", remove: "Remove", removing: "Removing…",
saved: "Saved. Your secret is stored securely and stays hidden — the field is blank on purpose. Leave it empty next time to keep it.",
errSave: "Could not save the keys.", errAction: "Action failed.",
},
},
Expand Down Expand Up @@ -806,6 +808,7 @@ const de: Dictionary = {
secretPlaceholderNew: "Client-Secret",
copy: "Kopieren", copied: "Kopiert!",
active: "Aktiv", save: "Speichern", saving: "Wird gespeichert…", remove: "Entfernen", removing: "Wird entfernt…",
saved: "Gespeichert. Dein Secret ist sicher hinterlegt und bleibt verborgen — das Feld ist absichtlich leer. Lass es beim nächsten Mal leer, um es zu behalten.",
errSave: "Credentials konnten nicht gespeichert werden.", errAction: "Aktion fehlgeschlagen.",
},
captcha: {
Expand All @@ -820,6 +823,7 @@ const de: Dictionary = {
secretPlaceholderSet: "•••••••• (unverändert — leer lassen, um es zu behalten)",
secretPlaceholderNew: "Secret Key",
active: "Aktiv", save: "Speichern", saving: "Wird gespeichert…", remove: "Entfernen", removing: "Wird entfernt…",
saved: "Gespeichert. Dein Secret ist sicher hinterlegt und bleibt verborgen — das Feld ist absichtlich leer. Lass es beim nächsten Mal leer, um es zu behalten.",
errSave: "Keys konnten nicht gespeichert werden.", errAction: "Aktion fehlgeschlagen.",
},
},
Expand Down Expand Up @@ -1273,6 +1277,7 @@ const hu: Dictionary = {
secretPlaceholderNew: "Client Secret",
copy: "Másol", copied: "Másolva!",
active: "Aktív", save: "Mentés", saving: "Mentés…", remove: "Eltávolítás", removing: "Eltávolítás…",
saved: "Mentve. A titok biztonságosan tárolva és rejtve marad — a mező szándékosan üres. Hagyd üresen legközelebb a megtartásához.",
errSave: "A hitelesítő adatok mentése sikertelen.", errAction: "A művelet sikertelen.",
},
captcha: {
Expand All @@ -1287,6 +1292,7 @@ const hu: Dictionary = {
secretPlaceholderSet: "•••••••• (változatlan — hagyd üresen a megtartásához)",
secretPlaceholderNew: "Secret Key",
active: "Aktív", save: "Mentés", saving: "Mentés…", remove: "Eltávolítás", removing: "Eltávolítás…",
saved: "Mentve. A titok biztonságosan tárolva és rejtve marad — a mező szándékosan üres. Hagyd üresen legközelebb a megtartásához.",
errSave: "A kulcsok mentése sikertelen.", errAction: "A művelet sikertelen.",
},
},
Expand Down