feat(card): 'Finish your card' checklist (photo, name, styles/level, crew)

A card is only as good as its content, so the studio now shows what's missing
with one-tap links to fix it, instead of a subtle amber hint.
This commit is contained in:
SkateSpots 2026-10-02 12:38:44 +07:00
parent 00753b2769
commit 80e9552d11
3 changed files with 43 additions and 0 deletions

View file

@ -6387,6 +6387,12 @@ walletUi: {
editSkaterProfile: 'add your styles & level', editSkaterProfile: 'add your styles & level',
noPhoto: 'No profile photo yet —', noPhoto: 'No profile photo yet —',
addPhoto: 'add one in your profile', addPhoto: 'add one in your profile',
finishTitle: 'Finish your card',
finishDesc: 'A photo and a Skater profile make it look real.',
checkPhoto: 'Profile photo',
checkName: 'Display name',
checkProfile: 'Styles & level',
checkCrew: 'Join a crew',
accent: 'Accent colour', accent: 'Accent colour',
accentHint: 'Overrides the theme accent', accentHint: 'Overrides the theme accent',
accentClear: 'Use theme colour', accentClear: 'Use theme colour',

View file

@ -6387,6 +6387,12 @@ walletUi: {
editSkaterProfile: 'añade tus estilos y nivel', editSkaterProfile: 'añade tus estilos y nivel',
noPhoto: 'Aún no tienes foto de perfil —', noPhoto: 'Aún no tienes foto de perfil —',
addPhoto: 'añádela en tu perfil', addPhoto: 'añádela en tu perfil',
finishTitle: 'Completa tu carné',
finishDesc: 'Una foto y un perfil de skater lo hacen parecer real.',
checkPhoto: 'Foto de perfil',
checkName: 'Nombre visible',
checkProfile: 'Estilos y nivel',
checkCrew: 'Únete a una cuadrilla',
accent: 'Color de acento', accent: 'Color de acento',
accentHint: 'Sustituye el acento del tema', accentHint: 'Sustituye el acento del tema',
accentClear: 'Usar color del tema', accentClear: 'Usar color del tema',

View file

@ -186,6 +186,15 @@ export default function RiderCardPage() {
const derivedLevel = skaterProfile?.level const derivedLevel = skaterProfile?.level
? (SKATER_LEVELS[skaterProfile.level]?.label ?? '') ? (SKATER_LEVELS[skaterProfile.level]?.label ?? '')
: (skaterProfile?.customLevel ?? ''); : (skaterProfile?.customLevel ?? '');
// "Finish your card" checklist — a card is only as good as what's on it.
const cardChecks = [
{ done: !!metadata?.picture, label: t('cardUi.studio.checkPhoto'), to: '/settings?section=profile' },
{ done: !!metadata?.name, label: t('cardUi.studio.checkName'), to: '/settings?section=profile' },
{ done: (skaterProfile?.styles?.length ?? 0) > 0 || !!skaterProfile?.level, label: t('cardUi.studio.checkProfile'), to: '/skaters' },
{ done: memberships.length > 0, label: t('cardUi.studio.checkCrew'), to: '/crews' },
];
const incomplete = cardChecks.some((c) => !c.done);
const { mutate: publishEvent } = useNostrPublish(); const { mutate: publishEvent } = useNostrPublish();
const { mutateAsync: uploadFile, isPending: uploadingLogo } = useUploadFile(); const { mutateAsync: uploadFile, isPending: uploadingLogo } = useUploadFile();
const { toast } = useToast(); const { toast } = useToast();
@ -659,6 +668,27 @@ export default function RiderCardPage() {
{/* ══ RIGHT: inspector ══ */} {/* ══ RIGHT: inspector ══ */}
<div className="mt-8 space-y-6 lg:mt-0"> <div className="mt-8 space-y-6 lg:mt-0">
{/* Finish-your-card checklist (personal card) */}
{cardType === 'rider' && incomplete && (
<div className="rounded-xl border border-[#ccff00]/30 bg-[#ccff00]/5 p-4">
<div className="text-xs font-black uppercase tracking-wide text-[#ccff00]">{t('cardUi.studio.finishTitle')}</div>
<p className="mt-1 text-[11px] text-gray-400">{t('cardUi.studio.finishDesc')}</p>
<div className="mt-3 flex flex-wrap gap-1.5">
{cardChecks.map((c) => (
c.done ? (
<span key={c.label} className="inline-flex items-center gap-1 rounded-lg border border-white/10 bg-white/5 px-2.5 py-1 text-[11px] font-bold text-green-400">
✓ {c.label}
</span>
) : (
<Link key={c.label} to={c.to} className="inline-flex items-center gap-1 rounded-lg border border-[#ccff00]/40 bg-[#ccff00]/10 px-2.5 py-1 text-[11px] font-bold text-[#ccff00] hover:bg-[#ccff00]/20">
+ {c.label}
</Link>
)
))}
</div>
</div>
)}
{/* Card type */} {/* Card type */}
<div> <div>
<p className="mb-2 text-[10px] font-black uppercase tracking-widest text-gray-600">{t('labUi.rider.cardType')}</p> <p className="mb-2 text-[10px] font-black uppercase tracking-widest text-gray-600">{t('labUi.rider.cardType')}</p>
@ -814,6 +844,7 @@ export default function RiderCardPage() {
</p> </p>
)} )}
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div> <div>
<Label className="mb-1.5 block text-[10px] uppercase tracking-widest text-gray-500">{t('labUi.rider.ridesFor')}</Label> <Label className="mb-1.5 block text-[10px] uppercase tracking-widest text-gray-500">{t('labUi.rider.ridesFor')}</Label>