Skip to content

Commit c27c0dd

Browse files
committed
feat: workshop modal initial setup
1 parent 839d200 commit c27c0dd

File tree

8 files changed

+169
-6
lines changed

8 files changed

+169
-6
lines changed

next.config.mjs

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ const nextConfig = {
66
locales: ["en"],
77
defaultLocale: "en",
88
},
9-
109
// typescript errors & eslint complaints are checked in CI pipeline
1110
eslint: {
1211
ignoreDuringBuilds: true,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import * as React from "react"
2+
import { notFound, redirect } from "next/navigation"
3+
4+
import { redirects } from "@/config/constants"
5+
import { getUserSession } from "@/server/data/user"
6+
import { getWorkshop } from "@/server/data/workshop"
7+
import { getExactScheduled } from "@/utils/format-scheduled-date"
8+
import { Button } from "@/components/ui/button"
9+
import { Separator } from "@/components/ui/separator"
10+
import { CopyButton } from "@/components/copy-button"
11+
import { Icons } from "@/components/icons"
12+
import { ModalShell } from "@/components/modal-shell"
13+
import { PageHeader, PageHeaderHeading } from "@/components/page-header"
14+
import { OrganizerSection } from "@/app/(app)/workshop/[workshopId]/_components/organizer-section"
15+
import { OrganizerSectionSkeleton } from "@/app/(app)/workshop/[workshopId]/_components/organizer-section-skeleton"
16+
import { WorkshopSettings } from "@/app/(app)/workshop/[workshopId]/_components/workshop-settings"
17+
18+
interface WorkshopModalProps {
19+
params: {
20+
workshopId: string
21+
}
22+
}
23+
24+
export default async function WorkshopModal({ params }: WorkshopModalProps) {
25+
const workshopId = decodeURIComponent(params.workshopId)
26+
27+
const workshop = await getWorkshop(workshopId)
28+
29+
if (!workshop) {
30+
return notFound()
31+
}
32+
33+
const { user } = await getUserSession()
34+
35+
if (!user) {
36+
redirect(redirects.toLogin)
37+
}
38+
39+
const isCurrentUserWorkshop = workshop.organizerId === user.id
40+
41+
return (
42+
<ModalShell>
43+
<div className="flex w-full flex-col items-start space-y-1">
44+
<div className="flex w-full items-start justify-between">
45+
<PageHeader>
46+
<PageHeaderHeading>{workshop.title}</PageHeaderHeading>
47+
</PageHeader>
48+
49+
<div className="flex items-center gap-1">
50+
<CopyButton
51+
value={workshop.accessCode}
52+
size="icon"
53+
className="rounded-full"
54+
/>
55+
{isCurrentUserWorkshop && <WorkshopSettings workshop={workshop} />}
56+
</div>
57+
</div>
58+
59+
<div className="flex items-center gap-2">
60+
<Icons.clock
61+
className="size-4 text-muted-foreground"
62+
aria-hidden="true"
63+
/>
64+
<p className="text-sm text-muted-foreground">
65+
{getExactScheduled(workshop.scheduled)}
66+
</p>
67+
</div>
68+
<div className="flex items-center gap-2">
69+
<Icons.watch
70+
className="size-4 text-muted-foreground"
71+
aria-hidden="true"
72+
/>
73+
<p className="text-sm text-muted-foreground">
74+
{workshop.duration} mins
75+
</p>
76+
</div>
77+
</div>
78+
79+
<Separator />
80+
81+
<div className="flex flex-col items-start space-y-4">
82+
<div className="space-y-1">
83+
<h4 className="font-medium sm:text-lg">About</h4>
84+
85+
<p className="max-w-lg text-sm text-muted-foreground">
86+
{workshop.description}
87+
</p>
88+
</div>
89+
90+
<React.Suspense fallback={<OrganizerSectionSkeleton />}>
91+
<OrganizerSection organizerId={workshop.organizerId} />
92+
</React.Suspense>
93+
94+
<div className="flex w-full justify-end">
95+
{!isCurrentUserWorkshop ? (
96+
<Button size="sm">
97+
{/* {isPending && (
98+
<Icons.spinner
99+
className="mr-2 size-4 animate-spin"
100+
aria-hidden="true"
101+
/>
102+
)} */}
103+
Register
104+
</Button>
105+
) : (
106+
<Button size="sm">Start</Button>
107+
)}
108+
</div>
109+
</div>
110+
</ModalShell>
111+
)
112+
}

src/app/(app)/@modal/default.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Default() {
2+
return null
3+
}

src/app/(app)/layout.tsx

+10-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ import { getUserSession } from "@/server/data/user"
55

66
import { Header } from "./_components/header"
77

8-
export default async function AppLayout({ children }: React.PropsWithChildren) {
8+
interface AppLayoutProps {
9+
children: React.ReactNode
10+
modal: React.ReactNode
11+
}
12+
13+
export default async function AppLayout({ children, modal }: AppLayoutProps) {
914
const { user } = await getUserSession()
1015

1116
if (!user) {
@@ -15,7 +20,10 @@ export default async function AppLayout({ children }: React.PropsWithChildren) {
1520
return (
1621
<div className="relative flex min-h-screen flex-col">
1722
<Header user={user} />
18-
<main className="flex-1">{children}</main>
23+
<main className="flex-1">
24+
{children}
25+
{modal}
26+
</main>
1927
</div>
2028
)
2129
}

src/app/(app)/workshop/[workshopId]/loading.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { OrganizerSectionSkeleton } from "./_components/organizer-section-skelet
77

88
export default function WorkshopLoading() {
99
return (
10-
<Shell className="max-w-xl gap-4 sm:mt-8">
10+
<Shell className="max-w-xl gap-4">
1111
<div className="flex w-full flex-col items-start space-y-2">
1212
<div className="flex w-full items-start justify-between">
1313
<Skeleton className="h-7 w-52" />

src/app/(app)/workshop/[workshopId]/page.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export default async function WorkshopPage({ params }: WorkshopPageProps) {
6969
const isCurrentUserWorkshop = workshop.organizerId === user.id
7070

7171
return (
72-
<Shell className="max-w-xl gap-4 sm:mt-8">
72+
<Shell className="max-w-xl gap-4">
7373
<div className="flex w-full flex-col items-start space-y-1">
7474
<div className="flex w-full items-start justify-between">
7575
<PageHeader>

src/components/modal-shell.tsx

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"use client"
2+
3+
import { useRouter } from "next/navigation"
4+
5+
import { cn } from "@/lib/utils"
6+
import { useMediaQuery } from "@/hooks/use-media-query"
7+
8+
import { Dialog, DialogContent, DialogOverlay } from "./ui/dialog"
9+
import { Drawer, DrawerContent, DrawerOverlay } from "./ui/drawer"
10+
11+
interface ModalShellProps extends React.HTMLAttributes<HTMLDivElement> {}
12+
13+
export function ModalShell({ children, className, ...props }: ModalShellProps) {
14+
const router = useRouter()
15+
const isDesktop = useMediaQuery("(min-width: 768px)")
16+
17+
const onOpenChange = () => router.back()
18+
19+
if (isDesktop) {
20+
return (
21+
<Dialog open={true} onOpenChange={onOpenChange}>
22+
<DialogOverlay />
23+
<DialogContent>
24+
<div className={cn(className)} {...props}>
25+
{children}
26+
</div>
27+
</DialogContent>
28+
</Dialog>
29+
)
30+
}
31+
32+
return (
33+
<Drawer open={true} onOpenChange={onOpenChange}>
34+
<DrawerOverlay />
35+
<DrawerContent>
36+
<div className={cn(className)} {...props}>
37+
{children}
38+
</div>
39+
</DrawerContent>
40+
</Drawer>
41+
)
42+
}

src/server/actions/workshop.ts

-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ export async function deleteWorkshopAction(id: string) {
8585
.where(and(eq(workshops.organizerId, user.id), eq(workshops.id, id)))
8686

8787
revalidateTag(`workshops-${user.id}`)
88-
revalidateTag(`workshops`)
8988

9089
return {
9190
error: null,

0 commit comments

Comments
 (0)