feat: add stream info editing functionality

This commit is contained in:
2025-01-24 23:32:20 +01:00
parent 34bf4dcbec
commit b830170642
19 changed files with 387 additions and 78 deletions

View File

@@ -1,9 +1,9 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
webpack: (config) => {
config.externals.push("@node-rs/argon2");
return config;
}
webpack: (config) => {
config.externals.push('@node-rs/argon2');
return config;
},
};
export default nextConfig;

View File

@@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "docker compose --file dev/docker-compose.yml up -d && next dev --turbo --experimental-https",
"dev": "docker compose --file dev/docker-compose.yml up -d && next dev --experimental-https --turbo",
"setup": "docker compose --file dev/docker-compose.yml up -d && prisma migrate deploy",
"build": "prisma generate && next build",
"start": "next start",
@@ -32,7 +32,7 @@
"livekit-server-sdk": "^2.9.7",
"lucia": "^3.2.2",
"lucide-react": "^0.473.0",
"next": "^15.1.2",
"next": "^15.1.6",
"next-themes": "^0.4.4",
"react": "19",
"react-dom": "19",
@@ -40,6 +40,7 @@
"sonner": "^1.4.41",
"tailwind-merge": "^2.2.2",
"tailwindcss-animate": "^1.0.7",
"valtio": "^2.1.2",
"zod": "^3.24.1"
},
"devDependencies": {

View File

@@ -0,0 +1,15 @@
-- CreateTable
CREATE TABLE "StreamInfo" (
"id" TEXT NOT NULL,
"username" TEXT NOT NULL,
"title" TEXT NOT NULL,
"thumbnail" TEXT NOT NULL,
"viewers" INTEGER NOT NULL,
"category" TEXT NOT NULL,
"startedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "StreamInfo_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "StreamInfo_username_key" ON "StreamInfo"("username");

View File

@@ -0,0 +1,8 @@
/*
Warnings:
- Added the required column `isLive` to the `StreamInfo` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "StreamInfo" ADD COLUMN "isLive" BOOLEAN NOT NULL;

View File

@@ -0,0 +1,11 @@
/*
Warnings:
- Added the required column `userId` to the `StreamInfo` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "StreamInfo" ADD COLUMN "userId" TEXT NOT NULL;
-- AddForeignKey
ALTER TABLE "StreamInfo" ADD CONSTRAINT "StreamInfo_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@@ -19,6 +19,7 @@ model User {
pfpUrl String
username String @unique
sessions Session[]
streams StreamInfo[]
}
model Session {
@@ -26,4 +27,18 @@ model Session {
userId String
expiresAt DateTime
user User @relation(references: [id], fields: [userId], onDelete: Cascade)
}
model StreamInfo {
id String @id @default(cuid())
username String @unique
title String
thumbnail String
viewers Int
category String
startedAt DateTime
isLive Boolean
ownedBy User @relation(fields: [userId], references: [id])
userId String
}

View File

@@ -1,8 +1,16 @@
import LiveStream from "@/components/app/Livestream/Livestream";
import prisma from "@/lib/db";
export default async function Page({ params }: { params: Promise<{ username: string }> }) {
const { username } = await params;
const streamInfo = await prisma.streamInfo.findUnique({
where: { username },
include: { ownedBy: true },
});
if (!streamInfo) {
return <div>Stream not found</div>;
}
return (
<LiveStream username={username} />
<LiveStream username={username} streamInfo={streamInfo} />
);
}

View File

@@ -8,6 +8,8 @@ import { Toaster } from '@/components/ui/sonner';
import { ThemeProvider } from '@/lib/providers/ThemeProvider';
import { SidebarProvider } from '@/components/ui/sidebar';
import Sidebar from '@/components/app/Sidebar/Sidebar';
import { cn } from '@/lib/utils';
import EditLivestream from '@/components/app/EditLivestream/EditLivestream';
const inter = Inter({ subsets: ['latin'] });
@@ -24,7 +26,7 @@ export default async function RootLayout({
const sessionData = await validateRequest();
return (
<html lang="en">
<body className="flex flex-col h-screen">
<body className={cn("flex flex-col h-screen", inter.className)}>
<SessionProvider value={sessionData}>
<ThemeProvider
attribute="class"
@@ -33,7 +35,8 @@ export default async function RootLayout({
disableTransitionOnChange
>
<SidebarProvider>
<Navbar />
{/* this promise is ugly but i'm lazy to fix the type errors */}
<Navbar editLivestream={Promise.resolve(<EditLivestream />)}/>
<div className="flex flex-1 pt-16"> {/* pt-16 for navbar height */}
<Sidebar className='pt-16' />
<main className="flex-1 overflow-auto">

View File

@@ -0,0 +1,63 @@
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
import { validateRequest } from '@/lib/auth';
import prisma from '@/lib/db';
import { roomService } from '@/lib/services/livekit';
import { UniversalForm } from '../UniversalForm/UniversalForm';
import { editStreamInfo } from '@/lib/form/actions';
export default async function EditLivestream() {
const { user } = await validateRequest();
if ((await prisma.streamInfo.count({ where: { username: user!.username } })) === 0) {
const isLive =
(await roomService.listRooms()).filter((r) => r.name === user!.username)[0].numPublishers >= 1;
await prisma.streamInfo.create({
data: {
username: user!.username,
title: 'Untitled',
category: 'Uncategorized',
startedAt: new Date(),
thumbnail: 'https://placehold.co/150',
viewers: 0,
isLive,
ownedBy: { connect: { username: user!.username } },
},
});
console.log('created');
}
const streamInfo = await prisma.streamInfo.findUnique({
where: { username: user!.username! },
});
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Edit Livestream</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Edit livestream</DialogTitle>
<DialogDescription>Regenerate a key or edit your stream metadata</DialogDescription>
</DialogHeader>
<UniversalForm
fields={[
{ name: 'username', label: 'Username', value: user?.username!, type: 'hidden' },
{ name: 'title', label: 'Title', type: 'text', value: streamInfo?.title },
{ name: 'category', label: 'Category', type: 'text', value: streamInfo?.category },
]}
schemaName="streamInfoEdit"
action={editStreamInfo}
submitButtonDivClassname="float-right"
submitText="Save"
key={streamInfo?.id}
/>
</DialogContent>
</Dialog>
);
}

View File

@@ -5,15 +5,16 @@ import { useEffect, useState } from 'react';
import StreamPlayer from '../StreamPlayer/StreamPlayer';
import UserInfoCard from '../UserInfoCard/UserInfoCard';
import ChatPanel from '../ChatPanel/ChatPanel';
import type { StreamInfo, User } from '@prisma/client';
export default function LiveStream({ username }: { username: string }) {
export default function LiveStream(props: Props) {
const [token, setToken] = useState('');
useEffect(() => {
fetch(`/api/livekit/viewerToken?room=${username}`)
fetch(`/api/livekit/viewerToken?room=${props.username}`)
.then((res) => res.json())
.then((data) => setToken(data.token));
}, [username]);
}, [props.username]);
if (!token) return <div>Loading...</div>;
@@ -22,10 +23,15 @@ export default function LiveStream({ username }: { username: string }) {
<div className="flex h-[calc(100vh-64px)] w-full">
<div className="flex-1">
<StreamPlayer />
<UserInfoCard />
<UserInfoCard streamInfo={props.streamInfo} />
</div>
<ChatPanel />
</div>
</LiveKitRoom>
);
}
interface Props {
username: string;
streamInfo: StreamInfo & { ownedBy: User };
}

View File

@@ -1,4 +1,4 @@
'use client';
'use client'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
@@ -19,9 +19,7 @@ import { ThemeSwitcher } from '../ThemeSwitcher/ThemeSwitcher';
import { Slack } from 'lucide-react';
import { SidebarTrigger } from '@/components/ui/sidebar';
export const links = [
{ href: '/srizan', name: 'test stream' },
];
export const links = [{ href: '/srizan', name: 'test stream' }];
function NavbarLinks() {
return (
@@ -35,12 +33,12 @@ function NavbarLinks() {
);
}
export default function Navbar() {
export default function Navbar(props: Props) {
const { user } = useSession();
return (
<>
<nav className="flex items-center h-16 px-4 border-b gap-3 w-full z-20 fixed top-0 left-0 shadow-md bg-mantle">
<div className='flex items-center'>
<div className="flex items-center">
<SidebarTrigger />
<Link href="/" className="flex items-center">
<Button>hackclub.tv</Button>
@@ -52,6 +50,7 @@ export default function Navbar() {
<NavbarLinks />
</div>
<div className="flex-1" />
{props.editLivestream}
<ThemeSwitcher />
{user ? (
<DropdownMenu>
@@ -88,3 +87,7 @@ export default function Navbar() {
</>
);
}
interface Props {
editLivestream: Promise<JSX.Element>;
}

View File

@@ -1,3 +1,5 @@
'use client';
import useFullscreen from '@/lib/hooks/useFullscreen';
import {
useTracks,

View File

@@ -1,6 +1,6 @@
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import { Path, PathValue, useForm } from 'react-hook-form';
import { Path, useForm } from 'react-hook-form';
import {
Form,
FormControl,
@@ -19,11 +19,10 @@ import React from 'react';
import { toast } from 'sonner';
import { Textarea } from '@/components/ui/textarea';
import { cn } from '@/lib/utils';
import { accountSchema } from '@/lib/form/zod';
import { streamInfoEditSchema } from '@/lib/form/zod';
export const schemaDb = [
{ name: 'login', zod: accountSchema },
{ name: 'register', zod: accountSchema },
{ name: 'streamInfoEdit', zod: streamInfoEditSchema }
] as const;
export function UniversalForm<T extends z.ZodType>({

View File

@@ -1,18 +1,19 @@
import { Button } from '@/components/ui/button';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import type { StreamInfo, User } from '@prisma/client';
export default function UserInfoCard() {
export default function UserInfoCard(props: Props) {
return (
<div className="bg-mantle rounded-lg p-4">
<div className="flex items-start justify-between mb-4">
<div className="flex items-center space-x-4">
<Avatar className="h-16 w-16">
<AvatarImage src="https://ca.slack-edge.com/T0266FRGM-U079VBNLTPD-1df1edc198bf-192" alt="Bartosz" />
<AvatarImage src={props.streamInfo.ownedBy.pfpUrl} alt={props.streamInfo.ownedBy.username} />
<AvatarFallback>CM</AvatarFallback>
</Avatar>
<div>
<h1 className="text-2xl font-bold">test stream</h1>
<p>probably bartosz</p>
<h1 className="text-2xl font-bold">{props.streamInfo.title}</h1>
<p>{props.streamInfo.username}</p>
</div>
</div>
<Button>Follow</Button>
@@ -35,3 +36,7 @@ export default function UserInfoCard() {
</div>
);
}
interface Props {
streamInfo: StreamInfo & { ownedBy: User };
}

View File

@@ -0,0 +1,122 @@
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}

View File

@@ -1,5 +1,5 @@
import { PrismaAdapter } from '@lucia-auth/adapter-prisma';
import { Lucia, Session, User } from 'lucia';
import { Lucia } from 'lucia';
import prisma from '../db';
import { cache } from 'react';
import { cookies } from 'next/headers';

30
src/lib/form/actions.ts Normal file
View File

@@ -0,0 +1,30 @@
'use server';
import { revalidatePath } from 'next/cache';
import { validateRequest } from '../auth';
import prisma from '../db';
import zodVerify from '../zodVerify';
import { streamInfoEditSchema } from './zod';
export async function editStreamInfo(prev: any, formData: FormData) {
const { user } = await validateRequest();
if (!user) {
return { success: false, error: 'Unauthorized' };
}
const zod = await zodVerify(streamInfoEditSchema, formData);
if (!zod.success) {
return zod;
}
await prisma.streamInfo.update({
where: { username: user.username },
data: zod.data,
}).catch((e) => {
console.error(e);
return { success: false, error: 'Internal server error' };
});
revalidatePath(`/${user.username}`);
return { success: true };
}

View File

@@ -3,4 +3,10 @@ import { z } from 'zod'
export const accountSchema = z.object({
username: z.string().min(3, { message: 'Mininum 3 characters' }).max(31, { message: 'Maximum 31 characters' }).regex(/^[a-z0-9_-]+$/, { message: 'Only characters from a-z, 0-9, underscores and dashes' }),
password: z.string().min(6, { message: 'Minimum 6 characters' }).max(255, { message: 'Maximum 255 characters' }),
})
})
export const streamInfoEditSchema = z.object({
username: z.string().min(1),
title: z.string().min(1),
category: z.string().min(1),
});

110
yarn.lock
View File

@@ -555,10 +555,10 @@
"@emnapi/runtime" "^1.3.1"
"@tybys/wasm-util" "^0.9.0"
"@next/env@15.1.4":
version "15.1.4"
resolved "https://registry.yarnpkg.com/@next/env/-/env-15.1.4.tgz#f727cc4f46527a5223ae894f9476466db9132e21"
integrity sha512-2fZ5YZjedi5AGaeoaC0B20zGntEHRhi2SdWcu61i48BllODcAmmtj8n7YarSPt4DaTsJaBFdxQAVEVzgmx2Zpw==
"@next/env@15.1.6":
version "15.1.6"
resolved "https://registry.yarnpkg.com/@next/env/-/env-15.1.6.tgz#2fa863d8c568a56b1c8328a86e621b8bdd4f2a20"
integrity sha512-d9AFQVPEYNr+aqokIiPLNK/MTyt3DWa/dpKveiAaVccUadFbhFEvY6FXYX2LJO2Hv7PHnLBu2oWwB4uBuHjr/w==
"@next/eslint-plugin-next@15.1.3":
version "15.1.3"
@@ -567,45 +567,45 @@
dependencies:
fast-glob "3.3.1"
"@next/swc-darwin-arm64@15.1.4":
version "15.1.4"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.1.4.tgz#49faf320d44d1a813e09501abfd952b0315385c9"
integrity sha512-wBEMBs+np+R5ozN1F8Y8d/Dycns2COhRnkxRc+rvnbXke5uZBHkUGFgWxfTXn5rx7OLijuUhyfB+gC/ap58dDw==
"@next/swc-darwin-arm64@15.1.6":
version "15.1.6"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.1.6.tgz#92f99badab6cb41f4c5c11a3feffa574bd6a9276"
integrity sha512-u7lg4Mpl9qWpKgy6NzEkz/w0/keEHtOybmIl0ykgItBxEM5mYotS5PmqTpo+Rhg8FiOiWgwr8USxmKQkqLBCrw==
"@next/swc-darwin-x64@15.1.4":
version "15.1.4"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.1.4.tgz#3c234986481e7db8957562b8dfeab2d44fe4c0a7"
integrity sha512-7sgf5rM7Z81V9w48F02Zz6DgEJulavC0jadab4ZsJ+K2sxMNK0/BtF8J8J3CxnsJN3DGcIdC260wEKssKTukUw==
"@next/swc-darwin-x64@15.1.6":
version "15.1.6"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-15.1.6.tgz#f56f4f8d5f6cb5d3915912ac95590d387f897da5"
integrity sha512-x1jGpbHbZoZ69nRuogGL2MYPLqohlhnT9OCU6E6QFewwup+z+M6r8oU47BTeJcWsF2sdBahp5cKiAcDbwwK/lg==
"@next/swc-linux-arm64-gnu@15.1.4":
version "15.1.4"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.1.4.tgz#d71c5a55106327b50ff194dd40e11c3ca7f744a7"
integrity sha512-JaZlIMNaJenfd55kjaLWMfok+vWBlcRxqnRoZrhFQrhM1uAehP3R0+Aoe+bZOogqlZvAz53nY/k3ZyuKDtT2zQ==
"@next/swc-linux-arm64-gnu@15.1.6":
version "15.1.6"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.1.6.tgz#0aaffae519c93d1006419d7b98c34ebfd80ecacd"
integrity sha512-jar9sFw0XewXsBzPf9runGzoivajeWJUc/JkfbLTC4it9EhU8v7tCRLH7l5Y1ReTMN6zKJO0kKAGqDk8YSO2bg==
"@next/swc-linux-arm64-musl@15.1.4":
version "15.1.4"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.1.4.tgz#c6b4cef0f5b943d6308fdb429ecb43be79afce31"
integrity sha512-7EBBjNoyTO2ipMDgCiORpwwOf5tIueFntKjcN3NK+GAQD7OzFJe84p7a2eQUeWdpzZvhVXuAtIen8QcH71ZCOQ==
"@next/swc-linux-arm64-musl@15.1.6":
version "15.1.6"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.1.6.tgz#e7398d3d31ca60033f708a718cd6c31edcee2e9a"
integrity sha512-+n3u//bfsrIaZch4cgOJ3tXCTbSxz0s6brJtU3SzLOvkJlPQMJ+eHVRi6qM2kKKKLuMY+tcau8XD9CJ1OjeSQQ==
"@next/swc-linux-x64-gnu@15.1.4":
version "15.1.4"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.1.4.tgz#2b15f959ac6653800c0df8247489d54982926786"
integrity sha512-9TGEgOycqZFuADyFqwmK/9g6S0FYZ3tphR4ebcmCwhL8Y12FW8pIBKJvSwV+UBjMkokstGNH+9F8F031JZKpHw==
"@next/swc-linux-x64-gnu@15.1.6":
version "15.1.6"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.1.6.tgz#d76c72508f4d79d6016cab0c52640b93e590cffb"
integrity sha512-SpuDEXixM3PycniL4iVCLyUyvcl6Lt0mtv3am08sucskpG0tYkW1KlRhTgj4LI5ehyxriVVcfdoxuuP8csi3kQ==
"@next/swc-linux-x64-musl@15.1.4":
version "15.1.4"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.1.4.tgz#2b9d3ca3c3f506e3515b13c00e19d3031535bb44"
integrity sha512-0578bLRVDJOh+LdIoKvgNDz77+Bd85c5JrFgnlbI1SM3WmEQvsjxTA8ATu9Z9FCiIS/AliVAW2DV/BDwpXbtiQ==
"@next/swc-linux-x64-musl@15.1.6":
version "15.1.6"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.1.6.tgz#0b8ba80a53e65bf8970ed11ea923001e2512c7cb"
integrity sha512-L4druWmdFSZIIRhF+G60API5sFB7suTbDRhYWSjiw0RbE+15igQvE2g2+S973pMGvwN3guw7cJUjA/TmbPWTHQ==
"@next/swc-win32-arm64-msvc@15.1.4":
version "15.1.4"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.1.4.tgz#74928a6e824b258a071d30872c00417ee79d4ee7"
integrity sha512-JgFCiV4libQavwII+kncMCl30st0JVxpPOtzWcAI2jtum4HjYaclobKhj+JsRu5tFqMtA5CJIa0MvYyuu9xjjQ==
"@next/swc-win32-arm64-msvc@15.1.6":
version "15.1.6"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.1.6.tgz#81b5dbbfdada2c05deef688e799af4a24097b65f"
integrity sha512-s8w6EeqNmi6gdvM19tqKKWbCyOBvXFbndkGHl+c9YrzsLARRdCHsD9S1fMj8gsXm9v8vhC8s3N8rjuC/XrtkEg==
"@next/swc-win32-x64-msvc@15.1.4":
version "15.1.4"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.1.4.tgz#188bce4c231f5ab0e7eaca55170764f7e8229db2"
integrity sha512-xxsJy9wzq7FR5SqPCUqdgSXiNXrMuidgckBa8nH9HtjjxsilgcN6VgXF6tZ3uEWuVEadotQJI8/9EQ6guTC4Yw==
"@next/swc-win32-x64-msvc@15.1.6":
version "15.1.6"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.1.6.tgz#131993c45ffd124fb4b15258e2f3f9669c143e3c"
integrity sha512-6xomMuu54FAFxttYr5PJbEfu96godcxBTRk1OhAvJq0/EnmFU/Ybiax30Snis4vdWZ9LGpf7Roy5fSs7v/5ROQ==
"@node-rs/argon2-android-arm-eabi@2.0.2":
version "2.0.2"
@@ -3254,12 +3254,12 @@ next-themes@^0.4.4:
resolved "https://registry.yarnpkg.com/next-themes/-/next-themes-0.4.4.tgz#ce6f68a4af543821bbc4755b59c0d3ced55c2d13"
integrity sha512-LDQ2qIOJF0VnuVrrMSMLrWGjRMkq+0mpgl6e0juCLqdJ+oo8Q84JRWT6Wh11VDQKkMMe+dVzDKLWs5n87T+PkQ==
next@^15.1.2:
version "15.1.4"
resolved "https://registry.yarnpkg.com/next/-/next-15.1.4.tgz#cb2aee3fbb772e20b98ccc2f0806249c09f780a2"
integrity sha512-mTaq9dwaSuwwOrcu3ebjDYObekkxRnXpuVL21zotM8qE2W0HBOdVIdg2Li9QjMEZrj73LN96LcWcz62V19FjAg==
next@^15.1.6:
version "15.1.6"
resolved "https://registry.yarnpkg.com/next/-/next-15.1.6.tgz#ce22fd0a8f36da1fc4aba86e3ec7e98eb248c555"
integrity sha512-Hch4wzbaX0vKQtalpXvUiw5sYivBy4cm5rzUKrBnUB/y436LGrvOUqYvlSeNVCWFO/770gDlltR9gqZH62ct4Q==
dependencies:
"@next/env" "15.1.4"
"@next/env" "15.1.6"
"@swc/counter" "0.1.3"
"@swc/helpers" "0.5.15"
busboy "1.6.0"
@@ -3267,14 +3267,14 @@ next@^15.1.2:
postcss "8.4.31"
styled-jsx "5.1.6"
optionalDependencies:
"@next/swc-darwin-arm64" "15.1.4"
"@next/swc-darwin-x64" "15.1.4"
"@next/swc-linux-arm64-gnu" "15.1.4"
"@next/swc-linux-arm64-musl" "15.1.4"
"@next/swc-linux-x64-gnu" "15.1.4"
"@next/swc-linux-x64-musl" "15.1.4"
"@next/swc-win32-arm64-msvc" "15.1.4"
"@next/swc-win32-x64-msvc" "15.1.4"
"@next/swc-darwin-arm64" "15.1.6"
"@next/swc-darwin-x64" "15.1.6"
"@next/swc-linux-arm64-gnu" "15.1.6"
"@next/swc-linux-arm64-musl" "15.1.6"
"@next/swc-linux-x64-gnu" "15.1.6"
"@next/swc-linux-x64-musl" "15.1.6"
"@next/swc-win32-arm64-msvc" "15.1.6"
"@next/swc-win32-x64-msvc" "15.1.6"
sharp "^0.33.5"
node-domexception@^1.0.0:
@@ -3632,6 +3632,11 @@ prop-types@^15.8.1:
object-assign "^4.1.1"
react-is "^16.13.1"
proxy-compare@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/proxy-compare/-/proxy-compare-3.0.1.tgz#3262cff3a25a6dedeaa299f6cf2369d6f7588a94"
integrity sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q==
punycode@^2.1.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
@@ -4507,6 +4512,13 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
valtio@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/valtio/-/valtio-2.1.2.tgz#1785eb6c72ff50f42d8909a1c1f451f68bc59112"
integrity sha512-fhekN5Rq7dvHULHHBlJeXHrQDl0Jj9GXfNavCm3gkD06crGchaG1nf/J7gSlfZU2wPcRdVS5jBKWHtE2NNz97A==
dependencies:
proxy-compare "^3.0.0"
wcwidth@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"