From a2d8a1e08075467ee6d516f69ab8a3a53fa222be Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Mon, 23 Jun 2025 22:21:48 +0300 Subject: [PATCH] Connect auth to backend and persist theme preference --- .../PaymentForm.js | 0 .../src/app/components/ClientLayoutEffect.js | 21 ++++++ webui/src/app/components/ImprinkAppBar.js | 66 +++++++++++++++++++ .../src/app/components/theme/ThemeContext.js | 46 ++++++++++++- .../{ => theme}/ThemeToggleButton.js | 2 +- webui/src/app/layout.js | 4 ++ webui/src/app/page.js | 16 +---- webui/src/app/token/route.js | 10 +-- webui/src/lib/api.js | 22 ------- webui/src/lib/clientApi.js | 31 +++++++++ webui/src/lib/serverApi.js | 8 +++ 11 files changed, 181 insertions(+), 45 deletions(-) rename webui/{src/app/components => auth0-templates}/PaymentForm.js (100%) create mode 100644 webui/src/app/components/ClientLayoutEffect.js create mode 100644 webui/src/app/components/ImprinkAppBar.js rename webui/src/app/components/{ => theme}/ThemeToggleButton.js (92%) delete mode 100644 webui/src/lib/api.js create mode 100644 webui/src/lib/clientApi.js create mode 100644 webui/src/lib/serverApi.js diff --git a/webui/src/app/components/PaymentForm.js b/webui/auth0-templates/PaymentForm.js similarity index 100% rename from webui/src/app/components/PaymentForm.js rename to webui/auth0-templates/PaymentForm.js diff --git a/webui/src/app/components/ClientLayoutEffect.js b/webui/src/app/components/ClientLayoutEffect.js new file mode 100644 index 0000000..9f52a2a --- /dev/null +++ b/webui/src/app/components/ClientLayoutEffect.js @@ -0,0 +1,21 @@ +'use client'; + +import { useEffect } from 'react'; +import axios from 'axios'; + +export default function ClientLayoutEffect() { + useEffect(() => { + async function fetchData() { + try { + const res = await axios.get('/token'); + console.log('Token response:', res.data); + } catch (error) { + console.error('Token fetch error:', error); + } + } + + fetchData().then(r => console.log("Ok")); + }, []); + + return null; +} \ No newline at end of file diff --git a/webui/src/app/components/ImprinkAppBar.js b/webui/src/app/components/ImprinkAppBar.js new file mode 100644 index 0000000..45587fd --- /dev/null +++ b/webui/src/app/components/ImprinkAppBar.js @@ -0,0 +1,66 @@ +'use client' + +import {AppBar, Button, Toolbar, Typography, Avatar, Box} from "@mui/material"; +import { useUser } from "@auth0/nextjs-auth0"; +import ThemeToggleButton from "@/app/components/theme/ThemeToggleButton"; + +export default function ImprinkAppBar() { + const { user, error, isLoading } = useUser(); + + return ( + + + + Modern App + + + + {isLoading ? ( + + Loading... + + ) : user ? ( + + + + {user.name} + + + + ) : ( + + + + + )} + + + ) +} \ No newline at end of file diff --git a/webui/src/app/components/theme/ThemeContext.js b/webui/src/app/components/theme/ThemeContext.js index ae25725..55017ed 100644 --- a/webui/src/app/components/theme/ThemeContext.js +++ b/webui/src/app/components/theme/ThemeContext.js @@ -1,16 +1,58 @@ 'use client'; -import { createContext, useContext, useState } from 'react'; +import { createContext, useContext, useState, useEffect } from 'react'; const ThemeContext = createContext({ isDarkMode: true }); +function getInitialTheme() { + if (typeof window === 'undefined') { + return null; + } + + const savedTheme = localStorage.getItem('theme-preference'); + if (savedTheme) { + return savedTheme === 'dark'; + } + + return window.matchMedia('(prefers-color-scheme: dark)').matches; +} + export function ThemeContextProvider({ children }) { const [isDarkMode, setIsDarkMode] = useState(true); + const [isInitialized, setIsInitialized] = useState(false); + + useEffect(() => { + const initialTheme = getInitialTheme(); + if (initialTheme !== null) { + setIsDarkMode(initialTheme); + } + setIsInitialized(true); + }, []); const toggleTheme = () => { - setIsDarkMode(!isDarkMode); + const newTheme = !isDarkMode; + setIsDarkMode(newTheme); + + if (typeof window !== 'undefined') { + localStorage.setItem('theme-preference', newTheme ? 'dark' : 'light'); + } }; + if (!isInitialized) { + return ( +
+ {children} +
+ ); + } + return ( {children} diff --git a/webui/src/app/components/ThemeToggleButton.js b/webui/src/app/components/theme/ThemeToggleButton.js similarity index 92% rename from webui/src/app/components/ThemeToggleButton.js rename to webui/src/app/components/theme/ThemeToggleButton.js index 54f0f8d..c228443 100644 --- a/webui/src/app/components/ThemeToggleButton.js +++ b/webui/src/app/components/theme/ThemeToggleButton.js @@ -1,7 +1,7 @@ 'use client'; import { IconButton } from '@mui/material'; -import { useTheme } from './theme/ThemeContext'; +import { useTheme } from './ThemeContext'; export default function ThemeToggleButton() { const { isDarkMode, toggleTheme } = useTheme(); diff --git a/webui/src/app/layout.js b/webui/src/app/layout.js index 3e9fc1e..1ee1f4a 100644 --- a/webui/src/app/layout.js +++ b/webui/src/app/layout.js @@ -2,6 +2,8 @@ import { Inter } from 'next/font/google'; import MuiThemeProvider from './components/theme/MuiThemeProvider'; import { ThemeContextProvider } from './components/theme/ThemeContext'; import { AppRouterCacheProvider } from "@mui/material-nextjs/v13-appRouter"; +import ImprinkAppBar from "@/app/components/ImprinkAppBar"; +import ClientLayoutEffect from "@/app/components/ClientLayoutEffect"; const inter = Inter({ subsets: ['latin'] }); @@ -17,6 +19,8 @@ export default function RootLayout({children}) { + + {children} diff --git a/webui/src/app/page.js b/webui/src/app/page.js index e3f550b..661b9d1 100644 --- a/webui/src/app/page.js +++ b/webui/src/app/page.js @@ -9,25 +9,12 @@ import { CardContent, TextField, Chip, - AppBar, - Toolbar, Grid, } from '@mui/material'; -import ThemeToggleButton from './components/ThemeToggleButton'; export default function HomePage() { - return ( - <> - - - - Modern App - - - - - + return ( @@ -100,6 +87,5 @@ export default function HomePage() { - ); } \ No newline at end of file diff --git a/webui/src/app/token/route.js b/webui/src/app/token/route.js index a3fad83..9ffd1a3 100644 --- a/webui/src/app/token/route.js +++ b/webui/src/app/token/route.js @@ -1,6 +1,6 @@ import { NextResponse } from 'next/server'; import {auth0} from "@/lib/auth0"; -import api from "@/lib/api"; +import serverApi from "@/lib/serverApi"; export async function GET() { try { @@ -8,13 +8,13 @@ export async function GET() { if (!token) { return NextResponse.json({ error: 'No access token found' }, { status: 401 }); } - await api.post('/users/sync', {}, { - headers: { Cookie: `access_token=${token}` } + await serverApi.post('/users/me/sync', null, { + headers: { Authorization: `Bearer ${token}`} }); - return NextResponse.json({ access_token: token }); + return NextResponse.json("Ok"); } catch (error) { - console.error('Error in /api/token:', error); + console.error('Error in /serverApi/token:', error); return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); } } \ No newline at end of file diff --git a/webui/src/lib/api.js b/webui/src/lib/api.js deleted file mode 100644 index 8cf8595..0000000 --- a/webui/src/lib/api.js +++ /dev/null @@ -1,22 +0,0 @@ -import axios from "axios"; - -const api = axios.create({ - baseURL: process.env.NEXT_PUBLIC_API_URL, - withCredentials: true, -}); - -api.interceptors.request.use((config) => { - if (typeof window !== 'undefined') { - const token = localStorage.getItem('access_token'); - if (token) { - config.headers.Authorization = `Bearer ${token}`; - } else { - console.log("Token not found in localStorage, please auth!"); - } - } - return config; -}, (error) => { - return Promise.reject(error); -}); - -export default api; \ No newline at end of file diff --git a/webui/src/lib/clientApi.js b/webui/src/lib/clientApi.js new file mode 100644 index 0000000..de600a8 --- /dev/null +++ b/webui/src/lib/clientApi.js @@ -0,0 +1,31 @@ +import axios from "axios"; + +const clientApi = axios.create({ + baseURL: process.env.NEXT_PUBLIC_API_URL, + withCredentials: true, +}); + +clientApi.interceptors.request.use(async (config) => { + if (typeof window === 'undefined') return config; + + try { + const res = await fetch('/auth/access-token'); + if (!res.ok) + throw new Error('Failed to fetch token'); + const data = await res.json(); + + if (data.token) { + config.headers.Authorization = `Bearer ${data.token}`; + } else { + console.warn('No token received from /auth/access-token'); + } + } catch (err) { + console.error('Error fetching token:', err); + } + + return config; +}, error => { + return Promise.reject(error); +}); + +export default clientApi; \ No newline at end of file diff --git a/webui/src/lib/serverApi.js b/webui/src/lib/serverApi.js new file mode 100644 index 0000000..5371f2f --- /dev/null +++ b/webui/src/lib/serverApi.js @@ -0,0 +1,8 @@ +import axios from "axios"; + +const serverApi = axios.create({ + baseURL: process.env.NEXT_PUBLIC_API_URL, + withCredentials: true, +}); + +export default serverApi; \ No newline at end of file