Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 23 additions & 7 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { BrowserRouter, Routes, Route, useLocation } from 'react-router-dom';
import { LanguageProvider } from './context/LanguageContext';
import { AuthProvider, useAuth } from "./auth/AuthProvider";
import ScrollToTop from './components/ScrollToTop';
Expand All @@ -15,21 +15,24 @@ import Me from "./pages/profile/Me";
import Settings from "./pages/profile/Settings";
import Products from "./pages/profile/Products";
import ProtectedRoute from "./auth/ProtectedRoute";
import AccountLayout from "./pages/profile/AccountLayout";
import DashboardLayout from "./pages/profile/DashboardLayout";
import VerifyEmail from "./pages/VerifyEmail";
import ResetPassword from "./pages/ResetPassword";
import UserProfile from "./pages/user/UserProfile";
import GitHubCallback from "./pages/GitHubCallback";
import Services from "./pages/services";

function AppContent() {
function AppShell() {
const { user } = useAuth();
const location = useLocation();
const showNavbar = !location.pathname.startsWith('/me');
const showFooter = !location.pathname.startsWith('/me');

return (
<BrowserRouter>
<>
<ScrollToTop />
<div id="page" style={{ opacity: 1, transition: 'opacity 0.4s ease' }}>
<Navbar />
{showNavbar ? <Navbar /> : null}

<Routes>
<Route path="/" element={<Home />} />
Expand All @@ -47,21 +50,34 @@ function AppContent() {
path="/me"
element={
<ProtectedRoute>
<AccountLayout />
<DashboardLayout />
</ProtectedRoute>
}
>
<Route index element={<Me />} />
<Route path="products" element={<Products />} />
<Route path="settings" element={<Settings />} />
<Route path="licenses" element={<Me />} />
<Route path="billing" element={<Products />} />
<Route path="orders" element={<Products />} />
<Route path="security" element={<Settings />} />
<Route path="api" element={<Products />} />
</Route>
<Route path="/user/:username" element={<UserProfile />} />
<Route path="/github/callback" element={<GitHubCallback />} />
<Route path="*" element={<NotFound />} />
</Routes>

<Footer />
{showFooter ? <Footer /> : null}
</div>
</>
);
}

function AppContent() {
return (
<BrowserRouter>
<AppShell />
</BrowserRouter>
);
}
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/components/icons/svgs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
RepoForkedIcon,
StarIcon,
TrophyIcon,
CreditCardIcon,
PackageIcon
} from "@primer/octicons-react";

const ICON_SIZE = 16;
Expand Down Expand Up @@ -62,6 +64,8 @@ export const SvgProfile = () => <PersonIcon size={ICON_SIZE} color={ICON_COLOR}
export const SvgSettings = () => <GearIcon size={ICON_SIZE} color={ICON_COLOR} />;
export const SvgProducts = () => <ProjectIcon size={ICON_SIZE} color={ICON_COLOR} />;
export const SvgBadges = () => <TrophyIcon size={ICON_SIZE} color={ICON_COLOR} />;
export const SvgBilling = () => <CreditCardIcon size={ICON_SIZE} color={ICON_COLOR} />;
export const SvgOrders = () => <PackageIcon size={ICON_SIZE} color={ICON_COLOR} />;

function ServiceSvg({ children, size = 24, ...props }) {
return (
Expand Down
1 change: 0 additions & 1 deletion frontend/src/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,6 @@
"account.me.avatar_error.no_file": "Bitte wähle eine Datei aus.",
"account.me.avatar_error.upload_failed": "Avatar konnte nicht hochgeladen werden.",
"account.me.bio_label": "Bio",
"account.me.bio_placeholder": "Erzähl kurz etwas über dich…",
"account.me.location_label": "Standort",
"account.me.save_profile": "Speichern",
"account.me.saving_profile": "Speichern",
Expand Down
1 change: 0 additions & 1 deletion frontend/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,6 @@
"account.me.avatar_error.no_file": "Please select a file.",
"account.me.avatar_error.upload_failed": "Could not upload the avatar.",
"account.me.bio_label": "Bio",
"account.me.bio_placeholder": "Tell people a little about yourself…",
"account.me.location_label": "Location",
"account.me.save_profile": "Save",
"account.me.saving_profile": "Saving…",
Expand Down
12 changes: 3 additions & 9 deletions frontend/src/pages/profile/AccountLayout.jsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import { useState } from "react";
import { Outlet } from "react-router-dom";
import Sidebar from "./Sidebar";
import "../../styles/Me.css";

export default function AccountLayout() {
const [open, setOpen] = useState(true);

return (
<div className="me-layout">
<Sidebar open={open} toggle={() => setOpen(prev => !prev)} />

<div className="me-content">
<div className="dashboard-layout">
<main className="page-shell">
<Outlet />
</div>
</main>
</div>
);
}
36 changes: 36 additions & 0 deletions frontend/src/pages/profile/DashboardLayout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useEffect, useState } from "react";
import { Outlet, useLocation } from "react-router-dom";
import Sidebar from "./Sidebar";
import "../../styles/Me.css";

export default function DashboardLayout() {
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
const location = useLocation();

useEffect(() => {
setMobileSidebarOpen(false);
}, [location.pathname]);

return (
<div className="dashboard-layout">
<Sidebar open={mobileSidebarOpen} onClose={() => setMobileSidebarOpen(false)} />

<div className="dashboard-content">
<button
type="button"
className="dashboard-mobile-toggle"
onClick={() => setMobileSidebarOpen((prev) => !prev)}
aria-label="Toggle dashboard navigation"
>
<span />
<span />
<span />
</button>

<div className="page-shell">
<Outlet />
</div>
</div>
</div>
);
}
17 changes: 9 additions & 8 deletions frontend/src/pages/profile/Me.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -349,13 +349,15 @@ const Me = () => {
if (!user) return <div className="me-loading">{t("account.loading")}</div>;

return (
<div className="me-layout">
<main className="me-content">
<section className="account-panel">
<div className="account-header">
<p className="journey-kicker">{t("account.me.profile_details")}</p>
</div>

{profileError ? <p className="settings-error me-top-msg">{profileError}</p> : null}
{profileSuccess ? <p className="settings-success me-top-msg">{profileSuccess}</p> : null}
{profileError ? <p className="settings-error me-top-msg">{profileError}</p> : null}
{profileSuccess ? <p className="settings-success me-top-msg">{profileSuccess}</p> : null}

<div className="me-header">
<div className="me-header">
<div
className="avatar avatar-editable"
onClick={onAvatarClick}
Expand Down Expand Up @@ -465,6 +467,7 @@ const Me = () => {
<button
key={b.id}
className={`me-badge-card me-badge-card--${b.rarity}`}
data-rarity={b.rarity}
onClick={() => setActiveBadge(b)}
style={{ "--badge-color": b.color, "--badge-glow": RARITY_GLOW[b.rarity] ?? "transparent" }}
>
Expand Down Expand Up @@ -498,8 +501,6 @@ const Me = () => {
</button>
</div>

</main>

{activeBadge ? (
<div className="badge-modal-backdrop" onClick={() => setActiveBadge(null)}>
<div
Expand Down Expand Up @@ -539,7 +540,7 @@ const Me = () => {
</div>
</div>
) : null}
</div>
</section>
);
};

Expand Down
32 changes: 16 additions & 16 deletions frontend/src/pages/profile/Products.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@ const Projects = () => {
];

return (
<div className="me-layout">
<main className="me-content">

<div className="projects-grid">

{projects.map((p, i) => (
<div className="project-card" key={i}>
<h3>{p.name}</h3>
<p>{p.desc}</p>
</div>
))}

</div>

</main>
</div>
<section className="account-panel">
<div className="account-header">
<p className="journey-kicker">{t("account.products.title")}</p>
<h1 className="account-page-title">{t("account.products.title")}</h1>
<p className="account-page-subtitle">{t("account.products.subtitle")}</p>
</div>

<div className="projects-grid">
{projects.map((p, i) => (
<article className="project-card" key={i}>
<h3>{p.name}</h3>
<p>{p.desc}</p>
</article>
))}
</div>
</section>
);
};

Expand Down
14 changes: 7 additions & 7 deletions frontend/src/pages/profile/Settings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -510,11 +510,12 @@ const Settings = () => {
if (loading || !user) return <div className="me-loading">{t("account.loading")}</div>;

return (
<div className="me-layout settings-layout">
<main className="me-content settings-content">
<div className="me-settings">
<h1 className="settings-page-title">{t("account.settings.title")}</h1>
<p className="settings-page-subtitle">{t("account.settings.subtitle")}</p>
<section className="account-panel settings-panel">
<div className="account-header">
<p className="journey-kicker">{t("account.settings.title")}</p>
</div>

<div className="me-settings">

{error ? <p className="settings-error">{error}</p> : null}

Expand Down Expand Up @@ -749,8 +750,7 @@ const Settings = () => {
</div>
</div>
) : null}
</main>
</div>
</section>
);
};

Expand Down
102 changes: 74 additions & 28 deletions frontend/src/pages/profile/Sidebar.jsx
Original file line number Diff line number Diff line change
@@ -1,46 +1,92 @@
import React from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { SvgProfile, SvgSettings, SvgProducts } from "../../components/icons/svgs";
import {
SvgProfile,
SvgSettings,
SvgProducts,
SvgBadges,
SvgBilling,
SvgOrders
} from "../../components/icons/svgs";
Comment thread
LUCA-PYTHON marked this conversation as resolved.
Dismissed
import { useLanguage } from "../../context/LanguageContext";
import "../../styles/Me.css"
import { useAuth } from "../../auth/AuthProvider";
import "../../styles/Me.css";

const Sidebar = ({ open, toggle }) => {
const Sidebar = ({ open, onClose }) => {
const navigate = useNavigate();
const location = useLocation();
const { t } = useLanguage();
const { user, clearAuth } = useAuth();

const items = [
{ label: t("account.sidebar.profile"), path: "/me", icon: SvgProfile },
{ label: t("account.sidebar.products"), path: "/me/products", icon: SvgProducts },
{ label: t("account.sidebar.settings"), path: "/me/settings", icon: SvgSettings }
{ label: "Mein Profil", path: "/me", icon: SvgProfile },
{ label: "Produkte", path: "/me/products", icon: SvgProducts },
{ label: "Billing", path: "/me/billing", icon: SvgBilling },
{ label: "Bestellungen", path: "/me/orders", icon: SvgOrders },
{ label: "Einstellungen", path: "/me/settings", icon: SvgSettings },
];

const logout = async () => {
clearAuth();
navigate("/login");
};

return (
<aside className={`me-sidebar ${open ? "open" : "closed"}`}>
<div className="me-sidebar-inner">
<>
<div
className={`dashboard-sidebar-backdrop ${open ? "visible" : ""}`}
onClick={onClose}
aria-hidden="true"
/>

<aside className={`dashboard-sidebar ${open ? "open" : ""}`}>
<div className="dashboard-sidebar-profile">
<div className="dashboard-avatar-wrap">
{user?.avatar ? (
<img src={user.avatar} alt={user.username || user.email} className="dashboard-avatar" />
) : (
<span className="dashboard-avatar-letter">
{(user?.username || user?.email || "U").charAt(0).toUpperCase()}
</span>
)}
</div>

<div className="me-toggle" onClick={toggle}>
<div className="dashboard-sidebar-meta">
<strong>{user?.username || user?.email || "Nexory User"}</strong>
<span>{user?.email || "member@nexory.dev"}</span>
</div>
</div>

{items.map((item) => {
const Icon = item.icon;
const active = location.pathname === item.path;

return (
<div
key={item.path}
className={`me-nav-item ${active ? "active" : ""}`}
onClick={() => navigate(item.path)}
>
<Icon />
{open && <span>{item.label}</span>}
</div>
);
})}

</div>
</aside>
<nav className="dashboard-nav">
{items.map((item) => {
const Icon = item.icon;
const active = location.pathname === item.path;

return (
<button
key={item.path + item.label}
type="button"
className={`dashboard-nav-item ${active ? "active" : ""}`}
onClick={() => {
onClose?.();
navigate(item.path);
}}
>
<span className="dashboard-nav-icon"><Icon /></span>
<span>{item.label}</span>
</button>
);
})}
</nav>

<div className="dashboard-sidebar-footer">
<button type="button" className="dashboard-nav-item dashboard-logout" onClick={logout}>
<span className="dashboard-nav-icon">↗</span>
<span>Logout</span>
</button>
</div>
</aside>
</>
);
};

Expand Down
Loading