6b99bcea7f
## Summary - Add a dedicated **Experience** step to the partner apply wizard (milestones, ≥200-char narrative, proof URL) before commercials - Stop collecting `applicationNotes`; rename Expertise chrome away from “experience” - Replace the post-submit Cal.com embed with a review-and-reach-out thank-you so unqualified inbound no longer books intros automatically **Companion PR (app):** #23224 — Partner schema, submit persistence, triage views, Tally CSV mapper (`twenty-partners` v1.4.0). Land the app PR with or before this one. ## Test plan - [ ] Open apply modal: wizard order is identity → profile → expertise → experience → commercials - [ ] Experience step blocks continue without ≥1 milestone, narrative ≥200 chars, and a valid https URL - [ ] Submit creates/updates Partner with the three experience fields (with #23224 deployed) - [ ] Success screen has no Cal embed / book-later CTA - [ ] `npx jest --config=jest.config.mjs partner-application` passes locally (71 tests) --------- Co-authored-by: Abdullah <125115953+mabdullahabaid@users.noreply.github.com>
93 lines
2.0 KiB
TypeScript
93 lines
2.0 KiB
TypeScript
'use client';
|
|
|
|
import { styled } from '@linaria/react';
|
|
|
|
import {
|
|
color,
|
|
FONT_WEIGHT,
|
|
fontFamily,
|
|
fontSize,
|
|
radius,
|
|
semanticColor,
|
|
spacing,
|
|
} from '@/tokens';
|
|
|
|
export type ChipOption<TValue extends string> = {
|
|
label: string;
|
|
value: TValue;
|
|
};
|
|
|
|
const PillGroup = styled.div`
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: ${spacing(2)};
|
|
`;
|
|
|
|
// Selected = the scheme inverted (ink fill / surface text): on the dark form a
|
|
// white pill with black text, the old site's selected state expressed as a rule.
|
|
const Pill = styled.button`
|
|
background: none;
|
|
border: 1px solid ${semanticColor.lineStrong};
|
|
border-radius: ${radius(8)};
|
|
color: ${semanticColor.ink};
|
|
cursor: pointer;
|
|
font-family: ${fontFamily('sans')};
|
|
font-size: ${fontSize(3.5)};
|
|
font-weight: ${FONT_WEIGHT.regular};
|
|
line-height: 1.2;
|
|
padding: ${spacing(1.5)} ${spacing(3)};
|
|
|
|
&[data-selected] {
|
|
background: ${semanticColor.ink};
|
|
border-color: ${semanticColor.ink};
|
|
color: ${semanticColor.surface};
|
|
}
|
|
|
|
&[data-invalid] {
|
|
border-color: ${color('error')};
|
|
}
|
|
|
|
&:focus-visible {
|
|
outline: 2px solid ${color('blue')};
|
|
outline-offset: 2px;
|
|
}
|
|
`;
|
|
|
|
export function ChipMultiSelect<TValue extends string>({
|
|
ariaLabel,
|
|
invalid = false,
|
|
onToggle,
|
|
options,
|
|
values,
|
|
}: {
|
|
ariaLabel: string;
|
|
invalid?: boolean;
|
|
onToggle: (value: TValue) => void;
|
|
options: readonly ChipOption<TValue>[];
|
|
values: readonly TValue[];
|
|
}) {
|
|
return (
|
|
<PillGroup
|
|
aria-invalid={invalid ? true : undefined}
|
|
aria-label={ariaLabel}
|
|
role="group"
|
|
>
|
|
{options.map((option) => {
|
|
const selected = values.includes(option.value);
|
|
return (
|
|
<Pill
|
|
aria-pressed={selected}
|
|
data-invalid={invalid && !selected ? '' : undefined}
|
|
data-selected={selected ? '' : undefined}
|
|
key={option.value}
|
|
onClick={() => onToggle(option.value)}
|
|
type="button"
|
|
>
|
|
{option.label}
|
|
</Pill>
|
|
);
|
|
})}
|
|
</PillGroup>
|
|
);
|
|
}
|