Add unified expertise tag system and round entry notifications

- ExpertiseSelect now fetches tags from database with category grouping
- Tags set by admin during invitation are locked and cannot be removed
- Onboarding merges user-selected tags with admin-preset tags
- MENTOR role now goes through onboarding flow
- Added migration for Round.entryNotificationType column
- Added seed script with ~90 comprehensive expertise tags

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-04 01:15:21 +01:00
parent 41a36f72b3
commit 8cdf6c9e5e
5 changed files with 475 additions and 80 deletions

View File

@@ -0,0 +1,4 @@
-- AddRoundEntryNotification
-- Adds the entryNotificationType column to Round table for configurable notifications
ALTER TABLE "Round" ADD COLUMN "entryNotificationType" TEXT;

View File

@@ -0,0 +1,189 @@
/**
* Seed script for expertise tags
*
* Run with: npx tsx prisma/seed-expertise-tags.ts
* Or in Docker: docker exec mopc-app npx tsx prisma/seed-expertise-tags.ts
*/
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
interface TagDefinition {
name: string
description: string
category: string
color: string
}
const EXPERTISE_TAGS: TagDefinition[] = [
// Marine Science & Biology
{ name: 'Marine Biology', description: 'Study of marine organisms and ecosystems', category: 'Marine Science', color: '#0ea5e9' },
{ name: 'Oceanography', description: 'Physical and chemical properties of the ocean', category: 'Marine Science', color: '#0284c7' },
{ name: 'Marine Ecology', description: 'Relationships between marine organisms and environment', category: 'Marine Science', color: '#0891b2' },
{ name: 'Fisheries Science', description: 'Management and conservation of fish populations', category: 'Marine Science', color: '#06b6d4' },
{ name: 'Marine Microbiology', description: 'Study of microorganisms in marine environments', category: 'Marine Science', color: '#22d3ee' },
{ name: 'Marine Genetics', description: 'Genetic research on marine species', category: 'Marine Science', color: '#67e8f9' },
{ name: 'Deep Sea Research', description: 'Exploration and study of deep ocean environments', category: 'Marine Science', color: '#164e63' },
// Conservation & Environment
{ name: 'Ocean Conservation', description: 'Protection and preservation of marine environments', category: 'Conservation', color: '#059669' },
{ name: 'Biodiversity', description: 'Marine species diversity and protection', category: 'Conservation', color: '#10b981' },
{ name: 'Coral Reef Restoration', description: 'Rehabilitation and protection of coral ecosystems', category: 'Conservation', color: '#f97316' },
{ name: 'Marine Protected Areas', description: 'Design and management of ocean reserves', category: 'Conservation', color: '#34d399' },
{ name: 'Species Conservation', description: 'Protection of endangered marine species', category: 'Conservation', color: '#6ee7b7' },
{ name: 'Habitat Restoration', description: 'Rehabilitation of damaged marine habitats', category: 'Conservation', color: '#a7f3d0' },
{ name: 'Wildlife Management', description: 'Management of marine wildlife populations', category: 'Conservation', color: '#047857' },
// Climate & Environment
{ name: 'Climate Science', description: 'Ocean-climate interactions and research', category: 'Climate', color: '#14b8a6' },
{ name: 'Climate Adaptation', description: 'Strategies for adapting to climate change impacts', category: 'Climate', color: '#2dd4bf' },
{ name: 'Climate Mitigation', description: 'Reducing greenhouse gas emissions', category: 'Climate', color: '#5eead4' },
{ name: 'Blue Carbon', description: 'Carbon sequestration in marine ecosystems', category: 'Climate', color: '#99f6e4' },
{ name: 'Ocean Acidification', description: 'Impact of CO2 on ocean chemistry', category: 'Climate', color: '#0d9488' },
{ name: 'Sea Level Rise', description: 'Causes and impacts of rising sea levels', category: 'Climate', color: '#115e59' },
// Pollution & Waste
{ name: 'Plastic Pollution', description: 'Marine plastic waste and solutions', category: 'Pollution', color: '#84cc16' },
{ name: 'Waste Management', description: 'Marine and coastal waste solutions', category: 'Pollution', color: '#65a30d' },
{ name: 'Microplastics Research', description: 'Study of microplastic impacts', category: 'Pollution', color: '#a3e635' },
{ name: 'Chemical Pollution', description: 'Industrial and agricultural runoff', category: 'Pollution', color: '#bef264' },
{ name: 'Oil Spill Response', description: 'Prevention and cleanup of oil spills', category: 'Pollution', color: '#4d7c0f' },
{ name: 'Water Quality', description: 'Monitoring and improving water quality', category: 'Pollution', color: '#3f6212' },
{ name: 'Circular Economy', description: 'Reducing waste through circular systems', category: 'Pollution', color: '#d9f99d' },
// Sustainable Industries
{ name: 'Sustainable Fishing', description: 'Environmentally responsible fishing practices', category: 'Sustainable Industries', color: '#22c55e' },
{ name: 'Aquaculture', description: 'Sustainable fish and seafood farming', category: 'Sustainable Industries', color: '#16a34a' },
{ name: 'Sustainable Shipping', description: 'Green maritime transportation', category: 'Sustainable Industries', color: '#6366f1' },
{ name: 'Sustainable Tourism', description: 'Eco-friendly coastal and marine tourism', category: 'Sustainable Industries', color: '#4f46e5' },
{ name: 'Blue Economy', description: 'Sustainable use of ocean resources', category: 'Sustainable Industries', color: '#3b82f6' },
{ name: 'Seaweed Industry', description: 'Sustainable seaweed cultivation and products', category: 'Sustainable Industries', color: '#15803d' },
{ name: 'Seafood Traceability', description: 'Supply chain transparency in seafood', category: 'Sustainable Industries', color: '#166534' },
// Technology & Innovation
{ name: 'Marine Technology', description: 'Technological solutions for ocean challenges', category: 'Technology', color: '#8b5cf6' },
{ name: 'Ocean Robotics', description: 'Autonomous underwater vehicles and drones', category: 'Technology', color: '#7c3aed' },
{ name: 'Remote Sensing', description: 'Satellite and sensor-based ocean monitoring', category: 'Technology', color: '#a78bfa' },
{ name: 'Data Science', description: 'Ocean data analysis and modeling', category: 'Technology', color: '#c4b5fd' },
{ name: 'AI & Machine Learning', description: 'Artificial intelligence for marine research', category: 'Technology', color: '#6d28d9' },
{ name: 'Biotechnology', description: 'Marine-derived biotechnology applications', category: 'Technology', color: '#5b21b6' },
{ name: 'Sensors & IoT', description: 'Internet of Things for ocean monitoring', category: 'Technology', color: '#4c1d95' },
{ name: 'Blockchain', description: 'Distributed ledger for transparency and traceability', category: 'Technology', color: '#ddd6fe' },
// Energy
{ name: 'Renewable Energy', description: 'Marine renewable energy sources', category: 'Energy', color: '#eab308' },
{ name: 'Offshore Wind', description: 'Wind energy from offshore installations', category: 'Energy', color: '#ca8a04' },
{ name: 'Wave Energy', description: 'Power generation from ocean waves', category: 'Energy', color: '#facc15' },
{ name: 'Tidal Energy', description: 'Power generation from tidal movements', category: 'Energy', color: '#fde047' },
{ name: 'Ocean Thermal Energy', description: 'OTEC and thermal gradient technologies', category: 'Energy', color: '#a16207' },
// Policy & Governance
{ name: 'Environmental Policy', description: 'Ocean and environmental policy development', category: 'Policy', color: '#a855f7' },
{ name: 'International Law', description: 'Law of the sea and maritime regulations', category: 'Policy', color: '#9333ea' },
{ name: 'Ocean Governance', description: 'Management and governance of ocean spaces', category: 'Policy', color: '#c084fc' },
{ name: 'Regulatory Compliance', description: 'Environmental regulations and compliance', category: 'Policy', color: '#e879f9' },
{ name: 'Stakeholder Engagement', description: 'Engaging communities and stakeholders', category: 'Policy', color: '#d946ef' },
{ name: 'Advocacy', description: 'Promoting ocean conservation causes', category: 'Policy', color: '#a21caf' },
// Business & Finance
{ name: 'Entrepreneurship', description: 'Ocean-focused startup development', category: 'Business', color: '#f43f5e' },
{ name: 'Investment & Finance', description: 'Sustainable ocean investment', category: 'Business', color: '#e11d48' },
{ name: 'Impact Investing', description: 'Investment for environmental impact', category: 'Business', color: '#fb7185' },
{ name: 'Business Development', description: 'Growing ocean-focused businesses', category: 'Business', color: '#fda4af' },
{ name: 'Social Enterprise', description: 'Businesses with social and environmental missions', category: 'Business', color: '#be123c' },
{ name: 'Carbon Markets', description: 'Carbon credit trading and blue carbon markets', category: 'Business', color: '#9f1239' },
// Education & Outreach
{ name: 'Education & Outreach', description: 'Ocean literacy and public education', category: 'Education', color: '#ec4899' },
{ name: 'Research & Academia', description: 'Academic research and publication', category: 'Education', color: '#db2777' },
{ name: 'Citizen Science', description: 'Public participation in scientific research', category: 'Education', color: '#f472b6' },
{ name: 'Youth Engagement', description: 'Engaging young people in ocean conservation', category: 'Education', color: '#f9a8d4' },
{ name: 'Media & Communications', description: 'Science communication and journalism', category: 'Education', color: '#be185d' },
{ name: 'Documentary Filmmaking', description: 'Visual storytelling for ocean conservation', category: 'Education', color: '#9d174d' },
// Coastal & Community
{ name: 'Coastal Management', description: 'Integrated coastal zone management', category: 'Coastal', color: '#f59e0b' },
{ name: 'Community Development', description: 'Supporting coastal communities', category: 'Coastal', color: '#d97706' },
{ name: 'Small-scale Fisheries', description: 'Supporting artisanal and local fishers', category: 'Coastal', color: '#fbbf24' },
{ name: 'Indigenous Knowledge', description: 'Traditional ecological knowledge', category: 'Coastal', color: '#fcd34d' },
{ name: 'Disaster Resilience', description: 'Coastal hazard preparedness and response', category: 'Coastal', color: '#b45309' },
{ name: 'Mangrove Conservation', description: 'Protection and restoration of mangroves', category: 'Coastal', color: '#92400e' },
// Specific Focus Areas
{ name: 'Arctic & Antarctic', description: 'Polar ocean research and conservation', category: 'Regions', color: '#0369a1' },
{ name: 'Mediterranean', description: 'Mediterranean Sea conservation', category: 'Regions', color: '#0c4a6e' },
{ name: 'Pacific Islands', description: 'Pacific island marine conservation', category: 'Regions', color: '#075985' },
{ name: 'Coral Triangle', description: 'Indo-Pacific coral reef biodiversity', category: 'Regions', color: '#0284c7' },
// Cross-cutting
{ name: 'Project Management', description: 'Managing conservation and research projects', category: 'Skills', color: '#64748b' },
{ name: 'Grant Writing', description: 'Securing funding through proposals', category: 'Skills', color: '#475569' },
{ name: 'Monitoring & Evaluation', description: 'Measuring project impact and outcomes', category: 'Skills', color: '#334155' },
{ name: 'GIS & Mapping', description: 'Geographic information systems for ocean data', category: 'Skills', color: '#1e293b' },
{ name: 'Scientific Diving', description: 'Underwater research and data collection', category: 'Skills', color: '#94a3b8' },
]
async function main() {
console.log('🏷️ Seeding expertise tags...\n')
// Get existing tags
const existingTags = await prisma.expertiseTag.findMany({
select: { name: true },
})
const existingNames = new Set(existingTags.map((t) => t.name))
// Filter out tags that already exist
const newTags = EXPERTISE_TAGS.filter((t) => !existingNames.has(t.name))
if (newTags.length === 0) {
console.log('✅ All tags already exist in database')
return
}
// Get max sort order
const maxOrder = await prisma.expertiseTag.aggregate({
_max: { sortOrder: true },
})
const startOrder = (maxOrder._max.sortOrder || 0) + 1
// Create tags
const result = await prisma.expertiseTag.createMany({
data: newTags.map((tag, index) => ({
name: tag.name,
description: tag.description,
category: tag.category,
color: tag.color,
sortOrder: startOrder + index,
isActive: true,
})),
})
console.log(`✅ Created ${result.count} new expertise tags`)
console.log(` Skipped ${existingNames.size} existing tags`)
// Print summary by category
const byCategory = newTags.reduce(
(acc, tag) => {
acc[tag.category] = (acc[tag.category] || 0) + 1
return acc
},
{} as Record<string, number>
)
console.log('\n📊 Tags created by category:')
Object.entries(byCategory)
.sort(([a], [b]) => a.localeCompare(b))
.forEach(([category, count]) => {
console.log(` ${category}: ${count}`)
})
}
main()
.catch((e) => {
console.error('❌ Error seeding expertise tags:', e)
process.exit(1)
})
.finally(async () => {
await prisma.$disconnect()
})