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
|
|
@ -11,11 +11,13 @@ import { components } from "../generated/api";
|
|||
import constants from "../../../shared/constants.json";
|
||||
import { Permission } from "../auth-context";
|
||||
import { cardStyles, typographyStyles, buttonStyles } from "../styles/shared";
|
||||
import { useTranslation } from "../hooks/useTranslation";
|
||||
|
||||
// Use generated type from OpenAPI schema
|
||||
type Invite = components["schemas"]["UserInviteResponse"];
|
||||
|
||||
export default function InvitesPage() {
|
||||
const t = useTranslation("invites");
|
||||
const { user, isLoading, isAuthorized } = useRequireAuth({
|
||||
requiredPermission: Permission.VIEW_OWN_INVITES,
|
||||
fallbackRedirect: "/admin/trades",
|
||||
|
|
@ -62,26 +64,21 @@ export default function InvitesPage() {
|
|||
>
|
||||
<div style={styles.pageCard}>
|
||||
<div style={cardStyles.cardHeader}>
|
||||
<h1 style={cardStyles.cardTitle}>My Invites</h1>
|
||||
<p style={cardStyles.cardSubtitle}>
|
||||
Share your invite codes with friends to let them join
|
||||
</p>
|
||||
<h1 style={cardStyles.cardTitle}>{t("page.title")}</h1>
|
||||
<p style={cardStyles.cardSubtitle}>{t("page.subtitle")}</p>
|
||||
</div>
|
||||
|
||||
{(invites?.length ?? 0) === 0 ? (
|
||||
<EmptyState
|
||||
message="You don't have any invites yet."
|
||||
hint="Contact an admin if you need invite codes to share."
|
||||
/>
|
||||
<EmptyState message={t("page.noInvites")} hint={t("page.noInvitesHint")} />
|
||||
) : (
|
||||
<div style={styles.sections}>
|
||||
{/* Ready Invites */}
|
||||
{readyInvites.length > 0 && (
|
||||
<div style={styles.section}>
|
||||
<h2 style={typographyStyles.sectionTitle}>Available ({readyInvites.length})</h2>
|
||||
<p style={typographyStyles.sectionHint}>
|
||||
Share these links with people you want to invite
|
||||
</p>
|
||||
<h2 style={typographyStyles.sectionTitle}>
|
||||
{t("page.available", { count: readyInvites.length })}
|
||||
</h2>
|
||||
<p style={typographyStyles.sectionHint}>{t("page.availableHint")}</p>
|
||||
<div style={styles.inviteList}>
|
||||
{readyInvites.map((invite) => (
|
||||
<div key={invite.id} style={styles.inviteCard}>
|
||||
|
|
@ -91,7 +88,7 @@ export default function InvitesPage() {
|
|||
onClick={() => copyToClipboard(invite)}
|
||||
style={buttonStyles.accentButton}
|
||||
>
|
||||
{copiedId === invite.id ? "Copied!" : "Copy Link"}
|
||||
{copiedId === invite.id ? t("page.copied") : t("page.copyLink")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -103,14 +100,20 @@ export default function InvitesPage() {
|
|||
{/* Spent Invites */}
|
||||
{spentInvites.length > 0 && (
|
||||
<div style={styles.section}>
|
||||
<h2 style={typographyStyles.sectionTitle}>Used ({spentInvites.length})</h2>
|
||||
<h2 style={typographyStyles.sectionTitle}>
|
||||
{t("page.used", { count: spentInvites.length })}
|
||||
</h2>
|
||||
<div style={styles.inviteList}>
|
||||
{spentInvites.map((invite) => (
|
||||
<div key={invite.id} style={styles.inviteCardSpent}>
|
||||
<div style={styles.inviteCode}>{invite.identifier}</div>
|
||||
<div style={styles.inviteeMeta}>
|
||||
<StatusBadge variant="success">Used</StatusBadge>
|
||||
<span style={styles.inviteeEmail}>by {invite.used_by_email}</span>
|
||||
<StatusBadge variant="success">{t("page.usedStatus")}</StatusBadge>
|
||||
{invite.used_by_email && (
|
||||
<span style={styles.inviteeEmail}>
|
||||
{t("page.usedBy", { email: invite.used_by_email })}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -121,12 +124,14 @@ export default function InvitesPage() {
|
|||
{/* Revoked Invites */}
|
||||
{revokedInvites.length > 0 && (
|
||||
<div style={styles.section}>
|
||||
<h2 style={typographyStyles.sectionTitle}>Revoked ({revokedInvites.length})</h2>
|
||||
<h2 style={typographyStyles.sectionTitle}>
|
||||
{t("page.revoked", { count: revokedInvites.length })}
|
||||
</h2>
|
||||
<div style={styles.inviteList}>
|
||||
{revokedInvites.map((invite) => (
|
||||
<div key={invite.id} style={styles.inviteCardRevoked}>
|
||||
<div style={styles.inviteCode}>{invite.identifier}</div>
|
||||
<StatusBadge variant="error">Revoked</StatusBadge>
|
||||
<StatusBadge variant="error">{t("page.revokedStatus")}</StatusBadge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue