feat: update ui

Signed-off-by: Adithya Krishna <adithya@documenso.com>
This commit is contained in:
Adithya Krishna
2024-02-24 22:38:53 +05:30
committed by Mythie
parent b1261510d2
commit b498f8edb7
22 changed files with 442 additions and 237 deletions

View File

@ -2,8 +2,10 @@
import React, { useEffect, useState } from 'react';
import Image from 'next/image';
import { usePathname } from 'next/navigation';
import backgroundPattern from '@documenso/assets/images/background-lw-2.png';
import { cn } from '@documenso/ui/lib/utils';
import { AnnouncementBar } from '@documenso/ui/primitives/announcement-bar';
@ -39,7 +41,14 @@ export default function MarketingLayout({ children }: MarketingLayoutProps) {
'bg-background/50 backdrop-blur-md': scrollY > 5,
})}
>
<AnnouncementBar isShown={true} />
<div className="absolute -inset-0 -z-[1] opacity-100">
<Image
src={backgroundPattern}
alt="background pattern"
className="!h-34 w-full object-cover"
/>
</div>
<AnnouncementBar className="relative" isShown={true} />
<Header className="mx-auto h-16 max-w-screen-xl px-4 md:h-20 lg:px-8" />
</div>

View File

@ -1,4 +1,4 @@
import { HTMLAttributes } from 'react';
import type { HTMLAttributes } from 'react';
import Image from 'next/image';

View File

@ -1,7 +1,10 @@
import type { Metadata } from 'next';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import type { DocumentsPageViewProps } from './documents-page-view';
import { DocumentsPageView } from './documents-page-view';
import { PublicProfileIntro } from './username-claim/public-profile-intro';
export type DocumentsPageProps = {
searchParams?: DocumentsPageViewProps['searchParams'];
@ -11,6 +14,12 @@ export const metadata: Metadata = {
title: 'Documents',
};
export default function DocumentsPage({ searchParams = {} }: DocumentsPageProps) {
return <DocumentsPageView searchParams={searchParams} />;
export default async function DocumentsPage({ searchParams = {} }: DocumentsPageProps) {
const { user } = await getRequiredServerComponentSession();
return (
<>
<PublicProfileIntro user={user} />
<DocumentsPageView searchParams={searchParams} />
</>
);
}

View File

@ -0,0 +1,225 @@
'use client';
import React, { useRef, useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { BadgeCheck, File } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import Lucas from '@documenso/assets/images/Lucas.png';
import Timur from '@documenso/assets/images/Timur.png';
import type { User } from '@documenso/prisma/client';
import { TRPCClientError } from '@documenso/trpc/client';
import { trpc } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils';
import { Avatar, AvatarFallback, AvatarImage } from '@documenso/ui/primitives/avatar';
import { Button } from '@documenso/ui/primitives/button';
import { Card, CardHeader } from '@documenso/ui/primitives/card';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@documenso/ui/primitives/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { useToast } from '@documenso/ui/primitives/use-toast';
export const ZPublicProfileFormSchema = z.object({
profileURL: z.string().trim().min(1, { message: 'Please enter a valid URL slug.' }),
});
export type TPublicProfileFormSchema = z.infer<typeof ZPublicProfileFormSchema>;
export type PublicProfileIntroProps = {
user: User;
};
export const PublicProfileIntro = ({ user }: PublicProfileIntroProps) => {
const form = useForm<TPublicProfileFormSchema>({
values: {
profileURL: user.profileURL || '',
},
resolver: zodResolver(ZPublicProfileFormSchema),
});
const textRef = useRef<HTMLSpanElement>(null);
const { toast } = useToast();
const { mutateAsync: updatePublicProfile } = trpc.profile.updatePublicProfile.useMutation();
const isSaving = form.formState.isSubmitting;
const isProfileURLClaimed = user.profileURL ? false : true;
const [showClaimingDialog, setShowClaimingDialog] = useState(isProfileURLClaimed);
const [showClaimedDialog, setShowClaimedDialog] = useState(false);
const onFormSubmit = async ({ profileURL }: TPublicProfileFormSchema) => {
try {
await updatePublicProfile({
profileURL,
});
setShowClaimingDialog(false);
setShowClaimedDialog(true);
} catch (err) {
if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') {
toast({
title: 'An error occurred',
description: err.message,
variant: 'destructive',
});
} else {
toast({
title: 'An unknown error occurred',
variant: 'destructive',
description:
'We encountered an unknown error while attempting to save your details. Please try again later.',
});
}
}
};
return (
<>
<Dialog open={showClaimingDialog} onOpenChange={setShowClaimingDialog}>
<DialogContent position="center" className="pb-4">
<DialogHeader>
<DialogTitle className="font-semi-bold text-center text-xl">
Introducing public profile!
</DialogTitle>
<DialogDescription className="text-center">
Reserve your Documenso public profile username
</DialogDescription>
</DialogHeader>
<Card className="relative px-6 py-6">
<Card className="flex flex-col items-center px-6 py-6">
<code className="bg-muted rounded-md px-1 py-1 text-sm">
<span>documenso.com/u/timur</span>
</code>
<Avatar className="dark:border-border mt-2 h-12 w-12 border-2 border-solid border-white">
<AvatarImage className="AvatarImage" src={Timur.src} alt="Timur" />
<AvatarFallback className="text-xs text-gray-400">Timur</AvatarFallback>
</Avatar>
<div className="flex flex-row gap-x-2">
Timur Ercan <BadgeCheck fill="#A2E771" />
</div>
<span className="text-center">
Hey Im Timur <br /> Pick any of the following agreements below and start signing to
get started
</span>
</Card>
<Card className="mt-2 items-center">
<CardHeader className="p-2">Documents</CardHeader>
<hr className="mb-2" />
<div className="mb-2 flex flex-row items-center justify-between">
<div className="flex flex-row items-center gap-x-2">
<File className="ml-3" />
<div className="flex flex-col">
<span className="text-md">NDA.pdf</span>
<span className="text-muted-foregroun mt-0.5 text-xs">
Like to discuss about my work?
</span>
</div>
</div>
<Button className="mr-3" variant="default">
Sign
</Button>
</div>
</Card>
</Card>
<Form {...form}>
<form className={cn('flex w-full flex-col')} onSubmit={form.handleSubmit(onFormSubmit)}>
<fieldset className="flex w-full flex-col gap-y-4" disabled={isSaving}>
<FormField
control={form.control}
name="profileURL"
render={({ field }) => (
<FormItem>
<FormLabel>Public profile URL</FormLabel>
<FormControl>
<>
<Input type="text" className="mb-2 mt-2" {...field} />
<div className="mt-2">
<code className="bg-muted rounded-md px-1 py-1 text-sm">
<span ref={textRef} id="textToCopy">
documenso.com/u/
</span>
</code>
</div>
</>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</fieldset>
<div className="mt-4 text-center">
<Button type="submit" loading={isSaving}>
Claim your username
</Button>
</div>
</form>
</Form>
</DialogContent>
</Dialog>
<Dialog open={showClaimedDialog} onOpenChange={setShowClaimedDialog}>
<DialogContent position="center" className="pb-4">
<DialogHeader>
<DialogTitle className="font-semi-bold text-center text-xl">All set!</DialogTitle>
<DialogDescription className="text-center">
We will let you know as soon as this feature is launched
</DialogDescription>
</DialogHeader>
<Card className="relative px-6 py-6">
<Card className="flex flex-col items-center px-6 py-6">
<code className="bg-muted rounded-md px-1 py-1 text-sm">
<span>documenso.com/u/lucas</span>
</code>
<Avatar className="dark:border-border mt-2 h-12 w-12 border-2 border-solid border-white">
<AvatarImage className="AvatarImage" src={Lucas.src} alt="Lucas" />
<AvatarFallback className="text-xs text-gray-400">Timur</AvatarFallback>
</Avatar>
<div className="flex flex-row gap-x-2">
Lucas Smith <BadgeCheck fill="#A2E771" />
</div>
<div className="flex inline-flex h-full w-full flex-col items-center justify-center gap-3 py-2">
<Skeleton className="w-75 h-4 animate-none rounded-full" />
<Skeleton className="w-50 h-4 animate-none rounded-full" />
</div>
</Card>
<Card className="mt-2 items-center">
<CardHeader className="p-2">Documents</CardHeader>
<hr className="mb-2" />
<div className="mb-2 flex flex-row items-center justify-between">
<div className="flex flex-row items-center gap-x-2">
<File className="ml-3" />
<div className="flex flex-col">
<span className="text-md">NDA.pdf</span>
<span className="text-muted-foregroun mt-0.5 text-xs">
Like to discuss about my work?
</span>
</div>
</div>
<Button className="mr-3" variant="default">
Sign
</Button>
</div>
</Card>
</Card>
</DialogContent>
</Dialog>
</>
);
};

View File

@ -1,30 +1,30 @@
import * as React from 'react';
import type { Metadata } from 'next';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-component-session';
import { Switch } from '@documenso/ui/primitives/switch';
import Link from 'next/link';
import { SettingsHeader } from '~/components/(dashboard)/settings/layout/header';
import { PublicProfileForm } from '~/components/forms/public-profile';
export const metadata: Metadata = {
title: 'Public Profile',
};
export default async function PublicProfilePage() {
const { user } = await getRequiredServerComponentSession();
export default function PublicProfilePage() {
return (
<>
<SettingsHeader
title="Public profile"
subtitle="You can choose to enable/disable your profile for public view"
titleChildren={<Switch></Switch>}
subtitle=""
className="max-w-xl"
titleChildren={
<Link
href="#"
className="bg-primary dark:text-background ml-2 rounded-full px-2 py-1 text-xs font-semibold sm:px-3"
>
Coming soon!
</Link>
}
/>
<PublicProfileForm user={user} className="max-w-xl" />
</>
);
}

View File

@ -20,7 +20,7 @@ export default function SignInLayout({ children }: SignInLayoutProps) {
<Image
src={backgroundPattern}
alt="background pattern"
className="flex flex-col overflow-hidden dark:brightness-95 dark:contrast-[70%] dark:invert dark:sepia"
className="flex min-h-screen flex-col overflow-hidden dark:brightness-95 dark:contrast-[70%] dark:invert dark:sepia"
/>
</div>
<div className="relative flex w-full max-w-md items-center gap-x-24">

View File

@ -0,0 +1,26 @@
import React from 'react';
import { Card } from '@documenso/ui/primitives/card';
import ClaimUsernameCard from '../../../components/(dashboard)/claim-username-card/claim-username-card';
import { NewHeader } from '../../../components/(dashboard)/layout/new/new-header';
type SignUpLayoutProps = {
children: React.ReactNode;
};
export default function SignUpLayout({ children }: SignUpLayoutProps) {
return (
<>
<NewHeader className="mx-auto h-16 max-w-screen-xl px-4 md:h-20 lg:px-8" />
<main className="bg-sand-100 flex flex-col items-center justify-center overflow-hidden px-6 py-16 md:p-10 lg:p-[7.0rem]">
<div className="flex w-full items-center gap-x-8">
<ClaimUsernameCard />
<Card className="px-6 py-6">
<div className="w-full">{children}</div>
</Card>
</div>
</main>
</>
);
}

View File

@ -9,8 +9,6 @@ import { decryptSecondaryData } from '@documenso/lib/server-only/crypto/decrypt'
import { SignUpForm } from '~/components/forms/signup';
import SignUpLayout from '../signup-layout';
export const metadata: Metadata = {
title: 'Sign Up',
};
@ -36,28 +34,26 @@ export default function SignUpPage({ searchParams }: SignUpPageProps) {
}
return (
<SignUpLayout>
<>
<h1 className="text-3xl font-semibold">Create a new account</h1>
<>
<h1 className="text-3xl font-semibold">Create a new account</h1>
<p className="text-muted-foreground/60 mt-2 text-sm">
Create your account and start using state-of-the-art document signing. Open and beautiful
signing is within your grasp.
</p>
<p className="text-muted-foreground/60 mt-2 text-sm">
Create your account and start using state-of-the-art document signing. Open and beautiful
signing is within your grasp.
</p>
<SignUpForm
className="mt-1"
initialEmail={email || undefined}
isGoogleSSOEnabled={IS_GOOGLE_SSO_ENABLED}
/>
<SignUpForm
className="mt-1"
initialEmail={email || undefined}
isGoogleSSOEnabled={IS_GOOGLE_SSO_ENABLED}
/>
<p className="text-muted-foreground mt-6 text-center text-sm">
Already have an account?{' '}
<Link href="/signin" className="text-primary duration-200 hover:opacity-70">
Sign in instead
</Link>
</p>
</>
</SignUpLayout>
<p className="text-muted-foreground mt-6 text-center text-sm">
Already have an account?{' '}
<Link href="/signin" className="text-primary duration-200 hover:opacity-70">
Sign in instead
</Link>
</p>
</>
);
}

View File

@ -0,0 +1,93 @@
'use client';
import React from 'react';
import Image from 'next/image';
import { BadgeCheck, File } from 'lucide-react';
import Timur from '@documenso/assets/images/Timur.png';
import backgroundPattern from '@documenso/assets/images/background-blog-og.png';
import { cn } from '@documenso/ui/lib/utils';
import { Avatar, AvatarFallback, AvatarImage } from '@documenso/ui/primitives/avatar';
import { Button } from '@documenso/ui/primitives/button';
import { Card, CardFooter, CardHeader } from '@documenso/ui/primitives/card';
export default function ClaimUsernameCard() {
const onSignUpClick = () => {};
return (
<div className="relative">
<div className="absolute -inset-96 -z-[1] flex items-center justify-center bg-contain opacity-50">
<Image
src={backgroundPattern}
alt="background pattern"
className="dark:brightness-95 dark:contrast-[100%] dark:invert"
/>
</div>
<Card className={cn('relative px-16 py-16')}>
<Card className="flex flex-col items-center px-6 py-6">
<code className="bg-muted rounded-md px-1 py-1 text-sm">
<span>documenso.com/u/timur</span>
</code>
<Avatar className="dark:border-border mt-2 h-12 w-12 border-2 border-solid border-white">
<AvatarImage className="AvatarImage" src={Timur.src} alt="Timur" />
<AvatarFallback className="text-xs text-gray-400">Timur</AvatarFallback>
</Avatar>
<div className="flex flex-row gap-x-2">
Timur Ercan <BadgeCheck fill="#A2E771" />
</div>
<span className="text-center">
Hey Im Timur <br /> Pick any of the following agreements below and start signing to get
started
</span>
</Card>
<Card className="mt-2 items-center">
<CardHeader className="p-2">Documents</CardHeader>
<hr className="mb-2" />
<div className="mb-2 flex flex-row items-center justify-between">
<div className="flex flex-row items-center gap-x-2">
<File className="ml-3" />
<div className="flex flex-col">
<span className="text-md">NDA.pdf</span>
<span className="text-muted-foregroun mt-0.5 text-xs">
Like to discuss about my work?
</span>
</div>
</div>
<Button className="mr-3" variant="default">
Sign
</Button>
</div>
<hr className="mb-2" />
<div className="mb-2 flex flex-row items-center justify-between">
<div className="flex flex-row items-center gap-x-2">
<File className="ml-3" />
<div className="flex flex-col">
<span className="text-md">NDA.pdf</span>
<span className="text-muted-foregroun mt-0.5 text-xs">
Like to discuss about my work?
</span>
</div>
</div>
<Button className="mr-3" variant="default">
Sign
</Button>
</div>
</Card>
<CardFooter className="mt-20 justify-center">
<Button
type="button"
variant="outline"
className="rounded-full bg-transparent backdrop-blur-sm"
onClick={onSignUpClick}
>
Claim Community Plan
<span className="bg-primary dark:text-background -mr-2.5 ml-2.5 rounded-full px-2 py-1.5 text-xs font-medium">
-80%
</span>
</Button>
</CardFooter>
</Card>
</div>
);
}

View File

@ -92,7 +92,7 @@ export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
</Link>
)}
<Link href="/settings/public-profile">
<Link href="#">
<Button
variant="ghost"
className={cn(
@ -102,6 +102,12 @@ export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
>
<Globe2 className="mr-2 h-5 w-5" />
Public profile
<Link
href="#"
className="bg-primary dark:text-background ml-2 rounded-full px-2 py-1 text-xs font-semibold sm:px-3"
>
Coming soon!
</Link>
</Button>
</Link>
</div>

View File

@ -95,7 +95,7 @@ export const MobileNav = ({ className, ...props }: MobileNavProps) => {
</Link>
)}
<Link href="/settings/public-profile">
<Link href="#">
<Button
variant="ghost"
className={cn(
@ -105,6 +105,12 @@ export const MobileNav = ({ className, ...props }: MobileNavProps) => {
>
<Globe2 className="mr-2 h-5 w-5" />
Public profile
<Link
href="#"
className="bg-primary dark:text-background ml-2 rounded-full px-2 py-1 text-xs font-semibold sm:px-3"
>
Coming soon!
</Link>
</Button>
</Link>
</div>

View File

@ -1,165 +0,0 @@
'use client';
import { useRef } from 'react';
import { useRouter } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod';
import { Copy } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import type { User } from '@documenso/prisma/client';
import { TRPCClientError } from '@documenso/trpc/client';
import { trpc } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@documenso/ui/primitives/form/form';
import { Input } from '@documenso/ui/primitives/input';
import { Textarea } from '@documenso/ui/primitives/textarea';
import { useToast } from '@documenso/ui/primitives/use-toast';
export const ZPublicProfileFormSchema = z.object({
profileURL: z.string().trim().min(1, { message: 'Please enter a valid URL slug.' }),
profileBio: z
.string()
.max(256, { message: 'Profile bio must not exceed 256 characters' })
.optional(),
});
export type TPublicProfileFormSchema = z.infer<typeof ZPublicProfileFormSchema>;
export type PublicProfileFormProps = {
className?: string;
user: User;
};
export const PublicProfileForm = ({ user, className }: PublicProfileFormProps) => {
const textRef = useRef<HTMLSpanElement>(null);
const { toast } = useToast();
const router = useRouter();
const form = useForm<TPublicProfileFormSchema>({
values: {
profileURL: user.profileURL || '',
},
resolver: zodResolver(ZPublicProfileFormSchema),
});
const isSaving = form.formState.isSubmitting;
const { mutateAsync: updatePublicProfile, data: profileURL } =
trpc.profile.updatePublicProfile.useMutation();
const copyTextToClipboard = async () => {
if (textRef.current) {
try {
await navigator.clipboard.writeText(textRef.current.textContent || '');
} catch (err) {
console.log('Failed to copy: ', err);
}
}
};
const onFormSubmit = async ({ profileURL, profileBio }: TPublicProfileFormSchema) => {
try {
await updatePublicProfile({
profileURL,
profileBio: profileBio || '',
});
toast({
title: 'Public profile updated',
description: 'Your public profile has been updated successfully.',
duration: 5000,
});
router.refresh();
} catch (err) {
if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') {
toast({
title: 'An error occurred',
description: err.message,
variant: 'destructive',
});
} else {
toast({
title: 'An unknown error occurred',
variant: 'destructive',
description:
'We encountered an unknown error while attempting to save your details. Please try again later.',
});
}
}
};
return (
<Form {...form}>
<form
className={(cn('flex w-full flex-col gap-y-4'), className)}
onSubmit={form.handleSubmit(onFormSubmit)}
>
<fieldset className="flex w-full flex-col gap-y-4" disabled={isSaving}>
<FormField
control={form.control}
name="profileURL"
render={({ field }) => (
<FormItem>
<FormLabel>Public profile URL</FormLabel>
<FormControl>
<>
<Input type="text" className="mb-2 mt-2" {...field} />
{profileURL && (
<code className="bg-muted rounded-md px-1 py-1 text-sm">
<span ref={textRef} id="textToCopy">
{profileURL}
</span>
<Button
className="pointer-events-none w-12 px-0"
onClick={copyTextToClipboard}
variant="ghost"
asChild
>
<Copy className="h-8 w-8" />
</Button>
</code>
)}
</>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="profileBio"
render={({ field }) => (
<FormItem>
<FormLabel>Bio</FormLabel>
<FormControl>
<Textarea className="mt-2" placeholder="Write about yourself..." {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</fieldset>
<div className="ml-auto mt-4">
<Button type="submit" loading={isSaving}>
{isSaving ? 'Saving changes...' : 'Save changes'}
</Button>
</div>
</form>
</Form>
);
};

View File

@ -1,6 +1,7 @@
'use client';
import { useRouter } from 'next/navigation';
import { useSearchParams } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod';
import { signIn } from 'next-auth/react';
@ -58,6 +59,9 @@ export const SignUpForm = ({ className, initialEmail, isGoogleSSOEnabled }: Sign
const { toast } = useToast();
const analytics = useAnalytics();
const router = useRouter();
const searchParams = useSearchParams();
const src = searchParams?.get('src') ?? null;
const form = useForm<TSignUpFormSchema>({
values: {
@ -89,6 +93,7 @@ export const SignUpForm = ({ className, initialEmail, isGoogleSSOEnabled }: Sign
analytics.capture('App: User Sign Up', {
email,
timestamp: new Date().toISOString(),
custom_campaign_params: { src },
});
} catch (err) {
if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') {
@ -133,7 +138,7 @@ export const SignUpForm = ({ className, initialEmail, isGoogleSSOEnabled }: Sign
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormLabel>Full Name</FormLabel>
<FormControl>
<Input type="text" {...field} />
</FormControl>
@ -147,7 +152,7 @@ export const SignUpForm = ({ className, initialEmail, isGoogleSSOEnabled }: Sign
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormLabel>Email Address</FormLabel>
<FormControl>
<Input type="email" {...field} />
</FormControl>

View File

@ -32,6 +32,7 @@ export function PostHogPageview() {
// Do nothing.
});
},
custom_campaign_params: ['src'],
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

View File

@ -1,14 +0,0 @@
import { prisma } from '@documenso/prisma';
import type { UserProfile } from '@documenso/prisma/client';
export interface GetPublicProfileByURLOptions {
profileURL: UserProfile['profileURL'];
}
export const getPublicProfileByURL = async ({ profileURL }: GetPublicProfileByURLOptions) => {
return await prisma.userProfile.findFirstOrThrow({
where: {
profileURL: profileURL,
},
});
};

View File

@ -5,27 +5,30 @@ import { getUserById } from './get-user-by-id';
export type UpdatePublicProfileOptions = {
id: User['id'];
userProfile: UserProfile;
profileURL: UserProfile['profileURL'];
};
export const updatePublicProfile = async ({ id, userProfile }: UpdatePublicProfileOptions) => {
export const updatePublicProfile = async ({ id, profileURL }: UpdatePublicProfileOptions) => {
const user = await getUserById({ id });
console.log('Adi', user);
// Existence check
await prisma.userProfile.findFirstOrThrow({
where: {
profileURL: user.profileURL ?? '',
profileURL: user.profileURL ?? undefined,
},
});
return await prisma.$transaction(async (tx) => {
await tx.userProfile.create({
data: {
profileURL,
},
});
await tx.userProfile.update({
where: {
profileURL: user.profileURL!,
profileURL: user.profileURL ?? undefined,
},
data: {
profileURL: userProfile.profileURL,
profileBio: userProfile.profileBio,
profileURL: profileURL,
},
});
});

View File

@ -80,11 +80,11 @@ export const profileRouter = router({
.input(ZUpdatePublicProfileMutationSchema)
.mutation(async ({ input, ctx }) => {
try {
const { profileURL, profileBio } = input;
const { profileURL } = input;
return await updatePublicProfile({
id: ctx.user.id,
userProfile: { profileURL, profileBio },
profileURL,
});
} catch (err) {
console.error(err);

View File

@ -18,7 +18,6 @@ export const ZUpdateProfileMutationSchema = z.object({
export const ZUpdatePublicProfileMutationSchema = z.object({
profileURL: z.string().min(1),
profileBio: z.string().max(256, { message: 'Profile bio must not exceed 256 characters' }),
});
export const ZUpdatePasswordMutationSchema = z.object({

View File

@ -1,18 +1,24 @@
import Link from 'next/link';
import { cn } from '../lib/utils';
interface AnnouncementBarProps {
isShown: boolean;
className: string;
}
export const AnnouncementBar: React.FC<AnnouncementBarProps> = ({ isShown }) => {
export const AnnouncementBar: React.FC<AnnouncementBarProps> = ({ isShown, className }) => {
return (
isShown && (
<div className="flex h-full w-full items-center justify-center gap-4 border-b-2 bg-green-400 p-1">
<div
className={cn(
'flex h-full w-full items-center justify-center gap-4 border-b-2 p-1',
className,
)}
>
<div className="text-center">
<span className="text-sm text-gray-800">
Claim your documenso public profile URL now!
</span>{' '}
<span className="text-sm font-medium text-gray-800">documenso.com/u/yourname</span>
<span className="text-sm text-white">Claim your documenso public profile URL now!</span>{' '}
<span className="text-sm font-medium text-white">documenso.com/u/yourname</span>
</div>
<div className="flex items-center justify-center gap-4 rounded-lg bg-white px-3 py-1">