mirror of
https://github.com/documenso/documenso.git
synced 2025-11-15 17:21:41 +10:00
fix: wip
This commit is contained in:
@ -1,15 +1,22 @@
|
||||
import { Outlet, redirect } from 'react-router';
|
||||
|
||||
import { LimitsProvider } from '@documenso/ee/server-only/limits/provider/client';
|
||||
import { getSiteSettings } from '@documenso/lib/server-only/site-settings/get-site-settings';
|
||||
import { SITE_SETTINGS_BANNER_ID } from '@documenso/lib/server-only/site-settings/schemas/banner';
|
||||
|
||||
import { AppBanner } from '~/components/(dashboard)/layout/app-banner';
|
||||
import { Header } from '~/components/(dashboard)/layout/header';
|
||||
import { VerifyEmailBanner } from '~/components/(dashboard)/layout/verify-email-banner';
|
||||
|
||||
import type { Route } from './+types/_layout';
|
||||
|
||||
export const loader = ({ context }: Route.LoaderArgs) => {
|
||||
export const loader = async ({ context }: Route.LoaderArgs) => {
|
||||
const { session } = context;
|
||||
|
||||
const banner = await getSiteSettings().then((settings) =>
|
||||
settings.find((setting) => setting.id === SITE_SETTINGS_BANNER_ID),
|
||||
);
|
||||
|
||||
if (!session) {
|
||||
throw redirect('/signin');
|
||||
}
|
||||
@ -17,18 +24,18 @@ export const loader = ({ context }: Route.LoaderArgs) => {
|
||||
return {
|
||||
user: session.user,
|
||||
teams: session.teams,
|
||||
banner,
|
||||
};
|
||||
};
|
||||
|
||||
export default function Layout({ loaderData }: Route.ComponentProps) {
|
||||
const { user, teams } = loaderData;
|
||||
const { user, teams, banner } = loaderData;
|
||||
|
||||
return (
|
||||
<LimitsProvider>
|
||||
{!user.emailVerified && <VerifyEmailBanner email={user.email} />}
|
||||
|
||||
{/* // Todo: Banner */}
|
||||
{/* <Banner /> */}
|
||||
{banner && <AppBanner banner={banner} />}
|
||||
|
||||
<Header user={user} teams={teams} />
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { BarChart3, FileStack, Settings, Trophy, Users, Wallet2 } from 'lucide-react';
|
||||
import { Link, Outlet, redirect, useLocation } from 'react-router';
|
||||
import { getRequiredSessionContext } from 'server/utils/get-required-session-context';
|
||||
import { getRequiredLoaderSession } from 'server/utils/get-required-session-context';
|
||||
|
||||
import { isAdmin } from '@documenso/lib/next-auth/guards/is-admin';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
@ -10,7 +10,7 @@ import { Button } from '@documenso/ui/primitives/button';
|
||||
import type { Route } from './+types/_layout';
|
||||
|
||||
export function loader({ context }: Route.LoaderArgs) {
|
||||
const { user } = getRequiredSessionContext(context);
|
||||
const { user } = getRequiredLoaderSession(context);
|
||||
|
||||
if (!user || !isAdmin(user)) {
|
||||
throw redirect('/documents');
|
||||
|
||||
@ -3,7 +3,7 @@ import { useLingui } from '@lingui/react';
|
||||
import { DocumentStatus, TeamMemberRole } from '@prisma/client';
|
||||
import { ChevronLeft, Clock9, Users2 } from 'lucide-react';
|
||||
import { Link, redirect } from 'react-router';
|
||||
import { getRequiredSessionContext } from 'server/utils/get-required-session-context';
|
||||
import { getRequiredLoaderSession } from 'server/utils/get-required-session-context';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
@ -34,7 +34,7 @@ import { DocumentPageViewRecipients } from '~/components/general/document/docume
|
||||
import type { Route } from './+types/$id._index';
|
||||
|
||||
export async function loader({ params, context }: Route.LoaderArgs) {
|
||||
const { user, currentTeam: team } = getRequiredSessionContext(context);
|
||||
const { user, currentTeam: team } = getRequiredLoaderSession(context);
|
||||
|
||||
const { id } = params;
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import { Plural, Trans } from '@lingui/macro';
|
||||
import { DocumentStatus as InternalDocumentStatus, TeamMemberRole } from '@prisma/client';
|
||||
import { ChevronLeft, Users2 } from 'lucide-react';
|
||||
import { Link, redirect } from 'react-router';
|
||||
import { getRequiredSessionContext } from 'server/utils/get-required-session-context';
|
||||
import { getRequiredLoaderSession } from 'server/utils/get-required-session-context';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||
@ -18,7 +18,7 @@ import { superLoaderJson, useSuperLoaderData } from '~/utils/super-json-loader';
|
||||
import type { Route } from './+types/$id.edit';
|
||||
|
||||
export async function loader({ params, context }: Route.LoaderArgs) {
|
||||
const { user, currentTeam: team } = getRequiredSessionContext(context);
|
||||
const { user, currentTeam: team } = getRequiredLoaderSession(context);
|
||||
|
||||
const { id } = params;
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import type { Recipient } from '@prisma/client';
|
||||
import { ChevronLeft } from 'lucide-react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { Link, redirect } from 'react-router';
|
||||
import { getRequiredSessionContext } from 'server/utils/get-required-session-context';
|
||||
import { getRequiredLoaderSession } from 'server/utils/get-required-session-context';
|
||||
|
||||
import { getDocumentById } from '@documenso/lib/server-only/document/get-document-by-id';
|
||||
import { getRecipientsForDocument } from '@documenso/lib/server-only/recipient/get-recipients-for-document';
|
||||
@ -25,7 +25,7 @@ import type { Route } from './+types/$id.logs';
|
||||
export async function loader({ params, context }: Route.LoaderArgs) {
|
||||
const { id } = params;
|
||||
|
||||
const { user, currentTeam: team } = getRequiredSessionContext(context);
|
||||
const { user, currentTeam: team } = getRequiredLoaderSession(context);
|
||||
|
||||
const documentId = Number(id);
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import type { TemplateDirectLink } from '@prisma/client';
|
||||
import { TemplateType } from '@prisma/client';
|
||||
import { getRequiredSessionContext } from 'server/utils/get-required-session-context';
|
||||
import { getRequiredLoaderSession } from 'server/utils/get-required-session-context';
|
||||
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { getUserPublicProfile } from '@documenso/lib/server-only/user/get-user-public-profile';
|
||||
@ -44,7 +44,7 @@ const teamProfileText = {
|
||||
};
|
||||
|
||||
export async function loader({ context }: Route.LoaderArgs) {
|
||||
const { user } = getRequiredSessionContext(context);
|
||||
const { user } = getRequiredLoaderSession(context);
|
||||
|
||||
const { profile } = await getUserPublicProfile({
|
||||
userId: user.id,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { getRequiredSessionContext } from 'server/utils/get-required-session-context';
|
||||
import { getRequiredLoaderSession } from 'server/utils/get-required-session-context';
|
||||
|
||||
import { getUserTokens } from '@documenso/lib/server-only/public-api/get-all-user-tokens';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@ -12,7 +12,7 @@ import { ApiTokenForm } from '~/components/forms/token';
|
||||
import type { Route } from './+types/index';
|
||||
|
||||
export async function loader({ context }: Route.LoaderArgs) {
|
||||
const { user } = getRequiredSessionContext(context);
|
||||
const { user } = getRequiredLoaderSession(context);
|
||||
|
||||
// Todo: Use TRPC & use table instead
|
||||
const tokens = await getUserTokens({ userId: user.id });
|
||||
|
||||
@ -3,7 +3,7 @@ import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { ChevronLeft } from 'lucide-react';
|
||||
import { Link, Outlet, isRouteErrorResponse, redirect, useNavigate } from 'react-router';
|
||||
import { getRequiredSessionContext } from 'server/utils/get-required-session-context';
|
||||
import { getRequiredLoaderSession } from 'server/utils/get-required-session-context';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
@ -15,7 +15,7 @@ import { TeamProvider } from '~/providers/team';
|
||||
import type { Route } from './+types/_layout';
|
||||
|
||||
export const loader = ({ context }: Route.LoaderArgs) => {
|
||||
const { currentTeam } = getRequiredSessionContext(context);
|
||||
const { currentTeam } = getRequiredLoaderSession(context);
|
||||
|
||||
if (!currentTeam) {
|
||||
throw redirect('/documents');
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { Outlet } from 'react-router';
|
||||
import { getRequiredTeamSessionContext } from 'server/utils/get-required-session-context';
|
||||
import { getRequiredLoaderTeamSession } from 'server/utils/get-required-session-context';
|
||||
|
||||
import { canExecuteTeamAction } from '@documenso/lib/utils/teams';
|
||||
|
||||
@ -9,10 +9,9 @@ import { TeamSettingsMobileNav } from '~/components/general/teams/team-settings-
|
||||
|
||||
import type { Route } from '../+types/_layout';
|
||||
|
||||
export async function loader({ context }: Route.LoaderArgs) {
|
||||
const { currentTeam: team } = getRequiredTeamSessionContext(context);
|
||||
export function loader({ context }: Route.LoaderArgs) {
|
||||
const { currentTeam: team } = getRequiredLoaderTeamSession(context);
|
||||
|
||||
// Todo: Test that 404 page shows up from error.
|
||||
if (!team || !canExecuteTeamAction('MANAGE_TEAM', team.currentTeamMember.role)) {
|
||||
throw new Response(null, { status: 401 }); // Unauthorized.
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Plural, Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { getRequiredTeamSessionContext } from 'server/utils/get-required-session-context';
|
||||
import { getRequiredLoaderTeamSession } from 'server/utils/get-required-session-context';
|
||||
import type Stripe from 'stripe';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
@ -16,7 +16,7 @@ import { TeamBillingPortalButton } from '~/components/general/teams/team-billing
|
||||
import type { Route } from './+types/billing';
|
||||
|
||||
export async function loader({ context }: Route.LoaderArgs) {
|
||||
const { currentTeam: team } = getRequiredTeamSessionContext(context);
|
||||
const { currentTeam: team } = getRequiredLoaderTeamSession(context);
|
||||
|
||||
let teamSubscription: Stripe.Subscription | null = null;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getRequiredTeamSessionContext } from 'server/utils/get-required-session-context';
|
||||
import { getRequiredLoaderTeamSession } from 'server/utils/get-required-session-context';
|
||||
|
||||
import { getTeamPublicProfile } from '@documenso/lib/server-only/team/get-team-public-profile';
|
||||
|
||||
@ -7,7 +7,7 @@ import PublicProfilePage from '~/routes/_authenticated+/settings+/public-profile
|
||||
import type { Route } from './+types/public-profile';
|
||||
|
||||
export async function loader({ context }: Route.LoaderArgs) {
|
||||
const { user, currentTeam: team } = getRequiredTeamSessionContext(context);
|
||||
const { user, currentTeam: team } = getRequiredLoaderTeamSession(context);
|
||||
|
||||
const { profile } = await getTeamPublicProfile({
|
||||
userId: user.id,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { getRequiredTeamSessionContext } from 'server/utils/get-required-session-context';
|
||||
import { getRequiredLoaderTeamSession } from 'server/utils/get-required-session-context';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { getTeamTokens } from '@documenso/lib/server-only/public-api/get-all-team-tokens';
|
||||
@ -13,7 +13,7 @@ import { ApiTokenForm } from '~/components/forms/token';
|
||||
import type { Route } from './+types/tokens';
|
||||
|
||||
export async function loader({ context }: Route.LoaderArgs) {
|
||||
const { user, currentTeam: team } = getRequiredTeamSessionContext(context);
|
||||
const { user, currentTeam: team } = getRequiredLoaderTeamSession(context);
|
||||
|
||||
const tokens = await getTeamTokens({ userId: user.id, teamId: team.id }).catch(() => null);
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import { Trans } from '@lingui/macro';
|
||||
import { DocumentSigningOrder, SigningStatus } from '@prisma/client';
|
||||
import { ChevronLeft, LucideEdit } from 'lucide-react';
|
||||
import { Link, redirect } from 'react-router';
|
||||
import { getRequiredSessionContext } from 'server/utils/get-required-session-context';
|
||||
import { getRequiredLoaderSession } from 'server/utils/get-required-session-context';
|
||||
|
||||
import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id';
|
||||
import { formatDocumentsPath, formatTemplatesPath } from '@documenso/lib/utils/teams';
|
||||
@ -25,7 +25,7 @@ import { superLoaderJson, useSuperLoaderData } from '~/utils/super-json-loader';
|
||||
import type { Route } from './+types/$id._index';
|
||||
|
||||
export async function loader({ params, context }: Route.LoaderArgs) {
|
||||
const { user, currentTeam: team } = getRequiredSessionContext(context);
|
||||
const { user, currentTeam: team } = getRequiredLoaderSession(context);
|
||||
|
||||
const { id } = params;
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { ChevronLeft } from 'lucide-react';
|
||||
import { Link, redirect } from 'react-router';
|
||||
import { getRequiredSessionContext } from 'server/utils/get-required-session-context';
|
||||
import { getRequiredLoaderSession } from 'server/utils/get-required-session-context';
|
||||
|
||||
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||
import { getTemplateById } from '@documenso/lib/server-only/template/get-template-by-id';
|
||||
@ -16,7 +16,7 @@ import { TemplateDirectLinkDialogWrapper } from '../../../components/dialogs/tem
|
||||
import type { Route } from './+types/$id.edit';
|
||||
|
||||
export async function loader({ context, params }: Route.LoaderArgs) {
|
||||
const { user, currentTeam: team } = getRequiredSessionContext(context);
|
||||
const { user, currentTeam: team } = getRequiredLoaderSession(context);
|
||||
|
||||
const { id } = params;
|
||||
|
||||
|
||||
@ -16,12 +16,6 @@ import { superLoaderJson, useSuperLoaderData } from '~/utils/super-json-loader';
|
||||
|
||||
import type { Route } from './+types/_index';
|
||||
|
||||
export type TemplatesDirectPageProps = {
|
||||
params: {
|
||||
token: string;
|
||||
};
|
||||
};
|
||||
|
||||
export async function loader({ params, context }: Route.LoaderArgs) {
|
||||
const { token } = params;
|
||||
|
||||
|
||||
@ -2,12 +2,6 @@ import { Outlet } from 'react-router';
|
||||
|
||||
import backgroundPattern from '@documenso/assets/images/background-pattern.png';
|
||||
|
||||
import type { Route } from './+types/_layout';
|
||||
|
||||
export const loader = async (args: Route.LoaderArgs) => {
|
||||
//
|
||||
};
|
||||
|
||||
export default function Layout() {
|
||||
return (
|
||||
<main className="relative flex min-h-screen flex-col items-center justify-center overflow-hidden px-4 py-12 md:p-12 lg:p-24">
|
||||
|
||||
@ -17,7 +17,7 @@ export async function loader({ params }: Route.LoaderArgs) {
|
||||
const isValid = await getResetTokenValidity({ token });
|
||||
|
||||
if (!isValid) {
|
||||
redirect('/reset-password');
|
||||
throw redirect('/reset-password');
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@ -0,0 +1,152 @@
|
||||
import { ImageResponse } from 'next/og';
|
||||
|
||||
import { P, match } from 'ts-pattern';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
|
||||
import type { ShareHandlerAPIResponse } from '../api+/share';
|
||||
import type { Route } from './+types/share.$slug.opengraph';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
const CARD_OFFSET_TOP = 173;
|
||||
const CARD_OFFSET_LEFT = 307;
|
||||
const CARD_WIDTH = 590;
|
||||
const CARD_HEIGHT = 337;
|
||||
|
||||
const IMAGE_SIZE = {
|
||||
width: 1200,
|
||||
height: 630,
|
||||
};
|
||||
|
||||
export const loader = async ({ params }: Route.LoaderArgs) => {
|
||||
const { slug } = params;
|
||||
|
||||
const baseUrl = NEXT_PUBLIC_WEBAPP_URL();
|
||||
|
||||
const [interSemiBold, interRegular, caveatRegular, shareFrameImage] = await Promise.all([
|
||||
fetch(new URL(`${baseUrl}/fonts/inter-semibold.ttf`, import.meta.url)).then(async (res) =>
|
||||
res.arrayBuffer(),
|
||||
),
|
||||
fetch(new URL(`${baseUrl}/fonts/inter-regular.ttf`, import.meta.url)).then(async (res) =>
|
||||
res.arrayBuffer(),
|
||||
),
|
||||
fetch(new URL(`${baseUrl}/fonts/caveat-regular.ttf`, import.meta.url)).then(async (res) =>
|
||||
res.arrayBuffer(),
|
||||
),
|
||||
fetch(new URL(`${baseUrl}/static/og-share-frame2.png`, import.meta.url)).then(async (res) =>
|
||||
res.arrayBuffer(),
|
||||
),
|
||||
]);
|
||||
|
||||
const recipientOrSender: ShareHandlerAPIResponse = await fetch(
|
||||
new URL(`/api/share?slug=${slug}`, baseUrl),
|
||||
).then(async (res) => res.json());
|
||||
|
||||
if ('error' in recipientOrSender) {
|
||||
return Response.json({ error: 'Not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
const isRecipient = 'Signature' in recipientOrSender;
|
||||
|
||||
const signatureImage = match(recipientOrSender)
|
||||
.with({ signatures: P.array(P._) }, (recipient) => {
|
||||
return recipient.signatures?.[0]?.signatureImageAsBase64 || null;
|
||||
})
|
||||
.otherwise((sender) => {
|
||||
return sender.signature || null;
|
||||
});
|
||||
|
||||
const signatureName = match(recipientOrSender)
|
||||
.with({ signatures: P.array(P._) }, (recipient) => {
|
||||
return recipient.name || recipient.email;
|
||||
})
|
||||
.otherwise((sender) => {
|
||||
return sender.name || sender.email;
|
||||
});
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div tw="relative flex h-full w-full bg-white">
|
||||
{/* @ts-expect-error Lack of typing from ImageResponse */}
|
||||
<img src={shareFrameImage} alt="og-share-frame" tw="absolute inset-0 w-full h-full" />
|
||||
|
||||
{signatureImage ? (
|
||||
<div
|
||||
tw="absolute py-6 px-12 flex items-center justify-center text-center"
|
||||
style={{
|
||||
top: `${CARD_OFFSET_TOP}px`,
|
||||
left: `${CARD_OFFSET_LEFT}px`,
|
||||
width: `${CARD_WIDTH}px`,
|
||||
height: `${CARD_HEIGHT}px`,
|
||||
}}
|
||||
>
|
||||
<img src={signatureImage} alt="signature" tw="opacity-60 h-full max-w-[100%]" />
|
||||
</div>
|
||||
) : (
|
||||
<p
|
||||
tw="absolute py-6 px-12 -mt-2 flex items-center justify-center text-center text-slate-500"
|
||||
style={{
|
||||
fontFamily: 'Caveat',
|
||||
fontSize: `${Math.max(
|
||||
Math.min((CARD_WIDTH * 1.5) / signatureName.length, 80),
|
||||
36,
|
||||
)}px`,
|
||||
top: `${CARD_OFFSET_TOP}px`,
|
||||
left: `${CARD_OFFSET_LEFT}px`,
|
||||
width: `${CARD_WIDTH}px`,
|
||||
height: `${CARD_HEIGHT}px`,
|
||||
}}
|
||||
>
|
||||
{signatureName}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div
|
||||
tw="absolute flex w-full"
|
||||
style={{
|
||||
top: `${CARD_OFFSET_TOP - 78}px`,
|
||||
left: `${CARD_OFFSET_LEFT}px`,
|
||||
}}
|
||||
>
|
||||
<h2
|
||||
tw="text-xl"
|
||||
style={{
|
||||
color: '#828282',
|
||||
fontFamily: 'Inter',
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
{isRecipient ? 'Document Signed!' : 'Document Sent!'}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
...IMAGE_SIZE,
|
||||
fonts: [
|
||||
{
|
||||
name: 'Caveat',
|
||||
data: caveatRegular,
|
||||
style: 'italic',
|
||||
},
|
||||
{
|
||||
name: 'Inter',
|
||||
data: interRegular,
|
||||
style: 'normal',
|
||||
weight: 400,
|
||||
},
|
||||
{
|
||||
name: 'Inter',
|
||||
data: interSemiBold,
|
||||
style: 'normal',
|
||||
weight: 600,
|
||||
},
|
||||
],
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, OPTIONS',
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
39
apps/remix/app/routes/_unauthenticated+/share.$slug.tsx
Normal file
39
apps/remix/app/routes/_unauthenticated+/share.$slug.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import { redirect } from 'react-router';
|
||||
|
||||
import { NEXT_PUBLIC_MARKETING_URL } from '@documenso/lib/constants/app';
|
||||
|
||||
import type { Route } from './+types/share.$slug';
|
||||
|
||||
// Todo: Test meta.
|
||||
export function meta({ params: { slug } }: Route.MetaArgs) {
|
||||
return [
|
||||
{ title: 'Documenso - Share' },
|
||||
{ description: 'I just signed a document in style with Documenso!' },
|
||||
{
|
||||
openGraph: {
|
||||
title: 'Documenso - Join the open source signing revolution',
|
||||
description: 'I just signed with Documenso!',
|
||||
type: 'website',
|
||||
images: [`/share/${slug}/opengraph`],
|
||||
},
|
||||
},
|
||||
{
|
||||
twitter: {
|
||||
site: '@documenso',
|
||||
card: 'summary_large_image',
|
||||
images: [`/share/${slug}/opengraph`],
|
||||
description: 'I just signed with Documenso!',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export const loader = ({ request }: Route.LoaderArgs) => {
|
||||
const userAgent = request.headers.get('User-Agent') ?? '';
|
||||
|
||||
if (/bot|facebookexternalhit|WhatsApp|google|bing|duckduckbot|MetaInspector/i.test(userAgent)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return redirect(NEXT_PUBLIC_MARKETING_URL());
|
||||
};
|
||||
73
apps/remix/app/routes/api+/branding.logo.team.$teamId.ts
Normal file
73
apps/remix/app/routes/api+/branding.logo.team.$teamId.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import sharp from 'sharp';
|
||||
|
||||
import { getFile } from '@documenso/lib/universal/upload/get-file';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import type { Route } from './+types/branding.logo.team.$teamId';
|
||||
|
||||
export async function loader({ params }: Route.LoaderArgs) {
|
||||
const teamId = Number(params.teamId);
|
||||
|
||||
if (teamId === 0 || Number.isNaN(teamId)) {
|
||||
return Response.json(
|
||||
{
|
||||
status: 'error',
|
||||
message: 'Invalid team ID',
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const settings = await prisma.teamGlobalSettings.findFirst({
|
||||
where: {
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!settings || !settings.brandingEnabled) {
|
||||
return Response.json(
|
||||
{
|
||||
status: 'error',
|
||||
message: 'Not found',
|
||||
},
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
if (!settings.brandingLogo) {
|
||||
return Response.json(
|
||||
{
|
||||
status: 'error',
|
||||
message: 'Not found',
|
||||
},
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
const file = await getFile(JSON.parse(settings.brandingLogo)).catch(() => null);
|
||||
|
||||
if (!file) {
|
||||
return Response.json(
|
||||
{
|
||||
status: 'error',
|
||||
message: 'Not found',
|
||||
},
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
const img = await sharp(file)
|
||||
.toFormat('png', {
|
||||
quality: 80,
|
||||
})
|
||||
.toBuffer();
|
||||
|
||||
return new Response(img, {
|
||||
headers: {
|
||||
'Content-Type': 'image/png',
|
||||
'Content-Length': img.length.toString(),
|
||||
// Stale while revalidate for 1 hours to 24 hours
|
||||
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400',
|
||||
},
|
||||
});
|
||||
}
|
||||
22
apps/remix/app/routes/api+/health.ts
Normal file
22
apps/remix/app/routes/api+/health.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
export async function loader() {
|
||||
try {
|
||||
await prisma.$queryRaw`SELECT 1`;
|
||||
|
||||
return Response.json({
|
||||
status: 'ok',
|
||||
message: 'All systems operational',
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
return Response.json(
|
||||
{
|
||||
status: 'error',
|
||||
message: err instanceof Error ? err.message : 'Unknown error',
|
||||
},
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
27
apps/remix/app/routes/api+/share.ts
Normal file
27
apps/remix/app/routes/api+/share.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { getRecipientOrSenderByShareLinkSlug } from '@documenso/lib/server-only/share/get-recipient-or-sender-by-share-link-slug';
|
||||
|
||||
import type { Route } from './+types/share';
|
||||
|
||||
export type ShareHandlerAPIResponse =
|
||||
| Awaited<ReturnType<typeof getRecipientOrSenderByShareLinkSlug>>
|
||||
| { error: string };
|
||||
|
||||
// Todo: Test
|
||||
export async function loader({ request }: Route.LoaderArgs) {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
const slug = url.searchParams.get('slug');
|
||||
|
||||
if (typeof slug !== 'string') {
|
||||
throw new Error('Invalid slug');
|
||||
}
|
||||
|
||||
const data = await getRecipientOrSenderByShareLinkSlug({
|
||||
slug,
|
||||
});
|
||||
|
||||
return Response.json(data);
|
||||
} catch (error) {
|
||||
return Response.json({ error: 'Not found' }, { status: 404 });
|
||||
}
|
||||
}
|
||||
11
apps/remix/app/routes/api+/stripe.webhook.ts
Normal file
11
apps/remix/app/routes/api+/stripe.webhook.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { stripeWebhookHandler } from '@documenso/ee/server-only/stripe/webhook/handler';
|
||||
|
||||
// Todo
|
||||
// export const config = {
|
||||
// api: { bodyParser: false },
|
||||
// };
|
||||
import type { Route } from './+types/webhook.trigger';
|
||||
|
||||
export async function loader({ request }: Route.LoaderArgs) {
|
||||
return stripeWebhookHandler(request);
|
||||
}
|
||||
17
apps/remix/app/routes/api+/webhook.trigger.ts
Normal file
17
apps/remix/app/routes/api+/webhook.trigger.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { handlerTriggerWebhooks } from '@documenso/lib/server-only/webhooks/trigger/handler';
|
||||
|
||||
import type { Route } from './+types/webhook.trigger';
|
||||
|
||||
// Todo
|
||||
// export const config = {
|
||||
// maxDuration: 300,
|
||||
// api: {
|
||||
// bodyParser: {
|
||||
// sizeLimit: '50mb',
|
||||
// },
|
||||
// },
|
||||
// };
|
||||
|
||||
export async function loader({ request }: Route.LoaderArgs) {
|
||||
return handlerTriggerWebhooks(request);
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { data } from 'react-router';
|
||||
import { getRequiredSessionContext } from 'server/utils/get-required-session-context';
|
||||
import { getRequiredLoaderSession } from 'server/utils/get-required-session-context';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||
@ -48,7 +48,7 @@ export async function loader({ params, context }: Route.LoaderArgs) {
|
||||
);
|
||||
}
|
||||
|
||||
const { user } = getRequiredSessionContext(context);
|
||||
const { user } = getRequiredLoaderSession(context);
|
||||
|
||||
const { derivedRecipientAccessAuth } = extractDocumentAuthMethods({
|
||||
documentAuth: template.authOptions,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { data } from 'react-router';
|
||||
import { getRequiredSessionContext } from 'server/utils/get-required-session-context';
|
||||
import { getRequiredLoaderSession } from 'server/utils/get-required-session-context';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||
@ -27,7 +27,7 @@ export async function loader({ params, context }: Route.LoaderArgs) {
|
||||
|
||||
const token = params.url;
|
||||
|
||||
const { user } = getRequiredSessionContext(context);
|
||||
const { user } = getRequiredLoaderSession(context);
|
||||
|
||||
const [document, fields, recipient] = await Promise.all([
|
||||
getDocumentAndSenderByToken({
|
||||
|
||||
Reference in New Issue
Block a user