Skip to content

Commit f66f805

Browse files
committed
feat: complete workshop session
1 parent 421e72a commit f66f805

File tree

10 files changed

+258
-93
lines changed

10 files changed

+258
-93
lines changed

src/app/(app)/session/[workshopId]/_components/session-call.tsx

+9-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { type User } from "lucia"
1818
import "@stream-io/video-react-sdk/dist/css/styles.css"
1919

2020
import { generateStreamTokenAction } from "@/server/actions/stream"
21+
import { MarkWorkshopHasCompleted } from "@/server/actions/workshop"
2122
import { Icons } from "@/components/icons"
2223

2324
interface SessionCallProps {
@@ -81,10 +82,15 @@ export function SessionCall({
8182
<StreamCall call={call}>
8283
<div className="grid w-full space-x-10 lg:grid-cols-[1fr_20rem]">
8384
<div className="flex flex-col items-center justify-center space-y-4">
84-
<SpeakerLayout participantsBarPosition="left" />
85+
<SpeakerLayout />
8586
<CallControls
86-
onLeave={async () => {
87-
if (isOrganizer) await call.endCall()
87+
onLeave={() => {
88+
if (isOrganizer) {
89+
MarkWorkshopHasCompleted(workshopId).then(() =>
90+
call.endCall()
91+
)
92+
}
93+
8894
router.push("/dashboard")
8995
}}
9096
/>

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

+9-10
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,21 @@ export default async function SessionPage({ params }: SessionPageProps) {
3030
}
3131

3232
const isOrganizer = workshop.organizerId === user.id
33+
const registrants = await getWorkshopRegistrants(workshop.id)
34+
const isRegistrant = registrants.some(
35+
(registrant) => registrant.id === user.id
36+
)
3337

34-
if (workshop.hasCompleted) {
35-
redirect(`/workshop/${workshop.id}`)
38+
if (!isOrganizer && !isRegistrant) {
39+
redirect(`/dashboard`)
3640
}
3741

38-
if (!workshop.hasStarted && !isOrganizer) {
42+
if (workshop.hasCompleted) {
3943
redirect(`/workshop/${workshop.id}`)
4044
}
4145

42-
const registrants = await getWorkshopRegistrants(workshop.id)
43-
const isCurrentUserARegistrant = registrants.some(
44-
(registrant) => registrant.id === user.id
45-
)
46-
47-
if (workshop.hasStarted && !isCurrentUserARegistrant) {
48-
redirect(`/dashboard`)
46+
if (!workshop.hasStarted) {
47+
redirect(`/workshop/${workshop.id}`)
4948
}
5049

5150
return (

src/app/(app)/workshop/[workshopId]/_components/register-button.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ interface RegisterButtonProps {
1616
workshopId: string
1717
workshopTitle: string
1818
isCurrentUserRegistered: boolean
19+
isDisabled: boolean
1920
}
2021

2122
export function RegisterButton({
2223
userId,
2324
workshopId,
2425
isCurrentUserRegistered,
26+
isDisabled,
2527
}: RegisterButtonProps) {
2628
const [isPending, startTransition] = React.useTransition()
2729

@@ -51,10 +53,9 @@ export function RegisterButton({
5153

5254
return (
5355
<Button
54-
type="submit"
5556
onClick={handleOnclickRegisterButton}
5657
size="sm"
57-
disabled={isPending}
58+
disabled={isPending || isDisabled}
5859
>
5960
{isPending && (
6061
<Icons.spinner
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,50 @@
11
"use client"
22

3-
import Link from "next/link"
3+
import * as React from "react"
4+
import { useRouter } from "next/navigation"
45

5-
import { cn } from "@/lib/utils"
6-
import { buttonVariants } from "@/components/ui/button"
6+
import { startWorkshopAction } from "@/server/actions/workshop"
7+
import { showErrorToast } from "@/utils/handle-error"
8+
import { Button } from "@/components/ui/button"
9+
import { Icons } from "@/components/icons"
710

8-
interface StartWorkshopButtonProps {
11+
interface RegisterButtonProps {
912
workshopId: string
13+
isDisabled: boolean
1014
}
1115

12-
export function StartWorkshopButton({ workshopId }: StartWorkshopButtonProps) {
16+
export function StartWorkshopButton({
17+
workshopId,
18+
isDisabled,
19+
}: RegisterButtonProps) {
20+
const router = useRouter()
21+
const [isPending, startTransition] = React.useTransition()
22+
23+
const handleStartWorkshop = () => {
24+
startTransition(async () => {
25+
const { error } = await startWorkshopAction(workshopId)
26+
27+
if (error) {
28+
showErrorToast(error)
29+
}
30+
31+
router.push(`/session/${workshopId}`)
32+
})
33+
}
34+
1335
return (
14-
<Link
15-
href={`/session/${workshopId}`}
16-
className={cn(buttonVariants({ size: "sm" }))}
36+
<Button
37+
onClick={handleStartWorkshop}
38+
size="sm"
39+
disabled={isPending || isDisabled}
1740
>
18-
Start Workshop
19-
</Link>
41+
{isPending && (
42+
<Icons.spinner
43+
className="mr-2 size-4 animate-spin"
44+
aria-hidden={true}
45+
/>
46+
)}
47+
Start
48+
</Button>
2049
)
2150
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import Link from "next/link"
2+
3+
import { cn } from "@/lib/utils"
4+
import { Button, buttonVariants } from "@/components/ui/button"
5+
6+
import { RegisterButton } from "./register-button"
7+
import { StartWorkshopButton } from "./start-workshop-button"
8+
9+
interface WorkshopButtonProps {
10+
userId: string
11+
workshopId: string
12+
workshopTitle: string
13+
isUserOrganizer: boolean
14+
isUserRegistered: boolean
15+
hasWorkshopStarted: boolean
16+
hasWorkshopCompleted: boolean
17+
}
18+
19+
export function WorkshopButton({
20+
hasWorkshopStarted,
21+
isUserOrganizer,
22+
workshopTitle,
23+
isUserRegistered,
24+
workshopId,
25+
userId,
26+
hasWorkshopCompleted,
27+
}: WorkshopButtonProps) {
28+
if (hasWorkshopCompleted) {
29+
return (
30+
<Button disabled size="sm">
31+
Ended
32+
</Button>
33+
)
34+
}
35+
36+
if (isUserOrganizer) {
37+
return (
38+
<StartWorkshopButton
39+
workshopId={workshopId}
40+
isDisabled={hasWorkshopStarted}
41+
/>
42+
)
43+
}
44+
45+
if (!hasWorkshopStarted) {
46+
return (
47+
<RegisterButton
48+
isDisabled={hasWorkshopStarted}
49+
userId={userId}
50+
workshopId={workshopId}
51+
isCurrentUserRegistered={isUserRegistered}
52+
workshopTitle={workshopTitle}
53+
/>
54+
)
55+
}
56+
57+
return (
58+
<Link
59+
className={cn(buttonVariants({ size: "sm" }))}
60+
href={`/session/${workshopId}`}
61+
>
62+
Join
63+
</Link>
64+
)
65+
}

src/app/(app)/workshop/[workshopId]/_components/workshop-settings.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export function WorkshopSettings({ workshop }: WorkshopSettingsProps) {
3939
<DeleteWorkshopAlert />
4040
<DropdownMenu open={open} onOpenChange={setOpen}>
4141
<DropdownMenuTrigger asChild>
42-
<Button size="icon" variant="ghost">
42+
<Button size="icon" variant="ghost" className="size-8">
4343
<Icons.more className="size-5" aria-hidden="true" />
4444
<span className="sr-only">Workshop Settings Dropdown</span>
4545
</Button>

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

+36-27
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { getWorkshopRegistrants } from "@/server/data/registration"
88
import { getUserSession } from "@/server/data/user"
99
import { getWorkshop, getWorkshopMetadata } from "@/server/data/workshop"
1010
import { getExactScheduled } from "@/utils/format-scheduled-date"
11+
import { Badge } from "@/components/ui/badge"
1112
import { Separator } from "@/components/ui/separator"
1213
import { CopyButton } from "@/components/copy-button"
1314
import { Icons } from "@/components/icons"
@@ -16,8 +17,7 @@ import { Shell } from "@/components/shell"
1617

1718
import { OrganizerSection } from "./_components/organizer-section"
1819
import { OrganizerSectionSkeleton } from "./_components/organizer-section-skeleton"
19-
import { RegisterButton } from "./_components/register-button"
20-
import { StartWorkshopButton } from "./_components/start-workshop-button"
20+
import { WorkshopButton } from "./_components/workshop-button"
2121
import { WorkshopRegistrants } from "./_components/workshop-registrants"
2222
import { WorkshopSettings } from "./_components/workshop-settings"
2323

@@ -62,27 +62,29 @@ export default async function WorkshopPage({ params }: WorkshopPageProps) {
6262

6363
const registrants = await getWorkshopRegistrants(workshop.id)
6464

65-
const isCurrentUserWorkshop = workshop.organizerId === user.id
66-
const isCurrentUserRegistered = registrants.some(
65+
const isUserOrganizer = workshop.organizerId === user.id
66+
const isUserRegistered = registrants.some(
6767
(registrant) => registrant.id === user.id
6868
)
6969

7070
return (
7171
<Shell className="max-w-xl gap-4">
7272
<div className="flex w-full flex-col items-start space-y-1">
73-
<div className="flex w-full items-start justify-between">
74-
<PageHeader>
75-
<PageHeaderHeading>{workshop.title}</PageHeaderHeading>
76-
</PageHeader>
77-
78-
<div className="flex items-center gap-1">
79-
<CopyButton
80-
value={workshop.accessCode}
81-
size="icon"
82-
className="rounded-full"
83-
/>
84-
{isCurrentUserWorkshop && <WorkshopSettings workshop={workshop} />}
73+
<div className="flex w-full items-center justify-between">
74+
<div className="flex items-center gap-2">
75+
<PageHeader>
76+
<PageHeaderHeading>{workshop.title}</PageHeaderHeading>
77+
</PageHeader>
78+
<Badge>
79+
{workshop.hasCompleted
80+
? "Ended"
81+
: workshop.hasStarted
82+
? "Ongoing"
83+
: "Open"}
84+
</Badge>
8585
</div>
86+
87+
{isUserOrganizer && <WorkshopSettings workshop={workshop} />}
8688
</div>
8789

8890
<div className="flex items-center gap-2">
@@ -123,17 +125,24 @@ export default async function WorkshopPage({ params }: WorkshopPageProps) {
123125
<OrganizerSection organizerId={workshop.organizerId} />
124126
</React.Suspense>
125127

126-
<div className="flex w-full justify-end">
127-
{!isCurrentUserWorkshop ? (
128-
<RegisterButton
129-
userId={user.id}
130-
workshopId={workshop.id}
131-
isCurrentUserRegistered={isCurrentUserRegistered}
132-
workshopTitle={workshop.title}
133-
/>
134-
) : (
135-
<StartWorkshopButton workshopId={workshop.id} />
136-
)}
128+
<div className="flex w-full justify-end space-x-4">
129+
<CopyButton
130+
variant="secondary"
131+
className="gap-2 text-secondary-foreground"
132+
value={workshop.accessCode}
133+
>
134+
Copy code
135+
</CopyButton>
136+
137+
<WorkshopButton
138+
userId={user.id}
139+
workshopId={workshop.id}
140+
isUserOrganizer={isUserOrganizer}
141+
isUserRegistered={isUserRegistered}
142+
workshopTitle={workshop.title}
143+
hasWorkshopStarted={workshop.hasStarted}
144+
hasWorkshopCompleted={workshop.hasCompleted}
145+
/>
137146
</div>
138147
</div>
139148
</Shell>

src/components/copy-button.tsx

+8-2
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,20 @@ import { cn } from "@/lib/utils"
77
import { Icons } from "./icons"
88
import { Button, type ButtonProps } from "./ui/button"
99

10-
export function CopyButton({ value, className, ...props }: ButtonProps) {
10+
export function CopyButton({
11+
value,
12+
className,
13+
children,
14+
...props
15+
}: ButtonProps) {
1116
const [isCopied, setIsCopied] = React.useState(false)
1217

1318
return (
1419
<Button
1520
type="button"
1621
variant="ghost"
1722
size="sm"
18-
className={cn("text-muted-foreground hover:bg-transparent", className)}
23+
className={cn("text-muted-foreground", className)}
1924
onClick={() => {
2025
if (typeof window === "undefined") return
2126
setIsCopied(true)
@@ -29,6 +34,7 @@ export function CopyButton({ value, className, ...props }: ButtonProps) {
2934
) : (
3035
<Icons.copy className="size-4" aria-hidden="true" />
3136
)}
37+
{children}
3238
</Button>
3339
)
3440
}

0 commit comments

Comments
 (0)