) : (
- {f.label}
+ {f.labelKey ? t(f.labelKey) : f.label}
= {
accentChipHover: 'hover:border-fuchsia-500/60 hover:text-fuchsia-400',
icon: Palette,
rateUnits: pickRateUnits('piece', 'project', 'hour', 'day'),
+ extraFields: [
+ { tag: 'deliverables', label: 'Deliverables', labelKey: 'labUi.craftFields.deliverables', placeholder: 'e.g. print-ready files, source files, deck graphics' },
+ { tag: 'turnaround', label: 'Turnaround', labelKey: 'labUi.craftFields.turnaround', placeholder: 'e.g. 3–5 days' },
+ ],
rateLabel: 'Rate',
portfolioLabel: 'Portfolio / Shop link',
skillSections: [
@@ -118,6 +122,8 @@ export const BECOME_ROLES: Record = {
],
extraFields: [
{ tag: 'crew', label: 'Graffiti Crew', placeholder: 'Your writing crew (optional)', helper: 'Separate from your skate crew — the writers you paint with.' },
+ { tag: 'surfaces', label: 'Surfaces / Walls', labelKey: 'labUi.craftFields.surfaces', placeholder: 'e.g. concrete, brick, permitted walls' },
+ { tag: 'max_size', label: 'Max Size', labelKey: 'labUi.craftFields.maxSize', placeholder: 'e.g. up to 10 m' },
],
},
},
@@ -150,6 +156,8 @@ export const BECOME_ROLES: Record = {
{ tag: 'bandcamp', label: 'Bandcamp', type: 'url', placeholder: 'https://…bandcamp.com' },
{ tag: 'soundcloud', label: 'SoundCloud', type: 'url', placeholder: 'https://soundcloud.com/…' },
{ tag: 'youtube', label: 'YouTube', type: 'url', placeholder: 'https://youtube.com/…' },
+ { tag: 'set_length', label: 'Set Length', labelKey: 'labUi.craftFields.setLength', placeholder: 'e.g. 30–45 min' },
+ { tag: 'tech_rider', label: 'Tech Rider', labelKey: 'labUi.craftFields.techRider', placeholder: 'e.g. PA, mics, backline' },
],
},
},
@@ -179,6 +187,8 @@ export const BECOME_ROLES: Record = {
],
extraFields: [
{ tag: 'events_judged', label: 'Events Judged', placeholder: 'e.g. 40+ contests, 3 World Tour stops', helper: 'Notable events, comps or tours you have officiated.' },
+ { tag: 'travel', label: 'Travel', labelKey: 'labUi.craftFields.travel', placeholder: 'e.g. regional / nationwide / international' },
+ { tag: 'certifications', label: 'Certifications', labelKey: 'labUi.craftFields.certifications', placeholder: 'e.g. national judging course' },
],
},
},
@@ -208,6 +218,8 @@ export const BECOME_ROLES: Record = {
],
extraFields: [
{ tag: 'events_hosted', label: 'Events Organized', placeholder: 'e.g. 25 jams, 3 city comps', helper: 'Notable events, comps or meetups you have run.' },
+ { tag: 'insurance', label: 'Insurance', labelKey: 'labUi.craftFields.insurance', placeholder: 'e.g. public liability included' },
+ { tag: 'permits', label: 'Permits', labelKey: 'labUi.craftFields.permits', placeholder: 'e.g. council permits handled' },
],
},
},
@@ -237,6 +249,8 @@ export const BECOME_ROLES: Record = {
extraFields: [
{ tag: 'studio', label: 'Studio / Shop', placeholder: 'Studio name (optional)' },
{ tag: 'books_open', label: 'Books Open', type: 'toggle', helper: 'Are you currently taking bookings?' },
+ { tag: 'deposit', label: 'Deposit', labelKey: 'labUi.craftFields.deposit', placeholder: 'e.g. 50 EUR non-refundable' },
+ { tag: 'minimum', label: 'Minimum', labelKey: 'labUi.craftFields.minimum', placeholder: 'e.g. 80 EUR' },
],
},
},
@@ -267,6 +281,7 @@ export const BECOME_ROLES: Record = {
extraFields: [
{ tag: 'credentials', label: 'Certifications', placeholder: 'e.g. BSc Physiotherapy, CSCS…', helper: 'List real qualifications — skaters trust verifiable credentials.' },
{ tag: 'remote', label: 'Remote / Online', type: 'toggle', helper: 'Do you offer online coaching or consultations?' },
+ { tag: 'modalities', label: 'Modalities', labelKey: 'labUi.craftFields.modalities', placeholder: 'e.g. hands-on, S&C, dry needling' },
],
},
},
@@ -297,6 +312,7 @@ export const BECOME_ROLES: Record = {
extraFields: [
{ tag: 'studio', label: 'Shop / Studio', placeholder: 'Shop or studio name (optional)' },
{ tag: 'books_open', label: 'Books Open', type: 'toggle', helper: 'Are you currently taking bookings?' },
+ { tag: 'duration', label: 'Appointment Length', labelKey: 'labUi.craftFields.duration', placeholder: 'e.g. 30–45 min' },
],
},
},
diff --git a/src/lib/craftFields.ts b/src/lib/craftFields.ts
new file mode 100644
index 0000000..21ced44
--- /dev/null
+++ b/src/lib/craftFields.ts
@@ -0,0 +1,64 @@
+import type { LabProfileType } from '@/hooks/useLabProfiles';
+
+/** One craft-specific freeform field shown on the Lab detail page. */
+export interface CraftDetailField {
+ tag: string;
+ labelKey: string;
+}
+
+/**
+ * Craft-specific freeform fields to surface on a provider's detail page.
+ *
+ * Keep this in sync with the `extraFields` declared in `becomeRoles.tsx` (and
+ * the bespoke become pages): a field with no entry here is stored but invisible.
+ * Toggle tags (value `"true"`) render as a localized "Yes".
+ */
+export const CRAFT_DETAIL_FIELDS: Partial> = {
+ artist: [
+ { tag: 'deliverables', labelKey: 'labUi.craftFields.deliverables' },
+ { tag: 'turnaround', labelKey: 'labUi.craftFields.turnaround' },
+ ],
+ graffiti: [
+ { tag: 'surfaces', labelKey: 'labUi.craftFields.surfaces' },
+ { tag: 'max_size', labelKey: 'labUi.craftFields.maxSize' },
+ ],
+ musician: [
+ { tag: 'set_length', labelKey: 'labUi.craftFields.setLength' },
+ { tag: 'tech_rider', labelKey: 'labUi.craftFields.techRider' },
+ ],
+ judge: [
+ { tag: 'travel', labelKey: 'labUi.craftFields.travel' },
+ { tag: 'certifications', labelKey: 'labUi.craftFields.certifications' },
+ ],
+ organizer: [
+ { tag: 'insurance', labelKey: 'labUi.craftFields.insurance' },
+ { tag: 'permits', labelKey: 'labUi.craftFields.permits' },
+ ],
+ tattoo: [
+ { tag: 'deposit', labelKey: 'labUi.craftFields.deposit' },
+ { tag: 'minimum', labelKey: 'labUi.craftFields.minimum' },
+ ],
+ trainer: [
+ { tag: 'modalities', labelKey: 'labUi.craftFields.modalities' },
+ ],
+ barber: [
+ { tag: 'duration', labelKey: 'labUi.craftFields.duration' },
+ ],
+ filmer: [
+ { tag: 'turnaround', labelKey: 'labUi.craftFields.turnaround' },
+ { tag: 'deliverables', labelKey: 'labUi.craftFields.deliverables' },
+ { tag: 'drone', labelKey: 'labUi.craftFields.drone' },
+ ],
+ editor: [
+ { tag: 'turnaround', labelKey: 'labUi.craftFields.turnaround' },
+ { tag: 'deliverables', labelKey: 'labUi.craftFields.deliverables' },
+ ],
+ photographer: [
+ { tag: 'turnaround', labelKey: 'labUi.craftFields.turnaround' },
+ { tag: 'deliverables', labelKey: 'labUi.craftFields.deliverables' },
+ ],
+ builder: [
+ { tag: 'lead_time', labelKey: 'labUi.craftFields.leadTime' },
+ { tag: 'warranty', labelKey: 'labUi.craftFields.warranty' },
+ ],
+};
diff --git a/src/pages/BecomeBuilderPage.tsx b/src/pages/BecomeBuilderPage.tsx
index 3f1bfcc..fab3a35 100644
--- a/src/pages/BecomeBuilderPage.tsx
+++ b/src/pages/BecomeBuilderPage.tsx
@@ -132,6 +132,8 @@ export default function BecomeBuilderPage() {
const [titleInput, setTitleInput] = useState('');
const [prefilled, setPrefilled] = useState(false);
const [extraTags, setExtraTags] = useState([]);
+ const [lead_time, setLeadTime] = useState('');
+ const [warranty, setWarranty] = useState('');
const [existingGeohash, setExistingGeohash] = useState(undefined);
// Prefill from an existing profile so this is an edit, not a blind overwrite.
@@ -154,6 +156,8 @@ export default function BecomeBuilderPage() {
setImage(tag('image') ?? null);
setExperience(tag('experience') ?? '');
setRate(tag('rate') ?? '');
+ setLeadTime(tag('lead_time') ?? '');
+ setWarranty(tag('warranty') ?? '');
setPhone(tag('phone') ?? '');
setEmail(tag('email') ?? '');
setWebsite(tag('website') ?? '');
@@ -296,6 +300,8 @@ export default function BecomeBuilderPage() {
tags.push(['entity', entityType]);
for (const extra of extraTags) tags.push(extra);
+ if (lead_time.trim()) tags.push(['lead_time', lead_time.trim()]);
+ if (warranty.trim()) tags.push(['warranty', warranty.trim()]);
if (entityType === 'collective') {
for (const pk of members) tags.push(['p', pk]);
if (crewRef) tags.push(['a', crewRef]);
@@ -621,6 +627,20 @@ export default function BecomeBuilderPage() {
{/* Optional Pro upgrade — free to list without it */}
+
+
+ {t('labUi.craftFields.detailsTitle')}
+
+ {t('labUi.craftFields.leadTime')}
+ setLeadTime(e.target.value)} placeholder="e.g. 2–4 weeks" className="bg-gray-800 border-gray-700" />
+
+
+ {t('labUi.craftFields.warranty')}
+ setWarranty(e.target.value)} placeholder="e.g. 12 months on ramps" className="bg-gray-800 border-gray-700" />
+
+
+
+
([]);
+ const [turnaround, setTurnaround] = useState('');
+ const [deliverables, setDeliverables] = useState('');
const [existingGeohash, setExistingGeohash] = useState(undefined);
// Prefill from an existing profile so this is an edit, not a blind overwrite.
@@ -108,6 +110,8 @@ export default function BecomeEditorPage() {
setImage(tag('image') ?? null);
setExperience(tag('experience') ?? '');
setRate(tag('rate') ?? '');
+ setTurnaround(tag('turnaround') ?? '');
+ setDeliverables(tag('deliverables') ?? '');
setPhone(tag('phone') ?? '');
setEmail(tag('email') ?? '');
setWebsite(tag('website') ?? '');
@@ -228,6 +232,8 @@ export default function BecomeEditorPage() {
tags.push(['entity', entityType]);
for (const extra of extraTags) tags.push(extra);
+ if (turnaround.trim()) tags.push(['turnaround', turnaround.trim()]);
+ if (deliverables.trim()) tags.push(['deliverables', deliverables.trim()]);
if (entityType === 'collective') {
for (const pk of members) tags.push(['p', pk]);
if (crewRef) tags.push(['a', crewRef]);
@@ -515,6 +521,20 @@ export default function BecomeEditorPage() {
{/* Optional Pro upgrade — free to list without it */}
+
+
+ {t('labUi.craftFields.detailsTitle')}
+
+ {t('labUi.craftFields.turnaround')}
+ setTurnaround(e.target.value)} placeholder="e.g. 2–5 days" className="bg-gray-800 border-gray-700" />
+
+
+ {t('labUi.craftFields.deliverables')}
+ setDeliverables(e.target.value)} placeholder="e.g. 4K master, vertical cut, captions" className="bg-gray-800 border-gray-700" />
+
+
+
+
([]);
+ const [turnaround, setTurnaround] = useState('');
+ const [deliverables, setDeliverables] = useState('');
+ const [drone, setDrone] = useState('');
const [existingGeohash, setExistingGeohash] = useState(undefined);
// Prefill from an existing profile so this is an edit, not a blind overwrite.
@@ -103,6 +106,9 @@ export default function BecomeFilmerPage() {
setImage(tag('image') ?? null);
setExperience(tag('experience') ?? '');
setRate(tag('rate') ?? '');
+ setTurnaround(tag('turnaround') ?? '');
+ setDeliverables(tag('deliverables') ?? '');
+ setDrone(tag('drone') ?? '');
setPhone(tag('phone') ?? '');
setEmail(tag('email') ?? '');
setWebsite(tag('website') ?? '');
@@ -248,6 +254,9 @@ export default function BecomeFilmerPage() {
tags.push(['entity', entityType]);
for (const extra of extraTags) tags.push(extra);
+ if (turnaround.trim()) tags.push(['turnaround', turnaround.trim()]);
+ if (deliverables.trim()) tags.push(['deliverables', deliverables.trim()]);
+ if (drone.trim()) tags.push(['drone', drone.trim()]);
if (entityType === 'collective') {
for (const pk of members) tags.push(['p', pk]);
if (crewRef) tags.push(['a', crewRef]);
@@ -584,6 +593,24 @@ export default function BecomeFilmerPage() {
{/* Optional Pro upgrade — free to list without it */}
+
+
+ {t('labUi.craftFields.detailsTitle')}
+
+ {t('labUi.craftFields.turnaround')}
+ setTurnaround(e.target.value)} placeholder="e.g. 1–2 weeks" className="bg-gray-800 border-gray-700" />
+
+
+ {t('labUi.craftFields.deliverables')}
+ setDeliverables(e.target.value)} placeholder="e.g. master + raw, 4K" className="bg-gray-800 border-gray-700" />
+
+
+ {t('labUi.craftFields.drone')}
+ setDrone(e.target.value)} placeholder="e.g. FPV, Mavic 3, none" className="bg-gray-800 border-gray-700" />
+
+
+
+
([]);
+ const [turnaround, setTurnaround] = useState('');
+ const [deliverables, setDeliverables] = useState('');
const [existingGeohash, setExistingGeohash] = useState(undefined);
// Prefill from an existing profile so this is an edit, not a blind overwrite.
@@ -167,6 +169,8 @@ export default function BecomePhotographerPage() {
setImage(tag('image') ?? null);
setExperience(tag('experience') ?? '');
setRate(tag('rate') ?? '');
+ setTurnaround(tag('turnaround') ?? '');
+ setDeliverables(tag('deliverables') ?? '');
setPhone(tag('phone') ?? '');
setEmail(tag('email') ?? '');
setWebsite(tag('website') ?? '');
@@ -320,6 +324,8 @@ export default function BecomePhotographerPage() {
tags.push(['entity', entityType]);
for (const extra of extraTags) tags.push(extra);
+ if (turnaround.trim()) tags.push(['turnaround', turnaround.trim()]);
+ if (deliverables.trim()) tags.push(['deliverables', deliverables.trim()]);
if (entityType === 'collective') {
for (const pk of members) tags.push(['p', pk]);
if (crewRef) tags.push(['a', crewRef]);
@@ -650,6 +656,20 @@ export default function BecomePhotographerPage() {