Phase 6: Translate User Pages - exchange, trades, invites, profile
- Expand exchange.json with all exchange page strings (page, steps, detailsStep, bookingStep, confirmationStep, priceDisplay) - Create trades.json translation files for es, en, ca - Create invites.json translation files for es, en, ca - Create profile.json translation files for es, en, ca - Translate exchange page and all components (ExchangeDetailsStep, BookingStep, ConfirmationStep, StepIndicator, PriceDisplay) - Translate trades page (titles, sections, buttons, status labels) - Translate invites page (titles, sections, status badges, copy button) - Translate profile page (form labels, hints, placeholders, messages) - Update IntlProvider to load all new namespaces - All frontend tests passing
This commit is contained in:
parent
7dd13292a0
commit
246553c402
22 changed files with 559 additions and 115 deletions
|
|
@ -19,6 +19,7 @@ import {
|
|||
utilityStyles,
|
||||
} from "../styles/shared";
|
||||
import { validateProfileFields } from "../utils/validation";
|
||||
import { useTranslation } from "../hooks/useTranslation";
|
||||
|
||||
// Use generated type from OpenAPI schema
|
||||
type ProfileData = components["schemas"]["ProfileResponse"];
|
||||
|
|
@ -41,6 +42,7 @@ function toFormData(data: ProfileData): FormData {
|
|||
}
|
||||
|
||||
export default function ProfilePage() {
|
||||
const t = useTranslation("profile");
|
||||
const { user, isLoading, isAuthorized } = useRequireAuth({
|
||||
requiredPermission: Permission.MANAGE_OWN_PROFILE,
|
||||
fallbackRedirect: "/admin/trades",
|
||||
|
|
@ -88,7 +90,7 @@ export default function ProfilePage() {
|
|||
setGodfatherEmail(data.godfather_email ?? null);
|
||||
} catch (err) {
|
||||
console.error("Profile load error:", err);
|
||||
setToast({ message: "Failed to load profile", type: "error" });
|
||||
setToast({ message: t("messages.loadError"), type: "error" });
|
||||
} finally {
|
||||
setIsLoadingProfile(false);
|
||||
}
|
||||
|
|
@ -141,16 +143,16 @@ export default function ProfilePage() {
|
|||
const formValues = toFormData(data);
|
||||
setFormData(formValues);
|
||||
setOriginalData(formValues);
|
||||
setToast({ message: "Profile saved successfully!", type: "success" });
|
||||
setToast({ message: t("messages.saveSuccess"), type: "success" });
|
||||
} catch (err) {
|
||||
console.error("Profile save error:", err);
|
||||
const fieldErrors = extractFieldErrors(err);
|
||||
if (fieldErrors?.detail?.field_errors) {
|
||||
setErrors(fieldErrors.detail.field_errors);
|
||||
setToast({ message: "Please fix the errors below", type: "error" });
|
||||
setToast({ message: t("messages.fixErrors"), type: "error" });
|
||||
} else {
|
||||
setToast({
|
||||
message: extractApiErrorMessage(err, "Network error. Please try again."),
|
||||
message: extractApiErrorMessage(err, t("messages.networkError")),
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
|
|
@ -181,16 +183,16 @@ export default function ProfilePage() {
|
|||
<div style={layoutStyles.contentCentered}>
|
||||
<div style={styles.profileCard}>
|
||||
<div style={cardStyles.cardHeader}>
|
||||
<h1 style={cardStyles.cardTitle}>My Profile</h1>
|
||||
<p style={cardStyles.cardSubtitle}>Manage your contact information</p>
|
||||
<h1 style={cardStyles.cardTitle}>{t("page.title")}</h1>
|
||||
<p style={cardStyles.cardSubtitle}>{t("page.subtitle")}</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} style={formStyles.form}>
|
||||
{/* Login email - read only */}
|
||||
<div style={formStyles.field}>
|
||||
<label style={styles.labelWithBadge}>
|
||||
Login Email
|
||||
<span style={utilityStyles.readOnlyBadge}>Read only</span>
|
||||
{t("form.email")}
|
||||
<span style={utilityStyles.readOnlyBadge}>{t("form.readOnly")}</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
|
|
@ -198,36 +200,32 @@ export default function ProfilePage() {
|
|||
style={{ ...formStyles.input, ...formStyles.inputReadOnly }}
|
||||
disabled
|
||||
/>
|
||||
<span style={formStyles.hint}>
|
||||
This is your login email and cannot be changed here.
|
||||
</span>
|
||||
<span style={formStyles.hint}>{t("form.emailHint")}</span>
|
||||
</div>
|
||||
|
||||
{/* Godfather - shown if user was invited */}
|
||||
{godfatherEmail && (
|
||||
<div style={formStyles.field}>
|
||||
<label style={styles.labelWithBadge}>
|
||||
Invited By
|
||||
<span style={utilityStyles.readOnlyBadge}>Read only</span>
|
||||
{t("form.invitedBy")}
|
||||
<span style={utilityStyles.readOnlyBadge}>{t("form.readOnly")}</span>
|
||||
</label>
|
||||
<div style={styles.godfatherBox}>
|
||||
<span style={styles.godfatherEmail}>{godfatherEmail}</span>
|
||||
</div>
|
||||
<span style={formStyles.hint}>The user who invited you to join.</span>
|
||||
<span style={formStyles.hint}>{t("form.invitedByHint")}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={utilityStyles.divider} />
|
||||
|
||||
<p style={styles.sectionLabel}>Contact Details</p>
|
||||
<p style={styles.sectionHint}>
|
||||
These are for communication purposes only — they won't affect your login.
|
||||
</p>
|
||||
<p style={styles.sectionLabel}>{t("form.contactDetails")}</p>
|
||||
<p style={styles.sectionHint}>{t("form.contactDetailsHint")}</p>
|
||||
|
||||
{/* Contact email */}
|
||||
<div style={formStyles.field}>
|
||||
<label htmlFor="contact_email" style={formStyles.label}>
|
||||
Contact Email
|
||||
{t("form.contactEmail")}
|
||||
</label>
|
||||
<input
|
||||
id="contact_email"
|
||||
|
|
@ -238,7 +236,7 @@ export default function ProfilePage() {
|
|||
...formStyles.input,
|
||||
...(errors.contact_email ? formStyles.inputError : {}),
|
||||
}}
|
||||
placeholder="alternate@example.com"
|
||||
placeholder={t("placeholders.contactEmail")}
|
||||
/>
|
||||
{errors.contact_email && (
|
||||
<span style={formStyles.errorText}>{errors.contact_email}</span>
|
||||
|
|
@ -248,7 +246,7 @@ export default function ProfilePage() {
|
|||
{/* Telegram */}
|
||||
<div style={formStyles.field}>
|
||||
<label htmlFor="telegram" style={formStyles.label}>
|
||||
Telegram
|
||||
{t("form.telegram")}
|
||||
</label>
|
||||
<input
|
||||
id="telegram"
|
||||
|
|
@ -259,7 +257,7 @@ export default function ProfilePage() {
|
|||
...formStyles.input,
|
||||
...(errors.telegram ? formStyles.inputError : {}),
|
||||
}}
|
||||
placeholder="@username"
|
||||
placeholder={t("placeholders.telegram")}
|
||||
/>
|
||||
{errors.telegram && <span style={formStyles.errorText}>{errors.telegram}</span>}
|
||||
</div>
|
||||
|
|
@ -267,7 +265,7 @@ export default function ProfilePage() {
|
|||
{/* Signal */}
|
||||
<div style={formStyles.field}>
|
||||
<label htmlFor="signal" style={formStyles.label}>
|
||||
Signal
|
||||
{t("form.signal")}
|
||||
</label>
|
||||
<input
|
||||
id="signal"
|
||||
|
|
@ -278,7 +276,7 @@ export default function ProfilePage() {
|
|||
...formStyles.input,
|
||||
...(errors.signal ? formStyles.inputError : {}),
|
||||
}}
|
||||
placeholder="username.01"
|
||||
placeholder={t("placeholders.signal")}
|
||||
/>
|
||||
{errors.signal && <span style={formStyles.errorText}>{errors.signal}</span>}
|
||||
</div>
|
||||
|
|
@ -286,7 +284,7 @@ export default function ProfilePage() {
|
|||
{/* Nostr npub */}
|
||||
<div style={formStyles.field}>
|
||||
<label htmlFor="nostr_npub" style={formStyles.label}>
|
||||
Nostr (npub)
|
||||
{t("form.nostrNpub")}
|
||||
</label>
|
||||
<input
|
||||
id="nostr_npub"
|
||||
|
|
@ -297,7 +295,7 @@ export default function ProfilePage() {
|
|||
...formStyles.input,
|
||||
...(errors.nostr_npub ? formStyles.inputError : {}),
|
||||
}}
|
||||
placeholder="npub1..."
|
||||
placeholder={t("placeholders.nostrNpub")}
|
||||
/>
|
||||
{errors.nostr_npub && <span style={formStyles.errorText}>{errors.nostr_npub}</span>}
|
||||
</div>
|
||||
|
|
@ -311,7 +309,7 @@ export default function ProfilePage() {
|
|||
}}
|
||||
disabled={!canSubmit}
|
||||
>
|
||||
{isSubmitting ? "Saving..." : "Save Changes"}
|
||||
{isSubmitting ? t("form.saving") : t("form.saveChanges")}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue