168 lines
4.6 KiB
Plaintext
168 lines
4.6 KiB
Plaintext
---
|
|
import { differenceInCalendarDays } from 'date-fns'
|
|
|
|
import AnnouncementBanner from '../components/AnnouncementBanner.astro'
|
|
import BaseHead from '../components/BaseHead.astro'
|
|
import Footer from '../components/Footer.astro'
|
|
import Header from '../components/Header.astro'
|
|
import { cn } from '../lib/cn'
|
|
import { pluralize } from '../lib/pluralize'
|
|
import { prisma } from '../lib/prisma'
|
|
|
|
import type { AstroChildren } from '../lib/astro'
|
|
import type { Prisma } from '@prisma/client'
|
|
import type { ComponentProps } from 'astro/types'
|
|
|
|
import '@fontsource-variable/space-grotesk'
|
|
import '../styles/global.css'
|
|
|
|
type Props = ComponentProps<typeof BaseHead> & {
|
|
children: AstroChildren
|
|
errors?: string[]
|
|
success?: string[]
|
|
classNames?: {
|
|
body?: string
|
|
main?: string
|
|
footer?: string
|
|
}
|
|
showSplashText?: boolean
|
|
widthClassName?:
|
|
| 'container'
|
|
| 'max-w-none'
|
|
| 'max-w-screen-2xl'
|
|
| 'max-w-screen-lg'
|
|
| 'max-w-screen-md'
|
|
| 'max-w-screen-sm'
|
|
| 'max-w-screen-xl'
|
|
| 'max-w-screen-xs'
|
|
isErrorPage?: boolean
|
|
}
|
|
|
|
const {
|
|
errors = [],
|
|
success = [],
|
|
classNames,
|
|
widthClassName = 'max-w-screen-2xl',
|
|
showSplashText,
|
|
isErrorPage,
|
|
...baseHeadProps
|
|
} = Astro.props
|
|
|
|
const actualErrors = [...errors, ...Astro.locals.banners.errors]
|
|
const actualSuccess = [...success, ...Astro.locals.banners.successes]
|
|
|
|
const currentDate = new Date()
|
|
const announcement = await Astro.locals.banners.try(
|
|
'Unable to load announcements.',
|
|
() =>
|
|
prisma.announcement.findFirst({
|
|
where: {
|
|
isActive: true,
|
|
startDate: { lte: currentDate },
|
|
OR: [{ endDate: null }, { endDate: { gt: currentDate } }],
|
|
},
|
|
select: {
|
|
id: true,
|
|
content: true,
|
|
type: true,
|
|
link: true,
|
|
linkText: true,
|
|
startDate: true,
|
|
endDate: true,
|
|
isActive: true,
|
|
},
|
|
orderBy: [{ type: 'desc' }, { createdAt: 'desc' }],
|
|
}),
|
|
null
|
|
)
|
|
|
|
function getDeletionAnnouncement(
|
|
user: Prisma.UserGetPayload<{ select: { scheduledDeletionAt: true } }> | null,
|
|
currentDate: Date = new Date()
|
|
) {
|
|
if (!user?.scheduledDeletionAt) return null
|
|
const daysUntilDeletion = differenceInCalendarDays(user.scheduledDeletionAt, currentDate)
|
|
|
|
return {
|
|
id: 0,
|
|
content: `Your account will be deleted ${daysUntilDeletion <= 0 ? 'today' : `in ${daysUntilDeletion.toLocaleString()} ${pluralize('day', daysUntilDeletion)}`} due to inactivity.`,
|
|
type: 'ALERT' as const,
|
|
link: '/account',
|
|
linkText: 'Prevent deletion',
|
|
startDate: currentDate,
|
|
endDate: null,
|
|
isActive: true,
|
|
}
|
|
}
|
|
const deletionAnnouncement = getDeletionAnnouncement(Astro.locals.user, currentDate)
|
|
---
|
|
|
|
<html lang="en" transition:name="root" transition:animate="none">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<BaseHead {...baseHeadProps} />
|
|
</head>
|
|
<body
|
|
class={cn('bg-night-700 text-day-300 flex min-h-dvh flex-col *:shrink-0', classNames?.body)}
|
|
data-is-error-page={isErrorPage ? '' : undefined}
|
|
data-is-logged-in={Astro.locals.user !== null ? '' : undefined}
|
|
>
|
|
{announcement && <AnnouncementBanner announcement={announcement} transition:name="header-announcement" />}
|
|
{
|
|
deletionAnnouncement && (
|
|
<AnnouncementBanner
|
|
announcement={deletionAnnouncement}
|
|
transition:name="deletion-warning-announcement"
|
|
/>
|
|
)
|
|
}
|
|
<Header
|
|
classNames={{
|
|
nav: cn(
|
|
(widthClassName === 'max-w-none' || widthClassName === 'max-w-screen-2xl') && 'lg:px-8 2xl:px-12',
|
|
widthClassName
|
|
),
|
|
}}
|
|
showSplashText={showSplashText}
|
|
/>
|
|
|
|
{
|
|
actualSuccess.length > 0 && (
|
|
<ul class="container mx-auto my-4 space-y-4 px-4">
|
|
{actualSuccess.map((message) => (
|
|
<li class="font-title rounded-lg border border-green-500/30 bg-green-500/20 px-4 py-3 text-green-400">
|
|
{message}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)
|
|
}
|
|
|
|
{
|
|
actualErrors.length > 0 && (
|
|
<ul class="container mx-auto my-4 space-y-4 px-4">
|
|
{actualErrors.map((error) => (
|
|
<li class="font-title rounded-lg border border-red-500/30 bg-red-500/20 px-4 py-3 text-red-400">
|
|
{error}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)
|
|
}
|
|
|
|
<main
|
|
class={cn(
|
|
'container mx-auto mt-4 mb-12 grow px-4',
|
|
classNames?.main,
|
|
(widthClassName === 'max-w-none' || widthClassName === 'max-w-screen-2xl') && 'lg:px-8 2xl:px-12',
|
|
widthClassName
|
|
)}
|
|
>
|
|
<slot />
|
|
</main>
|
|
|
|
<Footer class={classNames?.footer} />
|
|
</body>
|
|
</html>
|