feat(m4): project & member management with tests and e2e

- ProjectRepository, ProjectMemberRepository, minimal NotificationRepository
- ProjectService: create/update/archive/delete project, add/remove member,
  permission checks (isMember/admin), member-added notification, getDashboard
  skeleton, getMyProjects, getMemberRole
- projectValidator, API services factory
- API routes: projects CRUD, members list/add/remove
- Screens: dashboard (project list + create form), project overview, members,
  settings; layout (Header/Sidebar/ProjectNav) and project components
- Unit tests (Project/ProjectMember repositories, ProjectService),
  integration project-member-permission, e2e project-management
This commit is contained in:
Ken Yasue
2026-06-25 01:02:10 +02:00
parent 8a67523c0e
commit 9eb391ea8d
30 changed files with 2230 additions and 14 deletions

View File

@ -1,5 +1,9 @@
import { redirect } from 'next/navigation';
import { getCurrentUser } from '@/lib/auth/getCurrentUser';
import { getCurrentUser, toPublicUser } from '@/lib/auth/getCurrentUser';
import { createProjectService } from '@/lib/api/services';
import { Header } from '@/components/layout/Header';
import { ProjectCard } from '@/components/project/ProjectCard';
import { CreateProjectForm } from '@/components/project/CreateProjectForm';
export const dynamic = 'force-dynamic';
@ -9,19 +13,32 @@ export default async function DashboardPage() {
redirect('/login');
}
const service = createProjectService();
const projects = service.getMyProjects(user.id);
return (
<main className="min-h-screen bg-gray-50 p-8">
<div className="mx-auto max-w-3xl">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold"></h1>
<a href="/profile" className="text-sm text-blue-600 hover:underline">
{user.name}
</a>
</div>
<p className="mt-4 text-gray-600">
</p>
</div>
</main>
<div className="min-h-screen bg-gray-50">
<Header user={toPublicUser(user)} />
<main className="mx-auto max-w-4xl space-y-6 p-6">
<h1 className="text-2xl font-bold"></h1>
<CreateProjectForm />
<section>
<h2 className="mb-3 text-sm font-semibold text-gray-700">
</h2>
{projects.length === 0 ? (
<p className="text-sm text-gray-400">
</p>
) : (
<div className="grid gap-3 sm:grid-cols-2">
{projects.map((project) => (
<ProjectCard key={project.id} project={project} />
))}
</div>
)}
</section>
</main>
</div>
);
}