Skip to content
Closed
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
144 changes: 71 additions & 73 deletions app/routes/_app+/recipients+/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import { json, type LoaderFunctionArgs } from '@remix-run/node'
import { Link, NavLink, Outlet, useLoaderData } from '@remix-run/react'
import { useState } from 'react'
import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx'
import { ButtonLink } from '#app/components/ui/button.tsx'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '#app/components/ui/dropdown-menu.tsx'
import { Icon } from '#app/components/ui/icon.tsx'
import { SimpleTooltip } from '#app/components/ui/tooltip.js'
import { requireUserId } from '#app/utils/auth.server.js'
Expand Down Expand Up @@ -70,7 +63,6 @@ export async function loader({ request }: LoaderFunctionArgs) {

export default function RecipientsLayout() {
const { recipients } = useLoaderData<typeof loader>()
const [isOpen, setIsOpen] = useState(false)

return (
<div className="container mx-auto flex min-h-0 flex-grow flex-col px-4 pt-4 md:px-8 md:pt-8">
Expand All @@ -93,73 +85,79 @@ export default function RecipientsLayout() {

<div className="bg-background-alt flex min-h-0 flex-1 flex-col">
<div className="flex flex-col gap-4 overflow-visible border-b-2 py-4 pl-1 pr-4">
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
<DropdownMenuTrigger className="hover:bg-background-alt-hover cursor-pointer px-2 py-1">
<Icon name="chevron-down">Select recipient</Icon>
</DropdownMenuTrigger>
<DropdownMenuContent className="min-w-64">
{recipients.map((recipient) => (
<DropdownMenuItem asChild key={recipient.id}>
<NavLink
to={recipient.id}
preventScrollReset
onClick={() => setIsOpen(false)}
className={cn(
'flex w-full items-center gap-2 overflow-x-auto rounded-sm px-2 py-1.5 text-xl transition-colors',
'hover:bg-accent hover:text-accent-foreground',
'focus:bg-accent focus:text-accent-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
)}
>
{({ isActive }) => (
<div className="flex items-center gap-1">
<Icon
name="arrow-right"
size="sm"
className={cn(
isActive ? 'opacity-100' : 'opacity-0',
'transition-opacity',
)}
/>
{recipient.name}
{recipient.disabled ? (
<SimpleTooltip content="Recipient is disabled">
<Icon
name="lock-closed"
className="text-muted-foreground"
title="recipient is disabled"
/>
</SimpleTooltip>
) : recipient.cronError ? (
<SimpleTooltip
content={`Invalid cron: ${recipient.cronError}`}
>
<Icon
name="exclamation-circle-outline"
className="text-destructive"
title="invalid cron schedule"
/>
</SimpleTooltip>
) : recipient._count.messages > 0 ? null : (
<SimpleTooltip content="No messages scheduled">
<Icon
name="exclamation-circle-outline"
className="text-danger-foreground"
title="no messages scheduled"
/>
</SimpleTooltip>
)}
</div>
)}
</NavLink>
</DropdownMenuItem>
))}
{recipients.length === 0 && (
<div className="bg-warning-background text-warning-foreground px-4 py-2 text-sm">
No recipients found. Add one to get started.
<details className="rounded-lg border border-border bg-background px-3 py-3 shadow-sm">
<summary className="cursor-pointer list-none text-base font-semibold text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring [&::-webkit-details-marker]:hidden">
<span className="flex flex-wrap items-center gap-2">
<Icon name="chevron-down" className="text-muted-foreground" />
<span>Select a recipient</span>
<span className="text-sm font-normal text-muted-foreground">
{recipients.length} total
</span>
</span>
</summary>
<div className="mt-3">
{recipients.length === 0 ? (
<div className="rounded-md border border-dashed border-muted-foreground/40 px-4 py-3 text-sm text-muted-foreground">
No recipients found.{' '}
<Link to="new" className="text-foreground underline">
Add one to get started.
</Link>
</div>
) : (
<ul className="grid gap-2 sm:grid-cols-2 lg:grid-cols-3">
{recipients.map((recipient) => (
<li key={recipient.id}>
<NavLink
to={recipient.id}
preventScrollReset
className={({ isActive }) =>
cn(
'flex items-center justify-between gap-3 rounded-md border px-3 py-2 text-sm font-medium transition-colors',
'hover:bg-accent/40 hover:text-accent-foreground',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
isActive
? 'border-transparent bg-accent text-accent-foreground'
: 'border-border/70 text-foreground',
)
}
>
<span className="truncate">{recipient.name}</span>
<span className="flex items-center gap-1">
{recipient.disabled ? (
<SimpleTooltip content="Recipient is disabled">
<Icon
name="lock-closed"
className="text-muted-foreground"
title="recipient is disabled"
/>
</SimpleTooltip>
) : recipient.cronError ? (
<SimpleTooltip
content={`Invalid cron: ${recipient.cronError}`}
>
<Icon
name="exclamation-circle-outline"
className="text-destructive"
title="invalid cron schedule"
/>
</SimpleTooltip>
) : recipient._count.messages > 0 ? null : (
<SimpleTooltip content="No messages scheduled">
<Icon
name="exclamation-circle-outline"
className="text-danger-foreground"
title="no messages scheduled"
/>
</SimpleTooltip>
)}
</span>
</NavLink>
</li>
))}
</ul>
)}
</DropdownMenuContent>
</DropdownMenu>
</div>
</details>
</div>
<main className="flex-1 overflow-auto">
<Outlet />
Expand Down
Loading