├── .eslintrc.json ├── app ├── favicon.ico ├── (auth) │ ├── sign-in │ │ └── [[...sign-in]] │ │ │ └── page.tsx │ ├── sign-up │ │ └── [[...sign-up]] │ │ │ └── page.tsx │ ├── layout.tsx │ └── _components │ │ └── logo.tsx ├── api │ ├── uploadthing │ │ ├── route.ts │ │ └── core.ts │ └── webhooks │ │ ├── livekit │ │ └── route.ts │ │ └── clerk │ │ └── route.ts ├── (browse) │ ├── [username] │ │ ├── loading.tsx │ │ ├── error.tsx │ │ ├── not-found.tsx │ │ ├── page.tsx │ │ └── _components │ │ │ └── actions.tsx │ ├── (home) │ │ ├── page.tsx │ │ └── _components │ │ │ ├── results.tsx │ │ │ └── result-card.tsx │ ├── _components │ │ ├── navbar │ │ │ ├── index.tsx │ │ │ ├── logo.tsx │ │ │ ├── actions.tsx │ │ │ └── search.tsx │ │ ├── container.tsx │ │ └── sidebar │ │ │ ├── index.tsx │ │ │ ├── wrapper.tsx │ │ │ ├── recommended.tsx │ │ │ ├── following.tsx │ │ │ ├── toggle.tsx │ │ │ └── user-item.tsx │ ├── layout.tsx │ └── search │ │ ├── page.tsx │ │ └── _components │ │ ├── results.tsx │ │ └── result-card.tsx ├── (dashboard) │ └── u │ │ └── [username] │ │ ├── (home) │ │ ├── loading.tsx │ │ └── page.tsx │ │ ├── _components │ │ ├── sidebar │ │ │ ├── index.tsx │ │ │ ├── wrapper.tsx │ │ │ ├── toggle.tsx │ │ │ ├── navigation.tsx │ │ │ └── nav-item.tsx │ │ ├── navbar │ │ │ ├── index.tsx │ │ │ ├── actions.tsx │ │ │ └── logo.tsx │ │ └── container.tsx │ │ ├── chat │ │ ├── loading.tsx │ │ ├── page.tsx │ │ └── _components │ │ │ └── toggle-card.tsx │ │ ├── keys │ │ ├── _components │ │ │ ├── url-card.tsx │ │ │ ├── copy-button.tsx │ │ │ ├── key-card.tsx │ │ │ └── connect-modal.tsx │ │ └── page.tsx │ │ ├── layout.tsx │ │ └── community │ │ ├── page.tsx │ │ └── _components │ │ ├── unblock-button.tsx │ │ ├── columns.tsx │ │ └── data-table.tsx ├── error.tsx ├── not-found.tsx ├── layout.tsx └── globals.css ├── postcss.config.js ├── next.config.js ├── lib ├── stream-service.ts ├── db.ts ├── uploadthing.ts ├── utils.ts ├── auth-service.ts ├── user-service.ts ├── feed-service.ts ├── recommended-service.ts ├── search-service.ts ├── block-service.ts └── follow-service.ts ├── components ├── verified-mark.tsx ├── ui │ ├── skeleton.tsx │ ├── label.tsx │ ├── textarea.tsx │ ├── separator.tsx │ ├── input.tsx │ ├── slider.tsx │ ├── switch.tsx │ ├── tooltip.tsx │ ├── avatar.tsx │ ├── alert.tsx │ ├── scroll-area.tsx │ ├── button.tsx │ ├── table.tsx │ ├── dialog.tsx │ └── select.tsx ├── theme-provider.tsx ├── live-badge.tsx ├── stream-player │ ├── loading-video.tsx │ ├── offline-video.tsx │ ├── fullscreen-control.tsx │ ├── chat-header.tsx │ ├── chat-toggle.tsx │ ├── chat-message.tsx │ ├── variant-toggle.tsx │ ├── chat-list.tsx │ ├── volume-control.tsx │ ├── about-card.tsx │ ├── video.tsx │ ├── chat-info.tsx │ ├── community-item.tsx │ ├── actions.tsx │ ├── info-card.tsx │ ├── bio-modal.tsx │ ├── chat-community.tsx │ ├── chat-form.tsx │ ├── live-video.tsx │ ├── header.tsx │ ├── chat.tsx │ ├── index.tsx │ └── info-modal.tsx ├── hint.tsx ├── user-avatar.tsx └── thumbnail.tsx ├── store ├── use-sidebar.ts ├── use-creator-sidebar.ts └── use-chat-sidebar.ts ├── components.json ├── .gitignore ├── middleware.ts ├── public ├── vercel.svg ├── spooky.svg └── next.svg ├── actions ├── user.ts ├── follow.ts ├── block.ts ├── stream.ts ├── token.ts └── ingress.ts ├── tsconfig.json ├── hooks └── use-viewer-token.ts ├── README.md ├── tailwind.config.ts ├── package.json └── prisma └── schema.prisma /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yash-mewada/NexStream/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /app/(auth)/sign-in/[[...sign-in]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignIn } from "@clerk/nextjs"; 2 | 3 | export default function Page() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /app/(auth)/sign-up/[[...sign-up]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignUp } from "@clerk/nextjs"; 2 | 3 | export default function Page() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | domains: ["utfs.io"], 5 | }, 6 | }; 7 | 8 | module.exports = nextConfig; 9 | -------------------------------------------------------------------------------- /lib/stream-service.ts: -------------------------------------------------------------------------------- 1 | import { db } from "./db"; 2 | 3 | export const getStreamByUserId = async (userId: string) => { 4 | const stream = await db.stream.findUnique({ 5 | where: { userId }, 6 | }); 7 | 8 | return stream; 9 | }; 10 | -------------------------------------------------------------------------------- /app/api/uploadthing/route.ts: -------------------------------------------------------------------------------- 1 | import { createRouteHandler } from "uploadthing/next"; 2 | 3 | import { ourFileRouter } from "./core"; 4 | 5 | // Export routes for Next App Router 6 | export const { GET, POST } = createRouteHandler({ 7 | router: ourFileRouter, 8 | }); 9 | -------------------------------------------------------------------------------- /lib/db.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | declare global { 3 | var prisma: PrismaClient | undefined; 4 | } 5 | 6 | export const db = globalThis.prisma || new PrismaClient(); 7 | 8 | if (process.env.NODE_ENV !== "production") globalThis.prisma = db; 9 | -------------------------------------------------------------------------------- /app/(browse)/[username]/loading.tsx: -------------------------------------------------------------------------------- 1 | import { StreamPlayerSkeleton } from "@/components/stream-player"; 2 | 3 | const UserLoading = () => { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | }; 10 | 11 | export default UserLoading; 12 | -------------------------------------------------------------------------------- /app/(dashboard)/u/[username]/(home)/loading.tsx: -------------------------------------------------------------------------------- 1 | import { StreamPlayerSkeleton } from "@/components/stream-player"; 2 | 3 | const CreatorLoading = () => { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | }; 10 | 11 | export default CreatorLoading; 12 | -------------------------------------------------------------------------------- /components/verified-mark.tsx: -------------------------------------------------------------------------------- 1 | import { Check } from "lucide-react"; 2 | 3 | export const VerifiedMark = () => { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /app/(dashboard)/u/[username]/_components/sidebar/index.tsx: -------------------------------------------------------------------------------- 1 | import { Navigation } from "./navigation"; 2 | import { Toggle } from "./toggle"; 3 | import { Wrapper } from "./wrapper"; 4 | 5 | export const Sidebar = () => { 6 | return ( 7 | 8 | 9 | 10 | 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /lib/uploadthing.ts: -------------------------------------------------------------------------------- 1 | import { 2 | generateUploadButton, 3 | generateUploadDropzone, 4 | } from "@uploadthing/react"; 5 | 6 | import type { OurFileRouter } from "@/app/api/uploadthing/core"; 7 | 8 | export const UploadButton = generateUploadButton(); 9 | export const UploadDropzone = generateUploadDropzone(); 10 | -------------------------------------------------------------------------------- /components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /app/(auth)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Logo } from "./_components/logo"; 2 | 3 | const AuthLayout = ({ children }: { children: React.ReactNode }) => { 4 | return ( 5 |
6 | 7 | {children} 8 |
9 | ); 10 | }; 11 | 12 | export default AuthLayout; 13 | -------------------------------------------------------------------------------- /app/(browse)/(home)/page.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from "react"; 2 | 3 | import { Results, ResultsSkeleton } from "./_components/results"; 4 | 5 | export default function Page() { 6 | return ( 7 |
8 | }> 9 | 10 | 11 |
12 | ); 13 | }; -------------------------------------------------------------------------------- /app/(dashboard)/u/[username]/_components/navbar/index.tsx: -------------------------------------------------------------------------------- 1 | import { Actions } from "./actions"; 2 | import { Logo } from "./logo"; 3 | 4 | export const Navbar = () => { 5 | return ( 6 | 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { ThemeProvider as NextThemesProvider } from "next-themes"; 5 | import { type ThemeProviderProps } from "next-themes/dist/types"; 6 | 7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 8 | return {children}; 9 | } 10 | -------------------------------------------------------------------------------- /store/use-sidebar.ts: -------------------------------------------------------------------------------- 1 | import { create } from "zustand"; 2 | 3 | interface SidebarStore { 4 | collapsed: boolean; 5 | onExpand: () => void; 6 | onCollapse: () => void; 7 | } 8 | 9 | export const useSidebar = create((set) => ({ 10 | collapsed: false, 11 | onExpand: () => set(() => ({ collapsed: false })), 12 | onCollapse: () => set(() => ({ collapsed: true })), 13 | })); 14 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /store/use-creator-sidebar.ts: -------------------------------------------------------------------------------- 1 | import { create } from "zustand"; 2 | 3 | interface CreatorSidebarStore { 4 | collapsed: boolean; 5 | onExpand: () => void; 6 | onCollapse: () => void; 7 | } 8 | 9 | export const useCreatorSidebar = create((set) => ({ 10 | collapsed: false, 11 | onExpand: () => set(() => ({ collapsed: false })), 12 | onCollapse: () => set(() => ({ collapsed: true })), 13 | })); 14 | -------------------------------------------------------------------------------- /app/(browse)/_components/navbar/index.tsx: -------------------------------------------------------------------------------- 1 | import { Actions } from "./actions"; 2 | import { Logo } from "./logo"; 3 | import { Search } from "./search"; 4 | 5 | export const Navbar = () => { 6 | return ( 7 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /components/live-badge.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | 3 | interface LiveBadgeProps { 4 | className?: string; 5 | } 6 | 7 | export const LiveBadge = ({ className }: LiveBadgeProps) => { 8 | return ( 9 |
15 | Live 16 |
17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /app/error.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { Button } from "@/components/ui/button"; 3 | import Link from "next/link"; 4 | 5 | const ErrorPage = () => { 6 | return ( 7 |
8 |

Something went wrong

9 | 12 |
13 | ); 14 | }; 15 | 16 | export default ErrorPage; 17 | -------------------------------------------------------------------------------- /components/stream-player/loading-video.tsx: -------------------------------------------------------------------------------- 1 | import { Loader } from "lucide-react"; 2 | 3 | interface LoadingVideoProps { 4 | label: string; 5 | } 6 | 7 | export const LoadingVideo = ({ label }: LoadingVideoProps) => { 8 | return ( 9 |
10 | 11 |

{label}

12 |
13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /components/stream-player/offline-video.tsx: -------------------------------------------------------------------------------- 1 | import { WifiOff } from "lucide-react"; 2 | 3 | interface OfflineVideoProps { 4 | username: string; 5 | } 6 | 7 | export const OfflineVideo = ({ username }: OfflineVideoProps) => { 8 | return ( 9 |
10 | 11 |

{username} is offline

12 |
13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /app/(browse)/[username]/error.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { Button } from "@/components/ui/button"; 3 | import Link from "next/link"; 4 | 5 | const ErrorPage = () => { 6 | return ( 7 |
8 |

Something went wrong

9 | 12 |
13 | ); 14 | }; 15 | 16 | export default ErrorPage; 17 | -------------------------------------------------------------------------------- /app/(dashboard)/u/[username]/chat/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from "@/components/ui/skeleton"; 2 | import { ToggleCardSkeleton } from "./_components/toggle-card"; 3 | 4 | const ChatLoading = () => { 5 | return ( 6 |
7 | 8 |
9 | 10 | 11 | 12 |
13 |
14 | ); 15 | }; 16 | 17 | export default ChatLoading; 18 | -------------------------------------------------------------------------------- /app/not-found.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import Link from "next/link"; 3 | 4 | const NotFoundPage = () => { 5 | return ( 6 |
7 |

404

8 |

We couldn't find the page you were looking for.

9 | 12 |
13 | ); 14 | }; 15 | 16 | export default NotFoundPage; 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /app/(browse)/[username]/not-found.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import Link from "next/link"; 3 | 4 | const NotFoundPage = () => { 5 | return ( 6 |
7 |

404

8 |

We couldn't find the user you were looking for.

9 | 12 |
13 | ); 14 | }; 15 | 16 | export default NotFoundPage; 17 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | 8 | export const stringToColor = (str: string) => { 9 | let hash = 0; 10 | for (let i = 0; i < str.length; i++) { 11 | hash = str.charCodeAt(1) + ((hash << 5) - hash); 12 | } 13 | 14 | let color = "#"; 15 | for (let i = 0; i < 3; i++) { 16 | const value = (hash >> (i * 8)) & 0xff; 17 | color += ("00" + value.toString(16)).substr(-2); 18 | } 19 | return color; 20 | }; 21 | -------------------------------------------------------------------------------- /middleware.ts: -------------------------------------------------------------------------------- 1 | import { authMiddleware } from "@clerk/nextjs"; 2 | 3 | // This example protects all routes including api/trpc routes 4 | // Please edit this to allow other routes to be public as needed. 5 | // See https://clerk.com/docs/references/nextjs/auth-middleware for more information about configuring your Middleware 6 | export default authMiddleware({ 7 | publicRoutes: [ 8 | "/", 9 | "/api/webhooks(.*)", 10 | "/api/uploadthing", 11 | "/:username", 12 | "/search", 13 | ], 14 | }); 15 | 16 | export const config = { 17 | matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"], 18 | }; 19 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/(browse)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from "react"; 2 | import { Container } from "./_components/container"; 3 | import { Navbar } from "./_components/navbar"; 4 | import { Sidebar, SidebarSkeleton } from "./_components/sidebar"; 5 | 6 | const BrowseLayout = ({ children }: { children: React.ReactNode }) => { 7 | return ( 8 | <> 9 | 10 |
11 | }> 12 | 13 | 14 | {children} 15 |
16 | 17 | ); 18 | }; 19 | 20 | export default BrowseLayout; 21 | -------------------------------------------------------------------------------- /actions/user.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { getSelf } from "@/lib/auth-service"; 4 | import { db } from "@/lib/db"; 5 | import { User } from "@prisma/client"; 6 | import { revalidatePath } from "next/cache"; 7 | 8 | export const updateUser = async (values: Partial) => { 9 | const self = await getSelf(); 10 | 11 | const validData = { 12 | bio: values.bio, 13 | }; 14 | 15 | const user = await db.user.update({ 16 | where: { 17 | id: self.id, 18 | }, 19 | data: { 20 | ...validData, 21 | }, 22 | }); 23 | 24 | revalidatePath(`/u/${self.username}`); 25 | revalidatePath(`/${self.username}`); 26 | 27 | return user; 28 | }; 29 | -------------------------------------------------------------------------------- /app/(dashboard)/u/[username]/_components/sidebar/wrapper.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | import { useCreatorSidebar } from "@/store/use-creator-sidebar"; 5 | 6 | interface WrapperProps { 7 | children: React.ReactNode; 8 | } 9 | 10 | export const Wrapper = ({ children }: WrapperProps) => { 11 | const { collapsed } = useCreatorSidebar((state) => state); 12 | 13 | return ( 14 | 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /app/(browse)/search/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation"; 2 | import { Results, ResultsSkeleton } from "./_components/results"; 3 | import { Suspense } from "react"; 4 | 5 | interface SearchPageProps { 6 | searchParams: { 7 | term?: string; 8 | }; 9 | } 10 | 11 | const SearchPage = ({ searchParams }: SearchPageProps) => { 12 | if (!searchParams.term) { 13 | redirect("/"); 14 | } 15 | 16 | return ( 17 |
18 | }> 19 | 20 | 21 |
22 | ); 23 | }; 24 | 25 | export default SearchPage; 26 | -------------------------------------------------------------------------------- /store/use-chat-sidebar.ts: -------------------------------------------------------------------------------- 1 | import { create } from "zustand"; 2 | 3 | export enum ChatVariant { 4 | CHAT = "CHAT", 5 | COMMUNITY = "COMMUNITY", 6 | } 7 | 8 | interface ChatSidebarStore { 9 | collapsed: boolean; 10 | variant: ChatVariant; 11 | onExpand: () => void; 12 | onCollapse: () => void; 13 | onChangeVariant: (variant: ChatVariant) => void; 14 | } 15 | 16 | export const useChatSidebar = create((set) => ({ 17 | collapsed: false, 18 | variant: ChatVariant.CHAT, 19 | onExpand: () => set(() => ({ collapsed: false })), 20 | onCollapse: () => set(() => ({ collapsed: true })), 21 | onChangeVariant: (variant: ChatVariant) => set(() => ({ variant })), 22 | })); 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /app/(dashboard)/u/[username]/_components/navbar/actions.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import { UserButton } from "@clerk/nextjs"; 3 | import { LogOut } from "lucide-react"; 4 | import Link from "next/link"; 5 | 6 | export const Actions = () => { 7 | return ( 8 |
9 | 20 | 21 |
22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /app/(dashboard)/u/[username]/keys/_components/url-card.tsx: -------------------------------------------------------------------------------- 1 | import { Input } from "@/components/ui/input"; 2 | import { CopyButton } from "./copy-button"; 3 | 4 | interface UrlCardProps { 5 | value: string | null; 6 | } 7 | 8 | export const UrlCard = ({ value }: UrlCardProps) => { 9 | return ( 10 |
11 |
12 |

Server URL

13 |
14 |
15 | 16 | 17 |
18 |
19 |
20 |
21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /app/(dashboard)/u/[username]/(home)/page.tsx: -------------------------------------------------------------------------------- 1 | import { StreamPlayer } from "@/components/stream-player"; 2 | import { getUserByUsername } from "@/lib/user-service"; 3 | import { currentUser } from "@clerk/nextjs"; 4 | 5 | interface CreatorPageProps { 6 | params: { 7 | username: string; 8 | }; 9 | } 10 | 11 | const CreatorPage = async ({ params }: CreatorPageProps) => { 12 | const externalUser = await currentUser(); 13 | const user = await getUserByUsername(params.username); 14 | 15 | if (!user || user.externalUserId !== externalUser?.id || !user.stream) { 16 | throw new Error("Unauthorized"); 17 | } 18 | return ( 19 |
20 | 21 |
22 | ); 23 | }; 24 | 25 | export default CreatorPage; 26 | -------------------------------------------------------------------------------- /app/(auth)/_components/logo.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import { Poppins } from "next/font/google"; 3 | 4 | import { cn } from "@/lib/utils"; 5 | 6 | const font = Poppins({ 7 | subsets: ["latin"], 8 | weight: ["200", "300", "400", "500", "600", "700", "800"], 9 | }); 10 | 11 | export const Logo = () => { 12 | return ( 13 |
14 |
15 | NexStream 16 |
17 |
18 |

NexStream

19 |

Let's play

20 |
21 |
22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /app/(browse)/_components/container.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect } from "react"; 4 | import { useMediaQuery } from "usehooks-ts"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | import { useSidebar } from "@/store/use-sidebar"; 8 | 9 | interface ContainerProps { 10 | children: React.ReactNode; 11 | } 12 | export const Container = ({ children }: ContainerProps) => { 13 | const matches = useMediaQuery("(max-width: 1024px)"); 14 | const { collapsed, onCollapse, onExpand } = useSidebar((state) => state); 15 | 16 | useEffect(() => { 17 | if (matches) { 18 | onCollapse(); 19 | } else { 20 | onExpand(); 21 | } 22 | }, [matches, onCollapse, onExpand]); 23 | return ( 24 |
27 | {children} 28 |
29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /app/(dashboard)/u/[username]/layout.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation"; 2 | 3 | import { getSelfByUsername } from "@/lib/auth-service"; 4 | 5 | import { Navbar } from "./_components/navbar"; 6 | import { Sidebar } from "./_components/sidebar"; 7 | import { Container } from "./_components/container"; 8 | 9 | interface CreatorLayoutProps { 10 | params: { username: string }; 11 | children: React.ReactNode; 12 | } 13 | 14 | const CreatorLayout = async ({ params, children }: CreatorLayoutProps) => { 15 | const self = await getSelfByUsername(params.username); 16 | 17 | if (!self) { 18 | redirect("/"); 19 | } 20 | 21 | return ( 22 | <> 23 | 24 |
25 | 26 | {children} 27 |
28 | 29 | ); 30 | }; 31 | 32 | export default CreatorLayout; 33 | -------------------------------------------------------------------------------- /components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 |