Skip to content

Commit

Permalink
✨ Add footer and menu to error pages
Browse files Browse the repository at this point in the history
  • Loading branch information
perdy committed Mar 2, 2025
1 parent 9e937f1 commit a484d57
Show file tree
Hide file tree
Showing 16 changed files with 341 additions and 118 deletions.
6 changes: 5 additions & 1 deletion flama/debug/data_structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,11 @@ def from_exception(cls, exc: Exception, context: int = 10) -> "Error":
frames = inspect.getinnerframes(exc.__traceback__, context) if exc.__traceback__ else []
exc_cls = exc if inspect.isclass(exc) else exc.__class__
return cls(
error=f"{exc_cls.__module__}.{exc_cls.__name__}" if exc_cls.__module__ != "builtins" else exc_cls.__name__,
error=(
f"{exc_cls.__module__}.{exc_cls.__name__}"
if exc_cls.__module__ not in ("builtins", "__main__")
else exc_cls.__name__
),
description=str(exc),
traceback=[Frame.from_frame_info(frame=frame) for frame in frames],
)
Expand Down
55 changes: 15 additions & 40 deletions templates/src/apps/debug/error_404.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import ReactDOM from 'react-dom/client'

import { Request } from '@/data/debug'
import { EnvironmentTable, ErrorTitle, RequestTable, URLTree } from '@/ui/components'
import { FlamaLogo } from '@/ui/logos'
import { EnvironmentTable, ErrorTitle, Footer, Menu, RequestTable, Section, URLTree } from '@/ui/components'

import '@/styles/debug/error_404.css'

Expand All @@ -11,48 +10,24 @@ function Page() {

return (
<>
<Menu />
<header>
<div className="max-w-8xl mx-auto flex items-center justify-between gap-x-20 px-10 py-4">
<div>
<ErrorTitle error="Not Found" path={request.path} method={request.method} />
</div>
<div>
<FlamaLogo />
</div>
</div>
<Section id="error" border={false} className="mt-28">
<ErrorTitle error="Not Found" path={request.path} method={request.method} />
</Section>
</header>
<main>
<section id="url-tree">
<div className="mt-10 py-8">
<div className="max-w-8xl mx-auto px-10">
<h2 className="text-primary-700 text-2xl font-semibold">Application URLs</h2>
<div className="mt-10 w-full">
<URLTree />
</div>
</div>
</div>
</section>
<section id="request">
<div className="border-flama-500/50 from-flama-500/10 mt-16 border-t bg-linear-to-b py-8">
<div className="max-w-8xl mx-auto px-10">
<h2 className="text-primary-700 text-2xl font-semibold">Request</h2>
</div>
</div>
<div className="max-w-8xl mx-auto mt-10 w-full px-10">
<RequestTable />
</div>
</section>
<section id="environment">
<div className="border-flama-500/50 from-flama-500/10 mt-16 border-t bg-linear-to-b py-8">
<div className="max-w-8xl mx-auto px-10">
<h2 className="text-primary-700 w-full text-2xl font-semibold">Environment</h2>
</div>
</div>
<div className="max-w-8xl mx-auto my-10 w-full px-10">
<EnvironmentTable />
</div>
</section>
<Section id="url-tree" title="Application URLs">
<URLTree />
</Section>
<Section id="request" title="Request">
<RequestTable />
</Section>
<Section id="environment" title="Environment">
<EnvironmentTable />
</Section>
</main>
<Footer />
</>
)
}
Expand Down
60 changes: 15 additions & 45 deletions templates/src/apps/debug/error_500.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import ReactDOM from 'react-dom/client'

import { Error, Request } from '@/data/debug'
import { EnvironmentTable, ErrorTitle, ErrorTraceback, RequestTable } from '@/ui/components'
import { FlamaLogo } from '@/ui/logos'
import { EnvironmentTable, ErrorTitle, ErrorTraceback, Footer, Menu, RequestTable, Section } from '@/ui/components'

import '@/styles/debug/error_500.css'

Expand All @@ -12,53 +11,24 @@ function Page() {

return (
<>
<Menu />
<header>
<div className="max-w-8xl mx-auto flex items-center justify-between gap-x-20 px-10 py-4">
<div>
<ErrorTitle
error={error.error}
path={request.path}
method={request.method}
description={error.description}
/>
</div>
<div>
<FlamaLogo />
</div>
</div>
<Section id="error" border={false} className="mt-28">
<ErrorTitle error={error.error} path={request.path} method={request.method} description={error.description} />
</Section>
</header>
<main>
<section id="traceback">
<div className="mt-10 py-8">
<div className="max-w-8xl mx-auto px-10">
<h2 className="text-primary-700 text-2xl font-semibold">Traceback</h2>
<div className="mt-10 w-full">
<ErrorTraceback />
</div>
</div>
</div>
</section>
<section id="request">
<div className="border-flama-500/50 from-flama-500/10 mt-16 border-t bg-linear-to-b py-8">
<div className="max-w-8xl mx-auto px-10">
<h2 className="text-primary-700 text-2xl font-semibold">Request</h2>
</div>
</div>
<div className="max-w-8xl mx-auto mt-10 w-full px-10">
<RequestTable />
</div>
</section>
<section id="environment">
<div className="border-flama-500/50 from-flama-500/10 mt-16 border-t bg-linear-to-b py-8">
<div className="max-w-8xl mx-auto px-10">
<h2 className="text-primary-700 w-full text-2xl font-semibold">Environment</h2>
</div>
</div>
<div className="max-w-8xl mx-auto my-10 w-full px-10">
<EnvironmentTable />
</div>
</section>
<Section id="traceback" title="Traceback">
<ErrorTraceback />
</Section>
<Section id="request" title="Request">
<RequestTable />
</Section>
<Section id="environment" title="Environment">
<EnvironmentTable />
</Section>
</main>
<Footer />
</>
)
}
Expand Down
12 changes: 7 additions & 5 deletions templates/src/ui/components/ErrorTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ export interface ErrorTitleProps {
export default function ErrorTitle({ error, method, path, description }: ErrorTitleProps) {
return (
<>
<div className="text-2xl">
<span className="text-flama-500 font-bold">{error}</span> raised at <span className="font-bold">{method}</span>{' '}
<span className="font-mono">{path}</span>
</div>
{description && <div className="text-xl font-medium">{description}</div>}
<h1 className="text-2xl">
<span className="text-flama-500 font-bold">{error}</span>
<span className="pl-2">raised at</span>
<span className="pl-2 font-bold">{method}</span>
<span className="pl-2 font-mono">{path}</span>
</h1>
{description && <h2 className="text-xl font-medium">{description}</h2>}
</>
)
}
6 changes: 3 additions & 3 deletions templates/src/ui/components/ErrorTraceback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,11 @@ export default function ErrorTraceback() {
const code = useMemo(() => html.decode(traceback[selected].code), [selected, traceback])

return (
<div className="flex gap-8">
<div className="h-[672px] w-xs flex-none overflow-hidden">
<div className="flex flex-col gap-8 lg:flex-row">
<div className="h-[672px] max-h-[calc(85vh)] w-full flex-none overflow-hidden lg:w-xs">
<TracebackList traceback={traceback} selected={selected} setSelected={setSelected} />
</div>
<div className="h-[672px] flex-auto overflow-hidden">
<div className="h-[672px] max-h-[calc(85vh)] flex-auto overflow-hidden">
<Window title={traceback[selected].filename}>
<Code code={code} language="python" lines={{ type: 'number' }} selectedLine={traceback[selected].line} />
</Window>
Expand Down
38 changes: 38 additions & 0 deletions templates/src/ui/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { IconBrandGithub, IconBrandLinkedin, IconBrandMedium, IconBrandX } from '@tabler/icons-react'

import { Logo } from '@/ui/elements'

interface Social {
url: string
icon: React.ReactElement
}

const social: Social[] = [
{ url: 'https://github.com/vortico/', icon: <IconBrandGithub /> },
{ url: 'https://www.linkedin.com/company/vortico-tech/', icon: <IconBrandLinkedin /> },
{ url: 'https://twitter.com/vorticotech', icon: <IconBrandX /> },
{ url: 'https://dev.to/vortico', icon: <IconBrandMedium /> },
]

export default function Footer() {
return (
<footer>
<div className="border-vortico-500/50 border-t px-4 py-8 sm:px-6 md:px-8">
<div className="mx-auto flex max-w-7xl items-center justify-between">
<a href="https://vortico.tech">
<Logo logo="vortico" color="vortico" size="lg" />
</a>
<div className="divide-vortico-500/50 flex h-8 items-center justify-end divide-x">
{social.map((item, i) => (
<a key={i} href={item.url} className="px-2 first:pl-0 last:pr-0">
<div className="text-primary-400 hover:text-vortico-500 h-6 w-6 transition-colors duration-200">
{item.icon}
</div>
</a>
))}
</div>
</div>
</div>
</footer>
)
}
134 changes: 134 additions & 0 deletions templates/src/ui/components/Menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import React, { ReactElement, useCallback, useState } from 'react'

import { IconBook, IconBrandGithub, IconMenu2, IconScript, IconX } from '@tabler/icons-react'
import { createPortal } from 'react-dom'

import { Logo } from '@/ui/elements'

interface NavItem {
href: string
title: string
icon: ReactElement
label: string
}

function Nav({ onClick, ...props }: Omit<React.ComponentProps<'ul'>, 'onClick'> & { onClick?: () => void }) {
const entries: NavItem[] = [
{ href: 'https://flama.dev/docs/', title: 'Docs', icon: <IconBook />, label: 'Flama docs' },
{ href: 'https://flama.dev/blog/', title: 'Blog', icon: <IconScript />, label: 'Flama blog' },
]

return (
<nav>
<ul {...props}>
{entries.map(({ href, title, icon, label }, i) => (
<li key={i} onClick={onClick}>
<a href={href} aria-label={label}>
<div className="flex h-12 cursor-pointer items-center justify-start gap-2">
<div className="text-primary-400 hover:text-flama-500 h-6 w-6 transition-colors duration-200 md:hidden">
{icon}
</div>
<div className="text-primary-400 hover:text-flama-500 text-md font-medium transition-colors duration-200">
{title}
</div>
</div>
</a>
</li>
))}
</ul>
</nav>
)
}

interface SocialItem {
href: string
title: string
icon: ReactElement
label: string
}

function Social({ ...props }: React.ComponentProps<'ul'>) {
const entries: SocialItem[] = [
{ href: 'https://github.com/vortico/flama', title: 'GitHub', icon: <IconBrandGithub />, label: 'Flama on GitHub' },
]

return (
<div>
<ul {...props}>
{entries.map(({ href, title, icon, label }, i) => (
<li key={i}>
<a href={href} aria-label={label}>
<div className="flex h-12 cursor-pointer items-center justify-start gap-2">
<div className="text-primary-400 hover:text-flama-500 h-6 w-6 transition-colors duration-200">
{icon}
</div>
<div className="text-primary-400 hover:text-flama-500 text-md font-medium transition-colors duration-200 md:hidden">
{title}
</div>
</div>
</a>
</li>
))}
</ul>
</div>
)
}

function FloatMenu({ onClose }: { onClose: () => void }) {
return (
<div
className="bg-primary-100 fixed inset-x-0 top-0 z-50 h-screen w-screen overflow-auto"
aria-modal="true"
role="dialog"
>
<div className="border-flama-500 flex h-14 w-full items-center justify-between border-b px-4 sm:px-6 md:px-8">
<a href="https://flama.dev" className="block cursor-pointer" aria-label="Flama website">
<Logo logo="flama" color="flama" size="lg" />
</a>
<button className="h-6 w-6" onClick={onClose} aria-label="Close menu">
<IconX className="text-primary-400 hover:text-flama-500 h-full w-full transition-colors duration-200" />
</button>
</div>
<div className="w-full px-4 sm:px-6 md:px-8">
<Nav className="flex flex-col items-start justify-start" onClick={onClose} />
<Social className="flex flex-col items-start justify-start gap-8" />
</div>
</div>
)
}

function FixedMenu({ onOpen }: { onOpen: () => void }) {
return (
<>
<div className="flex h-14 w-full items-center justify-between">
<a href="https://flama.dev" className="block cursor-pointer" aria-label="Flama website">
<Logo logo="flama" color="flama" size="lg" />
</a>
<div className="hidden items-center justify-between gap-8 md:flex">
<Nav className="flex items-center justify-start gap-8" />
<Social className="flex items-center justify-start gap-8" />
</div>
<button className="block h-6 w-6 cursor-pointer md:hidden" onClick={onOpen} aria-label="Open menu">
<IconMenu2 className="h-full w-full" />
</button>
</div>
</>
)
}

export default function Menu() {
const [isOpen, setIsOpen] = useState<boolean>(false)

const onOpen = useCallback(() => setIsOpen(true), [setIsOpen])
const onClose = useCallback(() => setIsOpen(false), [setIsOpen])

return (
<div className="border-flama-500 supports-backdrop-blur fixed inset-x-0 top-0 z-10 mx-auto border-b bg-transparent backdrop-blur-md transition-colors duration-200">
<div className="divide-flama-500/50 mx-auto w-full divide-y px-4 sm:px-6 md:px-8">
<FixedMenu onOpen={onOpen} />
</div>

{isOpen && createPortal(<FloatMenu onClose={onClose} />, document.body)}
</div>
)
}
Loading

0 comments on commit a484d57

Please sign in to comment.