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
137 changes: 63 additions & 74 deletions app/routes/_app+/recipients+/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
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'
import { CronParseError, getNextScheduledTime } from '#app/utils/cron.server.ts'
import { prisma } from '#app/utils/db.server.ts'
Expand Down Expand Up @@ -70,7 +62,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 +84,71 @@ 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">
<details
className="group rounded-lg border border-border/60 bg-background px-3 py-2 shadow-sm"
open={recipients.length === 0}
>
<summary className="flex cursor-pointer list-none items-center justify-between gap-2 rounded-md px-2 py-1 text-sm font-semibold text-foreground hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 md:text-base">
<span>Recipient links</span>
<Icon
name="chevron-down"
className="text-muted-foreground transition-transform group-open:rotate-180"
/>
</summary>
<div className="mt-3 flex flex-col gap-1 pb-2">
{recipients.length === 0 ? (
<p className="text-sm text-muted-foreground">
No recipients found. Add one to get started.
</div>
</p>
) : (
recipients.map((recipient) => {
const statusText = recipient.disabled
? 'Disabled'
: recipient.cronError
? 'Invalid schedule'
: recipient._count.messages > 0
? null
: 'No messages'
const statusTone = recipient.cronError
? 'text-destructive'
: 'text-muted-foreground'
const statusTitle = recipient.cronError
? `Invalid cron: ${recipient.cronError}`
: undefined

return (
<NavLink
key={recipient.id}
to={recipient.id}
preventScrollReset
className={({ isActive }) =>
cn(
'flex items-center justify-between gap-3 rounded-md px-3 py-2 text-sm transition-colors',
'hover:bg-muted/60 hover:text-foreground',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
isActive
? 'bg-muted text-foreground font-semibold'
: 'text-muted-foreground',
)
}
>
<span className="min-w-0 flex-1 truncate text-left">
{recipient.name}
</span>
{statusText ? (
<span
className={cn('text-xs font-medium', statusTone)}
title={statusTitle}
>
{statusText}
</span>
) : null}
</NavLink>
)
})
)}
</DropdownMenuContent>
</DropdownMenu>
</div>
</details>
</div>
<main className="flex-1 overflow-auto">
<Outlet />
Expand Down
Loading