Built with the latest Next.js and TypeScript, a video conferencing tool. It enables users to securely log in, create meetings and access various meeting functionalities such as recording, screen sharing, and managing participants.
WIP
- Next.js
- TypeScript
- Clerk
- getstream
- shadcn
- Tailwind CSS
- Authentication: Implements authentication and authorization features using Clerk, allowing users to securely log in via social sign-on or traditional email and password methods, while ensuring appropriate access levels and permissions within the platform.
- New Meeting: Quickly start a new meeting, configuring camera and microphone settings before joining.-
- Meeting Controls: Participants have full control over meeting aspects, including recording, emoji reactions, screen sharing, muting/unmuting, sound adjustments, grid layout, participant list view, and individual participant management (pinning, muting, unmuting, blocking, allowing video share).-
- Exit Meeting: Participants can leave a meeting, or creators can end it for all attendees.-
- Schedule Future Meetings: Input meeting details (date, time) to schedule future meetings, accessible on the 'Upcoming Meetings' page for sharing the link or immediate start.-
- Past Meetings List: Access a list of previously held meetings, including details and metadata.-
- View Recorded Meetings: Access recordings of past meetings for review or reference.-
- Personal Room: Users have a personal room with a unique meeting link for instant meetings, shareable with others.-
- Join Meetings via Link: Easily join meetings created by others by providing a link.-
- Secure Real-time Functionality: All interactions within the platform are secure and occur in real-time, maintaining user privacy and data integrity.-
- Responsive Design: Follows responsive design principles to ensure optimal user experience across devices, adapting seamlessly to different screen sizes and resolutions.
Prerequisites
Make sure you have the following installed on your machine:
Cloning the Repository
git clone https://github.com/manavukani/connectly.git
Installation
Install the project dependencies using npm:
npm install
Set 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 dev
Open 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;