Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ export class HttpObservabilityInterceptor implements NestInterceptor {
? "http.mutation"
: "http.request";

this.logger.log(this.toJsonLog(this.buildBaseLog(request, event, statusCode, durationMs)));
if (event !== "http.request") {
this.logger.log(
this.toJsonLog(this.buildBaseLog(request, event, statusCode, durationMs)),
);
}
}),
catchError((error: unknown) => {
const durationMs = Number((performance.now() - startedAt).toFixed(1));
Expand Down
8 changes: 4 additions & 4 deletions apps/api/src/projects/guards/team-member.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
ExecutionContext,
ForbiddenException,
Injectable,
NotFoundException
NotFoundException,
} from "@nestjs/common";

import { PrismaService } from "../../prisma/prisma.service";
Expand All @@ -30,9 +30,9 @@ export class TeamMemberGuard implements CanActivate {
where: {
teamId_userId: {
teamId,
userId: request.user.sub
}
}
userId: request.user.sub,
},
},
});

if (!membership) {
Expand Down
8 changes: 4 additions & 4 deletions apps/api/src/teams/guards/team.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
ExecutionContext,
ForbiddenException,
Injectable,
NotFoundException
NotFoundException,
} from "@nestjs/common";

import { PrismaService } from "../../prisma/prisma.service";
Expand Down Expand Up @@ -31,9 +31,9 @@ export class TeamGuard implements CanActivate {
where: {
teamId_userId: {
teamId,
userId: request.user.sub
}
}
userId: request.user.sub,
},
},
});

if (!membership) {
Expand Down
14 changes: 11 additions & 3 deletions apps/web/app/(dashboard)/projects/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getMyTeams } from "@/actions/team.actions";
import { PageHeader } from "@/components/layout/page-header";
import { SectionSkeleton } from "@/components/layout/section-skeleton";
import { ProjectsBrowser, type GlobalProjectItem } from "@/components/projects/projects-browser";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";

async function ProjectsContent() {
Expand All @@ -15,9 +16,16 @@ async function ProjectsContent() {

if (teams.length === 0) {
return (
<Card className="text-sm text-muted-foreground">
No teams found. Create a team first to start managing projects.
</Card>
<div className="flex flex-col items-center justify-center rounded-xl border border-dashed border-border py-20 px-6 text-center">
<h2 className="text-2xl font-bold mb-3 tracking-tight">No teams found</h2>
<p className="text-muted-foreground mb-8 max-w-sm">
Create your first team to start organizing projects, tasks and collaborate with your
colleagues.
</p>
<Link href="/teams/new">
<Button className="font-semibold px-8 h-12">Create Team</Button>
</Link>
</div>
);
}

Expand Down
51 changes: 30 additions & 21 deletions apps/web/app/(dashboard)/teams/[teamId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ProjectOverviewCard } from "@/components/projects/project-overview-card
import { SectionSkeleton } from "@/components/layout/section-skeleton";
import { InviteMemberDialog } from "@/components/teams/invite-member-dialog";
import { TeamMembersDialog } from "@/components/teams/team-members-dialog";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";

async function TeamOverview({ teamId }: { teamId: string }) {
Expand Down Expand Up @@ -42,7 +43,9 @@ async function TeamOverview({ teamId }: { teamId: string }) {

<div className="grid gap-3 sm:grid-cols-2">
<Card className="grid gap-1 border-border bg-card">
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">Members</p>
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">
Members
</p>
<p className="text-2xl font-semibold text-foreground">{(team.members ?? []).length}</p>
<p className="inline-flex items-center gap-1 text-xs text-muted-foreground">
<Users className="h-3.5 w-3.5" />
Expand All @@ -51,7 +54,9 @@ async function TeamOverview({ teamId }: { teamId: string }) {
</Card>

<Card className="grid gap-1 border-border bg-card">
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">Projects</p>
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">
Projects
</p>
<p className="text-2xl font-semibold text-foreground">{projects.length}</p>
<p className="inline-flex items-center gap-1 text-xs text-muted-foreground">
<FolderKanban className="h-3.5 w-3.5" />
Expand All @@ -64,23 +69,23 @@ async function TeamOverview({ teamId }: { teamId: string }) {
<div className="flex flex-wrap items-center justify-between gap-2">
<div className="grid gap-0.5">
<h2 className="text-lg font-semibold text-foreground">Projects</h2>
<p className="text-xs text-muted-foreground">Track active streams and jump directly into delivery.</p>
<p className="text-xs text-muted-foreground">
Track active streams and jump directly into delivery.
</p>
</div>

<div className="flex flex-wrap items-center gap-2">
<Link
href={`/teams/${teamId}/projects/new`}
className="inline-flex items-center gap-1 rounded-md border border-border bg-popover px-3 py-2 text-sm text-foreground transition-colors hover:bg-accent"
>
<PlusCircle className="h-4 w-4" />
New project
<Link href={`/teams/${teamId}/projects/new`}>
<Button className="h-9 gap-1.5 px-3">
<PlusCircle className="h-4 w-4" />
New project
</Button>
</Link>
<Link
href={`/teams/${teamId}/projects`}
className="inline-flex items-center gap-1 rounded-md border border-border bg-popover px-3 py-2 text-sm text-foreground transition-colors hover:bg-accent"
>
<FolderKanban className="h-4 w-4" />
Browse all
<Link href={`/teams/${teamId}/projects`}>
<Button variant="secondary" className="h-9 gap-1.5 px-3">
<FolderKanban className="h-4 w-4" />
Browse all
</Button>
</Link>
</div>
</div>
Expand All @@ -92,18 +97,22 @@ async function TeamOverview({ teamId }: { teamId: string }) {
))}
</div>
) : (
<p className="text-sm text-muted-foreground">No projects yet. Use quick actions to create one.</p>
<div className="flex flex-col items-center justify-center rounded-xl border border-dashed border-border py-20 px-6 text-center">
<h2 className="text-2xl font-bold mb-3 tracking-tight">No projects yet</h2>
<p className="text-muted-foreground mb-8 max-w-sm">
Create your first project in this team to start tracking work and delivery progress.
</p>
<Link href={`/teams/${teamId}/projects/new`}>
<Button className="font-semibold px-8 h-12">Create Project</Button>
</Link>
</div>
)}
</section>
</div>
);
}

export default async function TeamPage({
params
}: {
params: Promise<{ teamId: string }>;
}) {
export default async function TeamPage({ params }: { params: Promise<{ teamId: string }> }) {
const { teamId } = await params;

return (
Expand Down
16 changes: 11 additions & 5 deletions apps/web/app/(dashboard)/teams/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ async function TeamsContent() {
const totalProjects = teams.reduce((accumulator, team) => accumulator + team.projectCount, 0);

return (
<section className="grid gap-4">
<section className="grid gap-8">
<div className="grid gap-3 sm:grid-cols-3">
<Card className="grid gap-1 border-border bg-card">
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">Teams</p>
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">
Teams
</p>
<p className="text-2xl font-semibold text-foreground">{teams.length}</p>
<p className="inline-flex items-center gap-1 text-xs text-muted-foreground">
<LayoutGrid className="h-3.5 w-3.5" />
Expand All @@ -28,7 +30,9 @@ async function TeamsContent() {
</Card>

<Card className="grid gap-1 border-border bg-card">
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">Members</p>
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">
Members
</p>
<p className="text-2xl font-semibold text-foreground">{totalMembers}</p>
<p className="inline-flex items-center gap-1 text-xs text-muted-foreground">
<Users className="h-3.5 w-3.5" />
Expand All @@ -37,7 +41,9 @@ async function TeamsContent() {
</Card>

<Card className="grid gap-1 border-border bg-card">
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">Projects</p>
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">
Projects
</p>
<p className="text-2xl font-semibold text-foreground">{totalProjects}</p>
<p className="inline-flex items-center gap-1 text-xs text-muted-foreground">
<FolderKanban className="h-3.5 w-3.5" />
Expand All @@ -54,7 +60,7 @@ async function TeamsContent() {
export default function TeamsPage() {
return (
<main className="grid gap-6">
<section className="grid gap-4">
<section className="grid gap-8">
<PageHeader
eyebrow="Workspace"
title="Teams"
Expand Down
15 changes: 13 additions & 2 deletions apps/web/components/projects/create-project-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,30 @@ export function CreateProjectForm({ teamId }: { teamId: string }) {
setError(null);

startTransition(async () => {
const result = await createProject(teamId, { name, description: description || undefined });
const result = await createProject(teamId, {
name,
description: description || undefined,
});
if (result.error) {
setError(result.error);
return;
}

setName("");
setDescription("");
if (result.data) {
router.push(`/teams/${teamId}/projects/${result.data.id}`);
}
router.refresh();
});
}}
>
<Input placeholder="Project name" value={name} onChange={(event) => setName(event.target.value)} required />
<Input
placeholder="Project name"
value={name}
onChange={(event) => setName(event.target.value)}
required
/>
<Textarea
placeholder="Description (optional)"
value={description}
Expand Down
15 changes: 14 additions & 1 deletion apps/web/components/projects/projects-browser.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"use client";

import Link from "next/link";
import { useMemo, useState } from "react";
import { Search } from "lucide-react";
import type { ProjectItem } from "@repo/types";
import { Button } from "@/components/ui/button";

import { ProjectOverviewCard } from "@/components/projects/project-overview-card";
import { Card } from "@/components/ui/card";
Expand Down Expand Up @@ -45,7 +47,18 @@ export function ProjectsBrowser({ items }: { items: GlobalProjectItem[] }) {
}, [items, query, sortBy]);

if (items.length === 0) {
return <Card className="text-sm text-muted-foreground">No projects available yet.</Card>;
return (
<div className="flex flex-col items-center justify-center rounded-xl border border-dashed border-border py-20 px-6 text-center">
<h2 className="text-2xl font-bold mb-3 tracking-tight">No projects yet</h2>
<p className="text-muted-foreground mb-8 max-w-sm">
Everything starts with a project. Jump into a team to create one and start delivering
value.
</p>
<Link href="/teams">
<Button className="font-semibold px-8 h-12">Browse Teams</Button>
</Link>
</div>
);
}

return (
Expand Down
7 changes: 6 additions & 1 deletion apps/web/components/teams/create-team-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ export function CreateTeamDialog() {

return (
<>
<Button onClick={() => setOpen(true)} type="button">
<Button
onClick={() => setOpen(true)}
type="button"
variant="primary"
className="font-semibold"
>
Create Team
</Button>
<Modal
Expand Down
11 changes: 10 additions & 1 deletion apps/web/components/teams/create-team-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,20 @@ export function CreateTeamForm({ onSuccess }: { onSuccess?: () => void } = {}) {
setName("");
setDescription("");
onSuccess?.();

if (result.data?.id) {
router.push(`/teams/${result.data.id}`);
}
router.refresh();
});
}}
>
<Input placeholder="Team name" value={name} onChange={(event) => setName(event.target.value)} required />
<Input
placeholder="Team name"
value={name}
onChange={(event) => setName(event.target.value)}
required
/>
<Textarea
placeholder="Description (optional)"
value={description}
Expand Down
14 changes: 12 additions & 2 deletions apps/web/components/teams/edit-team-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ export function EditTeamForm({ teamId, initialName, initialDescription }: EditTe
});
}}
>
<Input placeholder="Team name" value={name} onChange={(event) => setName(event.target.value)} required />
<Input
placeholder="Team name"
value={name}
onChange={(event) => setName(event.target.value)}
required
/>
<Textarea
placeholder="Description (optional)"
value={description}
Expand All @@ -51,7 +56,12 @@ export function EditTeamForm({ teamId, initialName, initialDescription }: EditTe
<Button type="submit" disabled={isPending}>
{isPending ? "Saving..." : "Save Changes"}
</Button>
<Button type="button" variant="ghost" disabled={isPending} onClick={() => router.push(`/teams/${teamId}`)}>
<Button
type="button"
variant="secondary"
disabled={isPending}
onClick={() => router.push(`/teams/${teamId}`)}
>
Cancel
</Button>
</div>
Expand Down
Loading