Ruperth commited on
Commit
5d39332
·
1 Parent(s): 9990888

feat: rebuild Layout with collapsible sidebar topbar toggles and Supabase link

Browse files

The shell now mirrors YouTube more closely with a sectioned sidebar, a search field, language and theme switches in the topbar, and a hamburger that collapses the sidebar on desktop or opens it as a drawer on mobile. Adds a Database entry that points to the Supabase editor so the team can jump straight to the data.

frontend/src/App.tsx CHANGED
@@ -1,22 +1,28 @@
1
  import { BrowserRouter, Route, Routes } from "react-router-dom";
2
  import { Layout } from "./components/Layout";
3
  import { AppProvider } from "./context/AppContext";
 
 
4
  import { HubPage } from "./pages/HubPage";
5
  import { SettingsPage } from "./pages/SettingsPage";
6
  import { WatchPage } from "./pages/WatchPage";
7
 
8
  export default function App() {
9
  return (
10
- <AppProvider>
11
- <BrowserRouter>
12
- <Routes>
13
- <Route element={<Layout />}>
14
- <Route index element={<WatchPage />} />
15
- <Route path="hub" element={<HubPage />} />
16
- <Route path="settings" element={<SettingsPage />} />
17
- </Route>
18
- </Routes>
19
- </BrowserRouter>
20
- </AppProvider>
 
 
 
 
21
  );
22
  }
 
1
  import { BrowserRouter, Route, Routes } from "react-router-dom";
2
  import { Layout } from "./components/Layout";
3
  import { AppProvider } from "./context/AppContext";
4
+ import { ThemeProvider } from "./context/ThemeContext";
5
+ import { I18nProvider } from "./i18n/I18nContext";
6
  import { HubPage } from "./pages/HubPage";
7
  import { SettingsPage } from "./pages/SettingsPage";
8
  import { WatchPage } from "./pages/WatchPage";
9
 
10
  export default function App() {
11
  return (
12
+ <ThemeProvider>
13
+ <I18nProvider>
14
+ <AppProvider>
15
+ <BrowserRouter>
16
+ <Routes>
17
+ <Route element={<Layout />}>
18
+ <Route index element={<WatchPage />} />
19
+ <Route path="hub" element={<HubPage />} />
20
+ <Route path="settings" element={<SettingsPage />} />
21
+ </Route>
22
+ </Routes>
23
+ </BrowserRouter>
24
+ </AppProvider>
25
+ </I18nProvider>
26
+ </ThemeProvider>
27
  );
28
  }
frontend/src/components/Layout.tsx CHANGED
@@ -1,25 +1,173 @@
 
1
  import { NavLink, Outlet } from "react-router-dom";
2
- import { ModelBanner } from "./ModelBanner";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
  export function Layout() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  return (
6
- <div className="app-shell">
7
- <nav className="sidebar">
8
- <div className="logo">
9
- <img src="/signalmod_logo.png" alt="SignalMod" className="logo-img" />
10
- </div>
11
- <NavLink to="/" end className={({ isActive }) => (isActive ? "nav active" : "nav")}>
12
- Watch
13
- </NavLink>
14
- <NavLink to="/hub" className={({ isActive }) => (isActive ? "nav active" : "nav")}>
15
- Moderator Hub
16
- </NavLink>
17
- <NavLink to="/settings" className={({ isActive }) => (isActive ? "nav active" : "nav")}>
18
- Settings
19
  </NavLink>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  </nav>
 
 
 
 
 
21
  <main className="main-content">
22
- {/* <ModelBanner /> */}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  <Outlet />
24
  </main>
25
  </div>
 
1
+ import { useState } from "react";
2
  import { NavLink, Outlet } from "react-router-dom";
3
+ import { useI18n } from "../i18n/I18nContext";
4
+ import { useTheme } from "../context/ThemeContext";
5
+ import {
6
+ DatabaseIcon,
7
+ GlobeIcon,
8
+ HistoryIcon,
9
+ HomeIcon,
10
+ HubIcon,
11
+ LibraryIcon,
12
+ MenuIcon,
13
+ MoonIcon,
14
+ SearchIcon,
15
+ SettingsIcon,
16
+ ShieldIcon,
17
+ ShortsIcon,
18
+ SubsIcon,
19
+ SunIcon,
20
+ WatchIcon,
21
+ YourVideosIcon,
22
+ } from "./icons";
23
+
24
+ const DATABASE_URL =
25
+ "https://supabase.com/dashboard/project/kxzxqlhdzkpcanotdbgl/editor/17547?schema=public&sort=created_at%3Adesc";
26
 
27
  export function Layout() {
28
+ const { t, toggleLocale } = useI18n();
29
+ const { theme, toggleTheme } = useTheme();
30
+ const [sidebarOpen, setSidebarOpen] = useState(false);
31
+ const [collapsed, setCollapsed] = useState(false);
32
+
33
+ const navLinkClass = ({ isActive }: { isActive: boolean }) =>
34
+ isActive ? "nav-item active" : "nav-item";
35
+
36
+ const closeMobile = () => setSidebarOpen(false);
37
+
38
+ const sidebarClass = [
39
+ "sidebar",
40
+ sidebarOpen ? "open" : "",
41
+ collapsed ? "collapsed" : "",
42
+ ]
43
+ .filter(Boolean)
44
+ .join(" ");
45
+
46
  return (
47
+ <div className={collapsed ? "app-shell collapsed-shell" : "app-shell"}>
48
+ <nav className={sidebarClass}>
49
+ <NavLink to="/" className="brand" onClick={closeMobile} aria-label="SignalMod">
50
+ <img src="/signalmod_logo.png" alt="SignalMod" className="brand-logo" />
 
 
 
 
 
 
 
 
 
51
  </NavLink>
52
+
53
+ <div className="sidebar-section">
54
+ <NavLink to="/" end className={navLinkClass} onClick={closeMobile} title={t.nav.home}>
55
+ <span className="nav-icon"><HomeIcon /></span>
56
+ <span className="nav-label">{t.nav.home}</span>
57
+ </NavLink>
58
+ <button className="nav-item" type="button" disabled title={t.nav.shorts}>
59
+ <span className="nav-icon"><ShortsIcon /></span>
60
+ <span className="nav-label">{t.nav.shorts}</span>
61
+ </button>
62
+ <button className="nav-item" type="button" disabled title={t.nav.subscriptions}>
63
+ <span className="nav-icon"><SubsIcon /></span>
64
+ <span className="nav-label">{t.nav.subscriptions}</span>
65
+ </button>
66
+ </div>
67
+
68
+ <div className="sidebar-section">
69
+ <button className="nav-item" type="button" title={t.nav.library}>
70
+ <span className="nav-icon"><LibraryIcon /></span>
71
+ <span className="nav-label">{t.nav.library}</span>
72
+ </button>
73
+ <button className="nav-item" type="button" title={t.nav.history}>
74
+ <span className="nav-icon"><HistoryIcon /></span>
75
+ <span className="nav-label">{t.nav.history}</span>
76
+ </button>
77
+ <button className="nav-item" type="button" title={t.nav.yourVideos}>
78
+ <span className="nav-icon"><YourVideosIcon /></span>
79
+ <span className="nav-label">{t.nav.yourVideos}</span>
80
+ </button>
81
+ </div>
82
+
83
+ <div className="sidebar-section">
84
+ <h3 className="sidebar-heading">
85
+ <span className="nav-icon"><ShieldIcon /></span>
86
+ <span className="nav-label">{t.nav.moderation}</span>
87
+ </h3>
88
+ <NavLink to="/" end className={navLinkClass} onClick={closeMobile} title={t.nav.watch}>
89
+ <span className="nav-icon"><WatchIcon /></span>
90
+ <span className="nav-label">{t.nav.watch}</span>
91
+ </NavLink>
92
+ <NavLink to="/hub" className={navLinkClass} onClick={closeMobile} title={t.nav.hub}>
93
+ <span className="nav-icon"><HubIcon /></span>
94
+ <span className="nav-label">{t.nav.hub}</span>
95
+ </NavLink>
96
+ <NavLink to="/settings" className={navLinkClass} onClick={closeMobile} title={t.nav.settings}>
97
+ <span className="nav-icon"><SettingsIcon /></span>
98
+ <span className="nav-label">{t.nav.settings}</span>
99
+ </NavLink>
100
+ <a
101
+ className="nav-item"
102
+ href={DATABASE_URL}
103
+ target="_blank"
104
+ rel="noopener noreferrer"
105
+ onClick={closeMobile}
106
+ title={t.nav.database}
107
+ >
108
+ <span className="nav-icon"><DatabaseIcon /></span>
109
+ <span className="nav-label">{t.nav.database}</span>
110
+ </a>
111
+ </div>
112
  </nav>
113
+
114
+ {sidebarOpen && (
115
+ <div className="sidebar-backdrop" onClick={closeMobile} />
116
+ )}
117
+
118
  <main className="main-content">
119
+ <header className="topbar">
120
+ <div className="topbar-left">
121
+ <button
122
+ className="icon-btn"
123
+ type="button"
124
+ onClick={() => {
125
+ if (window.matchMedia("(max-width: 820px)").matches) {
126
+ setSidebarOpen((v) => !v);
127
+ } else {
128
+ setCollapsed((v) => !v);
129
+ }
130
+ }}
131
+ aria-label={t.topbar.toggleSidebar}
132
+ title={t.topbar.toggleSidebar}
133
+ >
134
+ <MenuIcon />
135
+ </button>
136
+ </div>
137
+
138
+ <div className="topbar-search">
139
+ <SearchIcon />
140
+ <input
141
+ placeholder={t.topbar.searchPlaceholder}
142
+ aria-label={t.topbar.searchPlaceholder}
143
+ />
144
+ </div>
145
+
146
+ <div className="topbar-right">
147
+ <button
148
+ className="lang-toggle"
149
+ type="button"
150
+ onClick={toggleLocale}
151
+ title={t.topbar.toggleLanguage}
152
+ aria-label={t.topbar.toggleLanguage}
153
+ >
154
+ <GlobeIcon /> {t.topbar.languageLabel}
155
+ </button>
156
+ <button
157
+ className="theme-toggle"
158
+ type="button"
159
+ onClick={toggleTheme}
160
+ title={t.topbar.toggleTheme}
161
+ aria-label={t.topbar.toggleTheme}
162
+ >
163
+ {theme === "dark" ? <SunIcon /> : <MoonIcon />}
164
+ </button>
165
+ <button className="sign-in" type="button">
166
+ {t.topbar.signIn}
167
+ </button>
168
+ </div>
169
+ </header>
170
+
171
  <Outlet />
172
  </main>
173
  </div>