mirror of
https://github.com/documenso/documenso.git
synced 2025-11-18 10:42:01 +10:00
feat: migrate nextjs to rr7
This commit is contained in:
122
apps/remix/app/routes/_profile+/_layout.tsx
Normal file
122
apps/remix/app/routes/_profile+/_layout.tsx
Normal file
@ -0,0 +1,122 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { PlusIcon } from 'lucide-react';
|
||||
import { ChevronLeft } from 'lucide-react';
|
||||
import { Link, Outlet } from 'react-router';
|
||||
|
||||
import LogoIcon from '@documenso/assets/logo_icon.png';
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
|
||||
import { Header as AuthenticatedHeader } from '~/components/general/app-header';
|
||||
import { BrandingLogo } from '~/components/general/branding-logo';
|
||||
import { GenericErrorLayout } from '~/components/general/generic-error-layout';
|
||||
import { appMetaTags } from '~/utils/meta';
|
||||
|
||||
export function meta() {
|
||||
return appMetaTags('Profile');
|
||||
}
|
||||
|
||||
export default function PublicProfileLayout() {
|
||||
const session = useSession();
|
||||
|
||||
const [scrollY, setScrollY] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = () => {
|
||||
setScrollY(window.scrollY);
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', onScroll);
|
||||
|
||||
return () => window.removeEventListener('scroll', onScroll);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
{session ? (
|
||||
<AuthenticatedHeader user={session.user} teams={session.teams} />
|
||||
) : (
|
||||
<header
|
||||
className={cn(
|
||||
'supports-backdrop-blur:bg-background/60 bg-background/95 sticky top-0 z-[60] flex h-16 w-full items-center border-b border-b-transparent backdrop-blur duration-200',
|
||||
scrollY > 5 && 'border-b-border',
|
||||
)}
|
||||
>
|
||||
<div className="mx-auto flex w-full max-w-screen-xl items-center justify-between gap-x-4 px-4 md:px-8">
|
||||
<Link
|
||||
to="/"
|
||||
className="focus-visible:ring-ring ring-offset-background rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 md:inline"
|
||||
>
|
||||
<BrandingLogo className="hidden h-6 w-auto sm:block" />
|
||||
|
||||
<img
|
||||
src={LogoIcon}
|
||||
alt="Documenso Logo"
|
||||
width={48}
|
||||
height={48}
|
||||
className="h-10 w-auto sm:hidden dark:invert"
|
||||
/>
|
||||
</Link>
|
||||
|
||||
<div className="flex flex-row items-center justify-center">
|
||||
<p className="text-muted-foreground mr-4">
|
||||
<span className="text-sm sm:hidden">
|
||||
<Trans>Want your own public profile?</Trans>
|
||||
</span>
|
||||
<span className="hidden text-sm sm:block">
|
||||
<Trans>Like to have your own public profile with agreements?</Trans>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<Button asChild variant="secondary">
|
||||
<Link to="/signup">
|
||||
<div className="hidden flex-row items-center sm:flex">
|
||||
<PlusIcon className="mr-1 h-5 w-5" />
|
||||
<Trans>Create now</Trans>
|
||||
</div>
|
||||
|
||||
<span className="sm:hidden">
|
||||
<Trans>Create</Trans>
|
||||
</span>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
)}
|
||||
|
||||
<main className="my-8 px-4 md:my-12 md:px-8">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ErrorBoundary() {
|
||||
const errorCodeMap = {
|
||||
404: {
|
||||
subHeading: msg`404 Profile not found`,
|
||||
heading: msg`Oops! Something went wrong.`,
|
||||
message: msg`The profile you are looking for could not be found.`,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<GenericErrorLayout
|
||||
errorCodeMap={errorCodeMap}
|
||||
secondaryButton={null}
|
||||
primaryButton={
|
||||
<Button asChild className="w-32">
|
||||
<Link to="/">
|
||||
<ChevronLeft className="mr-2 h-4 w-4" />
|
||||
<Trans>Go Back</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
203
apps/remix/app/routes/_profile+/p.$url.tsx
Normal file
203
apps/remix/app/routes/_profile+/p.$url.tsx
Normal file
@ -0,0 +1,203 @@
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { FileIcon } from 'lucide-react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { Link, redirect } from 'react-router';
|
||||
|
||||
import { useOptionalSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { getPublicProfileByUrl } from '@documenso/lib/server-only/profile/get-public-profile-by-url';
|
||||
import { formatAvatarUrl } from '@documenso/lib/utils/avatars';
|
||||
import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
|
||||
import { formatDirectTemplatePath } from '@documenso/lib/utils/templates';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@documenso/ui/primitives/avatar';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@documenso/ui/primitives/table';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip';
|
||||
|
||||
import type { Route } from './+types/p.$url';
|
||||
|
||||
const BADGE_DATA = {
|
||||
Premium: {
|
||||
imageSrc: '/static/premium-user-badge.svg',
|
||||
name: 'Premium',
|
||||
},
|
||||
EarlySupporter: {
|
||||
imageSrc: '/static/early-supporter-badge.svg',
|
||||
name: 'Early supporter',
|
||||
},
|
||||
};
|
||||
|
||||
export async function loader({ params }: Route.LoaderArgs) {
|
||||
const { url: profileUrl } = params;
|
||||
|
||||
if (!profileUrl) {
|
||||
throw redirect('/');
|
||||
}
|
||||
|
||||
const publicProfile = await getPublicProfileByUrl({
|
||||
profileUrl,
|
||||
}).catch(() => null);
|
||||
|
||||
// Todo: Test
|
||||
if (!publicProfile || !publicProfile.profile.enabled) {
|
||||
throw new Response('Not Found', { status: 404 });
|
||||
}
|
||||
|
||||
return {
|
||||
publicProfile,
|
||||
};
|
||||
}
|
||||
|
||||
export default function PublicProfilePage({ loaderData }: Route.ComponentProps) {
|
||||
const { publicProfile } = loaderData;
|
||||
|
||||
const { profile, templates } = publicProfile;
|
||||
|
||||
const { user } = useOptionalSession();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-4 sm:py-32">
|
||||
<div className="flex flex-col items-center">
|
||||
<Avatar className="dark:border-border h-24 w-24 border-2 border-solid">
|
||||
{publicProfile.avatarImageId && (
|
||||
<AvatarImage src={formatAvatarUrl(publicProfile.avatarImageId)} />
|
||||
)}
|
||||
|
||||
<AvatarFallback className="text-sm text-gray-400">
|
||||
{extractInitials(publicProfile.name)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
<div className="mt-4 flex flex-row items-center justify-center">
|
||||
<h2 className="text-xl font-semibold md:text-2xl">{publicProfile.name}</h2>
|
||||
|
||||
{publicProfile.badge && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<img
|
||||
className="ml-2 flex items-center justify-center"
|
||||
alt="Profile badge"
|
||||
src={BADGE_DATA[publicProfile.badge.type].imageSrc}
|
||||
height={24}
|
||||
width={24}
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
|
||||
<TooltipContent className="flex flex-row items-start py-2 !pl-3 !pr-3.5">
|
||||
<img
|
||||
className="mt-0.5"
|
||||
alt="Profile badge"
|
||||
src={BADGE_DATA[publicProfile.badge.type].imageSrc}
|
||||
height={24}
|
||||
width={24}
|
||||
/>
|
||||
|
||||
<div className="ml-2">
|
||||
<p className="text-foreground text-base font-semibold">
|
||||
{BADGE_DATA[publicProfile.badge.type].name}
|
||||
</p>
|
||||
<p className="text-muted-foreground mt-0.5 text-sm">
|
||||
<Trans>
|
||||
Since {DateTime.fromJSDate(publicProfile.badge.since).toFormat('LLL ‘yy')}
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="text-muted-foreground mt-4 space-y-1">
|
||||
{(profile.bio ?? '').split('\n').map((line, index) => (
|
||||
<p
|
||||
key={index}
|
||||
className="max-w-[60ch] whitespace-pre-wrap break-words text-center text-sm"
|
||||
>
|
||||
{line}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{templates.length === 0 && (
|
||||
<div className="mt-4 w-full max-w-xl border-t pt-4">
|
||||
<p className="text-muted-foreground max-w-[60ch] whitespace-pre-wrap break-words text-center text-sm leading-relaxed">
|
||||
<Trans>
|
||||
It looks like {publicProfile.name} hasn't added any documents to their profile yet.
|
||||
</Trans>{' '}
|
||||
{!user?.id && (
|
||||
<span className="mt-2 inline-block">
|
||||
<Trans>
|
||||
While waiting for them to do so you can create your own Documenso account and get
|
||||
started with document signing right away.
|
||||
</Trans>
|
||||
</span>
|
||||
)}
|
||||
{'userId' in profile && user?.id === profile.userId && (
|
||||
<span className="mt-2 inline-block">
|
||||
<Trans>
|
||||
Go to your{' '}
|
||||
<Link to="/settings/public-profile" className="underline">
|
||||
public profile settings
|
||||
</Link>{' '}
|
||||
to add documents.
|
||||
</Trans>
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{templates.length > 0 && (
|
||||
<div className="mt-8 w-full max-w-xl rounded-md border">
|
||||
<Table className="w-full" overflowHidden>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-full rounded-tl-md bg-neutral-50 dark:bg-neutral-700">
|
||||
<Trans>Documents</Trans>
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{templates.map((template) => (
|
||||
<TableRow key={template.id}>
|
||||
<TableCell className="text-muted-foreground flex flex-col justify-between overflow-hidden text-sm sm:flex-row">
|
||||
<div className="flex flex-1 items-start justify-start gap-2">
|
||||
<FileIcon
|
||||
className="text-muted-foreground/40 h-8 w-8 flex-shrink-0"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
|
||||
<div className="flex flex-1 flex-col gap-4 overflow-hidden md:flex-row md:items-start md:justify-between">
|
||||
<div>
|
||||
<p className="text-foreground text-sm font-semibold leading-none">
|
||||
{template.publicTitle}
|
||||
</p>
|
||||
<p className="text-muted-foreground mt-1 line-clamp-3 max-w-[70ch] whitespace-normal text-xs">
|
||||
{template.publicDescription}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Button asChild className="w-20">
|
||||
<Link to={formatDirectTemplatePath(template.directLink.token)}>
|
||||
<Trans>Sign</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user