Initial commit: MOPC platform with Docker deployment setup
Full Next.js 15 platform with tRPC, Prisma, PostgreSQL, NextAuth. Includes production Dockerfile (multi-stage, port 7600), docker-compose with registry-based image pull, Gitea Actions CI workflow, nginx config for portal.monaco-opc.com, deployment scripts, and DEPLOYMENT.md guide. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
184
src/components/forms/apply-steps/step-team.tsx
Normal file
184
src/components/forms/apply-steps/step-team.tsx
Normal file
@@ -0,0 +1,184 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { motion, AnimatePresence } from 'motion/react'
|
||||
import { UseFormReturn, useFieldArray } from 'react-hook-form'
|
||||
import { Plus, Trash2, Users } from 'lucide-react'
|
||||
import { WizardStepContent } from '@/components/forms/form-wizard'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import type { ApplicationFormData } from '@/server/routers/application'
|
||||
import { TeamMemberRole } from '@prisma/client'
|
||||
|
||||
const roleOptions: { value: TeamMemberRole; label: string }[] = [
|
||||
{ value: 'MEMBER', label: 'Team Member' },
|
||||
{ value: 'ADVISOR', label: 'Advisor' },
|
||||
]
|
||||
|
||||
interface StepTeamProps {
|
||||
form: UseFormReturn<ApplicationFormData>
|
||||
}
|
||||
|
||||
export function StepTeam({ form }: StepTeamProps) {
|
||||
const { control, register, formState: { errors } } = form
|
||||
const { fields, append, remove } = useFieldArray({
|
||||
control,
|
||||
name: 'teamMembers',
|
||||
})
|
||||
|
||||
const addMember = () => {
|
||||
append({ name: '', email: '', role: 'MEMBER', title: '' })
|
||||
}
|
||||
|
||||
return (
|
||||
<WizardStepContent
|
||||
title="Your team members"
|
||||
description="Add the other members of your team. They will receive an invitation to create their account."
|
||||
>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
className="mx-auto max-w-lg space-y-6"
|
||||
>
|
||||
{fields.length === 0 ? (
|
||||
<div className="flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-muted-foreground/25 py-12 text-center">
|
||||
<Users className="mb-4 h-12 w-12 text-muted-foreground/50" />
|
||||
<p className="text-muted-foreground">
|
||||
No team members added yet.
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
You can add team members here, or skip this step if you're applying solo.
|
||||
</p>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={addMember}
|
||||
className="mt-4"
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Add Team Member
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<AnimatePresence mode="popLayout">
|
||||
{fields.map((field, index) => (
|
||||
<motion.div
|
||||
key={field.id}
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
className="rounded-lg border bg-card p-4"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<h4 className="font-medium text-sm text-muted-foreground">
|
||||
Team Member {index + 1}
|
||||
</h4>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => remove(index)}
|
||||
className="h-8 w-8 text-muted-foreground hover:text-destructive"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
{/* Name */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`teamMembers.${index}.name`}>
|
||||
Full Name <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id={`teamMembers.${index}.name`}
|
||||
placeholder="Jane Doe"
|
||||
{...register(`teamMembers.${index}.name`)}
|
||||
/>
|
||||
{errors.teamMembers?.[index]?.name && (
|
||||
<p className="text-sm text-destructive">
|
||||
{errors.teamMembers[index]?.name?.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Email */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`teamMembers.${index}.email`}>
|
||||
Email <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id={`teamMembers.${index}.email`}
|
||||
type="email"
|
||||
placeholder="jane@example.com"
|
||||
{...register(`teamMembers.${index}.email`)}
|
||||
/>
|
||||
{errors.teamMembers?.[index]?.email && (
|
||||
<p className="text-sm text-destructive">
|
||||
{errors.teamMembers[index]?.email?.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Role */}
|
||||
<div className="space-y-2">
|
||||
<Label>Role</Label>
|
||||
<Select
|
||||
value={form.watch(`teamMembers.${index}.role`)}
|
||||
onValueChange={(value) =>
|
||||
form.setValue(`teamMembers.${index}.role`, value as TeamMemberRole)
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select role" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{roleOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Title/Position */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={`teamMembers.${index}.title`}>
|
||||
Title/Position <span className="text-muted-foreground text-xs">(optional)</span>
|
||||
</Label>
|
||||
<Input
|
||||
id={`teamMembers.${index}.title`}
|
||||
placeholder="CTO, Designer, etc."
|
||||
{...register(`teamMembers.${index}.title`)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={addMember}
|
||||
className="w-full"
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Add Another Team Member
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
</WizardStepContent>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user