velxio/frontend/src/pages/UserProfilePage.tsx

124 lines
4.7 KiB
TypeScript
Raw Normal View History

import { useEffect, useState, useCallback } from 'react';
import { Link, useParams } from 'react-router-dom';
feat(i18n): translate AdminPage + UserProfile + Pricing + EditorPage shell (Editor block 10 — final cleanup) Closes the Phase 2 i18n rollout. Every visitor- and user-facing surface velxio renders in normal use now reads from t(). AdminPage (admin-only) - Header (panel title, logout) and the four tabs (Dashboard / Users / Projects / Boards). - Setup screen for first-admin creation (title, body, password fields + mismatch error + create-admin button). - Not-admin gate page. - EditUserModal (title, four labels, admin/active toggles, cancel/save). - UsersTab: search placeholder, count pluralisation, all 12 table columns, Activity / Edit / Delete actions, empty state, delete-confirm prompt with username interpolation. - ProjectsTab: search placeholder, count pluralisation, all 9 table columns, public/private badge labels, delete action + confirm with project-name interpolation, empty state. - All error messages (load failed / save failed / delete failed) fall back through t(). UserProfilePage - "New project" CTA, loading + empty + not-found states, "Private" project badge, "Copy shareable link" tooltip. - The /editor link uses localize() so /es/<username>'s "New project" button stays in Spanish. PricingPlaceholder - Title + the two paragraphs (self-hosted note + hosted Pro tier note + GitHub source note). Inline links wrapped via the Trans component so the link surface stays clickable in every locale without each translation having to re-write the HTML. EditorPage shell - Mobile bottom-tab labels (Code / Circuit), file-explorer toggle (Show / Hide), View mode aria-label, view-mode segmented control labels (Code / Both / Circuit), and the three "Drag to resize" handle tooltips on the panel splitters. Translations - en.json hand-curated for the new keys. - All 8 non-English locales auto-translated via the existing `npm run translate:i18n` pipeline (DeepSeek, ~5 min for the whole bundle, sameShape() validates each output before write). This closes Phase 2 of i18n. Phase 3 (DocsPage prose, AboutPage long-form paragraphs, the 15 SEO landing pages) is deliberately deferred — Docs/About are best handled by extracting the prose into JSON keys and running the same script, while the SEO pages are intentionally optimised for English keyword targeting and should not be machine-translated en masse.
2026-05-09 22:49:13 +07:00
import { useTranslation } from 'react-i18next';
import { getUserProjects, type ProjectResponse } from '../services/projectService';
import { useAuthStore } from '../store/useAuthStore';
import { AppHeader } from '../components/layout/AppHeader';
feat(i18n): translate AdminPage + UserProfile + Pricing + EditorPage shell (Editor block 10 — final cleanup) Closes the Phase 2 i18n rollout. Every visitor- and user-facing surface velxio renders in normal use now reads from t(). AdminPage (admin-only) - Header (panel title, logout) and the four tabs (Dashboard / Users / Projects / Boards). - Setup screen for first-admin creation (title, body, password fields + mismatch error + create-admin button). - Not-admin gate page. - EditUserModal (title, four labels, admin/active toggles, cancel/save). - UsersTab: search placeholder, count pluralisation, all 12 table columns, Activity / Edit / Delete actions, empty state, delete-confirm prompt with username interpolation. - ProjectsTab: search placeholder, count pluralisation, all 9 table columns, public/private badge labels, delete action + confirm with project-name interpolation, empty state. - All error messages (load failed / save failed / delete failed) fall back through t(). UserProfilePage - "New project" CTA, loading + empty + not-found states, "Private" project badge, "Copy shareable link" tooltip. - The /editor link uses localize() so /es/<username>'s "New project" button stays in Spanish. PricingPlaceholder - Title + the two paragraphs (self-hosted note + hosted Pro tier note + GitHub source note). Inline links wrapped via the Trans component so the link surface stays clickable in every locale without each translation having to re-write the HTML. EditorPage shell - Mobile bottom-tab labels (Code / Circuit), file-explorer toggle (Show / Hide), View mode aria-label, view-mode segmented control labels (Code / Both / Circuit), and the three "Drag to resize" handle tooltips on the panel splitters. Translations - en.json hand-curated for the new keys. - All 8 non-English locales auto-translated via the existing `npm run translate:i18n` pipeline (DeepSeek, ~5 min for the whole bundle, sameShape() validates each output before write). This closes Phase 2 of i18n. Phase 3 (DocsPage prose, AboutPage long-form paragraphs, the 15 SEO landing pages) is deliberately deferred — Docs/About are best handled by extracting the prose into JSON keys and running the same script, while the SEO pages are intentionally optimised for English keyword targeting and should not be machine-translated en masse.
2026-05-09 22:49:13 +07:00
import { useLocalizedHref } from '../i18n/useLocalizedNavigate';
import { useSEO } from '../utils/useSEO';
import './UserProfilePage.css';
export const UserProfilePage: React.FC = () => {
feat(i18n): translate AdminPage + UserProfile + Pricing + EditorPage shell (Editor block 10 — final cleanup) Closes the Phase 2 i18n rollout. Every visitor- and user-facing surface velxio renders in normal use now reads from t(). AdminPage (admin-only) - Header (panel title, logout) and the four tabs (Dashboard / Users / Projects / Boards). - Setup screen for first-admin creation (title, body, password fields + mismatch error + create-admin button). - Not-admin gate page. - EditUserModal (title, four labels, admin/active toggles, cancel/save). - UsersTab: search placeholder, count pluralisation, all 12 table columns, Activity / Edit / Delete actions, empty state, delete-confirm prompt with username interpolation. - ProjectsTab: search placeholder, count pluralisation, all 9 table columns, public/private badge labels, delete action + confirm with project-name interpolation, empty state. - All error messages (load failed / save failed / delete failed) fall back through t(). UserProfilePage - "New project" CTA, loading + empty + not-found states, "Private" project badge, "Copy shareable link" tooltip. - The /editor link uses localize() so /es/<username>'s "New project" button stays in Spanish. PricingPlaceholder - Title + the two paragraphs (self-hosted note + hosted Pro tier note + GitHub source note). Inline links wrapped via the Trans component so the link surface stays clickable in every locale without each translation having to re-write the HTML. EditorPage shell - Mobile bottom-tab labels (Code / Circuit), file-explorer toggle (Show / Hide), View mode aria-label, view-mode segmented control labels (Code / Both / Circuit), and the three "Drag to resize" handle tooltips on the panel splitters. Translations - en.json hand-curated for the new keys. - All 8 non-English locales auto-translated via the existing `npm run translate:i18n` pipeline (DeepSeek, ~5 min for the whole bundle, sameShape() validates each output before write). This closes Phase 2 of i18n. Phase 3 (DocsPage prose, AboutPage long-form paragraphs, the 15 SEO landing pages) is deliberately deferred — Docs/About are best handled by extracting the prose into JSON keys and running the same script, while the SEO pages are intentionally optimised for English keyword targeting and should not be machine-translated en masse.
2026-05-09 22:49:13 +07:00
const { t } = useTranslation();
const localize = useLocalizedHref();
const { username } = useParams<{ username: string }>();
useSEO({
title: `${username ?? 'User'} — Velxio Profile`,
description: `View Arduino and ESP32 projects by ${username ?? 'this user'} on Velxio.`,
url: `https://velxio.dev/${username ?? ''}`,
noindex: true,
});
const user = useAuthStore((s) => s.user);
const [projects, setProjects] = useState<ProjectResponse[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
if (!username) return;
setLoading(true);
getUserProjects(username)
.then(setProjects)
feat(i18n): translate AdminPage + UserProfile + Pricing + EditorPage shell (Editor block 10 — final cleanup) Closes the Phase 2 i18n rollout. Every visitor- and user-facing surface velxio renders in normal use now reads from t(). AdminPage (admin-only) - Header (panel title, logout) and the four tabs (Dashboard / Users / Projects / Boards). - Setup screen for first-admin creation (title, body, password fields + mismatch error + create-admin button). - Not-admin gate page. - EditUserModal (title, four labels, admin/active toggles, cancel/save). - UsersTab: search placeholder, count pluralisation, all 12 table columns, Activity / Edit / Delete actions, empty state, delete-confirm prompt with username interpolation. - ProjectsTab: search placeholder, count pluralisation, all 9 table columns, public/private badge labels, delete action + confirm with project-name interpolation, empty state. - All error messages (load failed / save failed / delete failed) fall back through t(). UserProfilePage - "New project" CTA, loading + empty + not-found states, "Private" project badge, "Copy shareable link" tooltip. - The /editor link uses localize() so /es/<username>'s "New project" button stays in Spanish. PricingPlaceholder - Title + the two paragraphs (self-hosted note + hosted Pro tier note + GitHub source note). Inline links wrapped via the Trans component so the link surface stays clickable in every locale without each translation having to re-write the HTML. EditorPage shell - Mobile bottom-tab labels (Code / Circuit), file-explorer toggle (Show / Hide), View mode aria-label, view-mode segmented control labels (Code / Both / Circuit), and the three "Drag to resize" handle tooltips on the panel splitters. Translations - en.json hand-curated for the new keys. - All 8 non-English locales auto-translated via the existing `npm run translate:i18n` pipeline (DeepSeek, ~5 min for the whole bundle, sameShape() validates each output before write). This closes Phase 2 of i18n. Phase 3 (DocsPage prose, AboutPage long-form paragraphs, the 15 SEO landing pages) is deliberately deferred — Docs/About are best handled by extracting the prose into JSON keys and running the same script, while the SEO pages are intentionally optimised for English keyword targeting and should not be machine-translated en masse.
2026-05-09 22:49:13 +07:00
.catch(() => setError(t('profile.notFound')))
.finally(() => setLoading(false));
}, [username]);
const isOwn = user?.username === username;
const [copiedId, setCopiedId] = useState<string | null>(null);
const handleCopyLink = useCallback((e: React.MouseEvent, projectId: string) => {
e.preventDefault(); // Don't navigate via the <Link>
e.stopPropagation();
const url = `${window.location.origin}/project/${projectId}`;
navigator.clipboard.writeText(url).then(() => {
setCopiedId(projectId);
setTimeout(() => setCopiedId(null), 2000);
});
}, []);
return (
<div className="profile-page">
<AppHeader />
<div className="profile-container">
<div className="profile-header">
<div className="profile-avatar">{username?.[0]?.toUpperCase()}</div>
<h1 className="profile-username">{username}</h1>
{isOwn && (
feat(i18n): translate AdminPage + UserProfile + Pricing + EditorPage shell (Editor block 10 — final cleanup) Closes the Phase 2 i18n rollout. Every visitor- and user-facing surface velxio renders in normal use now reads from t(). AdminPage (admin-only) - Header (panel title, logout) and the four tabs (Dashboard / Users / Projects / Boards). - Setup screen for first-admin creation (title, body, password fields + mismatch error + create-admin button). - Not-admin gate page. - EditUserModal (title, four labels, admin/active toggles, cancel/save). - UsersTab: search placeholder, count pluralisation, all 12 table columns, Activity / Edit / Delete actions, empty state, delete-confirm prompt with username interpolation. - ProjectsTab: search placeholder, count pluralisation, all 9 table columns, public/private badge labels, delete action + confirm with project-name interpolation, empty state. - All error messages (load failed / save failed / delete failed) fall back through t(). UserProfilePage - "New project" CTA, loading + empty + not-found states, "Private" project badge, "Copy shareable link" tooltip. - The /editor link uses localize() so /es/<username>'s "New project" button stays in Spanish. PricingPlaceholder - Title + the two paragraphs (self-hosted note + hosted Pro tier note + GitHub source note). Inline links wrapped via the Trans component so the link surface stays clickable in every locale without each translation having to re-write the HTML. EditorPage shell - Mobile bottom-tab labels (Code / Circuit), file-explorer toggle (Show / Hide), View mode aria-label, view-mode segmented control labels (Code / Both / Circuit), and the three "Drag to resize" handle tooltips on the panel splitters. Translations - en.json hand-curated for the new keys. - All 8 non-English locales auto-translated via the existing `npm run translate:i18n` pipeline (DeepSeek, ~5 min for the whole bundle, sameShape() validates each output before write). This closes Phase 2 of i18n. Phase 3 (DocsPage prose, AboutPage long-form paragraphs, the 15 SEO landing pages) is deliberately deferred — Docs/About are best handled by extracting the prose into JSON keys and running the same script, while the SEO pages are intentionally optimised for English keyword targeting and should not be machine-translated en masse.
2026-05-09 22:49:13 +07:00
<Link to={localize('/editor')} className="profile-new-btn">
+ {t('profile.newProject')}
</Link>
)}
</div>
feat(i18n): translate AdminPage + UserProfile + Pricing + EditorPage shell (Editor block 10 — final cleanup) Closes the Phase 2 i18n rollout. Every visitor- and user-facing surface velxio renders in normal use now reads from t(). AdminPage (admin-only) - Header (panel title, logout) and the four tabs (Dashboard / Users / Projects / Boards). - Setup screen for first-admin creation (title, body, password fields + mismatch error + create-admin button). - Not-admin gate page. - EditUserModal (title, four labels, admin/active toggles, cancel/save). - UsersTab: search placeholder, count pluralisation, all 12 table columns, Activity / Edit / Delete actions, empty state, delete-confirm prompt with username interpolation. - ProjectsTab: search placeholder, count pluralisation, all 9 table columns, public/private badge labels, delete action + confirm with project-name interpolation, empty state. - All error messages (load failed / save failed / delete failed) fall back through t(). UserProfilePage - "New project" CTA, loading + empty + not-found states, "Private" project badge, "Copy shareable link" tooltip. - The /editor link uses localize() so /es/<username>'s "New project" button stays in Spanish. PricingPlaceholder - Title + the two paragraphs (self-hosted note + hosted Pro tier note + GitHub source note). Inline links wrapped via the Trans component so the link surface stays clickable in every locale without each translation having to re-write the HTML. EditorPage shell - Mobile bottom-tab labels (Code / Circuit), file-explorer toggle (Show / Hide), View mode aria-label, view-mode segmented control labels (Code / Both / Circuit), and the three "Drag to resize" handle tooltips on the panel splitters. Translations - en.json hand-curated for the new keys. - All 8 non-English locales auto-translated via the existing `npm run translate:i18n` pipeline (DeepSeek, ~5 min for the whole bundle, sameShape() validates each output before write). This closes Phase 2 of i18n. Phase 3 (DocsPage prose, AboutPage long-form paragraphs, the 15 SEO landing pages) is deliberately deferred — Docs/About are best handled by extracting the prose into JSON keys and running the same script, while the SEO pages are intentionally optimised for English keyword targeting and should not be machine-translated en masse.
2026-05-09 22:49:13 +07:00
{loading && <p className="profile-muted">{t('profile.loading')}</p>}
{error && <p className="profile-error">{error}</p>}
{!loading && !error && projects.length === 0 && (
feat(i18n): translate AdminPage + UserProfile + Pricing + EditorPage shell (Editor block 10 — final cleanup) Closes the Phase 2 i18n rollout. Every visitor- and user-facing surface velxio renders in normal use now reads from t(). AdminPage (admin-only) - Header (panel title, logout) and the four tabs (Dashboard / Users / Projects / Boards). - Setup screen for first-admin creation (title, body, password fields + mismatch error + create-admin button). - Not-admin gate page. - EditUserModal (title, four labels, admin/active toggles, cancel/save). - UsersTab: search placeholder, count pluralisation, all 12 table columns, Activity / Edit / Delete actions, empty state, delete-confirm prompt with username interpolation. - ProjectsTab: search placeholder, count pluralisation, all 9 table columns, public/private badge labels, delete action + confirm with project-name interpolation, empty state. - All error messages (load failed / save failed / delete failed) fall back through t(). UserProfilePage - "New project" CTA, loading + empty + not-found states, "Private" project badge, "Copy shareable link" tooltip. - The /editor link uses localize() so /es/<username>'s "New project" button stays in Spanish. PricingPlaceholder - Title + the two paragraphs (self-hosted note + hosted Pro tier note + GitHub source note). Inline links wrapped via the Trans component so the link surface stays clickable in every locale without each translation having to re-write the HTML. EditorPage shell - Mobile bottom-tab labels (Code / Circuit), file-explorer toggle (Show / Hide), View mode aria-label, view-mode segmented control labels (Code / Both / Circuit), and the three "Drag to resize" handle tooltips on the panel splitters. Translations - en.json hand-curated for the new keys. - All 8 non-English locales auto-translated via the existing `npm run translate:i18n` pipeline (DeepSeek, ~5 min for the whole bundle, sameShape() validates each output before write). This closes Phase 2 of i18n. Phase 3 (DocsPage prose, AboutPage long-form paragraphs, the 15 SEO landing pages) is deliberately deferred — Docs/About are best handled by extracting the prose into JSON keys and running the same script, while the SEO pages are intentionally optimised for English keyword targeting and should not be machine-translated en masse.
2026-05-09 22:49:13 +07:00
<p className="profile-muted">{t('profile.empty')}</p>
)}
<div className="profile-grid">
{projects.map((p) => (
<Link key={p.id} to={`/${username}/${p.slug}`} className="profile-card">
<div className="profile-card-title">{p.name}</div>
{p.description && <div className="profile-card-desc">{p.description}</div>}
<div className="profile-card-meta">
<span className="profile-badge">{p.board_type}</span>
{!p.is_public && (
feat(i18n): translate AdminPage + UserProfile + Pricing + EditorPage shell (Editor block 10 — final cleanup) Closes the Phase 2 i18n rollout. Every visitor- and user-facing surface velxio renders in normal use now reads from t(). AdminPage (admin-only) - Header (panel title, logout) and the four tabs (Dashboard / Users / Projects / Boards). - Setup screen for first-admin creation (title, body, password fields + mismatch error + create-admin button). - Not-admin gate page. - EditUserModal (title, four labels, admin/active toggles, cancel/save). - UsersTab: search placeholder, count pluralisation, all 12 table columns, Activity / Edit / Delete actions, empty state, delete-confirm prompt with username interpolation. - ProjectsTab: search placeholder, count pluralisation, all 9 table columns, public/private badge labels, delete action + confirm with project-name interpolation, empty state. - All error messages (load failed / save failed / delete failed) fall back through t(). UserProfilePage - "New project" CTA, loading + empty + not-found states, "Private" project badge, "Copy shareable link" tooltip. - The /editor link uses localize() so /es/<username>'s "New project" button stays in Spanish. PricingPlaceholder - Title + the two paragraphs (self-hosted note + hosted Pro tier note + GitHub source note). Inline links wrapped via the Trans component so the link surface stays clickable in every locale without each translation having to re-write the HTML. EditorPage shell - Mobile bottom-tab labels (Code / Circuit), file-explorer toggle (Show / Hide), View mode aria-label, view-mode segmented control labels (Code / Both / Circuit), and the three "Drag to resize" handle tooltips on the panel splitters. Translations - en.json hand-curated for the new keys. - All 8 non-English locales auto-translated via the existing `npm run translate:i18n` pipeline (DeepSeek, ~5 min for the whole bundle, sameShape() validates each output before write). This closes Phase 2 of i18n. Phase 3 (DocsPage prose, AboutPage long-form paragraphs, the 15 SEO landing pages) is deliberately deferred — Docs/About are best handled by extracting the prose into JSON keys and running the same script, while the SEO pages are intentionally optimised for English keyword targeting and should not be machine-translated en masse.
2026-05-09 22:49:13 +07:00
<span className="profile-badge profile-badge-private">{t('profile.private')}</span>
)}
<span className="profile-date">{new Date(p.updated_at).toLocaleDateString()}</span>
{p.is_public && (
<button
className="profile-share-btn"
onClick={(e) => handleCopyLink(e, p.id)}
feat(i18n): translate AdminPage + UserProfile + Pricing + EditorPage shell (Editor block 10 — final cleanup) Closes the Phase 2 i18n rollout. Every visitor- and user-facing surface velxio renders in normal use now reads from t(). AdminPage (admin-only) - Header (panel title, logout) and the four tabs (Dashboard / Users / Projects / Boards). - Setup screen for first-admin creation (title, body, password fields + mismatch error + create-admin button). - Not-admin gate page. - EditUserModal (title, four labels, admin/active toggles, cancel/save). - UsersTab: search placeholder, count pluralisation, all 12 table columns, Activity / Edit / Delete actions, empty state, delete-confirm prompt with username interpolation. - ProjectsTab: search placeholder, count pluralisation, all 9 table columns, public/private badge labels, delete action + confirm with project-name interpolation, empty state. - All error messages (load failed / save failed / delete failed) fall back through t(). UserProfilePage - "New project" CTA, loading + empty + not-found states, "Private" project badge, "Copy shareable link" tooltip. - The /editor link uses localize() so /es/<username>'s "New project" button stays in Spanish. PricingPlaceholder - Title + the two paragraphs (self-hosted note + hosted Pro tier note + GitHub source note). Inline links wrapped via the Trans component so the link surface stays clickable in every locale without each translation having to re-write the HTML. EditorPage shell - Mobile bottom-tab labels (Code / Circuit), file-explorer toggle (Show / Hide), View mode aria-label, view-mode segmented control labels (Code / Both / Circuit), and the three "Drag to resize" handle tooltips on the panel splitters. Translations - en.json hand-curated for the new keys. - All 8 non-English locales auto-translated via the existing `npm run translate:i18n` pipeline (DeepSeek, ~5 min for the whole bundle, sameShape() validates each output before write). This closes Phase 2 of i18n. Phase 3 (DocsPage prose, AboutPage long-form paragraphs, the 15 SEO landing pages) is deliberately deferred — Docs/About are best handled by extracting the prose into JSON keys and running the same script, while the SEO pages are intentionally optimised for English keyword targeting and should not be machine-translated en masse.
2026-05-09 22:49:13 +07:00
title={t('profile.copyLink')}
>
{copiedId === p.id ? (
<svg
width="13"
height="13"
viewBox="0 0 24 24"
fill="none"
stroke="#4ade80"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="20 6 9 17 4 12" />
</svg>
) : (
<svg
width="13"
height="13"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
)}
</button>
)}
</div>
</Link>
))}
</div>
</div>
</div>
);
};