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:
2026-01-30 13:41:32 +01:00
commit a606292aaa
290 changed files with 70691 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
'use client'
import { motion } from 'motion/react'
import { UseFormReturn } from 'react-hook-form'
import { WizardStepContent } from '@/components/forms/form-wizard'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Textarea } from '@/components/ui/textarea'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import type { ApplicationFormData } from '@/server/routers/application'
import { OceanIssue } from '@prisma/client'
interface OceanIssueOption {
value: OceanIssue
label: string
}
const oceanIssueOptions: OceanIssueOption[] = [
{ value: 'POLLUTION_REDUCTION', label: 'Reduction of pollution (plastics, chemicals, noise, light,...)' },
{ value: 'CLIMATE_MITIGATION', label: 'Mitigation of climate change and sea-level rise' },
{ value: 'TECHNOLOGY_INNOVATION', label: 'Technology & innovations' },
{ value: 'SUSTAINABLE_SHIPPING', label: 'Sustainable shipping & yachting' },
{ value: 'BLUE_CARBON', label: 'Blue carbon' },
{ value: 'HABITAT_RESTORATION', label: 'Restoration of marine habitats & ecosystems' },
{ value: 'COMMUNITY_CAPACITY', label: 'Capacity building for coastal communities' },
{ value: 'SUSTAINABLE_FISHING', label: 'Sustainable fishing and aquaculture & blue food' },
{ value: 'CONSUMER_AWARENESS', label: 'Consumer awareness and education' },
{ value: 'OCEAN_ACIDIFICATION', label: 'Mitigation of ocean acidification' },
{ value: 'OTHER', label: 'Other' },
]
interface StepProjectProps {
form: UseFormReturn<ApplicationFormData>
}
export function StepProject({ form }: StepProjectProps) {
const { register, formState: { errors }, setValue, watch } = form
const oceanIssue = watch('oceanIssue')
const description = watch('description') || ''
return (
<WizardStepContent
title="Tell us about your project"
description="Share the details of your ocean protection initiative."
>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="mx-auto max-w-lg space-y-6"
>
{/* Project Name */}
<div className="space-y-2">
<Label htmlFor="projectName">
Name of your project/startup <span className="text-destructive">*</span>
</Label>
<Input
id="projectName"
placeholder="Ocean Guardian AI"
{...register('projectName')}
className="h-12 text-base"
/>
{errors.projectName && (
<p className="text-sm text-destructive">{errors.projectName.message}</p>
)}
</div>
{/* Team Name (optional) */}
<div className="space-y-2">
<Label htmlFor="teamName">
Team Name <span className="text-muted-foreground text-xs">(optional)</span>
</Label>
<Input
id="teamName"
placeholder="Blue Innovation Team"
{...register('teamName')}
className="h-12 text-base"
/>
</div>
{/* Ocean Issue */}
<div className="space-y-2">
<Label>
What type of ocean issue does your project address? <span className="text-destructive">*</span>
</Label>
<Select
value={oceanIssue}
onValueChange={(value) => setValue('oceanIssue', value as OceanIssue)}
>
<SelectTrigger className="h-12 text-base">
<SelectValue placeholder="Select an ocean issue" />
</SelectTrigger>
<SelectContent>
{oceanIssueOptions.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
{errors.oceanIssue && (
<p className="text-sm text-destructive">{errors.oceanIssue.message}</p>
)}
</div>
{/* Description */}
<div className="space-y-2">
<Label htmlFor="description">
Briefly describe your project idea and objectives <span className="text-destructive">*</span>
</Label>
<p className="text-xs text-muted-foreground">
Keep it brief - you&apos;ll have the opportunity to provide more details later.
</p>
<Textarea
id="description"
placeholder="Our project aims to..."
rows={5}
maxLength={2000}
{...register('description')}
className="text-base resize-none"
/>
<div className="flex justify-between text-xs text-muted-foreground">
<span>
{errors.description ? (
<span className="text-destructive">{errors.description.message}</span>
) : (
'Minimum 20 characters'
)}
</span>
<span>{description.length} characters</span>
</div>
</div>
</motion.div>
</WizardStepContent>
)
}