feat(34-06): wire useTranslation into routes and settings currency suggestion
- Add useTranslation to routes/index.tsx: home section headings use t() - Add useTranslation to routes/profile.tsx: all profile/security/danger zone strings use t() - Wire currency suggestion banner in settings.tsx with t() interpolation - Wire showConversions section title/description in settings.tsx - Add home and profile keys to en/common.json - Add currency.suggestion, currency.switch, showConversions to en/settings.json - Add corresponding German translations with proper umlauts to de/common.json and de/settings.json
This commit is contained in:
@@ -73,8 +73,42 @@
|
|||||||
"totalSpent": "Gesamtausgaben"
|
"totalSpent": "Gesamtausgaben"
|
||||||
},
|
},
|
||||||
"filter": {
|
"filter": {
|
||||||
"showing": "{{filtered}} von {{total}} Gegenstaenden",
|
"showing": "{{filtered}} von {{total}} Gegenständen",
|
||||||
"searchItems": "Gegenstaende suchen...",
|
"searchItems": "Gegenstände suchen...",
|
||||||
"allCategories": "Alle Kategorien"
|
"allCategories": "Alle Kategorien"
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"popularSetups": "Beliebte Setups",
|
||||||
|
"recentlyAdded": "Kürzlich hinzugefügt",
|
||||||
|
"trendingCategories": "Trend-Kategorien"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"title": "Profil",
|
||||||
|
"account": "Konto",
|
||||||
|
"accountInfo": "Ihre Kontoinformationen",
|
||||||
|
"email": "E-Mail",
|
||||||
|
"noEmail": "Keine E-Mail-Adresse hinterlegt",
|
||||||
|
"change": "Ändern",
|
||||||
|
"newEmailPlaceholder": "Neue E-Mail-Adresse",
|
||||||
|
"updating": "Wird aktualisiert...",
|
||||||
|
"updateEmail": "E-Mail aktualisieren",
|
||||||
|
"emailUpdated": "E-Mail aktualisiert",
|
||||||
|
"memberSince": "Mitglied seit",
|
||||||
|
"security": "Sicherheit",
|
||||||
|
"managePassword": "Passwort verwalten",
|
||||||
|
"currentPassword": "Aktuelles Passwort",
|
||||||
|
"newPassword": "Neues Passwort",
|
||||||
|
"password": "Passwort",
|
||||||
|
"confirmPassword": "Passwort bestätigen",
|
||||||
|
"passwordRequirements": "Das Passwort muss mindestens 8 Zeichen lang sein und Groß- und Kleinbuchstaben sowie eine Zahl enthalten.",
|
||||||
|
"passwordUpdated": "Passwort aktualisiert",
|
||||||
|
"changingPassword": "Wird geändert...",
|
||||||
|
"changePassword": "Passwort ändern",
|
||||||
|
"setPassword": "Passwort festlegen",
|
||||||
|
"dangerZone": "Gefahrenzone",
|
||||||
|
"dangerZoneDescription": "Konto und alle persönlichen Daten löschen. Öffentliche Setups werden als \"Gelöschter Benutzer\" angezeigt.",
|
||||||
|
"deleteAccount": "Konto löschen",
|
||||||
|
"deleteConfirmMessage": "Diese Aktion ist dauerhaft. Geben Sie LÖSCHEN ein, um zu bestätigen.",
|
||||||
|
"deleteConfirmPlaceholder": "Geben Sie LÖSCHEN ein, um zu bestätigen"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,14 @@
|
|||||||
"description": "Waehlen Sie die Einheit fuer die Gewichtsanzeige in der App"
|
"description": "Waehlen Sie die Einheit fuer die Gewichtsanzeige in der App"
|
||||||
},
|
},
|
||||||
"currency": {
|
"currency": {
|
||||||
"title": "Waehrung",
|
"title": "Währung",
|
||||||
"description": "Aendert das angezeigte Waehrungssymbol. Werte werden nicht umgerechnet."
|
"description": "Ändert das angezeigte Währungssymbol. Werte werden nicht umgerechnet.",
|
||||||
|
"suggestion": "Basierend auf Ihrer Region empfehlen wir {{symbol}} ({{code}})",
|
||||||
|
"switch": "Wechseln"
|
||||||
|
},
|
||||||
|
"showConversions": {
|
||||||
|
"title": "Umgerechnete Preise anzeigen",
|
||||||
|
"description": "Näherungsweise Umrechnungen anzeigen, wenn kein lokaler Preis verfügbar ist"
|
||||||
},
|
},
|
||||||
"apiKeys": {
|
"apiKeys": {
|
||||||
"title": "API-Schluessel",
|
"title": "API-Schluessel",
|
||||||
|
|||||||
@@ -76,5 +76,39 @@
|
|||||||
"showing": "Showing {{filtered}} of {{total}} items",
|
"showing": "Showing {{filtered}} of {{total}} items",
|
||||||
"searchItems": "Search items...",
|
"searchItems": "Search items...",
|
||||||
"allCategories": "All Categories"
|
"allCategories": "All Categories"
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"popularSetups": "Popular Setups",
|
||||||
|
"recentlyAdded": "Recently Added",
|
||||||
|
"trendingCategories": "Trending Categories"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"title": "Profile",
|
||||||
|
"account": "Account",
|
||||||
|
"accountInfo": "Your account information",
|
||||||
|
"email": "Email",
|
||||||
|
"noEmail": "No email on file",
|
||||||
|
"change": "Change",
|
||||||
|
"newEmailPlaceholder": "New email address",
|
||||||
|
"updating": "Updating...",
|
||||||
|
"updateEmail": "Update Email",
|
||||||
|
"emailUpdated": "Email updated",
|
||||||
|
"memberSince": "Member since",
|
||||||
|
"security": "Security",
|
||||||
|
"managePassword": "Manage your password",
|
||||||
|
"currentPassword": "Current Password",
|
||||||
|
"newPassword": "New Password",
|
||||||
|
"password": "Password",
|
||||||
|
"confirmPassword": "Confirm Password",
|
||||||
|
"passwordRequirements": "Password must be at least 8 characters with uppercase, lowercase, and a number.",
|
||||||
|
"passwordUpdated": "Password updated",
|
||||||
|
"changingPassword": "Changing...",
|
||||||
|
"changePassword": "Change Password",
|
||||||
|
"setPassword": "Set Password",
|
||||||
|
"dangerZone": "Danger Zone",
|
||||||
|
"dangerZoneDescription": "Delete your account and all personal data. Public setups will be attributed to \"Deleted User\".",
|
||||||
|
"deleteAccount": "Delete Account",
|
||||||
|
"deleteConfirmMessage": "This action is permanent. Type DELETE to confirm.",
|
||||||
|
"deleteConfirmPlaceholder": "Type DELETE to confirm"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,13 @@
|
|||||||
},
|
},
|
||||||
"currency": {
|
"currency": {
|
||||||
"title": "Currency",
|
"title": "Currency",
|
||||||
"description": "Changes the currency symbol displayed. This does not convert values."
|
"description": "Changes the currency symbol displayed. This does not convert values.",
|
||||||
|
"suggestion": "Based on your region, we suggest {{symbol}} ({{code}})",
|
||||||
|
"switch": "Switch"
|
||||||
|
},
|
||||||
|
"showConversions": {
|
||||||
|
"title": "Show Converted Prices",
|
||||||
|
"description": "Display approximate conversions when local price is not available"
|
||||||
},
|
},
|
||||||
"apiKeys": {
|
"apiKeys": {
|
||||||
"title": "API Keys",
|
"title": "API Keys",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { GlobalItemCard } from "../components/GlobalItemCard";
|
import { GlobalItemCard } from "../components/GlobalItemCard";
|
||||||
import { PublicSetupCard } from "../components/PublicSetupCard";
|
import { PublicSetupCard } from "../components/PublicSetupCard";
|
||||||
import {
|
import {
|
||||||
@@ -22,6 +23,7 @@ function LandingPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function PopularSetupsSection() {
|
function PopularSetupsSection() {
|
||||||
|
const { t } = useTranslation("common");
|
||||||
const { data, isLoading } = useDiscoverySetups(6);
|
const { data, isLoading } = useDiscoverySetups(6);
|
||||||
const setups = data?.items ?? [];
|
const setups = data?.items ?? [];
|
||||||
|
|
||||||
@@ -30,7 +32,7 @@ function PopularSetupsSection() {
|
|||||||
return (
|
return (
|
||||||
<section className="mb-12">
|
<section className="mb-12">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h2 className="text-lg font-semibold text-gray-900">Popular Setups</h2>
|
<h2 className="text-lg font-semibold text-gray-900">{t("home.popularSetups")}</h2>
|
||||||
</div>
|
</div>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<SectionSkeleton count={6} aspect="none" />
|
<SectionSkeleton count={6} aspect="none" />
|
||||||
@@ -46,6 +48,7 @@ function PopularSetupsSection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function RecentItemsSection() {
|
function RecentItemsSection() {
|
||||||
|
const { t } = useTranslation("common");
|
||||||
const { data, isLoading } = useDiscoveryItems(8);
|
const { data, isLoading } = useDiscoveryItems(8);
|
||||||
const items = data?.items ?? [];
|
const items = data?.items ?? [];
|
||||||
|
|
||||||
@@ -54,7 +57,7 @@ function RecentItemsSection() {
|
|||||||
return (
|
return (
|
||||||
<section className="mb-12">
|
<section className="mb-12">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h2 className="text-lg font-semibold text-gray-900">Recently Added</h2>
|
<h2 className="text-lg font-semibold text-gray-900">{t("home.recentlyAdded")}</h2>
|
||||||
</div>
|
</div>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<SectionSkeleton count={8} aspect="[4/3]" />
|
<SectionSkeleton count={8} aspect="[4/3]" />
|
||||||
@@ -79,6 +82,7 @@ function RecentItemsSection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function TrendingCategoriesSection() {
|
function TrendingCategoriesSection() {
|
||||||
|
const { t } = useTranslation("common");
|
||||||
const { data, isLoading } = useDiscoveryCategories(12);
|
const { data, isLoading } = useDiscoveryCategories(12);
|
||||||
const categories = data ?? [];
|
const categories = data ?? [];
|
||||||
|
|
||||||
@@ -88,7 +92,7 @@ function TrendingCategoriesSection() {
|
|||||||
<section className="mb-12">
|
<section className="mb-12">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h2 className="text-lg font-semibold text-gray-900">
|
<h2 className="text-lg font-semibold text-gray-900">
|
||||||
Trending Categories
|
{t("home.trendingCategories")}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
|
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { ProfileSection } from "../components/ProfileSection";
|
import { ProfileSection } from "../components/ProfileSection";
|
||||||
import {
|
import {
|
||||||
useChangeEmail,
|
useChangeEmail,
|
||||||
@@ -14,6 +15,7 @@ export const Route = createFileRoute("/profile")({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function ProfilePage() {
|
function ProfilePage() {
|
||||||
|
const { t } = useTranslation("common");
|
||||||
const { data: auth, isLoading } = useAuth();
|
const { data: auth, isLoading } = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -34,9 +36,9 @@ function ProfilePage() {
|
|||||||
to="/"
|
to="/"
|
||||||
className="text-sm text-gray-500 hover:text-gray-700 mb-2 inline-block"
|
className="text-sm text-gray-500 hover:text-gray-700 mb-2 inline-block"
|
||||||
>
|
>
|
||||||
← Back
|
← {t("actions.back")}
|
||||||
</Link>
|
</Link>
|
||||||
<h1 className="text-xl font-semibold text-gray-900">Profile</h1>
|
<h1 className="text-xl font-semibold text-gray-900">{t("profile.title")}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Section 1: Profile Info (D-02) */}
|
{/* Section 1: Profile Info (D-02) */}
|
||||||
@@ -74,6 +76,7 @@ function AccountInfoSection({
|
|||||||
email?: string;
|
email?: string;
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation("common");
|
||||||
const changeEmail = useChangeEmail();
|
const changeEmail = useChangeEmail();
|
||||||
const [editing, setEditing] = useState(false);
|
const [editing, setEditing] = useState(false);
|
||||||
const [newEmail, setNewEmail] = useState("");
|
const [newEmail, setNewEmail] = useState("");
|
||||||
@@ -83,7 +86,7 @@ function AccountInfoSection({
|
|||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
const memberSince = createdAt
|
const memberSince = createdAt
|
||||||
? new Intl.DateTimeFormat("en-US", {
|
? new Intl.DateTimeFormat(undefined, {
|
||||||
month: "long",
|
month: "long",
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
}).format(new Date(createdAt))
|
}).format(new Date(createdAt))
|
||||||
@@ -94,7 +97,7 @@ function AccountInfoSection({
|
|||||||
setMessage(null);
|
setMessage(null);
|
||||||
try {
|
try {
|
||||||
await changeEmail.mutateAsync({ newEmail: newEmail.trim() });
|
await changeEmail.mutateAsync({ newEmail: newEmail.trim() });
|
||||||
setMessage({ type: "success", text: "Email updated" });
|
setMessage({ type: "success", text: t("profile.emailUpdated") });
|
||||||
setEditing(false);
|
setEditing(false);
|
||||||
setNewEmail("");
|
setNewEmail("");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -105,16 +108,16 @@ function AccountInfoSection({
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-medium text-gray-900">Account</h3>
|
<h3 className="text-sm font-medium text-gray-900">{t("profile.account")}</h3>
|
||||||
<p className="text-xs text-gray-500 mt-0.5">Your account information</p>
|
<p className="text-xs text-gray-500 mt-0.5">{t("profile.accountInfo")}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Email row */}
|
{/* Email row */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-sm font-medium text-gray-700">Email</span>
|
<span className="text-sm font-medium text-gray-700">{t("profile.email")}</span>
|
||||||
<span className="text-sm text-gray-900 ml-3">
|
<span className="text-sm text-gray-900 ml-3">
|
||||||
{email || "No email on file"}
|
{email || t("profile.noEmail")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{!editing && (
|
{!editing && (
|
||||||
@@ -123,7 +126,7 @@ function AccountInfoSection({
|
|||||||
onClick={() => setEditing(true)}
|
onClick={() => setEditing(true)}
|
||||||
className="text-sm text-gray-600 hover:text-gray-800 transition-colors"
|
className="text-sm text-gray-600 hover:text-gray-800 transition-colors"
|
||||||
>
|
>
|
||||||
Change
|
{t("profile.change")}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -133,7 +136,7 @@ function AccountInfoSection({
|
|||||||
<form onSubmit={handleEmailChange} className="flex gap-2">
|
<form onSubmit={handleEmailChange} className="flex gap-2">
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
placeholder="New email address"
|
placeholder={t("profile.newEmailPlaceholder")}
|
||||||
value={newEmail}
|
value={newEmail}
|
||||||
onChange={(e) => setNewEmail(e.target.value)}
|
onChange={(e) => setNewEmail(e.target.value)}
|
||||||
required
|
required
|
||||||
@@ -144,7 +147,7 @@ function AccountInfoSection({
|
|||||||
disabled={changeEmail.isPending}
|
disabled={changeEmail.isPending}
|
||||||
className="px-4 py-2 text-sm font-medium text-white bg-gray-700 hover:bg-gray-800 disabled:opacity-50 rounded-lg transition-colors"
|
className="px-4 py-2 text-sm font-medium text-white bg-gray-700 hover:bg-gray-800 disabled:opacity-50 rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
{changeEmail.isPending ? "Updating..." : "Update Email"}
|
{changeEmail.isPending ? t("profile.updating") : t("profile.updateEmail")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -155,7 +158,7 @@ function AccountInfoSection({
|
|||||||
}}
|
}}
|
||||||
className="px-3 py-2 text-sm text-gray-500 hover:text-gray-700 transition-colors"
|
className="px-3 py-2 text-sm text-gray-500 hover:text-gray-700 transition-colors"
|
||||||
>
|
>
|
||||||
Cancel
|
{t("actions.cancel")}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
@@ -172,7 +175,7 @@ function AccountInfoSection({
|
|||||||
{memberSince && (
|
{memberSince && (
|
||||||
<div>
|
<div>
|
||||||
<span className="text-sm font-medium text-gray-700">
|
<span className="text-sm font-medium text-gray-700">
|
||||||
Member since
|
{t("profile.memberSince")}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-sm text-gray-500 ml-3">{memberSince}</span>
|
<span className="text-sm text-gray-500 ml-3">{memberSince}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -184,6 +187,7 @@ function AccountInfoSection({
|
|||||||
// ── Security Section ────────────────────────────────────────────────
|
// ── Security Section ────────────────────────────────────────────────
|
||||||
|
|
||||||
function SecuritySection() {
|
function SecuritySection() {
|
||||||
|
const { t } = useTranslation("common");
|
||||||
const { data: hasPasswordData } = useHasPassword();
|
const { data: hasPasswordData } = useHasPassword();
|
||||||
const changePassword = useChangePassword();
|
const changePassword = useChangePassword();
|
||||||
const hasPassword = hasPasswordData?.hasPassword ?? true;
|
const hasPassword = hasPasswordData?.hasPassword ?? true;
|
||||||
@@ -215,7 +219,7 @@ function SecuritySection() {
|
|||||||
currentPassword: hasPassword ? currentPassword : "",
|
currentPassword: hasPassword ? currentPassword : "",
|
||||||
newPassword,
|
newPassword,
|
||||||
});
|
});
|
||||||
setMessage({ type: "success", text: "Password updated" });
|
setMessage({ type: "success", text: t("profile.passwordUpdated") });
|
||||||
// Per T-28-08: clear password fields on success
|
// Per T-28-08: clear password fields on success
|
||||||
setCurrentPassword("");
|
setCurrentPassword("");
|
||||||
setNewPassword("");
|
setNewPassword("");
|
||||||
@@ -228,8 +232,8 @@ function SecuritySection() {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-medium text-gray-900">Security</h3>
|
<h3 className="text-sm font-medium text-gray-900">{t("profile.security")}</h3>
|
||||||
<p className="text-xs text-gray-500 mt-0.5">Manage your password</p>
|
<p className="text-xs text-gray-500 mt-0.5">{t("profile.managePassword")}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-3">
|
<form onSubmit={handleSubmit} className="space-y-3">
|
||||||
@@ -239,7 +243,7 @@ function SecuritySection() {
|
|||||||
htmlFor="currentPassword"
|
htmlFor="currentPassword"
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
className="block text-sm font-medium text-gray-700 mb-1"
|
||||||
>
|
>
|
||||||
Current Password
|
{t("profile.currentPassword")}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="currentPassword"
|
id="currentPassword"
|
||||||
@@ -256,7 +260,7 @@ function SecuritySection() {
|
|||||||
htmlFor="newPassword"
|
htmlFor="newPassword"
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
className="block text-sm font-medium text-gray-700 mb-1"
|
||||||
>
|
>
|
||||||
{hasPassword ? "New Password" : "Password"}
|
{hasPassword ? t("profile.newPassword") : t("profile.password")}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="newPassword"
|
id="newPassword"
|
||||||
@@ -272,7 +276,7 @@ function SecuritySection() {
|
|||||||
htmlFor="confirmPassword"
|
htmlFor="confirmPassword"
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
className="block text-sm font-medium text-gray-700 mb-1"
|
||||||
>
|
>
|
||||||
Confirm Password
|
{t("profile.confirmPassword")}
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="confirmPassword"
|
id="confirmPassword"
|
||||||
@@ -284,8 +288,7 @@ function SecuritySection() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-gray-400">
|
<p className="text-xs text-gray-400">
|
||||||
Password must be at least 8 characters with uppercase, lowercase, and
|
{t("profile.passwordRequirements")}
|
||||||
a number.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{message && (
|
{message && (
|
||||||
@@ -302,10 +305,10 @@ function SecuritySection() {
|
|||||||
className="px-4 py-2 text-sm font-medium text-white bg-gray-700 hover:bg-gray-800 disabled:opacity-50 rounded-lg transition-colors"
|
className="px-4 py-2 text-sm font-medium text-white bg-gray-700 hover:bg-gray-800 disabled:opacity-50 rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
{changePassword.isPending
|
{changePassword.isPending
|
||||||
? "Changing..."
|
? t("profile.changingPassword")
|
||||||
: hasPassword
|
: hasPassword
|
||||||
? "Change Password"
|
? t("profile.changePassword")
|
||||||
: "Set Password"}
|
: t("profile.setPassword")}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -315,6 +318,7 @@ function SecuritySection() {
|
|||||||
// ── Danger Zone Section ─────────────────────────────────────────────
|
// ── Danger Zone Section ─────────────────────────────────────────────
|
||||||
|
|
||||||
function DangerZoneSection() {
|
function DangerZoneSection() {
|
||||||
|
const { t } = useTranslation("common");
|
||||||
const deleteAccount = useDeleteAccount();
|
const deleteAccount = useDeleteAccount();
|
||||||
const [showConfirm, setShowConfirm] = useState(false);
|
const [showConfirm, setShowConfirm] = useState(false);
|
||||||
const [confirmation, setConfirmation] = useState("");
|
const [confirmation, setConfirmation] = useState("");
|
||||||
@@ -331,10 +335,9 @@ function DangerZoneSection() {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-medium text-gray-900">Danger Zone</h3>
|
<h3 className="text-sm font-medium text-gray-900">{t("profile.dangerZone")}</h3>
|
||||||
<p className="text-xs text-gray-500 mt-0.5">
|
<p className="text-xs text-gray-500 mt-0.5">
|
||||||
Delete your account and all personal data. Public setups will be
|
{t("profile.dangerZoneDescription")}
|
||||||
attributed to "Deleted User".
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -344,16 +347,16 @@ function DangerZoneSection() {
|
|||||||
onClick={() => setShowConfirm(true)}
|
onClick={() => setShowConfirm(true)}
|
||||||
className="px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 rounded-lg transition-colors"
|
className="px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
Delete Account
|
{t("profile.deleteAccount")}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<p className="text-sm text-red-600">
|
<p className="text-sm text-red-600">
|
||||||
This action is permanent. Type DELETE to confirm.
|
{t("profile.deleteConfirmMessage")}
|
||||||
</p>
|
</p>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Type DELETE to confirm"
|
placeholder={t("profile.deleteConfirmPlaceholder")}
|
||||||
value={confirmation}
|
value={confirmation}
|
||||||
onChange={(e) => setConfirmation(e.target.value)}
|
onChange={(e) => setConfirmation(e.target.value)}
|
||||||
className="w-full px-3 py-2 border border-red-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-red-200"
|
className="w-full px-3 py-2 border border-red-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-red-200"
|
||||||
@@ -367,7 +370,7 @@ function DangerZoneSection() {
|
|||||||
}}
|
}}
|
||||||
className="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
|
className="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
Cancel
|
{t("actions.cancel")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -375,7 +378,7 @@ function DangerZoneSection() {
|
|||||||
disabled={confirmation !== "DELETE" || deleteAccount.isPending}
|
disabled={confirmation !== "DELETE" || deleteAccount.isPending}
|
||||||
className="px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 disabled:opacity-50 rounded-lg transition-colors"
|
className="px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-700 disabled:opacity-50 rounded-lg transition-colors"
|
||||||
>
|
>
|
||||||
{deleteAccount.isPending ? "Deleting..." : "Delete Account"}
|
{deleteAccount.isPending ? t("actions.deleting") : t("profile.deleteAccount")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -295,10 +295,12 @@ function SettingsPage() {
|
|||||||
{showSuggestion && (
|
{showSuggestion && (
|
||||||
<div className="bg-blue-50 border border-blue-100 rounded-xl px-4 py-3 mb-4 flex items-center gap-3">
|
<div className="bg-blue-50 border border-blue-100 rounded-xl px-4 py-3 mb-4 flex items-center gap-3">
|
||||||
<span className="text-sm text-blue-700 flex-1">
|
<span className="text-sm text-blue-700 flex-1">
|
||||||
Based on your region, we suggest{" "}
|
{t("currency.suggestion", {
|
||||||
{CURRENCIES.find((c) => c.value === suggestedCurrency)?.label ??
|
symbol:
|
||||||
suggestedCurrency}{" "}
|
CURRENCIES.find((c) => c.value === suggestedCurrency)?.label ??
|
||||||
({suggestedCurrency})
|
suggestedCurrency,
|
||||||
|
code: suggestedCurrency,
|
||||||
|
})}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -311,13 +313,13 @@ function SettingsPage() {
|
|||||||
}}
|
}}
|
||||||
className="text-sm font-medium text-blue-700 hover:text-blue-800 underline shrink-0"
|
className="text-sm font-medium text-blue-700 hover:text-blue-800 underline shrink-0"
|
||||||
>
|
>
|
||||||
Switch
|
{t("currency.switch")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setSuggestionDismissed(true)}
|
onClick={() => setSuggestionDismissed(true)}
|
||||||
className="p-1 text-blue-400 hover:text-blue-600 rounded shrink-0"
|
className="p-1 text-blue-400 hover:text-blue-600 rounded shrink-0"
|
||||||
aria-label="Dismiss"
|
aria-label={t("common:actions.dismiss")}
|
||||||
>
|
>
|
||||||
<LucideIcon name="x" size={16} />
|
<LucideIcon name="x" size={16} />
|
||||||
</button>
|
</button>
|
||||||
@@ -428,10 +430,10 @@ function SettingsPage() {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-medium text-gray-900">
|
<h3 className="text-sm font-medium text-gray-900">
|
||||||
Show Converted Prices
|
{t("showConversions.title")}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs text-gray-500 mt-0.5">
|
<p className="text-xs text-gray-500 mt-0.5">
|
||||||
Display approximate conversions when local price is not available
|
{t("showConversions.description")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user