feat: round finalization with ranking-based outcomes + award pool notifications
All checks were successful
Build and Push Docker Image / build (push) Successful in 10m0s
All checks were successful
Build and Push Docker Image / build (push) Successful in 10m0s
- processRoundClose EVALUATION uses ranking scores + advanceMode config (threshold vs count) to auto-set proposedOutcome instead of defaulting all to PASSED - Advancement emails generate invite tokens for passwordless users with "Create Your Account" CTA; rejection emails have no link - Finalization UI shows account stats (invite vs dashboard link counts) - Fixed getFinalizationSummary ranking query (was using non-existent rankingsJson) - New award pool notification system: getAwardSelectionNotificationTemplate email, notifyEligibleProjects mutation with invite token generation, "Notify Pool" button on award detail page with custom message dialog Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -48,7 +48,10 @@ import {
|
||||
} from '@/components/ui/alert-dialog'
|
||||
import { CountrySelect } from '@/components/ui/country-select'
|
||||
import { Checkbox as CheckboxPrimitive } from '@/components/ui/checkbox'
|
||||
import { ProjectLogoUpload } from '@/components/shared/project-logo-upload'
|
||||
import { UserAvatar } from '@/components/shared/user-avatar'
|
||||
import {
|
||||
FolderOpen,
|
||||
Users,
|
||||
UserPlus,
|
||||
Crown,
|
||||
@@ -59,7 +62,14 @@ import {
|
||||
CheckCircle,
|
||||
Clock,
|
||||
FileText,
|
||||
ImageIcon,
|
||||
MapPin,
|
||||
Waves,
|
||||
GraduationCap,
|
||||
Heart,
|
||||
Calendar,
|
||||
} from 'lucide-react'
|
||||
import { formatDateOnly } from '@/lib/utils'
|
||||
|
||||
const inviteSchema = z.object({
|
||||
name: z.string().min(1, 'Name is required'),
|
||||
@@ -86,7 +96,21 @@ const statusLabels: Record<string, { label: string; icon: React.ComponentType<{
|
||||
SUSPENDED: { label: 'Suspended', icon: AlertCircle },
|
||||
}
|
||||
|
||||
export default function ApplicantTeamPage() {
|
||||
const OCEAN_ISSUE_LABELS: Record<string, string> = {
|
||||
POLLUTION_REDUCTION: 'Pollution Reduction',
|
||||
CLIMATE_MITIGATION: 'Climate Mitigation',
|
||||
TECHNOLOGY_INNOVATION: 'Technology Innovation',
|
||||
SUSTAINABLE_SHIPPING: 'Sustainable Shipping',
|
||||
BLUE_CARBON: 'Blue Carbon',
|
||||
HABITAT_RESTORATION: 'Habitat Restoration',
|
||||
COMMUNITY_CAPACITY: 'Community Capacity',
|
||||
SUSTAINABLE_FISHING: 'Sustainable Fishing',
|
||||
CONSUMER_AWARENESS: 'Consumer Awareness',
|
||||
OCEAN_ACIDIFICATION: 'Ocean Acidification',
|
||||
OTHER: 'Other',
|
||||
}
|
||||
|
||||
export default function ApplicantProjectPage() {
|
||||
const { data: session, status: sessionStatus } = useSession()
|
||||
const isAuthenticated = sessionStatus === 'authenticated'
|
||||
const [isInviteOpen, setIsInviteOpen] = useState(false)
|
||||
@@ -96,13 +120,20 @@ export default function ApplicantTeamPage() {
|
||||
{ enabled: isAuthenticated }
|
||||
)
|
||||
|
||||
const projectId = dashboardData?.project?.id
|
||||
const project = dashboardData?.project
|
||||
const projectId = project?.id
|
||||
const isIntakeOpen = dashboardData?.isIntakeOpen ?? false
|
||||
|
||||
const { data: teamData, isLoading: teamLoading, refetch } = trpc.applicant.getTeamMembers.useQuery(
|
||||
{ projectId: projectId! },
|
||||
{ enabled: !!projectId }
|
||||
)
|
||||
|
||||
const { data: logoUrl, refetch: refetchLogo } = trpc.applicant.getProjectLogoUrl.useQuery(
|
||||
{ projectId: projectId! },
|
||||
{ enabled: !!projectId }
|
||||
)
|
||||
|
||||
const inviteMutation = trpc.applicant.inviteTeamMember.useMutation({
|
||||
onSuccess: (result) => {
|
||||
if (result.requiresAccountSetup) {
|
||||
@@ -180,18 +211,18 @@ export default function ApplicantTeamPage() {
|
||||
)
|
||||
}
|
||||
|
||||
if (!projectId) {
|
||||
if (!projectId || !project) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold tracking-tight">Team</h1>
|
||||
<h1 className="text-2xl font-semibold tracking-tight">Project</h1>
|
||||
</div>
|
||||
<Card>
|
||||
<CardContent className="flex flex-col items-center justify-center py-12">
|
||||
<FileText className="h-12 w-12 text-muted-foreground/50 mb-4" />
|
||||
<h2 className="text-xl font-semibold mb-2">No Project</h2>
|
||||
<p className="text-muted-foreground text-center">
|
||||
Submit a project first to manage your team.
|
||||
Submit a project first to view details.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -210,159 +241,297 @@ export default function ApplicantTeamPage() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Project logo */}
|
||||
<div className="shrink-0 h-14 w-14 rounded-xl border bg-muted/50 flex items-center justify-center overflow-hidden">
|
||||
{logoUrl ? (
|
||||
<img src={logoUrl} alt={project.title} className="h-full w-full object-cover" />
|
||||
) : (
|
||||
<FolderOpen className="h-7 w-7 text-muted-foreground/60" />
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold tracking-tight flex items-center gap-2">
|
||||
<Users className="h-6 w-6" />
|
||||
Team Members
|
||||
<h1 className="text-2xl font-semibold tracking-tight">
|
||||
{project.title}
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Manage your project team
|
||||
{project.teamName ? `Team: ${project.teamName}` : 'Project details and team management'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{isTeamLead && (
|
||||
<Dialog open={isInviteOpen} onOpenChange={setIsInviteOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button>
|
||||
<UserPlus className="mr-2 h-4 w-4" />
|
||||
Invite Member
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Invite Team Member</DialogTitle>
|
||||
<DialogDescription>
|
||||
Send an invitation to join your project team. They will receive an email
|
||||
with instructions to create their account.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form onSubmit={form.handleSubmit(onInvite)} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">Full Name</Label>
|
||||
<Input
|
||||
id="name"
|
||||
placeholder="Jane Doe"
|
||||
{...form.register('name')}
|
||||
/>
|
||||
{form.formState.errors.name && (
|
||||
<p className="text-sm text-destructive">
|
||||
{form.formState.errors.name.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">Email Address</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="jane@example.com"
|
||||
{...form.register('email')}
|
||||
/>
|
||||
{form.formState.errors.email && (
|
||||
<p className="text-sm text-destructive">
|
||||
{form.formState.errors.email.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="role">Role</Label>
|
||||
<Select
|
||||
value={form.watch('role')}
|
||||
onValueChange={(value) => form.setValue('role', value as 'MEMBER' | 'ADVISOR')}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select role" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="MEMBER">Team Member</SelectItem>
|
||||
<SelectItem value="ADVISOR">Advisor</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="title">Title (optional)</Label>
|
||||
<Input
|
||||
id="title"
|
||||
placeholder="CTO, Designer..."
|
||||
{...form.register('title')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Nationality</Label>
|
||||
<CountrySelect
|
||||
value={form.watch('nationality') || ''}
|
||||
onChange={(v) => form.setValue('nationality', v)}
|
||||
placeholder="Select nationality"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Country of Residence</Label>
|
||||
<CountrySelect
|
||||
value={form.watch('country') || ''}
|
||||
onChange={(v) => form.setValue('country', v)}
|
||||
placeholder="Select country"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="institution">Institution (optional)</Label>
|
||||
<Input
|
||||
id="institution"
|
||||
placeholder="e.g., Ocean Research Institute"
|
||||
{...form.register('institution')}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckboxPrimitive
|
||||
id="sendInvite"
|
||||
checked={form.watch('sendInvite')}
|
||||
onCheckedChange={(checked) => form.setValue('sendInvite', !!checked)}
|
||||
/>
|
||||
<Label htmlFor="sendInvite" className="text-sm font-normal cursor-pointer">
|
||||
Send platform invite email
|
||||
</Label>
|
||||
</div>
|
||||
<div className="rounded-lg bg-muted/50 border p-3 text-sm">
|
||||
<p className="font-medium mb-1">What invited members can do:</p>
|
||||
<ul className="list-disc list-inside space-y-1 text-muted-foreground">
|
||||
<li>Upload documents for submission rounds</li>
|
||||
<li>View project status and competition progress</li>
|
||||
<li>Receive email notifications about round updates</li>
|
||||
</ul>
|
||||
<p className="mt-2 text-muted-foreground">Only the Team Lead can invite or remove members.</p>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setIsInviteOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={inviteMutation.isPending}>
|
||||
{inviteMutation.isPending && (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
)}
|
||||
Send Invitation
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Project Details Card */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<FileText className="h-5 w-5" />
|
||||
Project Information
|
||||
</CardTitle>
|
||||
{isIntakeOpen && (
|
||||
<Badge variant="outline" className="text-amber-600 border-amber-200 bg-amber-50">
|
||||
Editable during intake
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* Category & Ocean Issue badges */}
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{project.competitionCategory && (
|
||||
<Badge variant="outline" className="gap-1">
|
||||
<GraduationCap className="h-3 w-3" />
|
||||
{project.competitionCategory === 'STARTUP' ? 'Start-up' : 'Business Concept'}
|
||||
</Badge>
|
||||
)}
|
||||
{project.oceanIssue && (
|
||||
<Badge variant="outline" className="gap-1">
|
||||
<Waves className="h-3 w-3" />
|
||||
{OCEAN_ISSUE_LABELS[project.oceanIssue] || project.oceanIssue.replace(/_/g, ' ')}
|
||||
</Badge>
|
||||
)}
|
||||
{project.wantsMentorship && (
|
||||
<Badge variant="outline" className="gap-1 text-pink-600 border-pink-200 bg-pink-50">
|
||||
<Heart className="h-3 w-3" />
|
||||
Wants Mentorship
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
{project.description && (
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground mb-1">Description</p>
|
||||
<p className="text-sm whitespace-pre-wrap">{project.description}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Location, Institution, Founded */}
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
{(project.country || project.geographicZone) && (
|
||||
<div className="flex items-start gap-2">
|
||||
<MapPin className="h-4 w-4 text-muted-foreground mt-0.5" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">Location</p>
|
||||
<p className="text-sm">{project.geographicZone || project.country}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{project.institution && (
|
||||
<div className="flex items-start gap-2">
|
||||
<GraduationCap className="h-4 w-4 text-muted-foreground mt-0.5" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">Institution</p>
|
||||
<p className="text-sm">{project.institution}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{project.foundedAt && (
|
||||
<div className="flex items-start gap-2">
|
||||
<Calendar className="h-4 w-4 text-muted-foreground mt-0.5" />
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">Founded</p>
|
||||
<p className="text-sm">{formatDateOnly(project.foundedAt)}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Mentor info */}
|
||||
{project.mentorAssignment?.mentor && (
|
||||
<div className="rounded-lg border p-3 bg-muted/50">
|
||||
<p className="text-sm font-medium mb-1">Assigned Mentor</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{project.mentorAssignment.mentor.name} ({project.mentorAssignment.mentor.email})
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tags */}
|
||||
{project.tags && project.tags.length > 0 && (
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground mb-1">Tags</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{project.tags.map((tag: string) => (
|
||||
<Badge key={tag} variant="secondary" className="text-xs">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Project Logo */}
|
||||
{isTeamLead && projectId && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<ImageIcon className="h-5 w-5" />
|
||||
Project Logo
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Click the image to upload or change your project logo.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex justify-center">
|
||||
<ProjectLogoUpload
|
||||
projectId={projectId}
|
||||
currentLogoUrl={logoUrl}
|
||||
onUploadComplete={() => refetchLogo()}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Team Members List */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Team ({teamData?.teamMembers.length || 0} members)</CardTitle>
|
||||
<CardDescription>
|
||||
Everyone on this list can view and collaborate on this project.
|
||||
</CardDescription>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Users className="h-5 w-5" />
|
||||
Team ({teamData?.teamMembers.length || 0} members)
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Everyone on this list can view and collaborate on this project.
|
||||
</CardDescription>
|
||||
</div>
|
||||
{isTeamLead && (
|
||||
<Dialog open={isInviteOpen} onOpenChange={setIsInviteOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button size="sm">
|
||||
<UserPlus className="mr-2 h-4 w-4" />
|
||||
Invite
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Invite Team Member</DialogTitle>
|
||||
<DialogDescription>
|
||||
Send an invitation to join your project team. They will receive an email
|
||||
with instructions to create their account.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<form onSubmit={form.handleSubmit(onInvite)} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">Full Name</Label>
|
||||
<Input
|
||||
id="name"
|
||||
placeholder="Jane Doe"
|
||||
{...form.register('name')}
|
||||
/>
|
||||
{form.formState.errors.name && (
|
||||
<p className="text-sm text-destructive">
|
||||
{form.formState.errors.name.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">Email Address</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="jane@example.com"
|
||||
{...form.register('email')}
|
||||
/>
|
||||
{form.formState.errors.email && (
|
||||
<p className="text-sm text-destructive">
|
||||
{form.formState.errors.email.message}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="role">Role</Label>
|
||||
<Select
|
||||
value={form.watch('role')}
|
||||
onValueChange={(value) => form.setValue('role', value as 'MEMBER' | 'ADVISOR')}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select role" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="MEMBER">Team Member</SelectItem>
|
||||
<SelectItem value="ADVISOR">Advisor</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="title">Title (optional)</Label>
|
||||
<Input
|
||||
id="title"
|
||||
placeholder="CTO, Designer..."
|
||||
{...form.register('title')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Nationality</Label>
|
||||
<CountrySelect
|
||||
value={form.watch('nationality') || ''}
|
||||
onChange={(v) => form.setValue('nationality', v)}
|
||||
placeholder="Select nationality"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Country of Residence</Label>
|
||||
<CountrySelect
|
||||
value={form.watch('country') || ''}
|
||||
onChange={(v) => form.setValue('country', v)}
|
||||
placeholder="Select country"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="institution">Institution (optional)</Label>
|
||||
<Input
|
||||
id="institution"
|
||||
placeholder="e.g., Ocean Research Institute"
|
||||
{...form.register('institution')}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckboxPrimitive
|
||||
id="sendInvite"
|
||||
checked={form.watch('sendInvite')}
|
||||
onCheckedChange={(checked) => form.setValue('sendInvite', !!checked)}
|
||||
/>
|
||||
<Label htmlFor="sendInvite" className="text-sm font-normal cursor-pointer">
|
||||
Send platform invite email
|
||||
</Label>
|
||||
</div>
|
||||
<div className="rounded-lg bg-muted/50 border p-3 text-sm">
|
||||
<p className="font-medium mb-1">What invited members can do:</p>
|
||||
<ul className="list-disc list-inside space-y-1 text-muted-foreground">
|
||||
<li>Upload documents for submission rounds</li>
|
||||
<li>View project status and competition progress</li>
|
||||
<li>Receive email notifications about round updates</li>
|
||||
</ul>
|
||||
<p className="mt-2 text-muted-foreground">Only the Team Lead can invite or remove members.</p>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setIsInviteOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={inviteMutation.isPending}>
|
||||
{inviteMutation.isPending && (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
)}
|
||||
Send Invitation
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{teamData?.teamMembers.map((member) => {
|
||||
@@ -374,13 +543,16 @@ export default function ApplicantTeamPage() {
|
||||
className="flex items-center justify-between rounded-lg border p-4"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-muted">
|
||||
{member.role === 'LEAD' ? (
|
||||
<Crown className="h-5 w-5 text-yellow-500" />
|
||||
) : (
|
||||
<span className="text-sm font-medium">
|
||||
{member.user.name?.charAt(0).toUpperCase() || '?'}
|
||||
</span>
|
||||
<div className="relative">
|
||||
<UserAvatar
|
||||
user={member.user}
|
||||
avatarUrl={teamData?.avatarUrls?.[member.userId] || null}
|
||||
size="md"
|
||||
/>
|
||||
{member.role === 'LEAD' && (
|
||||
<div className="absolute -top-1 -right-1 flex h-4 w-4 items-center justify-center rounded-full bg-yellow-100 ring-2 ring-white">
|
||||
<Crown className="h-2.5 w-2.5 text-yellow-600" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
@@ -455,25 +627,6 @@ export default function ApplicantTeamPage() {
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Team Documents - visible via applicant documents page */}
|
||||
|
||||
{/* Info Card */}
|
||||
<Card className="bg-muted/50">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertCircle className="h-5 w-5 text-muted-foreground mt-0.5" />
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<p className="font-medium text-foreground">About Team Access</p>
|
||||
<p className="mt-1">
|
||||
All team members can view project details and status updates.
|
||||
Only the team lead can invite or remove team members.
|
||||
Invited members will receive an email to set up their account.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user