refactor: replace Wizard with CoopBuilder in navigation, enhance budget store structure, and streamline template components for improved user experience

This commit is contained in:
Jennie Robinson Faber 2025-08-17 17:25:04 +01:00
parent eede87a273
commit f67b138d95
33 changed files with 4970 additions and 2451 deletions

178
data/offerTemplates.ts Normal file
View file

@ -0,0 +1,178 @@
import type { OfferTemplate } from '~/composables/useOfferSuggestor';
export const offerTemplates: OfferTemplate[] = [
{
id: 'pitch-polish',
name: 'Pitch Polish',
type: 'clinic',
skillRequirements: ['writing', 'design'],
problemTargets: ['unclear-pitch', 'grant-budget-help'],
scope: [
'Comprehensive deck review and analysis',
'Rewrite key sections for clarity and impact',
'90-minute live presentation coaching session',
'Final edit pass with visual polish'
],
defaultDays: 2,
defaultHours: {
writing: 6,
design: 6,
pm: 2
},
whyThisTemplate: [
'Combines writing expertise with design polish',
'Time-boxed format keeps scope manageable',
'Live coaching session builds confidence',
'Immediate impact on funding success'
],
riskTemplate: 'Client may resist feedback on core concept or messaging'
},
{
id: 'brand-store-page-sprint',
name: 'Brand/Store Page Sprint',
type: 'sprint',
skillRequirements: ['design', 'dev'],
problemTargets: ['need-landing-store-page', 'marketing-assets'],
scope: [
'Develop clear messaging and brand voice',
'Design and build one-page marketing site',
'Create store page assets and layout',
'Implement responsive design and basic SEO'
],
defaultDays: 7,
defaultHours: {
design: 12,
writing: 6,
dev: 10,
pm: 4
},
whyThisTemplate: [
'Full-stack approach from concept to deployment',
'Combines brand strategy with technical execution',
'Creates immediate market presence',
'Scalable foundation for future marketing'
],
riskTemplate: 'Scope creep around additional pages or complex integrations'
},
{
id: 'dev-sprint',
name: 'Dev Sprint',
type: 'sprint',
skillRequirements: ['dev'],
problemTargets: ['vertical-slice', 'tech-debt'],
scope: [
'Backlog triage and feature prioritization',
'Implement 1-2 focused features or fixes',
'Create demo build for stakeholder review',
'Document changes and deployment process'
],
defaultDays: 7,
defaultHours: {
dev: 24,
qa: 4,
pm: 4
},
whyThisTemplate: [
'Focused development with clear deliverables',
'Includes quality assurance and project management',
'Demo build provides immediate feedback opportunity',
'Manageable scope reduces technical risk'
],
riskTemplate: 'Technical complexity may exceed initial estimates'
},
{
id: 'maintenance-retainer',
name: 'Maintenance Retainer',
type: 'retainer',
skillRequirements: ['dev', 'pm'],
problemTargets: ['launch-checklist'],
scope: [
'Handle small fixes and bug reports',
'Apply security and dependency updates',
'Provide technical support and guidance',
'Monthly progress reports and recommendations'
],
defaultDays: 30, // Monthly
defaultHours: {
dev: 6,
pm: 2
},
whyThisTemplate: [
'Predictable monthly income stream',
'Builds long-term client relationships',
'Low-risk work with defined boundaries',
'Efficient use of development skills'
],
riskTemplate: 'Client expectations may exceed allocated hours'
}
];
/**
* Lightweight matching rules for offer templates
*/
export function matchTemplateToInput(
selectedSkills: string[],
selectedProblems: string[]
): OfferTemplate[] {
const matches: OfferTemplate[] = [];
// Pitch Polish: Writing + Design + pitch/funding problems
if (selectedSkills.includes('writing') && selectedSkills.includes('design')) {
if (selectedProblems.includes('unclear-pitch') || selectedProblems.includes('grant-budget-help')) {
matches.push(offerTemplates[0]); // Pitch Polish
}
}
// Brand/Store Page: Design + Dev + website/marketing problems
if (selectedSkills.includes('design') && selectedSkills.includes('dev')) {
if (selectedProblems.includes('need-landing-store-page') || selectedProblems.includes('marketing-assets')) {
matches.push(offerTemplates[1]); // Brand/Store Page Sprint
}
}
// Dev Sprint: Dev + development-related problems
if (selectedSkills.includes('dev')) {
if (selectedProblems.includes('vertical-slice') || selectedProblems.includes('tech-debt')) {
matches.push(offerTemplates[2]); // Dev Sprint
}
}
// Maintenance Retainer: Dev + PM + launch/maintenance problems
if (selectedSkills.includes('dev') && selectedSkills.includes('pm')) {
if (selectedProblems.includes('launch-checklist') || matches.length === 0) { // Also use as fallback
matches.push(offerTemplates[3]); // Maintenance Retainer
}
}
return matches;
}
/**
* Get default hour allocation for a template based on available members
*/
export function getTemplateHours(
template: OfferTemplate,
availableSkills: string[]
): Array<{ skill: string; hours: number }> {
const allocations: Array<{ skill: string; hours: number }> = [];
// Convert template hours to skill-based allocations
if (template.defaultHours) {
Object.entries(template.defaultHours).forEach(([skill, hours]) => {
if (availableSkills.includes(skill)) {
allocations.push({ skill, hours });
}
});
}
return allocations;
}
/**
* Calculate total hours for a template
*/
export function getTemplateTotalHours(template: OfferTemplate): number {
if (!template.defaultHours) return template.defaultDays * 8; // Fallback: 8 hours per day
return Object.values(template.defaultHours).reduce((sum, hours) => sum + hours, 0);
}

157
data/skillsProblems.ts Normal file
View file

@ -0,0 +1,157 @@
import type { SkillTag, ProblemTag } from "~/types/coaching";
/**
* Standardized skills catalog for co-op offer generation
*/
export const skillsCatalog: SkillTag[] = [
{ id: "design", label: "Design" },
{ id: "writing", label: "Writing" },
{ id: "dev", label: "Dev" },
{ id: "pm", label: "PM" },
{ id: "qa", label: "QA" },
{ id: "teaching", label: "Teaching" },
{ id: "community", label: "Community" },
{ id: "marketing", label: "Marketing" },
{ id: "audio", label: "Audio" },
{ id: "art", label: "Art" },
{ id: "facilitation", label: "Facilitation" },
{ id: "ops", label: "Ops" }
];
/**
* Core problems that co-ops commonly solve, with realistic client examples
*/
export const problemsCatalog: ProblemTag[] = [
{
id: "unclear-pitch",
label: "Unclear pitch",
examples: [
"Our deck isn't landing with investors",
"Publishers don't get our concept",
"Feedback says the vision is confusing"
]
},
{
id: "need-landing-store-page",
label: "Need landing/store page",
examples: [
"We have no website presence",
"Need Steam page copy and layout",
"Want a simple marketing landing page"
]
},
{
id: "vertical-slice",
label: "Vertical slice",
examples: [
"Need a demo for potential funders",
"Want to test core mechanics with users",
"Prototype for investor meetings"
]
},
{
id: "community-plan",
label: "Community plan",
examples: [
"Our Discord server is inactive",
"We have no social media posting plan",
"Need a community building strategy"
]
},
{
id: "marketing-assets",
label: "Marketing assets",
examples: [
"Need trailer stills and screenshots",
"Press kit is completely missing",
"Want social media content templates"
]
},
{
id: "grant-budget-help",
label: "Grant budget help",
examples: [
"Need a budget narrative for arts council",
"Don't know how to price development time",
"Grant application requires detailed financials"
]
},
{
id: "launch-checklist",
label: "Launch checklist",
examples: [
"Need final QA pass before release",
"Store assets aren't ready",
"Want a pre-launch timeline and tasks"
]
},
{
id: "tech-debt",
label: "Tech debt",
examples: [
"Build process is broken and slow",
"Tooling needs major updates",
"Performance issues need addressing"
]
}
];
/**
* Skills grouped by common combinations for template matching
*/
export const skillCombinations = {
creative: ['design', 'art', 'writing'],
technical: ['dev', 'qa', 'ops'],
business: ['pm', 'marketing', 'facilitation'],
community: ['community', 'teaching', 'marketing'],
production: ['pm', 'qa', 'ops']
};
/**
* Problems grouped by solution type
*/
export const problemCategories = {
communication: ['unclear-pitch', 'grant-budget-help'],
marketing: ['need-landing-store-page', 'marketing-assets', 'community-plan'],
development: ['vertical-slice', 'tech-debt'],
operations: ['launch-checklist']
};
/**
* Helper function to get skills by category
*/
export function getSkillsByCategory(category: keyof typeof skillCombinations): SkillTag[] {
const skillIds = skillCombinations[category];
return skillsCatalog.filter(skill => skillIds.includes(skill.id));
}
/**
* Helper function to get problems by category
*/
export function getProblemsByCategory(category: keyof typeof problemCategories): ProblemTag[] {
const problemIds = problemCategories[category];
return problemsCatalog.filter(problem => problemIds.includes(problem.id));
}
/**
* Check if a skill combination is commonly used together
*/
export function areSkillsComplementary(skills: string[]): boolean {
// Check if skills fall within the same or complementary categories
const categories = Object.entries(skillCombinations);
for (const [category, categorySkills] of categories) {
const overlap = skills.filter(skill => categorySkills.includes(skill));
if (overlap.length >= 2) {
return true; // Found 2+ skills in same category
}
}
// Check cross-category combinations that work well together
const hasCreative = skills.some(s => skillCombinations.creative.includes(s));
const hasTechnical = skills.some(s => skillCombinations.technical.includes(s));
const hasBusiness = skills.some(s => skillCombinations.business.includes(s));
// Creative + Technical, Technical + Business, etc. are good combinations
return (hasCreative && hasTechnical) || (hasTechnical && hasBusiness) || (hasCreative && hasBusiness);
}