From f6d3f37a5faae947edc056b3e7735e32af2d5376 Mon Sep 17 00:00:00 2001 From: joylessorchid Date: Tue, 24 Mar 2026 12:14:49 +0300 Subject: [PATCH] feat: dark design system + full UI redesign + HTTPS for local dev - Design system: CSS custom properties (surface levels, accent, status colors, scrollbar, focus-visible) - Landing: hero with gradient title, feature cards with SVG icons - Auth pages: consistent card layout with design tokens - Dashboard: sticky top bar, room grid with status dots, empty state - Create room: toggle switches, form validation with spinners - Join flow: room info card with status badge, monospace code input - Room page: top bar with sidebar toggles (chat/lobby/moderation) - Chat: bubble messages with optimistic UI, empty state - Moderation: participant list with avatar initials, kick/ban - Lobby: waiting animation with pulsing rings, approve/reject - HTTPS: dev:https script, setup.sh auto-configures BETTER_AUTH_URL - Auth: trustedOrigins now includes both http:// and https:// --- package.json | 1 + setup.sh | 15 +- src/app/(dashboard)/dashboard/create/page.tsx | 197 +++++++++++------- src/app/(dashboard)/dashboard/page.tsx | 170 ++++++++++----- src/app/globals.css | 87 ++++++++ src/app/join/[code]/page.tsx | 74 +++++-- src/app/join/page.tsx | 47 +++-- src/app/layout.tsx | 4 +- src/app/login/page.tsx | 30 ++- src/app/page.tsx | 106 ++++++++-- src/app/register/page.tsx | 37 ++-- src/app/room/[code]/page.tsx | 100 +++++++-- src/components/lobby/LobbyManager.tsx | 30 ++- src/components/lobby/WaitingRoom.tsx | 32 +-- src/components/room/ChatPanel.tsx | 75 +++++-- src/components/room/ModerationPanel.tsx | 74 ++++--- src/lib/auth.ts | 1 + 17 files changed, 766 insertions(+), 314 deletions(-) diff --git a/package.json b/package.json index dbafe7d..2059c85 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "Livekit core - Server for VideoHosting", "scripts": { "dev": "next dev", + "dev:https": "next dev --experimental-https", "build": "next build", "start": "next start", "lint": "tsc --noEmit", diff --git a/setup.sh b/setup.sh index f2e3be3..e720671 100644 --- a/setup.sh +++ b/setup.sh @@ -1479,10 +1479,13 @@ OVERRIDE [[ -d ".next" ]] && rm -rf .next log_ok "Кеш очищен" - # Автоопределение BETTER_AUTH_URL (если не задан) - if ! grep -q "^BETTER_AUTH_URL=" .env 2>/dev/null; then - echo "BETTER_AUTH_URL=http://localhost:3000" >> .env - log_ok "BETTER_AUTH_URL=http://localhost:3000 добавлен в .env" + # Автоопределение BETTER_AUTH_URL (https для dev) + if grep -q "^BETTER_AUTH_URL=" .env 2>/dev/null; then + # Обновить на https если ещё http + sed -i 's|^BETTER_AUTH_URL=http://|BETTER_AUTH_URL=https://|' .env + else + echo "BETTER_AUTH_URL=https://localhost:3000" >> .env + log_ok "BETTER_AUTH_URL=https://localhost:3000 добавлен в .env" fi # Проверка порта 3000 @@ -1492,9 +1495,9 @@ OVERRIDE # Запуск echo "" - log_ok "${GREEN}${BOLD}Готово! Запускаю dev-сервер на порту 3000...${NC}" + log_ok "${GREEN}${BOLD}Готово! Запускаю dev-сервер (HTTPS) на порту 3000...${NC}" echo "" - exec npm run dev + exec npm run dev:https } # ============================================================ diff --git a/src/app/(dashboard)/dashboard/create/page.tsx b/src/app/(dashboard)/dashboard/create/page.tsx index 26c2b8c..157f227 100644 --- a/src/app/(dashboard)/dashboard/create/page.tsx +++ b/src/app/(dashboard)/dashboard/create/page.tsx @@ -45,94 +45,139 @@ export default function CreateRoomPage() { } return ( -
-
+
+ {/* Top bar */} +
+
+ +
+ + + + +
+ LiveServer + +
+
+ + {/* Form */} +
- ← Назад к комнатам + + + + + Назад к комнатам -

Создать комнату

+
+

Создать комнату

-
- {error && ( -
- {error} + + {error && ( +
+ {error} +
+ )} + +
+ + setName(e.target.value)} + className="w-full px-4 py-3 bg-surface-2 border border-border-default rounded-xl text-white placeholder:text-text-muted focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent transition-colors" + placeholder="Лекция по математике" + />
- )} -
- - setName(e.target.value)} - className="w-full px-4 py-2.5 bg-neutral-800 border border-neutral-700 rounded-lg focus:outline-none focus:border-blue-600 transition-colors" - placeholder="Лекция по математике" - /> -
- -
- - setPin(e.target.value)} - className="w-full px-4 py-2.5 bg-neutral-800 border border-neutral-700 rounded-lg focus:outline-none focus:border-blue-600 transition-colors" - placeholder="1234" - maxLength={8} - /> -
- -
-
+
+ {/* Lobby toggle */} +
+
+

Зал ожидания

+

Гости ждут одобрения хоста

+
+ +
- - + {/* Webinar mode toggle */} +
+
+

Режим вебинара

+

Только хост может говорить

+
+ +
+
+ + + +
); diff --git a/src/app/(dashboard)/dashboard/page.tsx b/src/app/(dashboard)/dashboard/page.tsx index fc4feea..72d0af1 100644 --- a/src/app/(dashboard)/dashboard/page.tsx +++ b/src/app/(dashboard)/dashboard/page.tsx @@ -19,10 +19,16 @@ const statusLabels: Record = { ENDED: "Завершена", }; -const statusColors: Record = { - WAITING: "bg-yellow-600/20 text-yellow-400", - ACTIVE: "bg-green-600/20 text-green-400", - ENDED: "bg-neutral-600/20 text-neutral-400", +const statusDotColors: Record = { + WAITING: "bg-warning", + ACTIVE: "bg-success", + ENDED: "bg-zinc-500", +}; + +const statusTextColors: Record = { + WAITING: "text-warning", + ACTIVE: "text-success", + ENDED: "text-zinc-500", }; export default function DashboardPage() { @@ -59,72 +65,126 @@ export default function DashboardPage() { if (isPending) { return ( -
-

Загрузка...

+
+
+
+

Загрузка...

+
); } return ( -
-
-

Мои комнаты

-
+
+ {/* Top bar */} +
+
+ +
+ + + + +
+ LiveServer + + +
+
+
+ {(session?.user?.name?.[0] || session?.user?.email?.[0] || "U").toUpperCase()} +
+ + {session?.user?.name || session?.user?.email} + +
+ +
+
+
+ + {/* Main content */} +
+
+
+

Мои комнаты

+ {!loading && rooms.length > 0 && ( +

{rooms.length} {rooms.length === 1 ? "комната" : rooms.length < 5 ? "комнаты" : "комнат"}

+ )} +
+ + Создать комнату -
-
- {loading ? ( -

Загрузка комнат...

- ) : rooms.length === 0 ? ( -
-

У вас пока нет комнат

- - Создать первую комнату - -
- ) : ( -
- {rooms.map((room) => ( + {loading ? ( +
+
+
+

Загрузка комнат...

+
+
+ ) : rooms.length === 0 ? ( +
+
+ + + + +
+

У вас пока нет комнат

+

Создайте первую комнату для видеоконференции

-
-

{room.name}

- - {statusLabels[room.status]} - -
-
- - {room.code} - - - {new Date(room.createdAt).toLocaleDateString("ru-RU")} - -
+ + + Создать комнату - ))} -
- )} +
+ ) : ( +
+ {rooms.map((room) => ( + +
+

+ {room.name} +

+
+ + {statusLabels[room.status]} +
+
+
+ + {room.code} + + + {new Date(room.createdAt).toLocaleDateString("ru-RU", { + day: "numeric", + month: "short", + year: "numeric", + })} + +
+ + ))} +
+ )} +
); } diff --git a/src/app/globals.css b/src/app/globals.css index f1d8c73..274ee52 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1 +1,88 @@ @import "tailwindcss"; + +/* ── Design tokens ── */ +@theme { + --color-surface-0: #0f0f0f; + --color-surface-1: #1a1a1a; + --color-surface-2: #252525; + --color-surface-3: #2f2f2f; + + --color-accent: #6366f1; + --color-accent-hover: #818cf8; + + --color-success: #22c55e; + --color-warning: #f59e0b; + --color-danger: #ef4444; + + --color-text-primary: #f5f5f5; + --color-text-secondary: #a3a3a3; + --color-text-muted: #636363; + + --color-border-subtle: #ffffff0d; + --color-border-default: #ffffff1a; + + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-full: 9999px; + + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4), 0 2px 4px -2px rgb(0 0 0 / 0.3); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.5), 0 4px 6px -4px rgb(0 0 0 / 0.4); + --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.5), 0 8px 10px -6px rgb(0 0 0 / 0.4); +} + +/* ── Base ── */ +body { + background-color: var(--color-surface-0); + color: var(--color-text-primary); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* ── Focus ── */ +*:focus-visible { + outline: 2px solid var(--color-accent); + outline-offset: 2px; + border-radius: var(--radius-sm); +} + +/* ── Transitions ── */ +button, +a, +input, +select, +textarea { + transition: background-color 150ms ease, color 150ms ease, border-color 150ms ease, + box-shadow 150ms ease, opacity 150ms ease; +} + +/* ── Scrollbar ── */ +* { + scrollbar-width: thin; + scrollbar-color: var(--color-surface-3) transparent; +} + +*::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +*::-webkit-scrollbar-track { + background: transparent; +} + +*::-webkit-scrollbar-thumb { + background-color: var(--color-surface-3); + border-radius: var(--radius-full); +} + +*::-webkit-scrollbar-thumb:hover { + background-color: var(--color-text-muted); +} + +/* ── Selection ── */ +::selection { + background-color: var(--color-accent); + color: white; +} diff --git a/src/app/join/[code]/page.tsx b/src/app/join/[code]/page.tsx index 8af75ad..5c5affa 100644 --- a/src/app/join/[code]/page.tsx +++ b/src/app/join/[code]/page.tsx @@ -110,8 +110,11 @@ export default function JoinCodePage() { if (loading) { return ( -
-

Загрузка...

+
+
+
+

Загрузка...

+
); } @@ -126,25 +129,44 @@ export default function JoinCodePage() { ); } + const statusLabel: Record = { + active: { text: "Активна", color: "bg-success/10 text-success border-success/20" }, + waiting: { text: "Ожидание", color: "bg-warning/10 text-warning border-warning/20" }, + finished: { text: "Завершена", color: "bg-surface-3/50 text-text-muted border-border-default" }, + }; + return ( -
-
+
+
{room ? ( <> -

{room.name}

-

- Код: {room.code} -

+ {/* Room info header */} +
+
+

{room.name}

+ {statusLabel[room.status] && ( + + {statusLabel[room.status].text} + + )} +
+
+ + + + {room.code} +
+
{error && ( -
+
{error}
)}
-
{room.hasPin && (
-