Compare commits

..

11 Commits

Author SHA1 Message Date
Mythie
e8c2ca8890 fix: mask documents in search 2024-01-22 12:32:19 +11:00
Lucas Smith
1191e1d9c3 feat: linear-gh blogpost (#827) 2024-01-20 12:18:23 +11:00
Lucas Smith
9c1e1f50a8 fix: mask recipient tokens for non-owners 2024-01-20 01:14:34 +00:00
Timur Ercan
efb9e9f3ec Merge branch 'main' into feature/linear-gh 2024-01-18 17:17:01 +01:00
Timur Ercan
a7672545d7 Update apps/marketing/content/blog/linear-gh.mdx
Co-authored-by: Adithya Krishna  <aadithya794@gmail.com>
2024-01-18 17:16:49 +01:00
Timur Ercan
1a10cd2ae1 Update apps/marketing/content/blog/linear-gh.mdx
Co-authored-by: Adithya Krishna  <aadithya794@gmail.com>
2024-01-18 17:16:28 +01:00
Timur Ercan
1a73f3e007 chore: feedback and phrasing 2024-01-11 14:27:44 +01:00
Timur Ercan
ea0120abc8 chore: typo 2024-01-10 16:51:42 +01:00
Timur Ercan
b501ffdee9 chore: images 2024-01-10 16:37:53 +01:00
Timur Ercan
31050d6b7b chore: spelling 2024-01-10 16:14:28 +01:00
Timur Ercan
ed1998278a feat: draft github blogpost 2024-01-10 16:14:02 +01:00
34 changed files with 309 additions and 542 deletions

View File

@@ -0,0 +1,115 @@
---
title: Moving from Linear to GitHub & LIVE Roadmap 2.0
description: We are leaving linear and are going all in on GitHub. Here is how we do it.
authorName: 'Timur Ercan'
authorImage: '/blog/blog-author-timur.jpeg'
authorRole: 'Co-Founder'
date: 2024-01-10
Tags:
- GitHub
- Backlog
- Roadmap
---
# From Linear to GitHub
> TLDR; We are leaving Linear and are using only GitHub going forward. We no longer communicate feature timelines, only what we are working on and what's next.
If you follow us, you know we have been in full-on build mode. We are building, the community is building, it's great. Building is our daily business, so we think a lot about improving our approach to doing it.
Our most recent approach is to reduce the number of tools and platforms we use. Every tool we use
- Reduces the average time you spend on the tool
- Reduces your focus
- Increases mental load to keep all points of interest in mind
We thought about where we spend the most time, and hardly surprising: it's GitHub. Not only do we spend a lot of time there, but we also WANT to spend a lot of time there because:
- It's where the community contributes, and we are all about community
- It's where we show the world what we are working on
# The old structure
So far, we have been using Linear for our Backlog/ Task Management and synced issues we want to showcase or work on with the community via synclinear.com. Not only did we have our development issues there, but since
we have our own resident founding designer, we created a proper design backlog to structure our design workflows.
# The new structure
We moved everything to GitHub once we realized our focus was already there. This has a few key benefits:
- Reducing dilution of attention and time: You can hang out on GitHub without risk of missing much
- Putting different aspects of Documenso close to each other: Development, Design, Community
- Keep long-term, niche, and very abstract issues out of the main repo so we don't get desensitized by large issue numbers
To achieve this, we created a few GitHub repositories to host issues, with the main repository remaining the central point of interest, especially for the community.
## 1. Main Repository - Day to day Issues and the shorter-term roadmap (LIVE Roadmap 2.0)
> [github.com/documenso/documenso](https://github.com/documenso/documenso)
Apart from the source code of the Documenso app and website, the main repo houses issues raised by the community and issues where we invite the community to participate.
With the overhauling of our issue management, we are also updating our progress communication. While the software and product development process is highly complex,
we try to give as much insight into what we do as possible. To that end, we went through 3 phases, three being what we do now.
1. **One extensive roadmap**: Initially we had one roadmap and were (very) slowly checking off boxes there (via a "Roadmap" milestone). While this is easy, it's also pretty imprecise and not practical as the project grows
2. **Estimated releases per quarter**: To give better guidance, we tried communicating our goals for the quarter; a pretty big window we thought we could roughly "hit". While the idea of not being too detailed was good, it is tough to estimate when some significant things are done if you do a lot of minor/ other things in parallel,
like working with the community and tuning things you go. Hitting time targets is tricky because there may be better things to do than sticking to that time target. This is always much easier to grasp for the people closely involved. The fallacy is to assume the thing you plan for exists in a vacuum.
3. Since we do not want to limit ourselves in choosing the most effective course but still give some insight into what's going on and what's coming up, we updated the live roadmap [https://documen.so/live](https://documen.so/live). It now shows what we are currently working on and what we plan on doing next. We do not provide
a specific timeline anymore since we couldn't even if we wanted to. Of course, we set our short-term goals based on what's best for the community. We give updates on the issues being worked on as well as possible.
## 2. Public Backlog - The longer-term roadmap
> [github.com/documenso/backlog](https://github.com/documenso/backlog)
The public backlog houses everything we want to build eventually. We do not provide a specific timeline of when that might happen. If we decide against something, it will be removed from the public backlog, as we consider this our long-term vision for Documenso. If you are interested in something on the roadmap, comment on the issue or post on Discord. This helps us gauge interest in specific features.
**Issues in the public backlog are not** available to be worked on. For issues to work on, please check the main repository issues. The issues found here are scoped broader since they are not meant for immediate execution but rather give a sense of where Documenso is going and what we consider part of our domain.
## 3. Internal Backlog
> github.com/documenso/backlog-internal
<figure>
<MdxNextImage
src="/blog/gh1.png"
width="1260"
height="630"
alt="GitHub: Development Board"
/>
<figcaption className="text-center">
Our internal Kanban for development
</figcaption>
</figure>
This serves as the direct replacement for our Linear backlog. Here, we manage issues that are either too small or short-term for inclusion in the long-term roadmap, yet too specialized or fundamental to be integrated into the main repository. Our development Kanban board is implemented using a GitHub project.
## 4. Internal Design Backlog
> github.com/documenso/design-internal
<figure>
<MdxNextImage
src="/blog/gh2.png"
width="1260"
height="630"
alt="GitHub: Design Board"
/>
<figcaption className="text-center">
Our internal Kanban for design
</figcaption>
</figure>
This is the design equivalent of the internal backlog. The internal design backlog houses our design projects that include the exploration of new features, detailed UI designs, and improving the platform overall.
It's similar to the Kanban board for the development backlog.
## 5. Public Design Repository
> [github.com/documenso/backlog-design](https://github.com/documenso/design)
While the internal design backlog also existed in Linear, the public design repository is new. Since designing in the open is tricky, we opted to publish the detailed design artifacts with the corresponding feature instead.
We already have design.documenso.com housing our general design system. Here, we will publish the specifics of how we applied this to each feature. We will publish the first artifacts here soon, what may be in the cards can be found on the [LIVE Roadmap](https://documen.so/live).
Feel free to connect with us on [Twitter / X](https://twitter.com/eltimuro) (DM open) or [Discord](https://documen.so/discord) if you have any questions or comments! We're always here to help and would love to hear from you :)
Best from Hamburg\
Timur

View File

@@ -1,4 +1,3 @@
const million = require('million/compiler');
/* eslint-disable @typescript-eslint/no-var-requires */
const fs = require('fs');
const path = require('path');
@@ -6,11 +5,11 @@ const { withContentlayer } = require('next-contentlayer');
const ENV_FILES = ['.env', '.env.local', `.env.${process.env.NODE_ENV || 'development'}`];
for (file of ENV_FILES) {
ENV_FILES.forEach((file) => {
require('dotenv').config({
path: path.join(__dirname, `../../${file}`),
});
}
});
// !: This is a temp hack to get caveat working without placing it back in the public directory.
// !: By inlining this at build time we should be able to sign faster.
@@ -95,6 +94,4 @@ const config = {
},
};
module.exports = million.next(
withContentlayer(config), { auto: { rsc: true } }
);
module.exports = withContentlayer(config);

View File

@@ -24,7 +24,6 @@
"lucide-react": "^0.279.0",
"luxon": "^3.4.0",
"micro": "^10.0.1",
"million": "^2.6.4",
"next": "14.0.3",
"next-auth": "4.24.5",
"next-contentlayer": "^0.3.4",

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

View File

@@ -20,7 +20,7 @@ export const generateMetadata = ({ params }: { params: { content: string } }) =>
const mdxComponents: MDXComponents = {
MdxNextImage: (props: { width: number; height: number; alt?: string; src: string }) => (
<Image width={props.width} height={props.height} src={props.src} alt={props.alt ?? ''} />
<Image {...props} alt={props.alt ?? ''} />
),
};

View File

@@ -25,7 +25,7 @@ export const generateMetadata = ({ params }: { params: { post: string } }) => {
const mdxComponents: MDXComponents = {
MdxNextImage: (props: { width: number; height: number; alt?: string; src: string }) => (
<Image width={props.width} height={props.height} src={props.src} alt={props.alt ?? ''} />
<Image {...props} alt={props.alt ?? ''} />
),
};

View File

@@ -1,6 +1,6 @@
'use client';
import type { HTMLAttributes } from 'react';
import { HTMLAttributes } from 'react';
import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
@@ -24,6 +24,7 @@ export const BarMetric = <T extends Record<string, Record<keyof T[string], unkno
label,
chartHeight = 400,
extraInfo,
...props
}: BarMetricProps<T>) => {
const formattedData = Object.keys(data)
.map((key) => ({
@@ -33,7 +34,7 @@ export const BarMetric = <T extends Record<string, Record<keyof T[string], unkno
.reverse();
return (
<div className={cn('flex flex-col', className)}>
<div className={cn('flex flex-col', className)} {...props}>
<div className="flex items-center px-4">
<h3 className="text-lg font-semibold">{title}</h3>
<span>{extraInfo}</span>

View File

@@ -1,7 +1,6 @@
'use client';
import type { HTMLAttributes } from 'react';
import { useEffect, useState } from 'react';
import { HTMLAttributes, useEffect, useState } from 'react';
import { Cell, Legend, Pie, PieChart, Tooltip } from 'recharts';
@@ -42,14 +41,14 @@ const renderCustomizedLabel = ({
export type CapTableProps = HTMLAttributes<HTMLDivElement>;
export const CapTable = ({ className }: CapTableProps) => {
export const CapTable = ({ className, ...props }: CapTableProps) => {
const [isSSR, setIsSSR] = useState(true);
useEffect(() => {
setIsSSR(false);
}, []);
return (
<div className={cn('flex flex-col', className)}>
<div className={cn('flex flex-col', className)} {...props}>
<h3 className="px-4 text-lg font-semibold">Cap Table</h3>
<div className="border-border mt-2.5 flex flex-1 items-center justify-center rounded-2xl border shadow-sm hover:shadow">
@@ -77,7 +76,7 @@ export const CapTable = ({ className }: CapTableProps) => {
/>
<Tooltip
formatter={(percent: number, name, props) => {
return [`${percent}%`, name || props.name || props.payload.name];
return [`${percent}%`, name || props['name'] || props['payload']['name']];
}}
/>
</PieChart>

View File

@@ -1,6 +1,6 @@
'use client';
import type { HTMLAttributes } from 'react';
import { HTMLAttributes } from 'react';
import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
@@ -11,14 +11,14 @@ export type FundingRaisedProps = HTMLAttributes<HTMLDivElement> & {
data: Record<string, string | number>[];
};
export const FundingRaised = ({ className, data }: FundingRaisedProps) => {
export const FundingRaised = ({ className, data, ...props }: FundingRaisedProps) => {
const formattedData = data.map((item) => ({
amount: Number(item.amount),
date: formatMonth(item.date as string),
}));
return (
<div className={cn('flex flex-col', className)}>
<div className={cn('flex flex-col', className)} {...props}>
<h3 className="px-4 text-lg font-semibold">Total Funding Raised</h3>
<div className="border-border mt-2.5 flex flex-1 flex-col items-center justify-center rounded-2xl border p-6 pl-2 pt-12 shadow-sm hover:shadow">

View File

@@ -1,4 +1,4 @@
import type { HTMLAttributes } from 'react';
import { HTMLAttributes } from 'react';
import { cn } from '@documenso/ui/lib/utils';
@@ -7,9 +7,9 @@ export type MetricCardProps = HTMLAttributes<HTMLDivElement> & {
value: string;
};
export const MetricCard = ({ className, title, value }: MetricCardProps) => {
export const MetricCard = ({ className, title, value, ...props }: MetricCardProps) => {
return (
<div className={cn('rounded-md border p-4 shadow-sm hover:shadow', className)}>
<div className={cn('rounded-md border p-4 shadow-sm hover:shadow', className)} {...props}>
<h4 className="text-muted-foreground text-sm font-medium">{title}</h4>
<p className="mb-2 mt-6 text-4xl font-bold">{value}</p>

View File

@@ -141,12 +141,7 @@ export default async function OpenPage() {
<p className="text-muted-foreground mt-4 max-w-[60ch] text-center text-lg leading-normal">
All our metrics, finances, and learnings are public. We believe in transparency and want
to share our journey with you. You can read more about why here:{' '}
<a
className="font-bold"
href="https://documenso.com/blog/pre-seed"
target="_blank"
rel="noreferrer"
>
<a className="font-bold" href="https://documenso.com/blog/pre-seed" target="_blank">
Announcing Open Metrics
</a>
</p>

View File

@@ -1,4 +1,4 @@
import type { HTMLAttributes } from 'react';
import { HTMLAttributes } from 'react';
import { cn } from '@documenso/ui/lib/utils';
import {
@@ -14,9 +14,9 @@ import { SALARY_BANDS } from '~/app/(marketing)/open/data';
export type SalaryBandsProps = HTMLAttributes<HTMLDivElement>;
export const SalaryBands = ({ className }: SalaryBandsProps) => {
export const SalaryBands = ({ className, ...props }: SalaryBandsProps) => {
return (
<div className={cn('flex flex-col', className)}>
<div className={cn('flex flex-col', className)} {...props}>
<h3 className="px-4 text-lg font-semibold">Global Salary Bands</h3>
<div className="border-border mt-2.5 flex-1 rounded-2xl border shadow-sm hover:shadow">
@@ -30,7 +30,7 @@ export const SalaryBands = ({ className }: SalaryBandsProps) => {
</TableHeader>
<TableBody>
{SALARY_BANDS.map((band, index) => (
<TableRow key={band.title + index.toString()}>
<TableRow key={index}>
<TableCell className="font-medium">{band.title}</TableCell>
<TableCell>{band.seniority}</TableCell>
<TableCell className="text-right">

View File

@@ -1,4 +1,4 @@
import type { HTMLAttributes } from 'react';
import { HTMLAttributes } from 'react';
import { cn } from '@documenso/ui/lib/utils';
import {
@@ -14,9 +14,9 @@ import { TEAM_MEMBERS } from './data';
export type TeamMembersProps = HTMLAttributes<HTMLDivElement>;
export const TeamMembers = ({ className }: TeamMembersProps) => {
export const TeamMembers = ({ className, ...props }: TeamMembersProps) => {
return (
<div className={cn('flex flex-col', className)}>
<div className={cn('flex flex-col', className)} {...props}>
<h2 className="px-4 text-2xl font-semibold">Team</h2>
<div className="border-border mt-2.5 flex-1 rounded-2xl border shadow-sm hover:shadow">

View File

@@ -34,7 +34,13 @@ export const ConfettiScreen = ({
}
return createPortal(
<Confetti className="w-full" numberOfPieces={numberOfPieces} width={width} height={height} />,
<Confetti
{...props}
className="w-full"
numberOfPieces={numberOfPieces}
width={width}
height={height}
/>,
document.body,
);
};

View File

@@ -1,4 +1,4 @@
import type { HTMLAttributes } from 'react';
import { HTMLAttributes } from 'react';
import Image from 'next/image';
@@ -11,9 +11,12 @@ import { Card, CardContent } from '@documenso/ui/primitives/card';
export type FasterSmarterBeautifulBentoProps = HTMLAttributes<HTMLDivElement>;
export const FasterSmarterBeautifulBento = ({ className }: FasterSmarterBeautifulBentoProps) => {
export const FasterSmarterBeautifulBento = ({
className,
...props
}: FasterSmarterBeautifulBentoProps) => {
return (
<div className={cn('relative', className)}>
<div className={cn('relative', className)} {...props}>
<div className="absolute inset-0 -z-10 flex items-center justify-center">
<Image
src={backgroundPattern}

View File

@@ -35,9 +35,9 @@ const FOOTER_LINKS = [
{ href: '/privacy', text: 'Privacy' },
];
export const Footer = ({ className }: FooterProps) => {
export const Footer = ({ className, ...props }: FooterProps) => {
return (
<div className={cn('border-t py-12', className)}>
<div className={cn('border-t py-12', className)} {...props}>
<div className="mx-auto flex w-full max-w-screen-xl flex-wrap items-start justify-between gap-8 px-8">
<div className="flex-shrink-0">
<Link href="/">
@@ -53,7 +53,7 @@ export const Footer = ({ className }: FooterProps) => {
<div className="mt-4 flex flex-wrap items-center gap-x-4 gap-y-4">
{SOCIAL_LINKS.map((link, index) => (
<Link
key={link.href + index.toString()}
key={index}
href={link.href}
target="_blank"
className="text-muted-foreground hover:text-muted-foreground/80"
@@ -67,7 +67,7 @@ export const Footer = ({ className }: FooterProps) => {
<div className="grid w-full max-w-sm grid-cols-2 gap-x-4 gap-y-2 md:w-auto md:gap-x-8">
{FOOTER_LINKS.map((link, index) => (
<Link
key={link.href + index.toString()}
key={index}
href={link.href}
target={link.target}
className="text-muted-foreground hover:text-muted-foreground/80 flex-shrink-0 break-words text-sm"

View File

@@ -15,7 +15,7 @@ import { MobileNavigation } from './mobile-navigation';
export type HeaderProps = HTMLAttributes<HTMLElement>;
export const Header = ({ className }: HeaderProps) => {
export const Header = ({ className, ...props }: HeaderProps) => {
const [isHamburgerMenuOpen, setIsHamburgerMenuOpen] = useState(false);
const { getFlag } = useFeatureFlags();
@@ -23,7 +23,7 @@ export const Header = ({ className }: HeaderProps) => {
const isSinglePlayerModeMarketingEnabled = getFlag('marketing_header_single_player_mode');
return (
<header className={cn('flex items-center justify-between', className)}>
<header className={cn('flex items-center justify-between', className)} {...props}>
<div className="flex items-center space-x-4">
<Link href="/" className="z-10" onClick={() => setIsHamburgerMenuOpen(false)}>
<Image

View File

@@ -3,8 +3,7 @@
import Image from 'next/image';
import Link from 'next/link';
import type { Variants } from 'framer-motion';
import { motion } from 'framer-motion';
import { Variants, motion } from 'framer-motion';
import { usePlausible } from 'next-plausible';
import { LuGithub } from 'react-icons/lu';
import { match } from 'ts-pattern';
@@ -50,7 +49,7 @@ const HeroTitleVariants: Variants = {
},
};
export const Hero = ({ className }: HeroProps) => {
export const Hero = ({ className, ...props }: HeroProps) => {
const event = usePlausible();
const { getFlag } = useFeatureFlags();
@@ -75,7 +74,7 @@ export const Hero = ({ className }: HeroProps) => {
};
return (
<motion.div className={cn('relative', className)}>
<motion.div className={cn('relative', className)} {...props}>
<div className="absolute -inset-24 -z-10">
<motion.div
className="flex h-full w-full origin-top-right items-center justify-center"

View File

@@ -1,4 +1,4 @@
import type { HTMLAttributes } from 'react';
import { HTMLAttributes } from 'react';
import Image from 'next/image';
@@ -11,9 +11,9 @@ import { Card, CardContent } from '@documenso/ui/primitives/card';
export type OpenBuildTemplateBentoProps = HTMLAttributes<HTMLDivElement>;
export const OpenBuildTemplateBento = ({ className }: OpenBuildTemplateBentoProps) => {
export const OpenBuildTemplateBento = ({ className, ...props }: OpenBuildTemplateBentoProps) => {
return (
<div className={cn('relative', className)}>
<div className={cn('relative', className)} {...props}>
<div className="absolute inset-0 -z-10 flex items-center justify-center">
<Image
src={backgroundPattern}

View File

@@ -15,13 +15,13 @@ export type PricingTableProps = HTMLAttributes<HTMLDivElement>;
const SELECTED_PLAN_BAR_LAYOUT_ID = 'selected-plan-bar';
export const PricingTable = ({ className }: PricingTableProps) => {
export const PricingTable = ({ className, ...props }: PricingTableProps) => {
const event = usePlausible();
const [period, setPeriod] = useState<'MONTHLY' | 'YEARLY'>('MONTHLY');
return (
<div className={cn('', className)}>
<div className={cn('', className)} {...props}>
<div className="flex items-center justify-center gap-x-6">
<AnimatePresence>
<motion.button

View File

@@ -1,4 +1,4 @@
import type { HTMLAttributes } from 'react';
import { HTMLAttributes } from 'react';
import Image from 'next/image';
@@ -12,9 +12,12 @@ import { Card, CardContent } from '@documenso/ui/primitives/card';
export type ShareConnectPaidWidgetBentoProps = HTMLAttributes<HTMLDivElement>;
export const ShareConnectPaidWidgetBento = ({ className }: ShareConnectPaidWidgetBentoProps) => {
export const ShareConnectPaidWidgetBento = ({
className,
...props
}: ShareConnectPaidWidgetBentoProps) => {
return (
<div className={cn('relative', className)}>
<div className={cn('relative', className)} {...props}>
<div className="absolute inset-0 -z-10 flex items-center justify-center">
<Image
src={backgroundPattern}

View File

@@ -55,7 +55,7 @@ type StepValues = (typeof STEP)[StepKeys];
export type WidgetProps = HTMLAttributes<HTMLDivElement>;
export const Widget = ({ className, children }: WidgetProps) => {
export const Widget = ({ className, children, ...props }: WidgetProps) => {
const { toast } = useToast();
const event = usePlausible();
@@ -148,19 +148,19 @@ export const Widget = ({ className, children }: WidgetProps) => {
const claimPlanInput = signatureDataUrl
? {
name,
email,
planId,
signatureDataUrl: signatureDataUrl,
signatureText: null,
}
name,
email,
planId,
signatureDataUrl: signatureDataUrl,
signatureText: null,
}
: {
name,
email,
planId,
signatureDataUrl: null,
signatureText: signatureText ?? '',
};
name,
email,
planId,
signatureDataUrl: null,
signatureText: signatureText ?? '',
};
const [result] = await Promise.all([claimPlan(claimPlanInput), delay]);
@@ -183,6 +183,7 @@ export const Widget = ({ className, children }: WidgetProps) => {
<Card
className={cn('mx-auto w-full max-w-4xl rounded-3xl before:rounded-3xl', className)}
gradient
{...props}
>
<div className="grid grid-cols-12 gap-y-8 overflow-hidden p-2 lg:gap-x-8">
<div className="text-muted-foreground col-span-12 flex flex-col gap-y-4 p-4 text-xs leading-relaxed lg:col-span-7">

View File

@@ -1,4 +1,4 @@
import type { SVGAttributes } from 'react';
import { SVGAttributes } from 'react';
export type BackgroundProps = Omit<SVGAttributes<SVGElement>, 'viewBox'>;

View File

@@ -3,7 +3,7 @@
import * as React from 'react';
import { ThemeProvider as NextThemesProvider } from 'next-themes';
import type { ThemeProviderProps } from 'next-themes/dist/types';
import { ThemeProviderProps } from 'next-themes/dist/types';
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;

View File

@@ -6,6 +6,7 @@ import { useCopyToClipboard } from '@documenso/lib/client-only/hooks/use-copy-to
import { getRecipientType } from '@documenso/lib/client-only/recipient-type';
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
import type { Recipient } from '@documenso/prisma/client';
import { cn } from '@documenso/ui/lib/utils';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { StackAvatar } from './stack-avatar';
@@ -19,6 +20,10 @@ export function AvatarWithRecipient({ recipient }: AvatarWithRecipientProps) {
const { toast } = useToast();
const onRecipientClick = () => {
if (!recipient.token) {
return;
}
void copy(`${process.env.NEXT_PUBLIC_WEBAPP_URL}/sign/${recipient.token}`).then(() => {
toast({
title: 'Copied to clipboard',
@@ -28,19 +33,22 @@ export function AvatarWithRecipient({ recipient }: AvatarWithRecipientProps) {
};
return (
<div className="my-1 flex cursor-pointer items-center gap-2" onClick={onRecipientClick}>
<div
className={cn('my-1 flex items-center gap-2', {
'cursor-pointer hover:underline': recipient.token,
})}
role={recipient.token ? 'button' : undefined}
title={recipient.token && 'Click to copy signing link for sending to recipient'}
onClick={onRecipientClick}
>
<StackAvatar
first={true}
key={recipient.id}
type={getRecipientType(recipient)}
fallbackText={recipientAbbreviation(recipient)}
/>
<span
className="text-muted-foreground text-sm hover:underline"
title="Click to copy signing link for sending to recipient"
>
{recipient.email}
</span>
<span className="text-muted-foreground text-sm">{recipient.email}</span>
</div>
);
}

466
package-lock.json generated
View File

@@ -43,7 +43,6 @@
"lucide-react": "^0.279.0",
"luxon": "^3.4.0",
"micro": "^10.0.1",
"million": "^2.6.4",
"next": "14.0.3",
"next-auth": "4.24.5",
"next-contentlayer": "^0.3.4",
@@ -246,18 +245,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@ampproject/remapping": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
"integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@aws-crypto/crc32": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz",
@@ -1076,9 +1063,9 @@
}
},
"node_modules/@babel/code-frame": {
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
"integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.4.tgz",
"integrity": "sha512-r1IONyb6Ia+jYR2vvIDhdWdlTGhqbBoFqLTQidzZ4kepUFH15ejXvFHxCVbtl7BOXIudsIubf4E81xeA3h3IXA==",
"dependencies": {
"@babel/highlight": "^7.23.4",
"chalk": "^2.4.2"
@@ -1151,117 +1138,6 @@
"node": ">=4"
}
},
"node_modules/@babel/compat-data": {
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz",
"integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
"version": "7.23.7",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz",
"integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.23.5",
"@babel/generator": "^7.23.6",
"@babel/helper-compilation-targets": "^7.23.6",
"@babel/helper-module-transforms": "^7.23.3",
"@babel/helpers": "^7.23.7",
"@babel/parser": "^7.23.6",
"@babel/template": "^7.22.15",
"@babel/traverse": "^7.23.7",
"@babel/types": "^7.23.6",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
"json5": "^2.2.3",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/babel"
}
},
"node_modules/@babel/core/node_modules/@babel/generator": {
"version": "7.23.6",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz",
"integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==",
"dependencies": {
"@babel/types": "^7.23.6",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core/node_modules/@babel/traverse": {
"version": "7.23.7",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz",
"integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==",
"dependencies": {
"@babel/code-frame": "^7.23.5",
"@babel/generator": "^7.23.6",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.23.6",
"@babel/types": "^7.23.6",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core/node_modules/@babel/types": {
"version": "7.23.6",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz",
"integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==",
"dependencies": {
"@babel/helper-string-parser": "^7.23.4",
"@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core/node_modules/globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/core/node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"bin": {
"json5": "lib/cli.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@babel/core/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@babel/generator": {
"version": "7.17.7",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz",
@@ -1283,42 +1159,6 @@
"node": ">=0.10.0"
}
},
"node_modules/@babel/helper-compilation-targets": {
"version": "7.23.6",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz",
"integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==",
"dependencies": {
"@babel/compat-data": "^7.23.5",
"@babel/helper-validator-option": "^7.23.5",
"browserslist": "^4.22.2",
"lru-cache": "^5.1.1",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dependencies": {
"yallist": "^3.0.2"
}
},
"node_modules/@babel/helper-compilation-targets/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@babel/helper-compilation-targets/node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
},
"node_modules/@babel/helper-environment-visitor": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
@@ -1376,80 +1216,6 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-imports": {
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz",
"integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==",
"dependencies": {
"@babel/types": "^7.22.15"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-imports/node_modules/@babel/types": {
"version": "7.23.6",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz",
"integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==",
"dependencies": {
"@babel/helper-string-parser": "^7.23.4",
"@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
"version": "7.23.3",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz",
"integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==",
"dependencies": {
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-module-imports": "^7.22.15",
"@babel/helper-simple-access": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/helper-validator-identifier": "^7.22.20"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-plugin-utils": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
"integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-simple-access": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
"integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
"dependencies": {
"@babel/types": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-simple-access/node_modules/@babel/types": {
"version": "7.23.6",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz",
"integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==",
"dependencies": {
"@babel/helper-string-parser": "^7.23.4",
"@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-split-export-declaration": {
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
@@ -1490,82 +1256,6 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-option": {
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz",
"integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helpers": {
"version": "7.23.8",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.8.tgz",
"integrity": "sha512-KDqYz4PiOWvDFrdHLPhKtCThtIcKVy6avWD2oG4GEvyQ+XDZwHD4YQd+H2vNMnq2rkdxsDkU82T+Vk8U/WXHRQ==",
"dependencies": {
"@babel/template": "^7.22.15",
"@babel/traverse": "^7.23.7",
"@babel/types": "^7.23.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helpers/node_modules/@babel/generator": {
"version": "7.23.6",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz",
"integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==",
"dependencies": {
"@babel/types": "^7.23.6",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helpers/node_modules/@babel/traverse": {
"version": "7.23.7",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz",
"integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==",
"dependencies": {
"@babel/code-frame": "^7.23.5",
"@babel/generator": "^7.23.6",
"@babel/helper-environment-visitor": "^7.22.20",
"@babel/helper-function-name": "^7.23.0",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.23.6",
"@babel/types": "^7.23.6",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helpers/node_modules/@babel/types": {
"version": "7.23.6",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz",
"integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==",
"dependencies": {
"@babel/helper-string-parser": "^7.23.4",
"@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helpers/node_modules/globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/highlight": {
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
@@ -1644,9 +1334,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.23.6",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz",
"integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==",
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.4.tgz",
"integrity": "sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ==",
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -1654,34 +1344,6 @@
"node": ">=6.0.0"
}
},
"node_modules/@babel/plugin-syntax-jsx": {
"version": "7.23.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz",
"integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-typescript": {
"version": "7.23.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz",
"integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/runtime": {
"version": "7.23.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.4.tgz",
@@ -7919,9 +7581,9 @@
}
},
"node_modules/browserslist": {
"version": "4.22.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz",
"integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==",
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz",
"integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==",
"funding": [
{
"type": "opencollective",
@@ -7937,9 +7599,9 @@
}
],
"dependencies": {
"caniuse-lite": "^1.0.30001565",
"electron-to-chromium": "^1.4.601",
"node-releases": "^2.0.14",
"caniuse-lite": "^1.0.30001541",
"electron-to-chromium": "^1.4.535",
"node-releases": "^2.0.13",
"update-browserslist-db": "^1.0.13"
},
"bin": {
@@ -8101,9 +7763,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001579",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz",
"integrity": "sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA==",
"version": "1.0.30001564",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001564.tgz",
"integrity": "sha512-DqAOf+rhof+6GVx1y+xzbFPeOumfQnhYzVnZD6LAXijR77yPtm9mfOcqOnT3mpnJiZVT+kwLAFnRlZcIz+c6bg==",
"funding": [
{
"type": "opencollective",
@@ -8884,11 +8546,6 @@
"node": ">=14"
}
},
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
},
"node_modules/cookie": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
@@ -9690,9 +9347,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.4.637",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.637.tgz",
"integrity": "sha512-G7j3UCOukFtxVO1vWrPQUoDk3kL70mtvjc/DC/k2o7lE0wAdq+Vwp1ipagOow+BH0uVztFysLWbkM/RTIrbK3w=="
"version": "1.4.593",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.593.tgz",
"integrity": "sha512-c7+Hhj87zWmdpmjDONbvNKNo24tvmD4mjal1+qqTYTrlF0/sNpAcDlU0Ki84ftA/5yj3BF2QhSGEC0Rky6larg=="
},
"node_modules/emoji-regex": {
"version": "9.2.2",
@@ -11399,14 +11056,6 @@
"node": ">=8"
}
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@@ -14476,54 +14125,6 @@
"node": ">=8.6"
}
},
"node_modules/million": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/million/-/million-2.6.4.tgz",
"integrity": "sha512-voUkdd/jHWrG+7NS+mX49Pat+POKdgGW78V7pYMSrTaOjUitR6ySEcAci8hn17Rsx1IMI3+5w41dkADM1J1ZEg==",
"dependencies": {
"@babel/core": "^7.21.0",
"@babel/generator": "^7.22.7",
"@babel/plugin-syntax-jsx": "^7.21.4",
"@babel/plugin-syntax-typescript": "^7.21.4",
"@babel/types": "^7.21.3",
"kleur": "^4.1.5",
"rollup": "^3.28.0",
"unplugin": "^1.3.1"
},
"bin": {
"million": "packages/cli/dist/index.js"
},
"funding": {
"url": "https://github.com/sponsors/aidenybai"
}
},
"node_modules/million/node_modules/@babel/generator": {
"version": "7.23.6",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz",
"integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==",
"dependencies": {
"@babel/types": "^7.23.6",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/million/node_modules/@babel/types": {
"version": "7.23.6",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz",
"integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==",
"dependencies": {
"@babel/helper-string-parser": "^7.23.4",
"@babel/helper-validator-identifier": "^7.22.20",
"to-fast-properties": "^2.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -14970,9 +14571,9 @@
}
},
"node_modules/node-releases": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw=="
"version": "2.0.13",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
"integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ=="
},
"node_modules/node-signpdf": {
"version": "2.0.0",
@@ -17314,6 +16915,7 @@
"version": "3.29.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
"integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
},
@@ -19202,17 +18804,6 @@
"node": ">= 0.8"
}
},
"node_modules/unplugin": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.6.0.tgz",
"integrity": "sha512-BfJEpWBu3aE/AyHx8VaNE/WgouoQxgH9baAiH82JjX8cqVyi3uJQstqwD5J+SZxIK326SZIhsSZlALXVBCknTQ==",
"dependencies": {
"acorn": "^8.11.2",
"chokidar": "^3.5.3",
"webpack-sources": "^3.2.3",
"webpack-virtual-modules": "^0.6.1"
}
},
"node_modules/unzipper": {
"version": "0.10.14",
"resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz",
@@ -19533,19 +19124,6 @@
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/webpack-sources": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/webpack-virtual-modules": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.1.tgz",
"integrity": "sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",

View File

@@ -7,6 +7,7 @@ import { SigningStatus } from '@documenso/prisma/client';
import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
import type { FindResultSet } from '../../types/find-result-set';
import { maskRecipientTokensForDocument } from '../../utils/mask-recipient-tokens-for-document';
export type FindDocumentsOptions = {
userId: number;
@@ -173,8 +174,15 @@ export const findDocuments = async ({
}),
]);
const maskedData = data.map((document) =>
maskRecipientTokensForDocument({
document,
user,
}),
);
return {
data,
data: maskedData,
count,
currentPage: Math.max(page, 1),
perPage,

View File

@@ -1,5 +1,5 @@
import { prisma } from '@documenso/prisma';
import { DocumentWithRecipient } from '@documenso/prisma/types/document-with-recipient';
import type { DocumentWithRecipient } from '@documenso/prisma/types/document-with-recipient';
export interface GetDocumentAndSenderByTokenOptions {
token: string;
@@ -58,7 +58,11 @@ export const getDocumentAndRecipientByToken = async ({
},
},
include: {
Recipient: true,
Recipient: {
where: {
token,
},
},
documentData: true,
},
});

View File

@@ -1,6 +1,8 @@
import { prisma } from '@documenso/prisma';
import { DocumentStatus } from '@documenso/prisma/client';
import { maskRecipientTokensForDocument } from '../../utils/mask-recipient-tokens-for-document';
export type SearchDocumentsWithKeywordOptions = {
query: string;
userId: number;
@@ -77,5 +79,12 @@ export const searchDocumentsWithKeyword = async ({
take: limit,
});
return documents;
const maskedDocuments = documents.map((document) =>
maskRecipientTokensForDocument({
document,
user,
}),
);
return maskedDocuments;
};

View File

@@ -5,14 +5,16 @@ import type { Prisma } from '@prisma/client';
import { prisma } from '@documenso/prisma';
export type UpdateDocumentOptions = {
documentId: number;
data: Prisma.DocumentUpdateInput;
userId: number;
documentId: number;
};
export const updateDocument = async ({ documentId, data }: UpdateDocumentOptions) => {
export const updateDocument = async ({ documentId, userId, data }: UpdateDocumentOptions) => {
return await prisma.document.update({
where: {
id: documentId,
userId,
},
data: {
...data,

View File

@@ -0,0 +1,38 @@
import type { User } from '@documenso/prisma/client';
import type { DocumentWithRecipients } from '@documenso/prisma/types/document-with-recipient';
export type MaskRecipientTokensForDocumentOptions<T extends DocumentWithRecipients> = {
document: T;
user?: User;
token?: string;
};
export const maskRecipientTokensForDocument = <T extends DocumentWithRecipients>({
document,
user,
token,
}: MaskRecipientTokensForDocumentOptions<T>) => {
const maskedRecipients = document.Recipient.map((recipient) => {
if (document.userId === user?.id) {
return recipient;
}
if (recipient.email === user?.email) {
return recipient;
}
if (recipient.token === token) {
return recipient;
}
return {
...recipient,
token: '',
};
});
return {
...document,
Recipient: maskedRecipients,
};
};

View File

@@ -41,7 +41,7 @@ model User {
twoFactorEnabled Boolean @default(false)
twoFactorBackupCodes String?
VerificationToken VerificationToken[]
Template Template[]
Template Template[]
@@index([email])
}
@@ -161,9 +161,9 @@ model DocumentMeta {
id String @id @default(cuid())
subject String?
message String?
timezone String? @default("Etc/UTC") @db.Text
password String?
dateFormat String? @default("yyyy-MM-dd hh:mm a") @db.Text
timezone String? @db.Text @default("Etc/UTC")
password String?
dateFormat String? @db.Text @default("yyyy-MM-dd hh:mm a")
documentId Int @unique
document Document @relation(fields: [documentId], references: [id], onDelete: Cascade)
}
@@ -184,19 +184,19 @@ enum SigningStatus {
}
model Recipient {
id Int @id @default(autoincrement())
id Int @id @default(autoincrement())
documentId Int?
templateId Int?
email String @db.VarChar(255)
name String @default("") @db.VarChar(255)
email String @db.VarChar(255)
name String @default("") @db.VarChar(255)
token String
expired DateTime?
signedAt DateTime?
readStatus ReadStatus @default(NOT_OPENED)
signingStatus SigningStatus @default(NOT_SIGNED)
sendStatus SendStatus @default(NOT_SENT)
Document Document? @relation(fields: [documentId], references: [id], onDelete: Cascade)
Template Template? @relation(fields: [templateId], references: [id], onDelete: Cascade)
Document Document? @relation(fields: [documentId], references: [id], onDelete: Cascade)
Template Template? @relation(fields: [templateId], references: [id], onDelete: Cascade)
Field Field[]
Signature Signature[]
@@ -280,10 +280,10 @@ model Template {
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
templateDocumentData DocumentData @relation(fields: [templateDocumentDataId], references: [id], onDelete: Cascade)
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
Recipient Recipient[]
Field Field[]
templateDocumentData DocumentData @relation(fields: [templateDocumentDataId], references: [id], onDelete: Cascade)
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
Recipient Recipient[]
Field Field[]
@@unique([templateDocumentDataId])
}

View File

@@ -18,6 +18,7 @@ export const seedDatabase = async () => {
create: {
name: 'Example User',
email: 'example@documenso.com',
emailVerified: new Date(),
password: hashSync('password'),
roles: [Role.USER],
},
@@ -31,6 +32,7 @@ export const seedDatabase = async () => {
create: {
name: 'Admin User',
email: 'admin@documenso.com',
emailVerified: new Date(),
password: hashSync('password'),
roles: [Role.USER, Role.ADMIN],
},