Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
20 changes: 14 additions & 6 deletions packages/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,22 @@ const LastFmPage = lazy(() => import('./pages/LastFm'))
const SpotifyPage = lazy(() => import('./pages/Spotify'))
const TermsOfServicePage = lazy(() => import('./pages/TermsOfService'))
const PrivacyPolicyPage = lazy(() => import('./pages/PrivacyPolicy'))
const DocsPage = lazy(() => import('./pages/Docs'))
const ChangelogPage = lazy(() => import('./pages/Changelog'))

const LEGAL_PATHS = [
const PUBLIC_PATH_PREFIXES = [
'/terms-of-service',
'/terms',
'/privacy-policy',
'/privacy',
'/docs',
'/changelog',
]

function isLegalPath(pathname: string) {
return LEGAL_PATHS.includes(pathname)
function isPublicPath(pathname: string) {
return PUBLIC_PATH_PREFIXES.some(
(p) => pathname === p || pathname.startsWith(`${p}/`),
)
}

function ForbiddenModulePage({ module }: { module: ModuleKey }) {
Expand Down Expand Up @@ -218,13 +224,15 @@ function AuthenticatedRoutes() {
)
}

function LegalRoutes() {
function PublicRoutes() {
return (
<Routes>
<Route path='/terms-of-service' element={<TermsOfServicePage />} />
<Route path='/terms' element={<TermsOfServicePage />} />
<Route path='/privacy-policy' element={<PrivacyPolicyPage />} />
<Route path='/privacy' element={<PrivacyPolicyPage />} />
<Route path='/docs' element={<DocsPage />} />
<Route path='/changelog' element={<ChangelogPage />} />
<Route
path='*'
element={<Navigate to='/terms-of-service' replace />}
Expand All @@ -248,12 +256,12 @@ function App() {
.catch(() => setIsReady(true))
}, [checkAuth])

if (isLegalPath(location.pathname)) {
if (isPublicPath(location.pathname)) {
return (
<div className='dark'>
<ErrorBoundary>
<Suspense fallback={<PageLoader />}>
<LegalRoutes />
<PublicRoutes />
</Suspense>
</ErrorBoundary>
<Analytics />
Expand Down
215 changes: 215 additions & 0 deletions packages/frontend/src/components/DocsShell/DocsShell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import { useEffect, useState, type ReactNode } from 'react'
import { Link, useLocation } from 'react-router-dom'
import { ArrowUpRight, Menu, X } from 'lucide-react'

export type DocsNavItem = {
label: string
href: string
external?: boolean
}

export type DocsNavGroup = {
heading: string
items: DocsNavItem[]
}

export type DocsTocItem = {
id: string
label: string
depth?: 2 | 3
}

type Props = {
nav: DocsNavGroup[]
toc?: DocsTocItem[]
breadcrumb: string
title: string
lastUpdated?: string
children: ReactNode
}

export default function DocsShell({ nav, toc, breadcrumb, title, lastUpdated, children }: Props) {

Check warning on line 31 in packages/frontend/src/components/DocsShell/DocsShell.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Mark the props of the component as read-only.

See more on https://sonarcloud.io/project/issues?id=LucasSantana-Dev_Lucky&issues=AZ4tLp0VfdkBgWhNqzp9&open=AZ4tLp0VfdkBgWhNqzp9&pullRequest=868
const location = useLocation()
const [sidebarOpen, setSidebarOpen] = useState(false)
const [activeId, setActiveId] = useState<string | null>(null)

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

useEffect(() => {
if (!toc?.length) return
const headings = toc
.map((t) => document.getElementById(t.id))
.filter((el): el is HTMLElement => Boolean(el))

if (!headings.length) return

const obs = new IntersectionObserver(
(entries) => {
const visible = entries
.filter((e) => e.isIntersecting)
.sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)
if (visible[0]) setActiveId(visible[0].target.id)
},
{ rootMargin: '-80px 0px -65% 0px', threshold: 0.1 },
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
)
headings.forEach((h) => obs.observe(h))
return () => obs.disconnect()
}, [toc])

return (
<div className='min-h-screen bg-lucky-surface-canvas text-white'>
<header className='sticky top-0 z-30 border-b border-lucky-border-soft bg-lucky-surface-canvas/85 backdrop-blur supports-[backdrop-filter]:bg-lucky-surface-canvas/65'>
<div className='mx-auto flex h-14 max-w-7xl items-center justify-between gap-3 px-4 md:px-6'>
<div className='flex items-center gap-2'>
<button
onClick={() => setSidebarOpen((v) => !v)}
aria-label={sidebarOpen ? 'Close navigation' : 'Open navigation'}
className='lg:hidden inline-flex h-9 w-9 items-center justify-center rounded-md text-lucky-text-muted hover:bg-lucky-surface-panel hover:text-lucky-text-strong focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-lucky-brand'
>
{sidebarOpen ? <X size={16} /> : <Menu size={16} />}
</button>
<Link
to='/'
className='inline-flex items-center gap-2 text-lucky-text-strong hover:text-lucky-brand transition-colors'
>
<img src='/lucky-logo.png' alt='Lucky' width='24' height='24' className='h-6 w-6 rounded-full' loading='eager' />
<span className='font-mono text-sm font-semibold tracking-tight'>
lucky<span className='text-lucky-brand'>.</span>
</span>
</Link>
<span className='ml-1 hidden text-lucky-border-strong md:inline'>/</span>
<span className='hidden font-mono text-xs uppercase tracking-[0.2em] text-lucky-text-muted md:inline'>
{breadcrumb}
</span>
</div>
<nav className='flex items-center gap-1 font-mono text-xs text-lucky-text-muted'>
<Link to='/docs' className='hidden sm:inline-flex items-center rounded-md px-2.5 py-1.5 hover:bg-lucky-surface-panel hover:text-lucky-text-strong transition-colors'>
docs
</Link>
<Link to='/changelog' className='hidden sm:inline-flex items-center rounded-md px-2.5 py-1.5 hover:bg-lucky-surface-panel hover:text-lucky-text-strong transition-colors'>
changelog
</Link>
<a
href='https://github.com/LucasSantana-Dev/Lucky'
target='_blank'
rel='noreferrer'
className='inline-flex items-center gap-1 rounded-md px-2.5 py-1.5 hover:bg-lucky-surface-panel hover:text-lucky-text-strong transition-colors'
>
github <ArrowUpRight size={11} aria-hidden />
</a>
</nav>
</div>
</header>

<div className='mx-auto grid max-w-7xl gap-0 px-0 md:px-6 lg:grid-cols-[16rem_minmax(0,1fr)_14rem]'>
<DocsSidebar nav={nav} open={sidebarOpen} />

<main className='min-w-0 px-4 py-10 md:px-8 md:py-14 lg:px-10'>
<article className='mx-auto max-w-2xl'>
<header className='mb-10 border-b border-lucky-border-soft pb-6'>
<p className='mb-3 font-mono text-[11px] uppercase tracking-[0.22em] text-lucky-text-muted'>
{breadcrumb}
</p>
<h1 className='text-3xl font-bold tracking-tight text-lucky-text-strong md:text-4xl'>{title}</h1>
{lastUpdated ? (
<p className='mt-3 font-mono text-xs text-lucky-text-muted'>
last updated: {lastUpdated}
</p>
) : null}
</header>
<div className='docs-prose'>{children}</div>
</article>
</main>

{toc?.length ? <DocsToc toc={toc} activeId={activeId} /> : <div className='hidden lg:block' />}
</div>
</div>
)
}

function DocsSidebar({ nav, open }: { nav: DocsNavGroup[]; open: boolean }) {

Check warning on line 132 in packages/frontend/src/components/DocsShell/DocsShell.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Mark the props of the component as read-only.

See more on https://sonarcloud.io/project/issues?id=LucasSantana-Dev_Lucky&issues=AZ4tLp0VfdkBgWhNqzp-&open=AZ4tLp0VfdkBgWhNqzp-&pullRequest=868
const { pathname } = useLocation()

return (
<aside
className={`${open ? 'block' : 'hidden'} lg:block border-r border-lucky-border-soft bg-lucky-surface-canvas`}
aria-label='Documentation navigation'
>
<div className='sticky top-14 max-h-[calc(100vh-3.5rem)] overflow-y-auto px-4 py-8 md:px-6'>
<nav className='space-y-7'>
{nav.map((group) => (
<div key={group.heading}>
<h4 className='mb-3 font-mono text-[10px] uppercase tracking-[0.22em] text-lucky-text-muted'>
{group.heading}
</h4>
<ul className='space-y-0.5'>
{group.items.map((item) => {
const isActive = !item.external && pathname === item.href
const base =
'group inline-flex w-full items-center justify-between gap-2 rounded-md px-2.5 py-1.5 text-sm transition-colors'
const inactiveCls = `${base} text-lucky-text-body hover:bg-lucky-surface-panel hover:text-lucky-text-strong`
const activeCls = `${base} bg-lucky-surface-panel text-lucky-text-strong shadow-[inset_2px_0_0_var(--color-lucky-brand)]`
if (item.external) {
return (
<li key={item.href}>
<a
href={item.href}
target='_blank'
rel='noreferrer'
className={inactiveCls}
>
<span>{item.label}</span>
<ArrowUpRight size={11} aria-hidden className='text-lucky-text-muted' />
</a>
</li>
)
}
return (
<li key={item.href}>
<Link to={item.href} className={isActive ? activeCls : inactiveCls}>
<span>{item.label}</span>
</Link>
</li>
)
})}
</ul>
</div>
))}
</nav>
</div>
</aside>
)
}

function DocsToc({ toc, activeId }: { toc: DocsTocItem[]; activeId: string | null }) {

Check warning on line 186 in packages/frontend/src/components/DocsShell/DocsShell.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Mark the props of the component as read-only.

See more on https://sonarcloud.io/project/issues?id=LucasSantana-Dev_Lucky&issues=AZ4tLp0VfdkBgWhNqzp_&open=AZ4tLp0VfdkBgWhNqzp_&pullRequest=868
return (
<aside className='hidden lg:block' aria-label='On this page'>
<div className='sticky top-14 max-h-[calc(100vh-3.5rem)] overflow-y-auto px-6 py-10'>
<h4 className='mb-3 font-mono text-[10px] uppercase tracking-[0.22em] text-lucky-text-muted'>
On this page
</h4>
<ul className='space-y-1.5 border-l border-lucky-border-soft pl-3'>
{toc.map((item) => {
const isActive = activeId === item.id
return (
<li key={item.id} className={item.depth === 3 ? 'pl-3' : ''}>
<a
href={`#${item.id}`}
className={`inline-block text-xs leading-snug transition-colors ${
isActive
? 'text-lucky-brand font-medium'
: 'text-lucky-text-muted hover:text-lucky-text-strong'
}`}
>
{item.label}
</a>
</li>
)
})}
</ul>
</div>
</aside>
)
}
2 changes: 1 addition & 1 deletion packages/frontend/src/components/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ function Layout({ children }: LayoutProps) {
</a>
<Sidebar />
<div className='flex min-w-0 flex-1 flex-col'>
<header className='lucky-shell-header sticky top-0 z-20 border-b border-lucky-border bg-lucky-bg-primary/92 relative'>
<header className='lucky-shell-header sticky top-0 z-30 border-b border-lucky-border bg-lucky-bg-primary relative'>
<div className='mx-auto flex w-full max-w-[1400px] items-center justify-between gap-4 px-4 py-3.5 md:px-6 md:py-4'>
<div className='min-w-0'>
<h1 className='type-title text-lucky-text-primary leading-tight'>
Expand Down
4 changes: 2 additions & 2 deletions packages/frontend/src/components/Music/AutoplayGenres.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default function AutoplayGenres({ guildId }: AutoplayGenresProps) {
try {
setError(null)
const response = await apiClient.get<{ genres: string[] }>(
`/api/guilds/${guildId}/autoplay/genres`,
`/guilds/${guildId}/autoplay/genres`,
)
setGenres(response.data.genres || [])
} catch (err) {
Expand Down Expand Up @@ -92,7 +92,7 @@ export default function AutoplayGenres({ guildId }: AutoplayGenresProps) {
setIsLoading(true)
try {
setError(null)
await apiClient.put(`/api/guilds/${guildId}/autoplay/genres`, {
await apiClient.put(`/guilds/${guildId}/autoplay/genres`, {
genres: updatedGenres,
})
setGenres(updatedGenres)
Expand Down
Loading
Loading