diff --git a/src/components/Login.jsx b/src/components/Login.jsx index 374e682..ba46f84 100644 --- a/src/components/Login.jsx +++ b/src/components/Login.jsx @@ -2,6 +2,17 @@ import React, { useState, useRef, useEffect } from "react"; import { toast } from "react-toastify"; import { API_URL } from "@/config"; +function getCookie(name) { + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) return decodeURIComponent(parts.pop().split(';').shift()); + return null; +} + +function clearCookie(name) { + document.cookie = `${name}=; Max-Age=0; path=/;`; +} + export default function LoginPage() { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); @@ -20,32 +31,24 @@ export default function LoginPage() { setLoading(true); try { - if (!username) { + if (!username || !password) { setLoading(false); - if (!toast.isActive("login-username-required-toast")) { - return toast.error("Username and password are required", - { - toastId: "login-missing-data-toast", - }); - } - } - if (!password) { - setLoading(false); - if (!toast.isActive("login-password-required-toast")) { - return toast.error("Username and password are required", - { - toastId: "login-missing-data-toast", - }); + if (!toast.isActive("login-missing-data-toast")) { + toast.error("Username and password are required", { + toastId: "login-missing-data-toast", + }); } + return; } - const formData = new URLSearchParams(); - formData.append("username", username); - formData.append("password", password); - formData.append("grant_type", "password"); - formData.append("scope", ""); - formData.append("client_id", ""); - formData.append("client_secret", ""); + const formData = new URLSearchParams({ + username, + password, + grant_type: "password", + scope: "", + client_id: "", + client_secret: "", + }); const resp = await fetch(`${API_URL}/auth/login`, { method: "POST", @@ -55,52 +58,40 @@ export default function LoginPage() { }); if (resp.status === 401) { - if (!toast.isActive("login-error-invalid-toast")) { - toast.error("Invalid username or password", { - toastId: "login-error-invalid-toast", - }); - } + toast.error("Invalid username or password", { + toastId: "login-error-invalid-toast", + }); setLoading(false); return; } if (!resp.ok) { const data = await resp.json().catch(() => ({})); - if (!toast.isActive("login-error-failed-toast")) { - toast.error(data.detail ? `Login failed: ${data.detail}` : "Login failed", - { - toastId: "login-error-failed-toast", - }); - } + toast.error(data.detail ? `Login failed: ${data.detail}` : "Login failed", { + toastId: "login-error-failed-toast", + }); setLoading(false); return; } const data = await resp.json(); if (data.access_token) { - if (!toast.isActive("login-success-toast")) { - toast.success("Login successful!", - { - toastId: "login-success-toast", - }); - } - window.location.href = "/TRip"; // TODO: fix, hardcoded + toast.success("Login successful!", { + toastId: "login-success-toast", + }); + const returnTo = getCookie("returnTo") || "/TRip"; + clearCookie("returnTo"); + window.location.href = returnTo; } else { - if (!toast.isActive("login-error-no-token-toast")) { - toast.error("Login failed: no access token received", - { - toastId: "login-error-no-token-toast", - }); - setLoading(false); - } + toast.error("Login failed: no access token received", { + toastId: "login-error-no-token-toast", + }); + setLoading(false); } } catch (error) { - if (!toast.isActive("login-error-network-toast")) { - toast.error("Network error during login", - { - toastId: "login-error-network-toast", - }); - } + toast.error("Network error during login", { + toastId: "login-error-network-toast", + }); console.error("Login error:", error); setLoading(false); } diff --git a/src/components/TRip/MediaRequestForm.jsx b/src/components/TRip/MediaRequestForm.jsx index 56e51f8..2ae9d7e 100644 --- a/src/components/TRip/MediaRequestForm.jsx +++ b/src/components/TRip/MediaRequestForm.jsx @@ -283,7 +283,9 @@ export default function MediaRequestForm() { link.href = url; const sanitize = (str) => str.replace(/[\\/:*?"<>|]/g, "_"); - const filename = `${sanitize(artist)} - ${sanitize(title)}.flac`; + const urlPath = new URL(data.stream_url).pathname; + const extension = urlPath.split('.').pop().split('?')[0] || 'flac'; + const filename = `${sanitize(artist)} - ${sanitize(title)}.${extension}`; link.download = filename; document.body.appendChild(link); diff --git a/src/layouts/Nav.astro b/src/layouts/Nav.astro index b8dbb03..49740b1 100644 --- a/src/layouts/Nav.astro +++ b/src/layouts/Nav.astro @@ -3,6 +3,8 @@ import { metaData } from "../config"; import { Icon } from "astro-icon/components"; import ExitToApp from '@mui/icons-material/ExitToApp'; +const isLoggedIn = Astro.cookies.get('access_token') || Astro.cookies.get('refresh_token'); + const navItems = [ { label: "Home", href: "/" }, { label: "Radio", href: "/radio" }, @@ -11,6 +13,7 @@ const navItems = [ { label: "TRip", href: "/TRip", auth: true }, { label: "Status", href: "https://status.boatson.boats", icon: ExitToApp }, { label: "Git", href: "https://kode.boatson.boats", icon: ExitToApp }, + // ...(isLoggedIn ? [{ label: "Logout", href: "#", onClick: "handleLogout()" }] : []), # todo ]; const currentPath = Astro.url.pathname; diff --git a/src/pages/TRip/index.astro b/src/pages/TRip/index.astro index 2edf82d..d175729 100644 --- a/src/pages/TRip/index.astro +++ b/src/pages/TRip/index.astro @@ -3,10 +3,11 @@ import Base from "@/layouts/Base.astro"; import Root from "@/components/AppLayout.jsx"; import { requireAuthHook } from "@/hooks/requireAuthHook"; - const user = await requireAuthHook(Astro); if (!user) { + const decodedUrl = decodeURIComponent(Astro.url.pathname + Astro.url.search); + Astro.cookies.set('returnTo', decodedUrl, { path: '/' }); return Astro.redirect('/login'); } diff --git a/src/pages/TRip/requests.astro b/src/pages/TRip/requests.astro index d3dc836..8a8ae1a 100644 --- a/src/pages/TRip/requests.astro +++ b/src/pages/TRip/requests.astro @@ -6,6 +6,8 @@ import { requireAuthHook } from "@/hooks/requireAuthHook"; const user = await requireAuthHook(Astro); if (!user) { + const decodedUrl = decodeURIComponent(Astro.url.pathname + Astro.url.search); + Astro.cookies.set('returnTo', decodedUrl, { path: '/' }); return Astro.redirect('/login'); } diff --git a/src/pages/lighting.astro b/src/pages/lighting.astro index 8205387..3fc3228 100644 --- a/src/pages/lighting.astro +++ b/src/pages/lighting.astro @@ -5,10 +5,11 @@ import { requireAuthHook } from "@/hooks/requireAuthHook"; const user = await requireAuthHook(Astro); -if (!user) { +if (!user || !user.roles.includes('lighting')) { + const decodedUrl = decodeURIComponent(Astro.url.pathname + Astro.url.search); + Astro.cookies.set('returnTo', decodedUrl, { path: '/' }); return Astro.redirect('/login'); } - --- diff --git a/src/utils/authFetch.js b/src/utils/authFetch.js index ee5d28e..26540aa 100644 --- a/src/utils/authFetch.js +++ b/src/utils/authFetch.js @@ -50,3 +50,11 @@ export async function refreshAccessToken(cookieHeader) { return null; } } + +export function handleLogout() { + document.cookie.split(";").forEach((cookie) => { + const name = cookie.split("=")[0].trim(); + document.cookie = `${name}=; Max-Age=0; path=/;`; + }); + window.location.href = "/"; +} \ No newline at end of file