Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { ReferralSignatureSetting } from "@/app/(app)/[emailAccountId]/assistant
import { LearnedPatternsSetting } from "@/app/(app)/[emailAccountId]/assistant/settings/LearnedPatternsSetting";
import { PersonalSignatureSetting } from "@/app/(app)/[emailAccountId]/assistant/settings/PersonalSignatureSetting";
import { MultiRuleSetting } from "@/app/(app)/[emailAccountId]/assistant/settings/MultiRuleSetting";
import { WritingStyleSetting } from "@/app/(app)/[emailAccountId]/assistant/settings/WritingStyleSetting";

export function SettingsTab() {
return (
<div className="space-y-2">
<DraftReplies />
<WritingStyleSetting />
<DraftKnowledgeSetting />
<MultiRuleSetting />
<AboutSetting />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
"use client";

import { useForm } from "react-hook-form";
import { useAction } from "next-safe-action/hooks";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/Input";
import { SettingCard } from "@/components/SettingCard";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { useAccount } from "@/providers/EmailAccountProvider";
import { toastError, toastSuccess } from "@/components/Toast";
import { useEmailAccountFull } from "@/hooks/useEmailAccountFull";
import { Skeleton } from "@/components/ui/skeleton";
import { LoadingContent } from "@/components/LoadingContent";
import { zodResolver } from "@hookform/resolvers/zod";
import {
type SaveWritingStyleBody,
saveWritingStyleBody,
} from "@/utils/actions/user.validation";
import { saveWritingStyleAction } from "@/utils/actions/user";

export function WritingStyleSetting() {
const { data, isLoading, error } = useEmailAccountFull();

const hasWritingStyle = !!data?.writingStyle;

return (
<SettingCard
title="Writing style"
description="Used to draft replies in your voice."
right={
<LoadingContent
loading={isLoading}
error={error}
loadingComponent={<Skeleton className="h-8 w-32" />}
>
<WritingStyleDialog currentWritingStyle={data?.writingStyle || ""}>
<Button variant="outline" size="sm">
{hasWritingStyle ? "Edit" : "Set"}
</Button>
</WritingStyleDialog>
</LoadingContent>
}
/>
);
}

function WritingStyleDialog({
children,
currentWritingStyle,
}: {
children: React.ReactNode;
currentWritingStyle: string;
}) {
const { emailAccountId } = useAccount();
const { mutate } = useEmailAccountFull();

const {
register,
formState: { errors },
handleSubmit,
} = useForm<SaveWritingStyleBody>({
defaultValues: { writingStyle: currentWritingStyle },
resolver: zodResolver(saveWritingStyleBody),
});

const { execute, isExecuting } = useAction(
saveWritingStyleAction.bind(null, emailAccountId),
{
onSuccess: () => {
toastSuccess({
description: "Writing style saved!",
});
},
onError: (error) => {
toastError({
description:
error.error.serverError ??
"An unknown error occurred while saving your writing style",
});
},
Comment thread
elie222 marked this conversation as resolved.
onSettled: () => {
mutate();
},
},
);

return (
<Dialog>
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>Writing style</DialogTitle>
<DialogDescription>
Used to draft replies in your voice.
</DialogDescription>
</DialogHeader>

<form onSubmit={handleSubmit(execute)}>
<Input
type="text"
autosizeTextarea
rows={8}
name="writingStyle"
label=""
registerProps={register("writingStyle")}
error={errors.writingStyle}
placeholder="Typical Length: 2-3 sentences
Formality: Informal but professional
Common Greeting: Hey,
Notable Traits:
- Uses contractions frequently
- Concise and direct responses
- Minimal closings"
/>
<Button type="submit" className="mt-8" loading={isExecuting}>
Save
</Button>
</form>
</DialogContent>
</Dialog>
);
}
71 changes: 31 additions & 40 deletions apps/web/app/(app)/[emailAccountId]/organization/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { slugify } from "@/utils/string";
import { useUser } from "@/hooks/useUser";
import { LoadingContent } from "@/components/LoadingContent";
import { useAccount } from "@/providers/EmailAccountProvider";
import { PageHeader } from "@/components/PageHeader";
import { PageWrapper } from "@/components/PageWrapper";

export default function CreateOrganizationPage() {
const router = useRouter();
Expand Down Expand Up @@ -61,47 +63,36 @@ export default function CreateOrganizationPage() {
);

return (
<LoadingContent loading={isLoading} error={error}>
<div className="container mx-auto py-8">
<div className="max-w-2xl mx-auto">
<div className="mb-8">
<h1 className="text-3xl font-bold mb-2">Create Organization</h1>
<p className="text-muted-foreground">
Set up your organization to collaborate with your team and manage
shared resources.
</p>
</div>
<PageWrapper className="max-w-2xl mx-auto">
<PageHeader title="Create Organization" />
<LoadingContent loading={isLoading} error={error}>
<form
className="max-w-sm space-y-4 mt-4"
onSubmit={handleSubmit(onSubmit)}
>
<Input
type="text"
name="name"
label="Organization Name"
placeholder="Apple Inc."
registerProps={register("name")}
error={errors.name}
/>

<form
className="max-w-sm space-y-4"
onSubmit={handleSubmit(onSubmit)}
>
<Input
type="text"
name="name"
label="Organization Name"
placeholder="Apple Inc."
registerProps={register("name")}
error={errors.name}
/>
<Input
type="text"
name="slug"
label="URL Slug"
placeholder="apple-inc"
registerProps={register("slug")}
error={errors.slug}
/>

<Input
type="text"
name="slug"
label="URL Slug"
placeholder="apple-inc"
registerProps={register("slug")}
error={errors.slug}
/>

<div className="flex gap-4 pt-4">
<Button type="submit" loading={isSubmitting}>
Create Organization
</Button>
</div>
</form>
</div>
</div>
</LoadingContent>
<Button type="submit" loading={isSubmitting}>
Create Organization
</Button>
</form>
</LoadingContent>
</PageWrapper>
);
}
1 change: 1 addition & 0 deletions apps/web/app/api/user/email-account/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ async function getEmailAccount({ emailAccountId }: { emailAccountId: string }) {
calendarBookingLink: true,
signature: true,
includeReferralSignature: true,
writingStyle: true,
},
});

Expand Down
13 changes: 13 additions & 0 deletions apps/web/utils/actions/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { headers } from "next/headers";
import {
saveAboutBody,
saveSignatureBody,
saveWritingStyleBody,
} from "@/utils/actions/user.validation";
import { clearLastEmailAccountCookie } from "@/utils/cookies.server";
import { aliasPosthogUser } from "@/utils/posthog";
Expand All @@ -36,6 +37,18 @@ export const saveSignatureAction = actionClient
});
});

export const saveWritingStyleAction = actionClient
.metadata({ name: "saveWritingStyle" })
.inputSchema(saveWritingStyleBody)
.action(
async ({ parsedInput: { writingStyle }, ctx: { emailAccountId } }) => {
await prisma.emailAccount.update({
where: { id: emailAccountId },
data: { writingStyle },
});
},
);

export const resetAnalyticsAction = actionClient
.metadata({ name: "resetAnalytics" })
.action(async ({ ctx: { emailAccountId } }) => {
Expand Down
5 changes: 5 additions & 0 deletions apps/web/utils/actions/user.validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ export const saveSignatureBody = z.object({
signature: z.string().max(10_000),
});
export type SaveSignatureBody = z.infer<typeof saveSignatureBody>;

export const saveWritingStyleBody = z.object({
writingStyle: z.string().max(2000),
});
export type SaveWritingStyleBody = z.infer<typeof saveWritingStyleBody>;
2 changes: 1 addition & 1 deletion version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v2.21.49
v2.21.50
Loading