generate a meeting platform where someone can create meeting , request users to join, remove user from the meeting, mute users in the meeting and unmute them also the one creating the meeting can show video and also hide video, .. emojis anre there where you can like the video messagin part will be added as time goes by also emojis will also be introduced
Installation
Install the project dependencies using npm:
npm installSet Up Environment Variables
Create a new file named .env in the root of your project and add the following content:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_STREAM_API_KEY=
STREAM_SECRET_KEY=Replace the placeholder values with your actual Clerk & getstream credentials. You can obtain these credentials by signing up on the Clerk website and getstream website
Running the Project
npm run devOpen http://localhost:3000 in your browser to view the project.
app/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* ======== stream css overrides ======== */
.str-video__call-stats {
max-width: 500px;
position: relative;
}
.str-video__speaker-layout__wrapper {
max-height: 700px;
}
.str-video__participant-details {
color: white;
}
.str-video__menu-container {
color: white;
}
.str-video__notification {
color: white;
}
.str-video__participant-list {
background-color: #1c1f2e;
padding: 10px;
border-radius: 10px;
color: white;
height: 100%;
}
.str-video__call-controls__button {
height: 40px;
}
.glassmorphism {
background: rgba(255, 255, 255, 0.25);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
}
.glassmorphism2 {
background: rgba(18, 17, 17, 0.25);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
/* ==== clerk class override ===== */
.cl-userButtonPopoverActionButtonIcon {
color: white;
}
.cl-logoBox {
height: 40px;
}
.cl-dividerLine {
background: #252a41;
height: 2px;
}
.cl-socialButtonsIconButton {
border: 3px solid #565761;
}
.cl-internal-wkkub3 {
color: white;
}
.cl-userButtonPopoverActionButton {
color: white;
}
/* =============================== */
@layer utilities {
.flex-center {
@apply flex justify-center items-center;
}
.flex-between {
@apply flex justify-between items-center;
}
}
/* animation */
.show-block {
width: 100%;
max-width: 350px;
display: block;
animation: show 0.7s forwards linear;
}
@keyframes show {
0% {
animation-timing-function: ease-in;
width: 0%;
}
100% {
animation-timing-function: ease-in;
width: 100%;
}
}tailwind.config.ts
import type { Config } from 'tailwindcss';
const config = {
darkMode: ['class'],
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
prefix: '',
theme: {
container: {
center: true,
padding: '2rem',
screens: {
'2xl': '1400px',
},
},
extend: {
colors: {
dark: {
1: '#1C1F2E',
2: '#161925',
3: '#252A41',
4: '#1E2757',
},
blue: {
1: '#0E78F9',
},
sky: {
1: '#C9DDFF',
2: '#ECF0FF',
3: '#F5FCFF',
},
orange: {
1: '#FF742E',
},
purple: {
1: '#830EF9',
},
yellow: {
1: '#F9A90E',
},
},
keyframes: {
'accordion-down': {
from: { height: '0' },
to: { height: 'var(--radix-accordion-content-height)' },
},
'accordion-up': {
from: { height: 'var(--radix-accordion-content-height)' },
to: { height: '0' },
},
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
},
backgroundImage: {
hero: "url('/images/hero-background.png')",
},
},
},
plugins: [require('tailwindcss-animate')],
} satisfies Config;
export default config;components/MeetingCard.tsx
"use client";
import Image from "next/image";
import { cn } from "@/lib/utils";
import { Button } from "./ui/button";
import { avatarImages } from "@/constants";
import { useToast } from "./ui/use-toast";
interface MeetingCardProps {
title: string;
date: string;
icon: string;
isPreviousMeeting?: boolean;
buttonIcon1?: string;
buttonText?: string;
handleClick: () => void;
link: string;
}
const MeetingCard = ({
icon,
title,
date,
isPreviousMeeting,
buttonIcon1,
handleClick,
link,
buttonText,
}: MeetingCardProps) => {
const { toast } = useToast();
return (
<section className="flex min-h-[258px] w-full flex-col justify-between rounded-[14px] bg-dark-1 px-5 py-8 xl:max-w-[568px]">
<article className="flex flex-col gap-5">
<Image src={icon} alt="upcoming" width={28} height={28} />
<div className="flex justify-between">
<div className="flex flex-col gap-2">
<h1 className="text-2xl font-bold">{title}</h1>
<p className="text-base font-normal">{date}</p>
</div>
</div>
</article>
<article className={cn("flex justify-center relative", {})}>
<div className="relative flex w-full max-sm:hidden">
{avatarImages.map((img, index) => (
<Image
key={index}
src={img}
alt="attendees"
width={40}
height={40}
className={cn("rounded-full", { absolute: index > 0 })}
style={{ top: 0, left: index * 28 }}
/>
))}
<div className="flex-center absolute left-[136px] size-10 rounded-full border-[5px] border-dark-3 bg-dark-4">
+5
</div>
</div>
{!isPreviousMeeting && (
<div className="flex gap-2">
<Button onClick={handleClick} className="rounded bg-blue-1 px-6">
{buttonIcon1 && (
<Image src={buttonIcon1} alt="feature" width={20} height={20} />
)}
{buttonText}
</Button>
<Button
onClick={() => {
navigator.clipboard.writeText(link);
toast({
title: "Link Copied",
});
}}
className="bg-dark-4 px-6"
>
<Image
src="/icons/copy.svg"
alt="feature"
width={20}
height={20}
/>
Copy Link
</Button>
</div>
)}
</article>
</section>
);
};
export default MeetingCard;