Skip to content

Commit 5071643

Browse files
Add "@radix-ui/react-toast" and "@stream-io/node-sdk" dependencies
1 parent e0763c3 commit 5071643

11 files changed

+740
-13
lines changed

actions/stream.actions.ts

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use server';
2+
3+
import { currentUser } from '@clerk/nextjs/server';
4+
import { StreamClient } from '@stream-io/node-sdk';
5+
6+
const STREAM_API_KEY = process.env.NEXT_PUBLIC_STREAM_API_KEY;
7+
const STREAM_API_SECRET = process.env.STREAM_SECRET_KEY;
8+
9+
export const tokenProvider = async () => {
10+
const user = await currentUser();
11+
12+
if (!user) throw new Error('User is not authenticated');
13+
if (!STREAM_API_KEY) throw new Error('Stream API key secret is missing');
14+
if (!STREAM_API_SECRET) throw new Error('Stream API secret is missing');
15+
16+
const streamClient = new StreamClient(STREAM_API_KEY, STREAM_API_SECRET);
17+
18+
const expirationTime = Math.floor(Date.now() / 1000) + 3600;
19+
const issuedAt = Math.floor(Date.now() / 1000) - 60;
20+
21+
const token = streamClient.createToken(user.id, expirationTime, issuedAt);
22+
23+
return token;
24+
};

app/(root)/layout.tsx

+10-8
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
import React, { ReactNode } from 'react'
1+
import { ReactNode } from 'react';
22

3-
const RootLayout = ({children}: {children: ReactNode}) => {
3+
import StreamVideoProvider from '@/poviders/StreamClientProvidet';
4+
5+
const RootLayout = ({ children }: Readonly<{ children: ReactNode }>) => {
46
return (
57
<main>
6-
7-
{children}
8-
8+
<StreamVideoProvider>
9+
{children}
10+
</StreamVideoProvider>
911
</main>
10-
)
11-
}
12+
);
13+
};
1214

13-
export default RootLayout
15+
export default RootLayout;

app/layout.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
22
import "./globals.css";
33
import { Inter } from "next/font/google";
44
import { ClerkProvider } from "@clerk/nextjs";
5+
import { Toaster } from "@/components/ui/toaster"
56

67
const inter = Inter({ subsets: ["latin"] });
78

@@ -34,6 +35,7 @@ export default function RootLayout({
3435
}}>
3536
<body className={`${inter.className} bg-dark-2`}>
3637
{children}
38+
<Toaster/>
3739
</body>
3840
</ClerkProvider>
3941
</html>

components/Loader.tsx

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import Image from 'next/image';
2+
3+
const Loader = () => {
4+
return (
5+
<div className="flex-center h-screen w-full">
6+
<Image
7+
src="/icons/loading-circle.svg"
8+
alt="Loading..."
9+
width={50}
10+
height={50}
11+
/>
12+
</div>
13+
);
14+
};
15+
16+
export default Loader;

components/MeetingTypeList.tsx

+49-3
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,60 @@ import React, { useState } from 'react'
33
import HomeCard from './HomeCard'
44
import { useRouter } from 'next/navigation'
55
import MeetingModal from './MeetingModal'
6+
import { useUser } from '@clerk/nextjs'
7+
import { DefaultReactionsMenu, useStreamVideoClient, Call } from '@stream-io/video-react-sdk'
8+
import { useToast } from "@/hooks/use-toast"
69

710
const MeetingTypeList = () => {
811
const router = useRouter();
912
const [meetingState, setMeetingState] =
1013
useState<'isSchedulemeeting' | 'isJoiningMeeting' | 'isInstantMeeting' | undefined>()
11-
12-
const createMeeting = () => {
13-
console.log('Instant Meeting Created');
14+
const { user } = useUser();
15+
const client = useStreamVideoClient();
16+
const[values, setValues] = useState({
17+
dateTime: new Date(),
18+
Description: '',
19+
link: ''
20+
})
21+
22+
const [callDetails, setCallDetails] = useState<Call>()
23+
const { toast } = useToast()
24+
25+
const createMeeting = async () => {
26+
if(!client || !user) return;
27+
28+
try{
29+
if (!values.dateTime) {
30+
toast({ title: 'Please select a date and time' });
31+
return;
32+
}
33+
34+
const id = crypto.randomUUID();
35+
const call = client.call('default', id);
36+
37+
if(!call) throw new Error("Failed to create a call");
38+
const startsAt = values.dateTime.toISOString() || new Date(Date.now()).toISOString();
39+
const description = values.Description || 'Instant meeting';
40+
41+
await call.getOrCreate({
42+
data: {
43+
starts_at: startsAt,
44+
custom: {
45+
description,
46+
},
47+
},
48+
})
49+
setCallDetails(call);
50+
if (!values.Description) {
51+
router.push(`/meeting/${call.id}`);
52+
}
53+
54+
toast({title: "Meeting created"})
55+
} catch(error){
56+
console.log(error);
57+
}toast({
58+
title: "Failed to create meeting",
59+
})
1460
}
1561

1662
return (

components/ui/toast.tsx

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
"use client"
2+
3+
import * as React from "react"
4+
import * as ToastPrimitives from "@radix-ui/react-toast"
5+
import { cva, type VariantProps } from "class-variance-authority"
6+
import { X } from "lucide-react"
7+
8+
import { cn } from "@/lib/utils"
9+
10+
const ToastProvider = ToastPrimitives.Provider
11+
12+
const ToastViewport = React.forwardRef<
13+
React.ElementRef<typeof ToastPrimitives.Viewport>,
14+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
15+
>(({ className, ...props }, ref) => (
16+
<ToastPrimitives.Viewport
17+
ref={ref}
18+
className={cn(
19+
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
20+
className
21+
)}
22+
{...props}
23+
/>
24+
))
25+
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
26+
27+
const toastVariants = cva(
28+
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border border-slate-200 p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full dark:border-slate-800",
29+
{
30+
variants: {
31+
variant: {
32+
default: "border bg-white text-slate-950 dark:bg-slate-950 dark:text-slate-50",
33+
destructive:
34+
"destructive group border-red-500 bg-red-500 text-slate-50 dark:border-red-900 dark:bg-red-900 dark:text-slate-50",
35+
},
36+
},
37+
defaultVariants: {
38+
variant: "default",
39+
},
40+
}
41+
)
42+
43+
const Toast = React.forwardRef<
44+
React.ElementRef<typeof ToastPrimitives.Root>,
45+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
46+
VariantProps<typeof toastVariants>
47+
>(({ className, variant, ...props }, ref) => {
48+
return (
49+
<ToastPrimitives.Root
50+
ref={ref}
51+
className={cn(toastVariants({ variant }), className)}
52+
{...props}
53+
/>
54+
)
55+
})
56+
Toast.displayName = ToastPrimitives.Root.displayName
57+
58+
const ToastAction = React.forwardRef<
59+
React.ElementRef<typeof ToastPrimitives.Action>,
60+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
61+
>(({ className, ...props }, ref) => (
62+
<ToastPrimitives.Action
63+
ref={ref}
64+
className={cn(
65+
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border border-slate-200 bg-transparent px-3 text-sm font-medium ring-offset-white transition-colors hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-slate-950 focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-slate-100/40 group-[.destructive]:hover:border-red-500/30 group-[.destructive]:hover:bg-red-500 group-[.destructive]:hover:text-slate-50 group-[.destructive]:focus:ring-red-500 dark:border-slate-800 dark:ring-offset-slate-950 dark:hover:bg-slate-800 dark:focus:ring-slate-300 dark:group-[.destructive]:border-slate-800/40 dark:group-[.destructive]:hover:border-red-900/30 dark:group-[.destructive]:hover:bg-red-900 dark:group-[.destructive]:hover:text-slate-50 dark:group-[.destructive]:focus:ring-red-900",
66+
className
67+
)}
68+
{...props}
69+
/>
70+
))
71+
ToastAction.displayName = ToastPrimitives.Action.displayName
72+
73+
const ToastClose = React.forwardRef<
74+
React.ElementRef<typeof ToastPrimitives.Close>,
75+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
76+
>(({ className, ...props }, ref) => (
77+
<ToastPrimitives.Close
78+
ref={ref}
79+
className={cn(
80+
"absolute right-2 top-2 rounded-md p-1 text-slate-950/50 opacity-0 transition-opacity hover:text-slate-950 focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600 dark:text-slate-50/50 dark:hover:text-slate-50",
81+
className
82+
)}
83+
toast-close=""
84+
{...props}
85+
>
86+
<X className="h-4 w-4" />
87+
</ToastPrimitives.Close>
88+
))
89+
ToastClose.displayName = ToastPrimitives.Close.displayName
90+
91+
const ToastTitle = React.forwardRef<
92+
React.ElementRef<typeof ToastPrimitives.Title>,
93+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
94+
>(({ className, ...props }, ref) => (
95+
<ToastPrimitives.Title
96+
ref={ref}
97+
className={cn("text-sm font-semibold", className)}
98+
{...props}
99+
/>
100+
))
101+
ToastTitle.displayName = ToastPrimitives.Title.displayName
102+
103+
const ToastDescription = React.forwardRef<
104+
React.ElementRef<typeof ToastPrimitives.Description>,
105+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
106+
>(({ className, ...props }, ref) => (
107+
<ToastPrimitives.Description
108+
ref={ref}
109+
className={cn("text-sm opacity-90", className)}
110+
{...props}
111+
/>
112+
))
113+
ToastDescription.displayName = ToastPrimitives.Description.displayName
114+
115+
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
116+
117+
type ToastActionElement = React.ReactElement<typeof ToastAction>
118+
119+
export {
120+
type ToastProps,
121+
type ToastActionElement,
122+
ToastProvider,
123+
ToastViewport,
124+
Toast,
125+
ToastTitle,
126+
ToastDescription,
127+
ToastClose,
128+
ToastAction,
129+
}

components/ui/toaster.tsx

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"use client"
2+
3+
import { useToast } from "@/hooks/use-toast"
4+
import {
5+
Toast,
6+
ToastClose,
7+
ToastDescription,
8+
ToastProvider,
9+
ToastTitle,
10+
ToastViewport,
11+
} from "@/components/ui/toast"
12+
13+
export function Toaster() {
14+
const { toasts } = useToast()
15+
16+
return (
17+
<ToastProvider>
18+
{toasts.map(function ({ id, title, description, action, ...props }) {
19+
return (
20+
<Toast key={id} {...props}>
21+
<div className="grid gap-1">
22+
{title && <ToastTitle>{title}</ToastTitle>}
23+
{description && (
24+
<ToastDescription>{description}</ToastDescription>
25+
)}
26+
</div>
27+
{action}
28+
<ToastClose />
29+
</Toast>
30+
)
31+
})}
32+
<ToastViewport />
33+
</ToastProvider>
34+
)
35+
}

0 commit comments

Comments
 (0)