Added CC Navbar
This commit is contained in:
@@ -1,18 +1,22 @@
|
|||||||
import { LitElement, html, css } from 'lit';
|
import { LitElement, html, css } from 'lit';
|
||||||
import { customElement } from 'lit/decorators.js';
|
import { customElement, state } from 'lit/decorators.js';
|
||||||
import './pages/not-found-page';
|
import './pages/not-found-page';
|
||||||
import './components/nav-bar';
|
import './components/nav-bar';
|
||||||
|
import './components/cc/cc-nav-bar';
|
||||||
import './components/footer-bar';
|
import './components/footer-bar';
|
||||||
import { Router } from './router/router';
|
import { Router } from './router/router';
|
||||||
import './pages/home-page';
|
import './pages/home-page';
|
||||||
import './pages/competition-page';
|
import './pages/competition-page';
|
||||||
import './pages/auth/login-page';
|
import './pages/auth/login-page';
|
||||||
import './pages/auth/register-page';
|
import './pages/auth/register-page';
|
||||||
|
import './pages/cc/cc-home-page';
|
||||||
|
|
||||||
@customElement('app-root')
|
@customElement('app-root')
|
||||||
export class AppRoot extends LitElement {
|
export class AppRoot extends LitElement {
|
||||||
private router!: Router;
|
private router!: Router;
|
||||||
|
|
||||||
|
@state() private isCompetitionCenter = false;
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
:host {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -41,14 +45,22 @@ export class AppRoot extends LitElement {
|
|||||||
{ path: '/competitions', view: () => document.createElement('competition-page') },
|
{ path: '/competitions', view: () => document.createElement('competition-page') },
|
||||||
{ path: '/login', view: () => document.createElement('login-page') },
|
{ path: '/login', view: () => document.createElement('login-page') },
|
||||||
{ path: '/register', view: () => document.createElement('register-page') },
|
{ path: '/register', view: () => document.createElement('register-page') },
|
||||||
|
{ path: '/cc/', view: () => document.createElement('cc-home-page') },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
this.router.onRouteChange = (path: string) => {
|
||||||
|
this.isCompetitionCenter = path.startsWith('/cc');
|
||||||
|
};
|
||||||
|
|
||||||
this.router.resolve();
|
this.router.resolve();
|
||||||
this.addEventListener('nav', (e: any) => this.router.navigate(e.detail.path));
|
this.addEventListener('nav', (e: any) => this.router.navigate(e.detail.path));
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
<nav-bar></nav-bar>
|
${this.isCompetitionCenter
|
||||||
|
? html`<cc-nav-bar></cc-nav-bar>`
|
||||||
|
: html`<nav-bar></nav-bar>`}
|
||||||
<main id="outlet"></main>
|
<main id="outlet"></main>
|
||||||
<footer-bar></footer-bar>
|
<footer-bar></footer-bar>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
nav {
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
background: var(--color-bg-nav);
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
user-select: none;
|
||||||
|
width: fit-content;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding-right: 1rem;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand img {
|
||||||
|
width: auto;
|
||||||
|
height: 2rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links {
|
||||||
|
display: flex;
|
||||||
|
gap: 1.25rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-text);
|
||||||
|
font-weight: 500;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
a::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -6px;
|
||||||
|
left: 0;
|
||||||
|
width: 0%;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--color-accent);
|
||||||
|
transition: 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover::after {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: none;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0.4rem 0.6rem;
|
||||||
|
color: var(--color-text);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { LitElement, html, css, unsafeCSS } from 'lit';
|
||||||
|
import { customElement, state } from 'lit/decorators.js';
|
||||||
|
import styles from './cc-nav-bar.css?inline';
|
||||||
|
import '../ui-link';
|
||||||
|
|
||||||
|
@customElement('cc-nav-bar')
|
||||||
|
export class CCNavBar extends LitElement {
|
||||||
|
static styles = css`${unsafeCSS(styles)}`;
|
||||||
|
|
||||||
|
@state() theme: 'light' | 'dark' =
|
||||||
|
(localStorage.getItem('theme') as 'light' | 'dark') ||
|
||||||
|
(window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
|
? 'dark'
|
||||||
|
: 'light');
|
||||||
|
|
||||||
|
firstUpdated() {
|
||||||
|
document.documentElement.setAttribute('data-theme', this.theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleTheme() {
|
||||||
|
this.theme = this.theme === 'light' ? 'dark' : 'light';
|
||||||
|
document.documentElement.setAttribute('data-theme', this.theme);
|
||||||
|
localStorage.setItem('theme', this.theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
navigate(path: string) {
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('nav', { detail: { path }, bubbles: true, composed: true })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<nav>
|
||||||
|
<div class="brand"><img src="/logo.svg" alt="Flight Score Logo" /> FlightScore</div>
|
||||||
|
<div class="links">
|
||||||
|
<ui-link href="/">Home</ui-link>
|
||||||
|
<ui-link href="/competitions">Competitions</ui-link>
|
||||||
|
<ui-link href="/login">Logout</ui-link>
|
||||||
|
|
||||||
|
|
||||||
|
<button @click=${this.toggleTheme}>
|
||||||
|
${this.theme === 'light' ? '🌙 Dark' : '☀️ Light'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@ export class NavBar extends LitElement {
|
|||||||
<ui-link href="/competitions">Competitions</ui-link>
|
<ui-link href="/competitions">Competitions</ui-link>
|
||||||
<ui-link href="/login">Login</ui-link>
|
<ui-link href="/login">Login</ui-link>
|
||||||
|
|
||||||
|
|
||||||
<button @click=${this.toggleTheme}>
|
<button @click=${this.toggleTheme}>
|
||||||
${this.theme === 'light' ? '🌙 Dark' : '☀️ Light'}
|
${this.theme === 'light' ? '🌙 Dark' : '☀️ Light'}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -68,7 +68,12 @@ export class LoginPage extends LitElement {
|
|||||||
password: this.password,
|
password: this.password,
|
||||||
});
|
});
|
||||||
localStorage.setItem('token', res.token);
|
localStorage.setItem('token', res.token);
|
||||||
window.dispatchEvent(new CustomEvent('auth-changed')); // for later reactive flows
|
window.dispatchEvent(new CustomEvent('auth-changed'));
|
||||||
|
dispatchEvent(new CustomEvent('nav', {
|
||||||
|
detail: { path: "/cc/" },
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
}));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.error = e.message || 'Login failed';
|
this.error = e.message || 'Login failed';
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { LitElement, html } from 'lit';
|
||||||
|
import { customElement } from 'lit/decorators.js';
|
||||||
|
|
||||||
|
@customElement('cc-home-page')
|
||||||
|
export class CCHomePage extends LitElement {
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<h1>Welcome to the Competition Center Homepage</h1>
|
||||||
|
<p>Analyze your tracks visually.</p>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,13 @@ export class CompetitionPage extends LitElement {
|
|||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
<h1>Welcome to the Competitions</h1>
|
<h1>Welcome to the Competitions</h1>
|
||||||
|
<h1>Welcome to the Competitions</h1>
|
||||||
|
<h1>Welcome to the Competitions</h1>
|
||||||
|
<h1>Welcome to the Competitions</h1>
|
||||||
|
<h1>Welcome to the Competitions</h1>
|
||||||
|
<h1>Welcome to the Competitions</h1>
|
||||||
|
<h1>Welcome to the Competitions</h1>
|
||||||
|
<h1>Welcome to the Competitions</h1>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,7 @@ export type Route = {
|
|||||||
export class Router {
|
export class Router {
|
||||||
private routes: Route[];
|
private routes: Route[];
|
||||||
private outlet: HTMLElement;
|
private outlet: HTMLElement;
|
||||||
|
onRouteChange?: (path: string) => void;
|
||||||
|
|
||||||
constructor(outlet: HTMLElement, routes: Route[]) {
|
constructor(outlet: HTMLElement, routes: Route[]) {
|
||||||
this.routes = routes;
|
this.routes = routes;
|
||||||
@@ -26,5 +27,9 @@ export class Router {
|
|||||||
this.outlet.append(
|
this.outlet.append(
|
||||||
match ? match.view() : document.createElement('not-found-page')
|
match ? match.view() : document.createElement('not-found-page')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (this.onRouteChange) {
|
||||||
|
this.onRouteChange(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user