Move website directory

This commit is contained in:
Aaron Po
2026-04-27 16:00:11 -04:00
parent 189bce040b
commit 5a21589029
58 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,144 @@
import {
Disclosure,
DisclosureButton,
DisclosurePanel,
Menu,
MenuButton,
MenuItem,
MenuItems,
} from '@headlessui/react';
import { Link } from 'react-router';
interface NavbarProps {
auth: {
username: string;
accessToken: string;
refreshToken: string;
userAccountId: string;
} | null;
}
export default function Navbar({ auth }: NavbarProps) {
const navLinks = [
{ to: '/theme', label: 'Theme' },
{ to: '/beers', label: 'Beers' },
{ to: '/breweries', label: 'Breweries' },
{ to: '/beer-styles', label: 'Beer Styles' },
];
return (
<Disclosure
as="nav"
className="sticky top-0 z-50 border-b border-base-300 bg-base-100 shadow-md"
>
{({ open }) => (
<>
<div className="navbar mx-auto max-w-7xl px-2 sm:px-4">
<div className="navbar-start gap-2">
<DisclosureButton
className="btn btn-ghost btn-square lg:hidden"
aria-label="Toggle navigation"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
className="h-5 w-5 stroke-current"
>
{open ? (
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M6 18L18 6M6 6l12 12"
/>
) : (
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4 6h16M4 12h16M4 18h16"
/>
)}
</svg>
</DisclosureButton>
<Link to="/" className="text-xl font-bold">
🍺 The Biergarten App
</Link>
</div>
<div className="navbar-center hidden lg:flex gap-2">
{navLinks.map((link) => (
<Link key={link.to} to={link.to} className="btn btn-ghost btn-sm">
{link.label}
</Link>
))}
</div>
<div className="navbar-end gap-2">
{!auth && (
<Link to="/register" className="btn btn-ghost btn-sm hidden sm:inline-flex">
Register User
</Link>
)}
{auth ? (
<>
<Link to="/dashboard" className="btn btn-primary btn-sm">
Dashboard
</Link>
<Menu as="div" className="relative">
<MenuButton className="btn btn-ghost btn-sm">
{auth.username}
</MenuButton>
<MenuItems className="menu absolute right-0 z-60 mt-2 w-52 rounded-box border border-base-300 bg-base-100 p-2 shadow-xl focus:outline-none">
<MenuItem>
{({ focus }) => (
<Link to="/dashboard" className={focus ? 'active' : ''}>
Dashboard
</Link>
)}
</MenuItem>
<MenuItem>
{({ focus }) => (
<Link to="/logout" className={focus ? 'active' : ''}>
Logout
</Link>
)}
</MenuItem>
</MenuItems>
</Menu>
</>
) : (
<Link to="/login" className="btn btn-primary btn-sm">
Login
</Link>
)}
</div>
</div>
<DisclosurePanel className="border-t border-base-300 bg-base-100 px-4 py-3 lg:hidden">
<div className="flex flex-col gap-2">
{navLinks.map((link) => (
<Link
key={link.to}
to={link.to}
className="btn btn-ghost btn-sm justify-start"
>
{link.label}
</Link>
))}
{!auth && (
<Link to="/register" className="btn btn-ghost btn-sm justify-start">
Register User
</Link>
)}
</div>
</DisclosurePanel>
</>
)}
</Disclosure>
);
}

View File

@@ -0,0 +1,40 @@
import { Description, Field, Label } from '@headlessui/react';
type FormFieldProps = React.InputHTMLAttributes<HTMLInputElement> & {
label: string;
error?: string;
hint?: string;
labelClassName?: string;
inputClassName?: string;
hintClassName?: string;
};
export default function FormField({
label,
error,
hint,
className,
labelClassName,
inputClassName,
hintClassName,
...inputProps
}: FormFieldProps) {
return (
<Field className={className ?? 'space-y-1'}>
<Label htmlFor={inputProps.id} className={labelClassName ?? 'label font-medium'}>
{label}
</Label>
<input
{...inputProps}
className={inputClassName ?? `input w-full ${error ? 'input-error' : ''}`}
/>
{error ? (
<Description className={hintClassName ?? 'label text-error'}>{error}</Description>
) : hint ? (
<Description className={hintClassName ?? 'label'}>{hint}</Description>
) : null}
</Field>
);
}

View File

@@ -0,0 +1,31 @@
import { Button } from '@headlessui/react';
interface SubmitButtonProps {
isSubmitting: boolean;
idleText: string;
submittingText: string;
className?: string;
}
export default function SubmitButton({
isSubmitting,
idleText,
submittingText,
className,
}: SubmitButtonProps) {
return (
<Button
type="submit"
disabled={isSubmitting}
className={className ?? 'btn btn-primary w-full mt-2'}
>
{isSubmitting ? (
<>
<span className="loading loading-spinner loading-sm" /> {submittingText}
</>
) : (
idleText
)}
</Button>
);
}

View File

@@ -0,0 +1,25 @@
import { Toaster } from 'react-hot-toast';
export default function ToastProvider() {
return (
<Toaster
position="top-right"
toastOptions={{
duration: 3500,
className: 'rounded-box border border-base-300 bg-base-100 text-base-content shadow-lg',
success: {
iconTheme: {
primary: 'var(--color-success)',
secondary: 'var(--color-success-content)',
},
},
error: {
iconTheme: {
primary: 'var(--color-error)',
secondary: 'var(--color-error-content)',
},
},
}}
/>
);
}

View File

@@ -0,0 +1,6 @@
import toast from 'react-hot-toast';
export const showSuccessToast = (message: string) => toast.success(message);
export const showErrorToast = (message: string) => toast.error(message);
export const showInfoToast = (message: string) => toast(message);
export const dismissToasts = () => toast.dismiss();