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>
95 lines
2.9 KiB
TypeScript
95 lines
2.9 KiB
TypeScript
import { PARTNER_APPLICATION_STEP_IDS } from './data/partner-application-step-ids';
|
|
import {
|
|
INITIAL_PARTNER_APPLICATION_STATE,
|
|
type PartnerApplicationAction,
|
|
type PartnerApplicationState,
|
|
} from './partner-application-state';
|
|
import { validatePartnerApplicationStep } from './validate-partner-application-step';
|
|
|
|
function dropError(
|
|
errors: Partial<Record<string, string>>,
|
|
field: string,
|
|
): Partial<Record<string, string>> {
|
|
if (errors[field] === undefined) return errors;
|
|
const { [field]: _dropped, ...rest } = errors;
|
|
return rest;
|
|
}
|
|
|
|
export function partnerApplicationReducer(
|
|
state: PartnerApplicationState,
|
|
action: PartnerApplicationAction,
|
|
): PartnerApplicationState {
|
|
switch (action.type) {
|
|
case 'SET_FIELD':
|
|
return {
|
|
...state,
|
|
[action.field]: action.value,
|
|
fieldErrors: dropError(state.fieldErrors, action.field),
|
|
};
|
|
case 'TOGGLE_SCOPE': {
|
|
const next = state.partnerScope.includes(action.value)
|
|
? state.partnerScope.filter((value) => value !== action.value)
|
|
: [...state.partnerScope, action.value];
|
|
return {
|
|
...state,
|
|
partnerScope: next,
|
|
fieldErrors: dropError(state.fieldErrors, 'partnerScope'),
|
|
};
|
|
}
|
|
case 'TOGGLE_LANGUAGE': {
|
|
const next = state.languages.includes(action.value)
|
|
? state.languages.filter((value) => value !== action.value)
|
|
: [...state.languages, action.value];
|
|
return { ...state, languages: next };
|
|
}
|
|
case 'TOGGLE_EXPERIENCE': {
|
|
const next = state.twentyExperience.includes(action.value)
|
|
? state.twentyExperience.filter((value) => value !== action.value)
|
|
: [...state.twentyExperience, action.value];
|
|
return {
|
|
...state,
|
|
twentyExperience: next,
|
|
fieldErrors: dropError(state.fieldErrors, 'twentyExperience'),
|
|
};
|
|
}
|
|
case 'SET_SKILLS':
|
|
return { ...state, skills: action.value };
|
|
case 'GO_NEXT': {
|
|
const errors = validatePartnerApplicationStep(state);
|
|
if (Object.keys(errors).length > 0) {
|
|
return { ...state, fieldErrors: errors };
|
|
}
|
|
const lastIndex = PARTNER_APPLICATION_STEP_IDS.length - 1;
|
|
return {
|
|
...state,
|
|
stepIndex: Math.min(state.stepIndex + 1, lastIndex),
|
|
fieldErrors: {},
|
|
};
|
|
}
|
|
case 'GO_BACK':
|
|
return {
|
|
...state,
|
|
stepIndex: Math.max(state.stepIndex - 1, 0),
|
|
fieldErrors: {},
|
|
};
|
|
case 'SET_SUBMITTING':
|
|
return { ...state, isSubmitting: action.value };
|
|
case 'SET_SUBMIT_ERROR':
|
|
return { ...state, submitError: action.value };
|
|
case 'SET_SUBMITTED':
|
|
return {
|
|
...state,
|
|
isSubmitted: true,
|
|
isSubmitting: false,
|
|
submitError: null,
|
|
};
|
|
case 'RESET':
|
|
return INITIAL_PARTNER_APPLICATION_STATE;
|
|
default: {
|
|
const _exhaustive: never = action;
|
|
void _exhaustive;
|
|
return state;
|
|
}
|
|
}
|
|
}
|