Arnav Singh commited on
Commit
e8795c0
·
unverified ·
2 Parent(s): b39ee1b57c2af0

Merge pull request #12 from ArnavSingh76533/copilot/update-front-page-ui-design

Browse files
components/ui/button.tsx ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from 'react'
2
+ import { Slot } from '@radix-ui/react-slot'
3
+ import { cva, type VariantProps } from 'class-variance-authority'
4
+
5
+ import { cn } from '@/lib/cn'
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
13
+ destructive:
14
+ 'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20',
15
+ outline:
16
+ 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground',
17
+ secondary:
18
+ 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
19
+ ghost:
20
+ 'hover:bg-accent hover:text-accent-foreground',
21
+ link: 'text-primary underline-offset-4 hover:underline',
22
+ },
23
+ size: {
24
+ default: 'h-9 px-4 py-2 has-[>svg]:px-3',
25
+ sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
26
+ lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
27
+ icon: 'size-9',
28
+ 'icon-sm': 'size-8',
29
+ 'icon-lg': 'size-10',
30
+ },
31
+ },
32
+ defaultVariants: {
33
+ variant: 'default',
34
+ size: 'default',
35
+ },
36
+ },
37
+ )
38
+
39
+ export interface ButtonProps
40
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
41
+ VariantProps<typeof buttonVariants> {
42
+ asChild?: boolean
43
+ }
44
+
45
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
46
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
47
+ const Comp = asChild ? Slot : 'button'
48
+ return (
49
+ <Comp
50
+ className={cn(buttonVariants({ variant, size, className }))}
51
+ ref={ref}
52
+ {...props}
53
+ />
54
+ )
55
+ }
56
+ )
57
+ Button.displayName = 'Button'
58
+
59
+ export { Button, buttonVariants }
components/ui/card.tsx ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from 'react'
2
+
3
+ import { cn } from '@/lib/cn'
4
+
5
+ function Card({ className, ...props }: React.ComponentProps<'div'>) {
6
+ return (
7
+ <div
8
+ className={cn(
9
+ 'flex flex-col gap-6 rounded-xl border py-6 shadow-sm',
10
+ className,
11
+ )}
12
+ {...props}
13
+ />
14
+ )
15
+ }
16
+
17
+ function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
18
+ return (
19
+ <div
20
+ className={cn(
21
+ 'grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6',
22
+ className,
23
+ )}
24
+ {...props}
25
+ />
26
+ )
27
+ }
28
+
29
+ function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
30
+ return (
31
+ <div
32
+ className={cn('leading-none font-semibold', className)}
33
+ {...props}
34
+ />
35
+ )
36
+ }
37
+
38
+ function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
39
+ return (
40
+ <div
41
+ className={cn('text-sm', className)}
42
+ {...props}
43
+ />
44
+ )
45
+ }
46
+
47
+ function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
48
+ return (
49
+ <div
50
+ className={cn('px-6', className)}
51
+ {...props}
52
+ />
53
+ )
54
+ }
55
+
56
+ function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
57
+ return (
58
+ <div
59
+ className={cn('flex items-center px-6', className)}
60
+ {...props}
61
+ />
62
+ )
63
+ }
64
+
65
+ export {
66
+ Card,
67
+ CardHeader,
68
+ CardFooter,
69
+ CardTitle,
70
+ CardDescription,
71
+ CardContent,
72
+ }
components/ui/input.tsx ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from 'react'
2
+
3
+ import { cn } from '@/lib/cn'
4
+
5
+ function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
6
+ return (
7
+ <input
8
+ type={type}
9
+ className={cn(
10
+ 'file:text-foreground placeholder:text-muted-foreground h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
11
+ 'focus-visible:ring-2 focus-visible:ring-primary-500/50',
12
+ className,
13
+ )}
14
+ {...props}
15
+ />
16
+ )
17
+ }
18
+
19
+ export { Input }
components/ui/label.tsx ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import * as LabelPrimitive from '@radix-ui/react-label'
5
+
6
+ import { cn } from '@/lib/cn'
7
+
8
+ function Label({
9
+ className,
10
+ ...props
11
+ }: React.ComponentProps<typeof LabelPrimitive.Root>) {
12
+ return (
13
+ <LabelPrimitive.Root
14
+ className={cn(
15
+ 'flex items-center gap-2 text-sm leading-none font-medium select-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
16
+ className,
17
+ )}
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+
23
+ export { Label }
components/ui/skeleton.tsx ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from 'react'
2
+ import { cn } from '@/lib/cn'
3
+
4
+ function Skeleton({ className, ...props }: React.ComponentProps<'div'>) {
5
+ return (
6
+ <div
7
+ className={cn('animate-pulse rounded-md bg-white/[0.02]', className)}
8
+ {...props}
9
+ />
10
+ )
11
+ }
12
+
13
+ export { Skeleton }
components/ui/switch.tsx ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import * as SwitchPrimitive from '@radix-ui/react-switch'
5
+
6
+ import { cn } from '@/lib/cn'
7
+
8
+ function Switch({
9
+ className,
10
+ ...props
11
+ }: React.ComponentProps<typeof SwitchPrimitive.Root>) {
12
+ return (
13
+ <SwitchPrimitive.Root
14
+ className={cn(
15
+ 'peer inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary-600 data-[state=unchecked]:bg-white/10',
16
+ className,
17
+ )}
18
+ {...props}
19
+ >
20
+ <SwitchPrimitive.Thumb
21
+ className={
22
+ 'pointer-events-none block size-4 rounded-full ring-0 transition-transform bg-white data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0'
23
+ }
24
+ />
25
+ </SwitchPrimitive.Root>
26
+ )
27
+ }
28
+
29
+ export { Switch }
lib/cn.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ import { clsx, type ClassValue } from 'clsx'
2
+ import { twMerge } from 'tailwind-merge'
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
package-lock.json CHANGED
@@ -5,6 +5,12 @@
5
  "packages": {
6
  "": {
7
  "dependencies": {
 
 
 
 
 
 
8
  "next": "14.2.10",
9
  "react": "^18.2.0",
10
  "react-beautiful-dnd": "^13.1.1",
@@ -16,6 +22,7 @@
16
  "socket.io": "^4.7.2",
17
  "socket.io-client": "^4.7.2",
18
  "swr": "^2.2.4",
 
19
  "unique-names-generator": "^4.7.1"
20
  },
21
  "devDependencies": {
@@ -950,6 +957,261 @@
950
  "url": "https://opencollective.com/unts"
951
  }
952
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
953
  "node_modules/@rushstack/eslint-patch": {
954
  "version": "1.5.1",
955
  "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.5.1.tgz",
@@ -1147,7 +1409,7 @@
1147
  "version": "18.2.18",
1148
  "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz",
1149
  "integrity": "sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==",
1150
- "dev": true,
1151
  "license": "MIT",
1152
  "dependencies": {
1153
  "@types/react": "*"
@@ -1885,6 +2147,18 @@
1885
  "node": ">= 6"
1886
  }
1887
  },
 
 
 
 
 
 
 
 
 
 
 
 
1888
  "node_modules/classnames": {
1889
  "version": "2.5.0",
1890
  "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.0.tgz",
@@ -1900,6 +2174,15 @@
1900
  "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
1901
  "license": "MIT"
1902
  },
 
 
 
 
 
 
 
 
 
1903
  "node_modules/color": {
1904
  "version": "4.2.3",
1905
  "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
@@ -4085,6 +4368,15 @@
4085
  "node": ">=10"
4086
  }
4087
  },
 
 
 
 
 
 
 
 
 
4088
  "node_modules/make-error": {
4089
  "version": "1.3.6",
4090
  "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
@@ -5655,6 +5947,16 @@
5655
  "url": "https://opencollective.com/unts"
5656
  }
5657
  },
 
 
 
 
 
 
 
 
 
 
5658
  "node_modules/tailwindcss": {
5659
  "version": "3.4.0",
5660
  "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.0.tgz",
 
5
  "packages": {
6
  "": {
7
  "dependencies": {
8
+ "@radix-ui/react-label": "^2.1.8",
9
+ "@radix-ui/react-slot": "^1.2.4",
10
+ "@radix-ui/react-switch": "^1.2.6",
11
+ "class-variance-authority": "^0.7.1",
12
+ "clsx": "^2.1.1",
13
+ "lucide-react": "^0.561.0",
14
  "next": "14.2.10",
15
  "react": "^18.2.0",
16
  "react-beautiful-dnd": "^13.1.1",
 
22
  "socket.io": "^4.7.2",
23
  "socket.io-client": "^4.7.2",
24
  "swr": "^2.2.4",
25
+ "tailwind-merge": "^3.4.0",
26
  "unique-names-generator": "^4.7.1"
27
  },
28
  "devDependencies": {
 
957
  "url": "https://opencollective.com/unts"
958
  }
959
  },
960
+ "node_modules/@radix-ui/primitive": {
961
+ "version": "1.1.3",
962
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
963
+ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
964
+ "license": "MIT"
965
+ },
966
+ "node_modules/@radix-ui/react-compose-refs": {
967
+ "version": "1.1.2",
968
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
969
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
970
+ "license": "MIT",
971
+ "peerDependencies": {
972
+ "@types/react": "*",
973
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
974
+ },
975
+ "peerDependenciesMeta": {
976
+ "@types/react": {
977
+ "optional": true
978
+ }
979
+ }
980
+ },
981
+ "node_modules/@radix-ui/react-context": {
982
+ "version": "1.1.2",
983
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
984
+ "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
985
+ "license": "MIT",
986
+ "peerDependencies": {
987
+ "@types/react": "*",
988
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
989
+ },
990
+ "peerDependenciesMeta": {
991
+ "@types/react": {
992
+ "optional": true
993
+ }
994
+ }
995
+ },
996
+ "node_modules/@radix-ui/react-label": {
997
+ "version": "2.1.8",
998
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz",
999
+ "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==",
1000
+ "license": "MIT",
1001
+ "dependencies": {
1002
+ "@radix-ui/react-primitive": "2.1.4"
1003
+ },
1004
+ "peerDependencies": {
1005
+ "@types/react": "*",
1006
+ "@types/react-dom": "*",
1007
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
1008
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1009
+ },
1010
+ "peerDependenciesMeta": {
1011
+ "@types/react": {
1012
+ "optional": true
1013
+ },
1014
+ "@types/react-dom": {
1015
+ "optional": true
1016
+ }
1017
+ }
1018
+ },
1019
+ "node_modules/@radix-ui/react-primitive": {
1020
+ "version": "2.1.4",
1021
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
1022
+ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
1023
+ "license": "MIT",
1024
+ "dependencies": {
1025
+ "@radix-ui/react-slot": "1.2.4"
1026
+ },
1027
+ "peerDependencies": {
1028
+ "@types/react": "*",
1029
+ "@types/react-dom": "*",
1030
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
1031
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1032
+ },
1033
+ "peerDependenciesMeta": {
1034
+ "@types/react": {
1035
+ "optional": true
1036
+ },
1037
+ "@types/react-dom": {
1038
+ "optional": true
1039
+ }
1040
+ }
1041
+ },
1042
+ "node_modules/@radix-ui/react-slot": {
1043
+ "version": "1.2.4",
1044
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
1045
+ "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
1046
+ "license": "MIT",
1047
+ "dependencies": {
1048
+ "@radix-ui/react-compose-refs": "1.1.2"
1049
+ },
1050
+ "peerDependencies": {
1051
+ "@types/react": "*",
1052
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1053
+ },
1054
+ "peerDependenciesMeta": {
1055
+ "@types/react": {
1056
+ "optional": true
1057
+ }
1058
+ }
1059
+ },
1060
+ "node_modules/@radix-ui/react-switch": {
1061
+ "version": "1.2.6",
1062
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz",
1063
+ "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==",
1064
+ "license": "MIT",
1065
+ "dependencies": {
1066
+ "@radix-ui/primitive": "1.1.3",
1067
+ "@radix-ui/react-compose-refs": "1.1.2",
1068
+ "@radix-ui/react-context": "1.1.2",
1069
+ "@radix-ui/react-primitive": "2.1.3",
1070
+ "@radix-ui/react-use-controllable-state": "1.2.2",
1071
+ "@radix-ui/react-use-previous": "1.1.1",
1072
+ "@radix-ui/react-use-size": "1.1.1"
1073
+ },
1074
+ "peerDependencies": {
1075
+ "@types/react": "*",
1076
+ "@types/react-dom": "*",
1077
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
1078
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1079
+ },
1080
+ "peerDependenciesMeta": {
1081
+ "@types/react": {
1082
+ "optional": true
1083
+ },
1084
+ "@types/react-dom": {
1085
+ "optional": true
1086
+ }
1087
+ }
1088
+ },
1089
+ "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-primitive": {
1090
+ "version": "2.1.3",
1091
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
1092
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
1093
+ "license": "MIT",
1094
+ "dependencies": {
1095
+ "@radix-ui/react-slot": "1.2.3"
1096
+ },
1097
+ "peerDependencies": {
1098
+ "@types/react": "*",
1099
+ "@types/react-dom": "*",
1100
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
1101
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1102
+ },
1103
+ "peerDependenciesMeta": {
1104
+ "@types/react": {
1105
+ "optional": true
1106
+ },
1107
+ "@types/react-dom": {
1108
+ "optional": true
1109
+ }
1110
+ }
1111
+ },
1112
+ "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-slot": {
1113
+ "version": "1.2.3",
1114
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
1115
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
1116
+ "license": "MIT",
1117
+ "dependencies": {
1118
+ "@radix-ui/react-compose-refs": "1.1.2"
1119
+ },
1120
+ "peerDependencies": {
1121
+ "@types/react": "*",
1122
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1123
+ },
1124
+ "peerDependenciesMeta": {
1125
+ "@types/react": {
1126
+ "optional": true
1127
+ }
1128
+ }
1129
+ },
1130
+ "node_modules/@radix-ui/react-use-controllable-state": {
1131
+ "version": "1.2.2",
1132
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
1133
+ "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
1134
+ "license": "MIT",
1135
+ "dependencies": {
1136
+ "@radix-ui/react-use-effect-event": "0.0.2",
1137
+ "@radix-ui/react-use-layout-effect": "1.1.1"
1138
+ },
1139
+ "peerDependencies": {
1140
+ "@types/react": "*",
1141
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1142
+ },
1143
+ "peerDependenciesMeta": {
1144
+ "@types/react": {
1145
+ "optional": true
1146
+ }
1147
+ }
1148
+ },
1149
+ "node_modules/@radix-ui/react-use-effect-event": {
1150
+ "version": "0.0.2",
1151
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
1152
+ "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
1153
+ "license": "MIT",
1154
+ "dependencies": {
1155
+ "@radix-ui/react-use-layout-effect": "1.1.1"
1156
+ },
1157
+ "peerDependencies": {
1158
+ "@types/react": "*",
1159
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1160
+ },
1161
+ "peerDependenciesMeta": {
1162
+ "@types/react": {
1163
+ "optional": true
1164
+ }
1165
+ }
1166
+ },
1167
+ "node_modules/@radix-ui/react-use-layout-effect": {
1168
+ "version": "1.1.1",
1169
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
1170
+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
1171
+ "license": "MIT",
1172
+ "peerDependencies": {
1173
+ "@types/react": "*",
1174
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1175
+ },
1176
+ "peerDependenciesMeta": {
1177
+ "@types/react": {
1178
+ "optional": true
1179
+ }
1180
+ }
1181
+ },
1182
+ "node_modules/@radix-ui/react-use-previous": {
1183
+ "version": "1.1.1",
1184
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz",
1185
+ "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==",
1186
+ "license": "MIT",
1187
+ "peerDependencies": {
1188
+ "@types/react": "*",
1189
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1190
+ },
1191
+ "peerDependenciesMeta": {
1192
+ "@types/react": {
1193
+ "optional": true
1194
+ }
1195
+ }
1196
+ },
1197
+ "node_modules/@radix-ui/react-use-size": {
1198
+ "version": "1.1.1",
1199
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
1200
+ "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
1201
+ "license": "MIT",
1202
+ "dependencies": {
1203
+ "@radix-ui/react-use-layout-effect": "1.1.1"
1204
+ },
1205
+ "peerDependencies": {
1206
+ "@types/react": "*",
1207
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1208
+ },
1209
+ "peerDependenciesMeta": {
1210
+ "@types/react": {
1211
+ "optional": true
1212
+ }
1213
+ }
1214
+ },
1215
  "node_modules/@rushstack/eslint-patch": {
1216
  "version": "1.5.1",
1217
  "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.5.1.tgz",
 
1409
  "version": "18.2.18",
1410
  "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz",
1411
  "integrity": "sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==",
1412
+ "devOptional": true,
1413
  "license": "MIT",
1414
  "dependencies": {
1415
  "@types/react": "*"
 
2147
  "node": ">= 6"
2148
  }
2149
  },
2150
+ "node_modules/class-variance-authority": {
2151
+ "version": "0.7.1",
2152
+ "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
2153
+ "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
2154
+ "license": "Apache-2.0",
2155
+ "dependencies": {
2156
+ "clsx": "^2.1.1"
2157
+ },
2158
+ "funding": {
2159
+ "url": "https://polar.sh/cva"
2160
+ }
2161
+ },
2162
  "node_modules/classnames": {
2163
  "version": "2.5.0",
2164
  "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.0.tgz",
 
2174
  "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
2175
  "license": "MIT"
2176
  },
2177
+ "node_modules/clsx": {
2178
+ "version": "2.1.1",
2179
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
2180
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
2181
+ "license": "MIT",
2182
+ "engines": {
2183
+ "node": ">=6"
2184
+ }
2185
+ },
2186
  "node_modules/color": {
2187
  "version": "4.2.3",
2188
  "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
 
4368
  "node": ">=10"
4369
  }
4370
  },
4371
+ "node_modules/lucide-react": {
4372
+ "version": "0.561.0",
4373
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.561.0.tgz",
4374
+ "integrity": "sha512-Y59gMY38tl4/i0qewcqohPdEbieBy7SovpBL9IFebhc2mDd8x4PZSOsiFRkpPcOq6bj1r/mjH/Rk73gSlIJP2A==",
4375
+ "license": "ISC",
4376
+ "peerDependencies": {
4377
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
4378
+ }
4379
+ },
4380
  "node_modules/make-error": {
4381
  "version": "1.3.6",
4382
  "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
 
5947
  "url": "https://opencollective.com/unts"
5948
  }
5949
  },
5950
+ "node_modules/tailwind-merge": {
5951
+ "version": "3.4.0",
5952
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
5953
+ "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==",
5954
+ "license": "MIT",
5955
+ "funding": {
5956
+ "type": "github",
5957
+ "url": "https://github.com/sponsors/dcastil"
5958
+ }
5959
+ },
5960
  "node_modules/tailwindcss": {
5961
  "version": "3.4.0",
5962
  "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.0.tgz",
package.json CHANGED
@@ -7,6 +7,12 @@
7
  "lint": "next lint"
8
  },
9
  "dependencies": {
 
 
 
 
 
 
10
  "next": "14.2.10",
11
  "react": "^18.2.0",
12
  "react-beautiful-dnd": "^13.1.1",
@@ -18,6 +24,7 @@
18
  "socket.io": "^4.7.2",
19
  "socket.io-client": "^4.7.2",
20
  "swr": "^2.2.4",
 
21
  "unique-names-generator": "^4.7.1"
22
  },
23
  "devDependencies": {
@@ -37,4 +44,4 @@
37
  "ts-node": "^10.9.2",
38
  "typescript": "5.3.3"
39
  }
40
- }
 
7
  "lint": "next lint"
8
  },
9
  "dependencies": {
10
+ "@radix-ui/react-label": "^2.1.8",
11
+ "@radix-ui/react-slot": "^1.2.4",
12
+ "@radix-ui/react-switch": "^1.2.6",
13
+ "class-variance-authority": "^0.7.1",
14
+ "clsx": "^2.1.1",
15
+ "lucide-react": "^0.561.0",
16
  "next": "14.2.10",
17
  "react": "^18.2.0",
18
  "react-beautiful-dnd": "^13.1.1",
 
24
  "socket.io": "^4.7.2",
25
  "socket.io-client": "^4.7.2",
26
  "swr": "^2.2.4",
27
+ "tailwind-merge": "^3.4.0",
28
  "unique-names-generator": "^4.7.1"
29
  },
30
  "devDependencies": {
 
44
  "ts-node": "^10.9.2",
45
  "typescript": "5.3.3"
46
  }
47
+ }
pages/global.css CHANGED
@@ -149,3 +149,62 @@ html {
149
  object-fit: contain;
150
  }
151
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  object-fit: contain;
150
  }
151
  }
152
+
153
+ /* New animations for front page */
154
+ @keyframes fade-in {
155
+ from {
156
+ opacity: 0;
157
+ }
158
+ to {
159
+ opacity: 1;
160
+ }
161
+ }
162
+
163
+ @keyframes fade-up {
164
+ from {
165
+ opacity: 0;
166
+ transform: translateY(8px);
167
+ }
168
+ to {
169
+ opacity: 1;
170
+ transform: translateY(0);
171
+ }
172
+ }
173
+
174
+ @keyframes pulse-slow {
175
+ 0%,
176
+ 100% {
177
+ opacity: 1;
178
+ transform: scale(1);
179
+ }
180
+ 50% {
181
+ opacity: 0.7;
182
+ transform: scale(1.01);
183
+ }
184
+ }
185
+
186
+ @keyframes bounce-subtle {
187
+ 0%,
188
+ 100% {
189
+ transform: translateY(0);
190
+ }
191
+ 50% {
192
+ transform: translateY(2px);
193
+ }
194
+ }
195
+
196
+ .animate-fade-in {
197
+ animation: fade-in 150ms ease-out forwards;
198
+ }
199
+
200
+ .animate-fade-up {
201
+ animation: fade-up 150ms ease-out both;
202
+ }
203
+
204
+ .animate-pulse-slow {
205
+ animation: pulse-slow 8s ease-in-out infinite;
206
+ }
207
+
208
+ .animate-bounce-subtle {
209
+ animation: bounce-subtle 2.5s ease-in-out infinite;
210
+ }
pages/index.tsx CHANGED
@@ -1,10 +1,15 @@
1
  import Layout from "../components/Layout"
2
  import { useState, useEffect } from "react"
3
- import InputText from "../components/input/InputText"
4
- import Button from "../components/action/Button"
5
  import { useRouter } from "next/router"
6
- import { Tooltip } from "react-tooltip"
7
  import useSWR from "swr"
 
 
 
 
 
 
 
 
8
 
9
  interface PublicRoom {
10
  id: string
@@ -12,23 +17,30 @@ interface PublicRoom {
12
  memberCount: number
13
  }
14
 
 
 
 
 
 
 
 
15
  export default function Index() {
16
  const router = useRouter()
17
- const { data } = useSWR("/api/stats", (url) =>
18
- fetch(url).then((r) => r.json())
19
- )
20
- const { data: roomsData } = useSWR<{ rooms: PublicRoom[] }>(
21
- "/api/rooms",
22
- (url) => fetch(url).then((r) => r.json()),
23
- { refreshInterval: 5000 } // Auto-refresh every 5 seconds
24
- )
25
-
26
  const [room, setRoom] = useState("")
27
  const [userName, setUserName] = useState("")
28
  const [isPublic, setIsPublic] = useState(false)
29
  const [nameError, setNameError] = useState("")
 
 
 
 
 
 
 
 
 
 
30
 
31
- // Load saved user name from localStorage on mount
32
  useEffect(() => {
33
  if (typeof window !== "undefined") {
34
  const savedName = localStorage.getItem("userName")
@@ -36,213 +48,311 @@ export default function Index() {
36
  setUserName(savedName)
37
  }
38
  }
 
 
 
 
 
 
 
39
  }, [])
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  return (
42
  <Layout meta={{ robots: "index, archive, follow" }} showNavbar={false}>
43
- <div className={"self-center flex justify-center items-center min-h-[70vh]"}>
44
- <form
45
- className={
46
- "flex flex-col gap-6 justify-center rounded-xl shadow-2xl p-8 bg-gradient-to-br from-dark-800 to-dark-900 m-8 max-w-md w-full border border-dark-700/50"
47
- }
48
- onSubmit={async (e) => {
49
- e.preventDefault()
50
-
51
- if (room.length >= 4) {
52
- // Save user name to localStorage
53
- if (userName.trim() && typeof window !== "undefined") {
54
- localStorage.setItem("userName", userName.trim())
55
- }
56
- await router.push("/room/" + room)
57
- }
58
- }}
59
  >
60
- <div className="text-center">
61
- <h1 className={"text-3xl font-bold bg-gradient-to-r from-primary-500 to-accent-500 bg-clip-text text-transparent mb-2"}>
62
- Welcome to Streamer
63
- </h1>
64
- <p className="text-dark-400 text-sm">Join or create a room to watch together</p>
65
- </div>
66
-
67
- {/* User Name Input */}
68
- <div>
69
- <label className="block text-sm font-medium text-dark-300 mb-1.5">
70
- Your Name {" "}
71
- <span className="text-xs text-dark-500">(optional for joining, required for creating)</span>
72
- </label>
73
- <InputText
74
- value={userName}
75
- placeholder={"Enter your display name"}
76
- onChange={(value) => {
77
- setUserName(value)
78
- setNameError("")
79
- }}
80
- />
81
- {nameError && (
82
- <p className="text-red-500 text-xs mt-1">{nameError}</p>
83
- )}
84
  </div>
85
-
86
- {/* Room ID Input */}
87
- <div>
88
- <label className="block text-sm font-medium text-dark-300 mb-1.5">
89
- Room ID
90
- </label>
91
- <InputText
92
- value={room}
93
- placeholder={"Enter a room ID"}
94
- onChange={(value) =>
95
- setRoom(value.toLowerCase().replace(/[^a-z]/g, ""))
96
- }
97
- />
98
  </div>
99
-
100
- <div className={"flex gap-3 justify-end"}>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  <Button
102
- tooltip={"Create a new personal room"}
103
- className={"px-4 py-2.5 font-medium"}
104
- actionClasses={
105
- "bg-accent-600 hover:bg-accent-700 active:bg-accent-800 shadow-lg hover:shadow-xl"
106
- }
107
  onClick={() => {
108
- // Validate name
109
- if (!userName.trim()) {
110
- setNameError("Please enter your name before creating a room")
111
- return
112
- }
113
-
114
- // Save user name to localStorage
115
- if (typeof window !== "undefined") {
116
- localStorage.setItem("userName", userName.trim())
117
- }
118
-
119
- fetch("/api/generate")
120
- .then((r) => r.json())
121
- .then(async ({ roomId }) => {
122
- if (
123
- typeof roomId === "string" &&
124
- roomId.length >= 4 &&
125
- roomId.match(/^[a-z]{4,}$/)
126
- ) {
127
- console.log("Generated new roomId:", roomId)
128
- // Store room metadata in sessionStorage
129
- if (typeof window !== "undefined") {
130
- sessionStorage.setItem(
131
- `room_${roomId}_meta`,
132
- JSON.stringify({ isPublic })
133
- )
134
- }
135
- await router.push("/room/" + roomId)
136
- } else {
137
- throw Error("Invalid roomId generated: " + roomId)
138
- }
139
- })
140
- .catch((error) => {
141
- console.error("Failed to generate new roomId", error)
142
- })
143
  }}
 
144
  >
145
- Generate room
146
- </Button>
147
- <Button
148
- tooltip={room.length < 4 ? "Invalid room id" : "Join room"}
149
- className={"px-4 py-2.5 font-medium"}
150
- actionClasses={
151
- room.length >= 4
152
- ? "bg-primary-600 hover:bg-primary-700 active:bg-primary-800 shadow-lg hover:shadow-xl hover:shadow-glow"
153
- : "bg-dark-700 hover:bg-dark-600 active:bg-red-700 cursor-not-allowed opacity-50"
154
- }
155
- disabled={room.length < 4}
156
- type={"submit"}
157
- >
158
- Join room
159
  </Button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  </div>
161
 
162
- {/* Public/Private Toggle */}
163
- <div className="flex items-center gap-3 p-3 bg-dark-800/50 rounded-lg border border-dark-700/30">
164
- <label className="flex items-center gap-2 cursor-pointer flex-1">
165
- <input
166
- type="checkbox"
167
- checked={isPublic}
168
- onChange={(e) => setIsPublic(e.target.checked)}
169
- className="w-4 h-4 rounded border-dark-600 text-primary-600 focus:ring-primary-500 focus:ring-offset-dark-900"
170
- />
171
- <span className="text-sm text-dark-300">
172
- Make room public (visible in lobby)
173
- </span>
174
- </label>
175
  </div>
176
-
177
- <div className={"mt-2 pt-4 border-t border-dark-700/50"}>
178
- <small className={"text-dark-400"}>
179
- <div className="font-medium text-dark-300 mb-1">Currently active:</div>
180
- <div className={"flex flex-row gap-4 text-sm"}>
181
- <div className="flex items-center gap-1">
182
- <span className="inline-block w-2 h-2 bg-primary-500 rounded-full"></span>
183
- <span>{data?.rooms || 0} Rooms</span>
184
- </div>
185
- <div className="flex items-center gap-1">
186
- <span className="inline-block w-2 h-2 bg-accent-500 rounded-full"></span>
187
- <span>{data?.users || 0} Users</span>
188
- </div>
189
  </div>
190
- </small>
191
- </div>
192
- </form>
193
- </div>
194
 
195
- {/* Public Rooms List */}
196
- {roomsData && roomsData.rooms && roomsData.rooms.length > 0 && (
197
- <div className="max-w-4xl mx-auto px-4 pb-8">
198
- <div className="bg-gradient-to-br from-dark-800 to-dark-900 rounded-xl shadow-2xl p-6 border border-dark-700/50">
199
- <h2 className="text-xl font-bold text-dark-200 mb-4 flex items-center gap-2">
200
- <span className="inline-block w-2 h-2 bg-primary-500 rounded-full"></span>
201
- Public Rooms
202
- </h2>
203
- <div className="space-y-2">
204
- {roomsData.rooms.map((room) => (
205
- <div
206
- key={room.id}
207
- className="flex items-center justify-between p-4 bg-dark-800/50 rounded-lg border border-dark-700/30 hover:border-primary-500/50 transition-colors"
208
- >
209
- <div className="flex-1">
210
- <div className="font-medium text-dark-200">
211
- Room: <span className="text-primary-400">{room.id}</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
  </div>
213
- <div className="text-sm text-dark-400 mt-1">
214
- Owner: {room.ownerName} • {room.memberCount} {room.memberCount === 1 ? "member" : "members"}
 
 
 
 
 
215
  </div>
216
- </div>
217
- <Button
218
- tooltip="Join this room"
219
- className="px-4 py-2 text-sm font-medium"
220
- actionClasses="bg-primary-600 hover:bg-primary-700 active:bg-primary-800"
221
- onClick={async () => {
222
- // Save user name if provided
223
- if (userName.trim() && typeof window !== "undefined") {
224
- localStorage.setItem("userName", userName.trim())
225
- }
226
- await router.push("/room/" + room.id)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
  }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  >
229
- Join
 
230
  </Button>
231
  </div>
232
- ))}
233
- </div>
234
- </div>
235
  </div>
236
- )}
237
-
238
- <Tooltip
239
- style={{
240
- backgroundColor: "var(--dark-700)",
241
- borderRadius: "0.5rem",
242
- padding: "0.5rem 0.75rem",
243
- fontSize: "0.875rem",
244
- }}
245
- />
246
  </Layout>
247
  )
248
  }
 
1
  import Layout from "../components/Layout"
2
  import { useState, useEffect } from "react"
 
 
3
  import { useRouter } from "next/router"
 
4
  import useSWR from "swr"
5
+ import { Button } from "@/components/ui/button"
6
+ import { Input } from "@/components/ui/input"
7
+ import { Label } from "@/components/ui/label"
8
+ import { Card, CardContent } from "@/components/ui/card"
9
+ import { Skeleton } from "@/components/ui/skeleton"
10
+ import { Switch } from "@/components/ui/switch"
11
+ import { Play, Users, ChevronRight, Search, Settings, ChevronDown } from "lucide-react"
12
+ import { cn } from "@/lib/cn"
13
 
14
  interface PublicRoom {
15
  id: string
 
17
  memberCount: number
18
  }
19
 
20
+ interface Stats {
21
+ rooms: number
22
+ users: number
23
+ }
24
+
25
+ const fetcher = (url: string) => fetch(url).then((r) => r.json())
26
+
27
  export default function Index() {
28
  const router = useRouter()
 
 
 
 
 
 
 
 
 
29
  const [room, setRoom] = useState("")
30
  const [userName, setUserName] = useState("")
31
  const [isPublic, setIsPublic] = useState(false)
32
  const [nameError, setNameError] = useState("")
33
+ const [isGenerating, setIsGenerating] = useState(false)
34
+ const [mounted, setMounted] = useState(false)
35
+ const [scrolled, setScrolled] = useState(false)
36
+
37
+ const { data: stats, isLoading: statsLoading } = useSWR<Stats>("/api/stats", fetcher)
38
+ const { data: roomsData, isLoading: roomsLoading } = useSWR<{ rooms: PublicRoom[] }>(
39
+ "/api/rooms",
40
+ fetcher,
41
+ { refreshInterval: 5000 }
42
+ )
43
 
 
44
  useEffect(() => {
45
  if (typeof window !== "undefined") {
46
  const savedName = localStorage.getItem("userName")
 
48
  setUserName(savedName)
49
  }
50
  }
51
+ setMounted(true)
52
+
53
+ const handleScroll = () => {
54
+ setScrolled(window.scrollY > 20)
55
+ }
56
+ window.addEventListener("scroll", handleScroll, { passive: true })
57
+ return () => window.removeEventListener("scroll", handleScroll)
58
  }, [])
59
 
60
+ const handleJoinRoom = async (roomId: string) => {
61
+ // Save user name if provided
62
+ if (userName.trim() && typeof window !== "undefined") {
63
+ localStorage.setItem("userName", userName.trim())
64
+ }
65
+ await router.push("/room/" + roomId)
66
+ }
67
+
68
+ const handleGenerateRoom = () => {
69
+ // Validate name
70
+ if (!userName.trim()) {
71
+ setNameError("Please enter your name before creating a room")
72
+ return
73
+ }
74
+
75
+ setIsGenerating(true)
76
+ // Save user name to localStorage
77
+ if (typeof window !== "undefined") {
78
+ localStorage.setItem("userName", userName.trim())
79
+ }
80
+
81
+ fetch("/api/generate")
82
+ .then((r) => r.json())
83
+ .then(async ({ roomId }) => {
84
+ if (
85
+ typeof roomId === "string" &&
86
+ roomId.length >= 4 &&
87
+ roomId.match(/^[a-z]{4,}$/)
88
+ ) {
89
+ console.log("Generated new roomId:", roomId)
90
+ // Store room metadata in sessionStorage
91
+ if (typeof window !== "undefined") {
92
+ sessionStorage.setItem(
93
+ `room_${roomId}_meta`,
94
+ JSON.stringify({ isPublic })
95
+ )
96
+ }
97
+ await router.push("/room/" + roomId)
98
+ } else {
99
+ throw Error("Invalid roomId generated: " + roomId)
100
+ }
101
+ })
102
+ .catch((error) => {
103
+ console.error("Failed to generate new roomId", error)
104
+ setIsGenerating(false)
105
+ })
106
+ }
107
+
108
  return (
109
  <Layout meta={{ robots: "index, archive, follow" }} showNavbar={false}>
110
+ <div className="min-h-screen bg-[#0B0E13]">
111
+ <nav
112
+ className={cn(
113
+ "fixed top-0 left-0 right-0 z-50 h-[52px] flex items-center justify-between px-6 transition-all duration-200",
114
+ scrolled ? "bg-[#0B0E13]/95 backdrop-blur-sm border-b border-white/[0.04]" : "bg-transparent",
115
+ )}
 
 
 
 
 
 
 
 
 
 
116
  >
117
+ <div className="flex items-center gap-3">
118
+ <div className="w-9 h-9 rounded-xl bg-gradient-to-br from-[#3B82F6] to-[#8B5CF6] flex items-center justify-center">
119
+ <Play className="w-4 h-4 fill-white text-white" />
120
+ </div>
121
+ <span className="text-lg font-bold text-white tracking-tight">Streamer</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  </div>
123
+ <div className="flex items-center gap-1.5">
124
+ <button className="w-9 h-9 rounded-full bg-white/[0.04] hover:bg-white/[0.06] flex items-center justify-center transition-colors duration-150">
125
+ <Search className="w-4 h-4 text-white/40" />
126
+ </button>
127
+ <button className="w-9 h-9 rounded-full bg-white/[0.04] hover:bg-white/[0.06] flex items-center justify-center transition-colors duration-150">
128
+ <Settings className="w-4 h-4 text-white/40" />
129
+ </button>
 
 
 
 
 
 
130
  </div>
131
+ </nav>
132
+
133
+ <div className="relative h-[80vh] flex items-center justify-center overflow-hidden">
134
+ <div className="absolute inset-0 bg-gradient-to-b from-[#0B0E13] via-[#0B0E13] to-[#0B0E13]" />
135
+ <div className="absolute top-1/3 left-1/4 w-[300px] h-[300px] rounded-full bg-[#3B82F6]/[0.06] blur-[100px] animate-pulse-slow" />
136
+ <div
137
+ className="absolute bottom-1/3 right-1/4 w-[250px] h-[250px] rounded-full bg-[#8B5CF6]/[0.05] blur-[80px] animate-pulse-slow"
138
+ style={{ animationDelay: "3s" }}
139
+ />
140
+
141
+ <div className="relative z-10 w-full max-w-6xl mx-auto px-6 text-center animate-fade-up">
142
+ <h1 className="text-4xl md:text-5xl lg:text-6xl font-extrabold tracking-tight leading-[1.1] text-white mb-4">
143
+ Watch Together,
144
+ <br />
145
+ <span className="bg-gradient-to-r from-[#3B82F6] to-[#8B5CF6] bg-clip-text text-transparent">
146
+ Stream Anywhere
147
+ </span>
148
+ </h1>
149
+ <p className="text-sm text-white/25 max-w-sm mx-auto mb-8 font-light">
150
+ Create a room and invite friends to watch in perfect sync
151
+ </p>
152
+
153
  <Button
154
+ size="lg"
 
 
 
 
155
  onClick={() => {
156
+ const formSection = document.getElementById("join-section")
157
+ formSection?.scrollIntoView({ behavior: "smooth" })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  }}
159
+ className="group h-12 px-8 bg-gradient-to-r from-[#3B82F6] to-[#8B5CF6] hover:from-[#4B8FF7] hover:to-[#9B6CF7] text-white text-base font-semibold rounded-2xl shadow-[0_0_24px_rgba(59,130,246,0.2)] hover:shadow-[0_0_32px_rgba(59,130,246,0.3)] transition-all duration-150 active:scale-[0.98]"
160
  >
161
+ <Play className="w-4 h-4 mr-2 fill-white" />
162
+ Get Started
163
+ <ChevronRight className="w-4 h-4 ml-1 group-hover:translate-x-0.5 transition-transform duration-150" />
 
 
 
 
 
 
 
 
 
 
 
164
  </Button>
165
+
166
+ <div className="flex items-center justify-center gap-2.5 mt-6">
167
+ <div className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-white/[0.02]">
168
+ <div className="w-1 h-1 bg-white/20 rounded-full" />
169
+ <span className="text-[11px] text-white/20">
170
+ {mounted && !statsLoading ? (stats?.rooms ?? 0) : "—"} Rooms
171
+ </span>
172
+ </div>
173
+ <div className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-white/[0.02]">
174
+ <div className="w-1 h-1 bg-white/20 rounded-full" />
175
+ <span className="text-[11px] text-white/20">
176
+ {mounted && !statsLoading ? (stats?.users ?? 0) : "—"} Watching
177
+ </span>
178
+ </div>
179
+ </div>
180
  </div>
181
 
182
+ <div
183
+ className="absolute bottom-8 left-1/2 -translate-x-1/2 flex flex-col items-center gap-1 animate-fade-in opacity-0"
184
+ style={{ animationDelay: "500ms", animationFillMode: "forwards" }}
185
+ >
186
+ <span className="text-[9px] text-white/15 font-medium tracking-wider uppercase">Scroll</span>
187
+ <ChevronDown className="w-3.5 h-3.5 text-white/15 animate-bounce-subtle" />
 
 
 
 
 
 
 
188
  </div>
189
+
190
+ <div className="absolute bottom-0 left-0 right-0 h-40 bg-gradient-to-t from-[#0B0E13] via-[#0B0E13]/90 to-transparent pointer-events-none" />
191
+ </div>
192
+
193
+ <div id="join-section" className="w-full max-w-6xl mx-auto px-6 py-10">
194
+ {roomsData && roomsData.rooms && roomsData.rooms.length > 0 && (
195
+ <div className="mb-12">
196
+ <div className="flex items-center justify-between mb-4">
197
+ <h2 className="text-lg font-semibold text-white">Trending Now</h2>
198
+ <span className="text-[11px] text-white/20">{roomsData.rooms.length} active</span>
 
 
 
199
  </div>
 
 
 
 
200
 
201
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
202
+ {roomsData.rooms.map((publicRoom, idx) => (
203
+ <button
204
+ key={publicRoom.id}
205
+ onClick={() => handleJoinRoom(publicRoom.id)}
206
+ className={cn(
207
+ "group relative rounded-xl overflow-hidden transition-all duration-150 text-left",
208
+ "bg-[#12151B] border border-white/[0.04]",
209
+ "hover:border-white/[0.08] hover:scale-[1.01]",
210
+ idx === 0 && "col-span-2 row-span-1 md:col-span-2",
211
+ )}
212
+ style={{ animation: `fade-up 150ms ease-out ${200 + idx * 40}ms both` }}
213
+ >
214
+ <div className="relative aspect-video bg-[#0B0E13] overflow-hidden">
215
+ <div className="w-full h-full bg-gradient-to-br from-[#3B82F6]/20 to-[#8B5CF6]/20 flex items-center justify-center">
216
+ <Play className="w-12 h-12 text-white/30" />
217
+ </div>
218
+ <div className="absolute inset-0 shadow-[inset_0_0_40px_rgba(0,0,0,0.3)]" />
219
+
220
+ <div className="absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity duration-150 flex items-center justify-center">
221
+ <div className="w-11 h-11 rounded-full bg-white/90 flex items-center justify-center scale-90 opacity-0 group-hover:scale-100 group-hover:opacity-100 transition-all duration-150">
222
+ <Play className="w-4 h-4 fill-[#0B0E13] text-[#0B0E13] ml-0.5" />
223
+ </div>
224
+ </div>
225
+
226
+ <div className="absolute bottom-2 right-2 px-1.5 py-0.5 rounded bg-black/50">
227
+ <span className="text-[9px] text-white/40 font-medium">Live</span>
228
+ </div>
229
+
230
+ {idx === 0 && (
231
+ <div className="absolute top-2 left-2 px-1.5 py-0.5 rounded bg-gradient-to-r from-[#3B82F6] to-[#8B5CF6]">
232
+ <span className="text-[9px] text-white font-medium">Featured</span>
233
+ </div>
234
+ )}
235
  </div>
236
+
237
+ <div className="p-3">
238
+ <div className="text-sm font-medium text-white/70 truncate">{publicRoom.ownerName}&apos;s Room</div>
239
+ <div className="flex items-center gap-1 text-[10px] text-white/20 mt-1">
240
+ <Users className="w-3 h-3" />
241
+ <span>{publicRoom.memberCount} watching</span>
242
+ </div>
243
  </div>
244
+ </button>
245
+ ))}
246
+
247
+ {!mounted || (roomsLoading && (!roomsData || roomsData.rooms.length === 0)) ? (
248
+ <>
249
+ {[1, 2, 3, 4].map((i) => (
250
+ <div
251
+ key={i}
252
+ className={cn(
253
+ "rounded-xl overflow-hidden bg-[#12151B] border border-white/[0.04]",
254
+ i === 1 && "col-span-2",
255
+ )}
256
+ >
257
+ <Skeleton className="aspect-video w-full bg-white/[0.02]" />
258
+ <div className="p-3 space-y-2">
259
+ <Skeleton className="h-4 w-3/4 bg-white/[0.02]" />
260
+ <Skeleton className="h-3 w-1/3 bg-white/[0.02]" />
261
+ </div>
262
+ </div>
263
+ ))}
264
+ </>
265
+ ) : null}
266
+ </div>
267
+ </div>
268
+ )}
269
+
270
+ <Card className="max-w-md mx-auto bg-[#12151B] border-white/[0.04] rounded-xl">
271
+ <CardContent className="pt-6 pb-6">
272
+ <div className="text-center mb-5">
273
+ <h2 className="text-base font-semibold text-white mb-1">Create Your Room</h2>
274
+ <p className="text-[11px] text-white/25">Start streaming together in seconds</p>
275
+ </div>
276
+
277
+ <form
278
+ onSubmit={(e) => {
279
+ e.preventDefault()
280
+ if (room.length >= 4) {
281
+ handleJoinRoom(room)
282
+ }
283
+ }}
284
+ className="space-y-3.5"
285
+ >
286
+ <div className="space-y-1.5">
287
+ <Label htmlFor="userName" className="text-[11px] font-medium text-white/40">
288
+ Your Name
289
+ </Label>
290
+ <Input
291
+ id="userName"
292
+ value={userName}
293
+ placeholder="Enter your display name"
294
+ onChange={(e) => {
295
+ setUserName(e.target.value)
296
+ setNameError("")
297
  }}
298
+ className={cn(
299
+ "bg-white/[0.03] border-white/[0.06] text-white placeholder:text-white/15 h-10 rounded-lg text-sm",
300
+ "focus-visible:ring-[#3B82F6]/15 focus-visible:border-white/[0.1] transition-all duration-150",
301
+ nameError && "border-red-500/40",
302
+ )}
303
+ />
304
+ {nameError && <p className="text-[10px] text-red-400/80">{nameError}</p>}
305
+ </div>
306
+
307
+ <div className="space-y-1.5">
308
+ <Label htmlFor="roomId" className="text-[11px] font-medium text-white/40">
309
+ Room ID <span className="text-white/15 font-normal">(optional)</span>
310
+ </Label>
311
+ <Input
312
+ id="roomId"
313
+ value={room}
314
+ placeholder="Enter a room ID to join"
315
+ onChange={(e) => setRoom(e.target.value.toLowerCase().replace(/[^a-z]/g, ""))}
316
+ className="bg-white/[0.03] border-white/[0.06] text-white placeholder:text-white/15 h-10 rounded-lg text-sm focus-visible:ring-[#3B82F6]/15 focus-visible:border-white/[0.1] transition-all duration-150"
317
+ />
318
+ </div>
319
+
320
+ <div className="flex items-center justify-between p-2.5 rounded-lg bg-white/[0.02] border border-white/[0.04]">
321
+ <Label htmlFor="public-toggle" className="cursor-pointer text-[11px] text-white/30">
322
+ Make room public
323
+ </Label>
324
+ <Switch
325
+ id="public-toggle"
326
+ checked={isPublic}
327
+ onCheckedChange={setIsPublic}
328
+ className="scale-[0.85]"
329
+ />
330
+ </div>
331
+
332
+ <div className="flex gap-2 pt-1">
333
+ <Button
334
+ type="button"
335
+ variant="outline"
336
+ className="flex-1 h-10 bg-transparent border-white/[0.06] text-white/35 hover:bg-white/[0.03] hover:text-white/50 hover:border-white/[0.08] rounded-lg text-sm transition-all duration-150 active:scale-[0.98]"
337
+ onClick={handleGenerateRoom}
338
+ disabled={isGenerating}
339
+ >
340
+ {isGenerating ? "Creating..." : "Generate"}
341
+ </Button>
342
+ <Button
343
+ type="submit"
344
+ disabled={room.length < 4}
345
+ className="flex-1 h-10 bg-gradient-to-r from-[#3B82F6] to-[#8B5CF6] hover:from-[#4B8FF7] hover:to-[#9B6CF7] text-white font-medium rounded-lg text-sm transition-all duration-150 active:scale-[0.98] disabled:opacity-25 disabled:cursor-not-allowed"
346
  >
347
+ Join Room
348
+ <ChevronRight className="w-3.5 h-3.5 ml-1" />
349
  </Button>
350
  </div>
351
+ </form>
352
+ </CardContent>
353
+ </Card>
354
  </div>
355
+ </div>
 
 
 
 
 
 
 
 
 
356
  </Layout>
357
  )
358
  }
public/streaming-room-.jpg ADDED
tsconfig.json CHANGED
@@ -5,6 +5,9 @@
5
  "jsx": "preserve",
6
  "lib": ["dom", "es2017"],
7
  "baseUrl": ".",
 
 
 
8
  "moduleResolution": "node",
9
  "strict": true,
10
  "allowJs": true,
 
5
  "jsx": "preserve",
6
  "lib": ["dom", "es2017"],
7
  "baseUrl": ".",
8
+ "paths": {
9
+ "@/*": ["./*"]
10
+ },
11
  "moduleResolution": "node",
12
  "strict": true,
13
  "allowJs": true,
yarn.lock CHANGED
@@ -216,6 +216,101 @@
216
  tiny-glob "^0.2.9"
217
  tslib "^2.4.0"
218
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  "@rushstack/eslint-patch@^1.3.3":
220
  version "1.5.1"
221
  resolved "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.5.1.tgz"
@@ -353,7 +448,7 @@
353
  dependencies:
354
  "@types/react" "*"
355
 
356
- "@types/react-dom@^18.2.18":
357
  version "18.2.18"
358
  resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz"
359
  integrity sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==
@@ -751,6 +846,13 @@ chokidar@^3.5.3:
751
  optionalDependencies:
752
  fsevents "~2.3.2"
753
 
 
 
 
 
 
 
 
754
  classnames@^2.3.0, classnames@^2.5.0:
755
  version "2.5.0"
756
  resolved "https://registry.npmjs.org/classnames/-/classnames-2.5.0.tgz"
@@ -761,6 +863,11 @@ client-only@^0.0.1, client-only@0.0.1:
761
  resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz"
762
  integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
763
 
 
 
 
 
 
764
  color-convert@^2.0.1:
765
  version "2.0.1"
766
  resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz"
@@ -1987,6 +2094,11 @@ lru-cache@^6.0.0:
1987
  dependencies:
1988
  yallist "^4.0.0"
1989
 
 
 
 
 
 
1990
  make-error@^1.1.1:
1991
  version "1.3.6"
1992
  resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz"
@@ -2387,7 +2499,7 @@ react-beautiful-dnd@^13.1.1:
2387
  redux "^4.0.4"
2388
  use-memo-one "^1.1.1"
2389
 
2390
- "react-dom@^16.8.5 || ^17.0.0 || ^18.0.0", react-dom@^18.2.0, react-dom@>=16.14.0:
2391
  version "18.2.0"
2392
  resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz"
2393
  integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
@@ -2448,7 +2560,7 @@ react-tooltip@*, react-tooltip@^5.25.1:
2448
  "@floating-ui/dom" "^1.0.0"
2449
  classnames "^2.3.0"
2450
 
2451
- "react@^16.11.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.3 || ^17 || ^18", "react@^16.8.5 || ^17.0.0 || ^18.0.0", react@^18.2.0, "react@>= 16.8.0", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", react@>=16.14.0, react@>=16.6.0:
2452
  version "18.2.0"
2453
  resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz"
2454
  integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
@@ -2834,6 +2946,11 @@ synckit@^0.8.4:
2834
  "@pkgr/utils" "^2.3.1"
2835
  tslib "^2.4.0"
2836
 
 
 
 
 
 
2837
  tailwindcss@^3.4.0:
2838
  version "3.4.0"
2839
  resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.0.tgz"
 
216
  tiny-glob "^0.2.9"
217
  tslib "^2.4.0"
218
 
219
+ "@radix-ui/primitive@1.1.3":
220
+ version "1.1.3"
221
+ resolved "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz"
222
+ integrity sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==
223
+
224
+ "@radix-ui/react-compose-refs@1.1.2":
225
+ version "1.1.2"
226
+ resolved "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz"
227
+ integrity sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==
228
+
229
+ "@radix-ui/react-context@1.1.2":
230
+ version "1.1.2"
231
+ resolved "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz"
232
+ integrity sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==
233
+
234
+ "@radix-ui/react-label@^2.1.8":
235
+ version "2.1.8"
236
+ resolved "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz"
237
+ integrity sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==
238
+ dependencies:
239
+ "@radix-ui/react-primitive" "2.1.4"
240
+
241
+ "@radix-ui/react-primitive@2.1.3":
242
+ version "2.1.3"
243
+ resolved "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz"
244
+ integrity sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==
245
+ dependencies:
246
+ "@radix-ui/react-slot" "1.2.3"
247
+
248
+ "@radix-ui/react-primitive@2.1.4":
249
+ version "2.1.4"
250
+ resolved "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz"
251
+ integrity sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==
252
+ dependencies:
253
+ "@radix-ui/react-slot" "1.2.4"
254
+
255
+ "@radix-ui/react-slot@^1.2.4", "@radix-ui/react-slot@1.2.4":
256
+ version "1.2.4"
257
+ resolved "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz"
258
+ integrity sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==
259
+ dependencies:
260
+ "@radix-ui/react-compose-refs" "1.1.2"
261
+
262
+ "@radix-ui/react-slot@1.2.3":
263
+ version "1.2.3"
264
+ resolved "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz"
265
+ integrity sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==
266
+ dependencies:
267
+ "@radix-ui/react-compose-refs" "1.1.2"
268
+
269
+ "@radix-ui/react-switch@^1.2.6":
270
+ version "1.2.6"
271
+ resolved "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz"
272
+ integrity sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==
273
+ dependencies:
274
+ "@radix-ui/primitive" "1.1.3"
275
+ "@radix-ui/react-compose-refs" "1.1.2"
276
+ "@radix-ui/react-context" "1.1.2"
277
+ "@radix-ui/react-primitive" "2.1.3"
278
+ "@radix-ui/react-use-controllable-state" "1.2.2"
279
+ "@radix-ui/react-use-previous" "1.1.1"
280
+ "@radix-ui/react-use-size" "1.1.1"
281
+
282
+ "@radix-ui/react-use-controllable-state@1.2.2":
283
+ version "1.2.2"
284
+ resolved "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz"
285
+ integrity sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==
286
+ dependencies:
287
+ "@radix-ui/react-use-effect-event" "0.0.2"
288
+ "@radix-ui/react-use-layout-effect" "1.1.1"
289
+
290
+ "@radix-ui/react-use-effect-event@0.0.2":
291
+ version "0.0.2"
292
+ resolved "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz"
293
+ integrity sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==
294
+ dependencies:
295
+ "@radix-ui/react-use-layout-effect" "1.1.1"
296
+
297
+ "@radix-ui/react-use-layout-effect@1.1.1":
298
+ version "1.1.1"
299
+ resolved "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz"
300
+ integrity sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==
301
+
302
+ "@radix-ui/react-use-previous@1.1.1":
303
+ version "1.1.1"
304
+ resolved "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz"
305
+ integrity sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==
306
+
307
+ "@radix-ui/react-use-size@1.1.1":
308
+ version "1.1.1"
309
+ resolved "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz"
310
+ integrity sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==
311
+ dependencies:
312
+ "@radix-ui/react-use-layout-effect" "1.1.1"
313
+
314
  "@rushstack/eslint-patch@^1.3.3":
315
  version "1.5.1"
316
  resolved "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.5.1.tgz"
 
448
  dependencies:
449
  "@types/react" "*"
450
 
451
+ "@types/react-dom@*", "@types/react-dom@^18.2.18":
452
  version "18.2.18"
453
  resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz"
454
  integrity sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==
 
846
  optionalDependencies:
847
  fsevents "~2.3.2"
848
 
849
+ class-variance-authority@^0.7.1:
850
+ version "0.7.1"
851
+ resolved "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz"
852
+ integrity sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==
853
+ dependencies:
854
+ clsx "^2.1.1"
855
+
856
  classnames@^2.3.0, classnames@^2.5.0:
857
  version "2.5.0"
858
  resolved "https://registry.npmjs.org/classnames/-/classnames-2.5.0.tgz"
 
863
  resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz"
864
  integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
865
 
866
+ clsx@^2.1.1:
867
+ version "2.1.1"
868
+ resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz"
869
+ integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
870
+
871
  color-convert@^2.0.1:
872
  version "2.0.1"
873
  resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz"
 
2094
  dependencies:
2095
  yallist "^4.0.0"
2096
 
2097
+ lucide-react@^0.561.0:
2098
+ version "0.561.0"
2099
+ resolved "https://registry.npmjs.org/lucide-react/-/lucide-react-0.561.0.tgz"
2100
+ integrity sha512-Y59gMY38tl4/i0qewcqohPdEbieBy7SovpBL9IFebhc2mDd8x4PZSOsiFRkpPcOq6bj1r/mjH/Rk73gSlIJP2A==
2101
+
2102
  make-error@^1.1.1:
2103
  version "1.3.6"
2104
  resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz"
 
2499
  redux "^4.0.4"
2500
  use-memo-one "^1.1.1"
2501
 
2502
+ "react-dom@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom@^16.8.5 || ^17.0.0 || ^18.0.0", react-dom@^18.2.0, react-dom@>=16.14.0:
2503
  version "18.2.0"
2504
  resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz"
2505
  integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
 
2560
  "@floating-ui/dom" "^1.0.0"
2561
  classnames "^2.3.0"
2562
 
2563
+ "react@^16.11.0 || ^17.0.0 || ^18.0.0", "react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.3 || ^17 || ^18", "react@^16.8.5 || ^17.0.0 || ^18.0.0", react@^18.2.0, "react@>= 16.8.0", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", react@>=16.14.0, react@>=16.6.0:
2564
  version "18.2.0"
2565
  resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz"
2566
  integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
 
2946
  "@pkgr/utils" "^2.3.1"
2947
  tslib "^2.4.0"
2948
 
2949
+ tailwind-merge@^3.4.0:
2950
+ version "3.4.0"
2951
+ resolved "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz"
2952
+ integrity sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==
2953
+
2954
  tailwindcss@^3.4.0:
2955
  version "3.4.0"
2956
  resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.0.tgz"