aboutsummaryrefslogtreecommitdiffstats
path: root/frontend/src/App.tsx
blob: 478444cecba69738bbae986e9738418148866ed8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import React, { useEffect, useState } from 'react';
import { BrowserRouter, Routes, Route, Navigate, useLocation } from 'react-router-dom';
import Login from './components/Login';
import './App.css';
import { apiFetch } from './utils';

// Protected Route wrapper
function RequireAuth({ children }: { children: React.ReactElement }) {
  const [auth, setAuth] = useState<boolean | null>(null);
  const location = useLocation();

  useEffect(() => {
    apiFetch('/api/auth')
      .then((res) => {
        if (res.ok) {
          setAuth(true);
        } else {
          setAuth(false);
        }
      })
      .catch(() => setAuth(false));
  }, []);

  if (auth === null) {
    return <div>Loading...</div>;
  }

  if (!auth) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return children;
}

import FeedList from './components/FeedList';
import FeedItems from './components/FeedItems';
import Settings from './components/Settings';

interface DashboardProps {
  theme: string;
  setTheme: (t: string) => void;
  fontTheme: string;
  setFontTheme: (t: string) => void;
}

function Dashboard({ theme, setTheme, fontTheme, setFontTheme }: DashboardProps) {
  const [sidebarVisible, setSidebarVisible] = useState(window.innerWidth > 768);

  useEffect(() => {
    const handleResize = () => {
      if (window.innerWidth > 768) {
        setSidebarVisible(true);
      } else {
        setSidebarVisible(false);
      }
    };
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return (
    <div
      className={`dashboard ${sidebarVisible ? 'sidebar-visible' : 'sidebar-hidden'} theme-${theme} font-${fontTheme}`}
    >
      <div className="dashboard-content">
        {(!sidebarVisible || window.innerWidth <= 768) && (
          <button
            className="sidebar-toggle fixed-toggle"
            onClick={() => setSidebarVisible(!sidebarVisible)}
            title={sidebarVisible ? "Hide Sidebar" : "Show Sidebar"}
          >
            🐱
          </button>
        )}
        {sidebarVisible && (
          <div
            className="sidebar-backdrop"
            onClick={() => setSidebarVisible(false)}
          />
        )}
        <aside className={`dashboard-sidebar ${sidebarVisible ? '' : 'hidden'}`}>
          <FeedList
            theme={theme}
            setTheme={setTheme}
            setSidebarVisible={setSidebarVisible}
            isMobile={window.innerWidth <= 768}
          />
        </aside>
        <main className="dashboard-main">
          <Routes>
            <Route path="/feed/:feedId" element={<FeedItems />} />
            <Route path="/tag/:tagName" element={<FeedItems />} />
            <Route path="/settings" element={<Settings fontTheme={fontTheme} setFontTheme={setFontTheme} />} />
            <Route path="/" element={<FeedItems />} />
          </Routes>
        </main>
      </div>
    </div>
  );
}

function App() {
  const [theme, setTheme] = useState(localStorage.getItem('neko-theme') || 'light');
  const [fontTheme, setFontTheme] = useState(localStorage.getItem('neko-font-theme') || 'default');

  const handleSetTheme = (newTheme: string) => {
    setTheme(newTheme);
    localStorage.setItem('neko-theme', newTheme);
  };

  const handleSetFontTheme = (newFontTheme: string) => {
    setFontTheme(newFontTheme);
    localStorage.setItem('neko-font-theme', newFontTheme);
  };

  const basename = window.location.pathname.startsWith('/v2') ? '/v2' : '/';

  return (
    <BrowserRouter basename={basename}>
      <Routes>
        <Route path="/login" element={<Login />} />
        <Route
          path="/*"
          element={
            <RequireAuth>
              <Dashboard
                theme={theme}
                setTheme={handleSetTheme}
                fontTheme={fontTheme}
                setFontTheme={handleSetFontTheme}
              />
            </RequireAuth>
          }
        />
      </Routes>
    </BrowserRouter>
  );
}

export default App;