From 5c92b1839ccf27f8d00408a23aea199313e59d7d Mon Sep 17 00:00:00 2001 From: Subham Bharadwaz Date: Sun, 12 May 2024 20:33:13 +0530 Subject: [PATCH 1/4] chore: :construction: Regular Modifications --- src/app/(editor)/editor/[entryId]/page.tsx | 2 +- .../(journal)/_components}/billing/billing-form.tsx | 0 .../journal => app/(journal)/_components}/editor.tsx | 0 .../_components}/journal-entry-create-button.tsx | 0 .../(journal)/_components}/journal-entry.tsx | 0 .../_components}/settings/appearance-form.tsx | 0 .../(journal)/_components}/settings/reminder-form.tsx | 6 +++--- .../(journal)/_components}/settings/user-name-form.tsx | 2 +- src/app/(journal)/journal/billing/page.tsx | 3 ++- src/app/(journal)/journal/loading.tsx | 5 +++-- src/app/(journal)/journal/page.tsx | 5 +++-- src/app/(journal)/journal/settings/page.tsx | 6 +++--- .../(marketing)/_components}/features/features.tsx | 0 .../_components}/features/featuresSection.tsx | 0 .../(marketing)/_components}/hero/heroImage.tsx | 2 +- .../(marketing)/_components}/hero/heroSection.tsx | 0 .../_components}/openSource/openSourceSection.tsx | 0 .../(marketing)/_components}/pricing/pricing.tsx | 0 .../_components}/pricing/pricingSection.tsx | 0 .../_components}/testimonials/testimonials.tsx | 0 .../_components}/testimonials/testimonialsSection.tsx | 0 src/app/(marketing)/page.tsx | 10 +++++----- src/middleware.ts | 2 +- 23 files changed, 23 insertions(+), 20 deletions(-) rename src/{components/journal => app/(journal)/_components}/billing/billing-form.tsx (100%) rename src/{components/journal => app/(journal)/_components}/editor.tsx (100%) rename src/{components/journal => app/(journal)/_components}/journal-entry-create-button.tsx (100%) rename src/{components/journal => app/(journal)/_components}/journal-entry.tsx (100%) rename src/{components/journal => app/(journal)/_components}/settings/appearance-form.tsx (100%) rename src/{components/journal => app/(journal)/_components}/settings/reminder-form.tsx (97%) rename src/{components/journal => app/(journal)/_components}/settings/user-name-form.tsx (98%) rename src/{components/marketing => app/(marketing)/_components}/features/features.tsx (100%) rename src/{components/marketing => app/(marketing)/_components}/features/featuresSection.tsx (100%) rename src/{components/marketing => app/(marketing)/_components}/hero/heroImage.tsx (93%) rename src/{components/marketing => app/(marketing)/_components}/hero/heroSection.tsx (100%) rename src/{components/marketing => app/(marketing)/_components}/openSource/openSourceSection.tsx (100%) rename src/{components/marketing => app/(marketing)/_components}/pricing/pricing.tsx (100%) rename src/{components/marketing => app/(marketing)/_components}/pricing/pricingSection.tsx (100%) rename src/{components/marketing => app/(marketing)/_components}/testimonials/testimonials.tsx (100%) rename src/{components/marketing => app/(marketing)/_components}/testimonials/testimonialsSection.tsx (100%) diff --git a/src/app/(editor)/editor/[entryId]/page.tsx b/src/app/(editor)/editor/[entryId]/page.tsx index 8908dd4..f774e90 100644 --- a/src/app/(editor)/editor/[entryId]/page.tsx +++ b/src/app/(editor)/editor/[entryId]/page.tsx @@ -3,7 +3,7 @@ import { JournalEntry, User } from "@prisma/client" import { getUserByClerkId } from "@/lib/auth" import { db } from "@/lib/db" -import Editor from "@/components/journal/editor" +import Editor from "@/app/(journal)/_components/editor" async function getPostForUser(entryId: JournalEntry["id"], userId: User["id"]) { return await db.journalEntry.findFirst({ diff --git a/src/components/journal/billing/billing-form.tsx b/src/app/(journal)/_components/billing/billing-form.tsx similarity index 100% rename from src/components/journal/billing/billing-form.tsx rename to src/app/(journal)/_components/billing/billing-form.tsx diff --git a/src/components/journal/editor.tsx b/src/app/(journal)/_components/editor.tsx similarity index 100% rename from src/components/journal/editor.tsx rename to src/app/(journal)/_components/editor.tsx diff --git a/src/components/journal/journal-entry-create-button.tsx b/src/app/(journal)/_components/journal-entry-create-button.tsx similarity index 100% rename from src/components/journal/journal-entry-create-button.tsx rename to src/app/(journal)/_components/journal-entry-create-button.tsx diff --git a/src/components/journal/journal-entry.tsx b/src/app/(journal)/_components/journal-entry.tsx similarity index 100% rename from src/components/journal/journal-entry.tsx rename to src/app/(journal)/_components/journal-entry.tsx diff --git a/src/components/journal/settings/appearance-form.tsx b/src/app/(journal)/_components/settings/appearance-form.tsx similarity index 100% rename from src/components/journal/settings/appearance-form.tsx rename to src/app/(journal)/_components/settings/appearance-form.tsx diff --git a/src/components/journal/settings/reminder-form.tsx b/src/app/(journal)/_components/settings/reminder-form.tsx similarity index 97% rename from src/components/journal/settings/reminder-form.tsx rename to src/app/(journal)/_components/settings/reminder-form.tsx index 2434317..d8c55de 100644 --- a/src/components/journal/settings/reminder-form.tsx +++ b/src/app/(journal)/_components/settings/reminder-form.tsx @@ -27,15 +27,15 @@ import { FormItem, FormLabel, FormMessage, -} from "../../ui/form" +} from "../../../../components/ui/form" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "../../ui/select" -import { Switch } from "../../ui/switch" +} from "../../../../components/ui/select" +import { Switch } from "../../../../components/ui/switch" interface ReminderFormProps extends React.HTMLAttributes { subscriptionPlan: UserSubscriptionPlan diff --git a/src/components/journal/settings/user-name-form.tsx b/src/app/(journal)/_components/settings/user-name-form.tsx similarity index 98% rename from src/components/journal/settings/user-name-form.tsx rename to src/app/(journal)/_components/settings/user-name-form.tsx index f0e53b5..8c389bb 100644 --- a/src/components/journal/settings/user-name-form.tsx +++ b/src/app/(journal)/_components/settings/user-name-form.tsx @@ -22,7 +22,7 @@ import { FormItem, FormLabel, FormMessage, -} from "../../ui/form" +} from "../../../../components/ui/form" interface UserNameFormProps extends React.HTMLAttributes { user: Pick diff --git a/src/app/(journal)/journal/billing/page.tsx b/src/app/(journal)/journal/billing/page.tsx index 2211417..8ae356c 100644 --- a/src/app/(journal)/journal/billing/page.tsx +++ b/src/app/(journal)/journal/billing/page.tsx @@ -27,9 +27,10 @@ import { import { CopyButton } from "@/components/copy-button" import { Header } from "@/components/header" import { Icons } from "@/components/icons" -import { BillingForm } from "@/components/journal/billing/billing-form" import { Shell } from "@/components/shell" +import { BillingForm } from "../../_components/billing/billing-form" + export const metadata = { title: "Billing", description: "Manage billing and your subscription plan.", diff --git a/src/app/(journal)/journal/loading.tsx b/src/app/(journal)/journal/loading.tsx index 678ff37..319ac99 100644 --- a/src/app/(journal)/journal/loading.tsx +++ b/src/app/(journal)/journal/loading.tsx @@ -1,8 +1,9 @@ import { Header } from "@/components/header" -import { JournalEntryItem } from "@/components/journal/journal-entry" -import JournalEntryCreateButton from "@/components/journal/journal-entry-create-button" import { Shell } from "@/components/shell" +import { JournalEntryItem } from "../_components/journal-entry" +import JournalEntryCreateButton from "../_components/journal-entry-create-button" + export default function JournalLoading() { return ( diff --git a/src/app/(journal)/journal/page.tsx b/src/app/(journal)/journal/page.tsx index db3bf69..b668258 100644 --- a/src/app/(journal)/journal/page.tsx +++ b/src/app/(journal)/journal/page.tsx @@ -5,10 +5,11 @@ import { db } from "@/lib/db" import { EmptyPlaceholder } from "@/components/empty-placeholder" import { Header } from "@/components/header" import { Icons } from "@/components/icons" -import { JournalEntryItem } from "@/components/journal/journal-entry" -import JournalEntryCreateButton from "@/components/journal/journal-entry-create-button" import { Shell } from "@/components/shell" +import { JournalEntryItem } from "../_components/journal-entry" +import JournalEntryCreateButton from "../_components/journal-entry-create-button" + export const metadata = { title: "Journal", } diff --git a/src/app/(journal)/journal/settings/page.tsx b/src/app/(journal)/journal/settings/page.tsx index d48f6fa..a0db7e2 100644 --- a/src/app/(journal)/journal/settings/page.tsx +++ b/src/app/(journal)/journal/settings/page.tsx @@ -16,10 +16,10 @@ import { import { Separator } from "@/components/ui/separator" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import { Header } from "@/components/header" -import { AppearanceForm } from "@/components/journal/settings/appearance-form" -import { ReminderForm } from "@/components/journal/settings/reminder-form" -import { UserNameForm } from "@/components/journal/settings/user-name-form" import { Shell } from "@/components/shell" +import { AppearanceForm } from "@/app/(journal)/_components/settings/appearance-form" +import { ReminderForm } from "@/app/(journal)/_components/settings/reminder-form" +import { UserNameForm } from "@/app/(journal)/_components/settings/user-name-form" export const metadata = { title: "Settings", diff --git a/src/components/marketing/features/features.tsx b/src/app/(marketing)/_components/features/features.tsx similarity index 100% rename from src/components/marketing/features/features.tsx rename to src/app/(marketing)/_components/features/features.tsx diff --git a/src/components/marketing/features/featuresSection.tsx b/src/app/(marketing)/_components/features/featuresSection.tsx similarity index 100% rename from src/components/marketing/features/featuresSection.tsx rename to src/app/(marketing)/_components/features/featuresSection.tsx diff --git a/src/components/marketing/hero/heroImage.tsx b/src/app/(marketing)/_components/hero/heroImage.tsx similarity index 93% rename from src/components/marketing/hero/heroImage.tsx rename to src/app/(marketing)/_components/hero/heroImage.tsx index b1b1a55..3773789 100644 --- a/src/components/marketing/hero/heroImage.tsx +++ b/src/app/(marketing)/_components/hero/heroImage.tsx @@ -5,7 +5,7 @@ import { useInView } from "react-intersection-observer" import { cn } from "@/lib/utils" -import heroDarkImage from "../../../../public/images/hero-dark.webp" +import heroDarkImage from "../../../../../public/images/hero-dark.webp" export const HeroImage = () => { const { ref, inView } = useInView({ threshold: 0.4, triggerOnce: true }) diff --git a/src/components/marketing/hero/heroSection.tsx b/src/app/(marketing)/_components/hero/heroSection.tsx similarity index 100% rename from src/components/marketing/hero/heroSection.tsx rename to src/app/(marketing)/_components/hero/heroSection.tsx diff --git a/src/components/marketing/openSource/openSourceSection.tsx b/src/app/(marketing)/_components/openSource/openSourceSection.tsx similarity index 100% rename from src/components/marketing/openSource/openSourceSection.tsx rename to src/app/(marketing)/_components/openSource/openSourceSection.tsx diff --git a/src/components/marketing/pricing/pricing.tsx b/src/app/(marketing)/_components/pricing/pricing.tsx similarity index 100% rename from src/components/marketing/pricing/pricing.tsx rename to src/app/(marketing)/_components/pricing/pricing.tsx diff --git a/src/components/marketing/pricing/pricingSection.tsx b/src/app/(marketing)/_components/pricing/pricingSection.tsx similarity index 100% rename from src/components/marketing/pricing/pricingSection.tsx rename to src/app/(marketing)/_components/pricing/pricingSection.tsx diff --git a/src/components/marketing/testimonials/testimonials.tsx b/src/app/(marketing)/_components/testimonials/testimonials.tsx similarity index 100% rename from src/components/marketing/testimonials/testimonials.tsx rename to src/app/(marketing)/_components/testimonials/testimonials.tsx diff --git a/src/components/marketing/testimonials/testimonialsSection.tsx b/src/app/(marketing)/_components/testimonials/testimonialsSection.tsx similarity index 100% rename from src/components/marketing/testimonials/testimonialsSection.tsx rename to src/app/(marketing)/_components/testimonials/testimonialsSection.tsx diff --git a/src/app/(marketing)/page.tsx b/src/app/(marketing)/page.tsx index 6aa24c5..cf984ec 100644 --- a/src/app/(marketing)/page.tsx +++ b/src/app/(marketing)/page.tsx @@ -1,8 +1,8 @@ -import FeaturesSection from "@/components/marketing/features/featuresSection" -import HeroSection from "@/components/marketing/hero/heroSection" -import OpenSourceSection from "@/components/marketing/openSource/openSourceSection" -import PricingSection from "@/components/marketing/pricing/pricingSection" -import TestimonialsSection from "@/components/marketing/testimonials/testimonialsSection" +import FeaturesSection from "./_components/features/featuresSection" +import HeroSection from "./_components/hero/heroSection" +import OpenSourceSection from "./_components/openSource/openSourceSection" +import PricingSection from "./_components/pricing/pricingSection" +import TestimonialsSection from "./_components/testimonials/testimonialsSection" export default function Home() { return ( diff --git a/src/middleware.ts b/src/middleware.ts index d8027fb..9f13c8d 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,6 +1,6 @@ import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server" -const isProtectedRoute = createRouteMatcher(["/editor(.*)", "/journal(.*)", ""]) +const isProtectedRoute = createRouteMatcher(["/editor(.*)", "/journal(.*)"]) export default clerkMiddleware((auth, req) => { if (!auth().userId && isProtectedRoute(req)) { From d5926196255deb182490e5a27f1aa66c5dd1e9b1 Mon Sep 17 00:00:00 2001 From: Subham Bharadwaz Date: Mon, 13 May 2024 16:58:58 +0530 Subject: [PATCH 2/4] feat: :sparkles: Added Server Actions - Converted all necessary API routes into Server Actions --- src/server/actions/journal.ts | 146 +++++++++++++++++++++++++++++++++ src/server/actions/reminder.ts | 63 ++++++++++++++ src/server/actions/stripe.ts | 104 +++++++++++++++++++++++ src/server/actions/user.ts | 52 ++++++++++++ 4 files changed, 365 insertions(+) create mode 100644 src/server/actions/journal.ts create mode 100644 src/server/actions/reminder.ts create mode 100644 src/server/actions/stripe.ts create mode 100644 src/server/actions/user.ts diff --git a/src/server/actions/journal.ts b/src/server/actions/journal.ts new file mode 100644 index 0000000..a9fe79d --- /dev/null +++ b/src/server/actions/journal.ts @@ -0,0 +1,146 @@ +"use server" + +import { revalidatePath } from "next/cache" +import { getUserSubscriptionPlan } from "@/server/actions/stripe" +import { z } from "zod" + +import { getUserByClerkId } from "@/lib/auth" +import { db } from "@/lib/db" +import { RequiresProPlanError } from "@/lib/exceptions" +import { entryPatchSchema } from "@/lib/validations/entry" +import { verifyCurrentUserHasAccessToEntry } from "@/lib/verify-current-user-has-access-to-entry" + +const entryCreateSchema = z.object({ + title: z.string(), + content: z.string().optional(), +}) + +export async function createJournalEntry( + rawInput: z.infer +) { + try { + const input = entryCreateSchema.parse(rawInput) + const user = await getUserByClerkId() + + if (!user) { + return { + error: "Unauthorized", + code: 403, + } + } + + const subscriptionPlan = await getUserSubscriptionPlan(user?.id) + if (!subscriptionPlan?.isPro) { + const count = await db.journalEntry.count({ + where: { + userId: user?.id, + }, + }) + + if (count >= 3) { + throw new RequiresProPlanError() + } + } + + const entry = await db.journalEntry.create({ + data: { + title: input.title, + content: input.content, + userId: user?.id, + }, + }) + revalidatePath("/journal") + return entry + } catch (error: unknown) { + if (error instanceof z.ZodError) { + return { + error: error.issues, + code: 422, + } + } + if (error instanceof RequiresProPlanError) { + return { + error: error.message, + code: 402, + } + } + return { + error, + code: 500, + } + } +} + +const routeContextSchema = z.object({ + params: z.object({ + entryId: z.string(), + }), +}) + +export async function deleteJournalEntry( + rawInput: z.infer +) { + try { + const { params } = routeContextSchema.parse(rawInput) + + if (!(await verifyCurrentUserHasAccessToEntry(params.entryId))) { + throw new Error("You don't have any access to this entry") + } + + await db.journalEntry.delete({ + where: { + id: params.entryId, + }, + }) + revalidatePath("/journal") + } catch (error: unknown) { + if (error instanceof z.ZodError) { + return { + error: error.issues, + code: 422, + } + } + return { + error, + code: 500, + } + } +} + +export async function editJournalEntry( + rawInput: z.infer, + data: z.infer +) { + try { + const { params } = routeContextSchema.parse(rawInput) + + if (!(await verifyCurrentUserHasAccessToEntry(params.entryId))) { + throw new Error("You don't have any access to this entry") + } + + const input = entryPatchSchema.parse(data) + + await db.journalEntry.update({ + where: { + id: params.entryId, + }, + data: { + title: input.title, + content: input.content, + }, + }) + + revalidatePath(`/editor/${params.entryId}`) + } catch (error) { + if (error instanceof z.ZodError) { + return { + error: error.issues, + code: 422, + } + } + return { + error, + code: 500, + } + } +} diff --git a/src/server/actions/reminder.ts b/src/server/actions/reminder.ts new file mode 100644 index 0000000..0f52929 --- /dev/null +++ b/src/server/actions/reminder.ts @@ -0,0 +1,63 @@ +"use server" + +import { revalidatePath } from "next/cache" +import { getUserSubscriptionPlan } from "@/server/actions/stripe" +import { z } from "zod" + +import { getUserByClerkId } from "@/lib/auth" +import { db } from "@/lib/db" +import { RequiresProPlanError } from "@/lib/exceptions" +import { reminderFormSchema } from "@/lib/validations/reminder" + +export async function updateReminder( + rawInput: z.infer +) { + try { + const user = await getUserByClerkId() + if (!user) { + throw new Error("Unauthorized") + } + + const subscriptionPlan = await getUserSubscriptionPlan(user?.id) + if (!subscriptionPlan?.isPro) { + throw new RequiresProPlanError() + } + + const payload = reminderFormSchema.parse(rawInput) + + const reminder = await db.reminder.upsert({ + where: { + userId: user.id, + }, + update: { + frequency: payload.frequency, + active: payload.active, + }, + create: { + frequency: payload.frequency, + active: payload.active, + userId: user?.id, + }, + }) + + revalidatePath("/journal/settings") + return reminder + } catch (error: unknown) { + if (error instanceof z.ZodError) { + return { + error: error.issues, + code: 422, + } + } + if (error instanceof RequiresProPlanError) { + return { + error: error.message, + code: 402, + } + } + return { + error, + code: 500, + } + } +} diff --git a/src/server/actions/stripe.ts b/src/server/actions/stripe.ts new file mode 100644 index 0000000..e1a7760 --- /dev/null +++ b/src/server/actions/stripe.ts @@ -0,0 +1,104 @@ +"use server" + +import { UserSubscriptionPlan } from "@/types" +import { z } from "zod" + +import { freePlan, proPlan } from "@/config/subscriptions" +import { getUserByClerkId } from "@/lib/auth" +import { db } from "@/lib/db" +import { stripe } from "@/lib/stripe" +import { absoluteUrl } from "@/lib/utils" + +const billingUrl = absoluteUrl("/journal/billing") + +export async function getUserSubscriptionPlan( + userId: string +): Promise { + const user = await db.user.findFirst({ + where: { + id: userId, + }, + select: { + stripeSubscriptionId: true, + stripeCurrentPeriodEnd: true, + stripeCustomerId: true, + stripePriceId: true, + }, + }) + + if (!user) { + throw new Error("User not found") + } + + // Check if user is on a pro plan. + const isPro = + user.stripePriceId && + user.stripeCurrentPeriodEnd?.getTime() + 86_400_000 > Date.now() + + const plan = isPro ? proPlan : freePlan + + return { + ...plan, + ...user, + stripeCurrentPeriodEnd: user.stripeCurrentPeriodEnd?.getTime(), + isPro, + } +} + +export async function stripeSubscription() { + try { + const user = await getUserByClerkId() + if (!user || !user.email) { + throw new Error("Unauthorized") + } + + const subscriptionPlan = await getUserSubscriptionPlan(user.id) + + // The user is on the pro plan. + // Create a portal session to manage subscription. + if (subscriptionPlan.isPro && subscriptionPlan.stripeCustomerId) { + const stripeSession = await stripe.billingPortal.sessions.create({ + customer: subscriptionPlan.stripeCustomerId, + return_url: billingUrl, + }) + return { + url: stripeSession.url, + } + } + + // The user is on the free plan. + // Create a checkout session to upgrade. + const stripeSession = await stripe.checkout.sessions.create({ + success_url: billingUrl, + cancel_url: billingUrl, + payment_method_types: ["card"], + mode: "subscription", + billing_address_collection: "auto", + customer_email: user.email, + line_items: [ + { + price: proPlan.stripePriceId, + quantity: 1, + }, + ], + metadata: { + userId: user.id, + }, + }) + + return { + url: stripeSession.url, + } + } catch (error: unknown) { + if (error instanceof z.ZodError) { + return { + error: error.issues, + code: 422, + } + } + return { + error, + code: 500, + } + } +} diff --git a/src/server/actions/user.ts b/src/server/actions/user.ts new file mode 100644 index 0000000..d1cc940 --- /dev/null +++ b/src/server/actions/user.ts @@ -0,0 +1,52 @@ +"use server" + +import { revalidatePath } from "next/cache" +import { z } from "zod" + +import { getUserByClerkId } from "@/lib/auth" +import { db } from "@/lib/db" +import { userNameSchema } from "@/lib/validations/user" + +const routeContextSchema = z.object({ + params: z.object({ + userId: z.string(), + }), +}) + +export async function updateUserName( + rawInput: z.infer, + input: z.infer +) { + try { + const { params } = routeContextSchema.parse(rawInput) + + const user = await getUserByClerkId() + if (!user || params.userId !== user.id) { + throw new Error("Unauthorized") + } + + const payload = userNameSchema.parse(input) + + await db.user.update({ + where: { + id: user.id, + }, + data: { + name: payload.name, + }, + }) + + revalidatePath("/journal/settings") + } catch (error: unknown) { + if (error instanceof z.ZodError) { + return { + error: error.issues, + code: 422, + } + } + return { + error, + code: 500, + } + } +} From e7028c5a5660d409fced46a3246be7b9546311b8 Mon Sep 17 00:00:00 2001 From: Subham Bharadwaz Date: Mon, 13 May 2024 17:01:06 +0530 Subject: [PATCH 3/4] feat: :sparkles: Added Queries - Created server-only quries --- src/server/queries/editor.ts | 17 ++++++++++++++++ src/server/queries/journal.ts | 27 +++++++++++++++++++++++++ src/server/queries/reminder.ts | 37 ++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 src/server/queries/editor.ts create mode 100644 src/server/queries/journal.ts create mode 100644 src/server/queries/reminder.ts diff --git a/src/server/queries/editor.ts b/src/server/queries/editor.ts new file mode 100644 index 0000000..93a3c27 --- /dev/null +++ b/src/server/queries/editor.ts @@ -0,0 +1,17 @@ +import "server-only" + +import { JournalEntry, User } from "@prisma/client" + +import { db } from "@/lib/db" + +export async function getMyEntry( + entryId: JournalEntry["id"], + userId: User["id"] +) { + return await db.journalEntry.findFirst({ + where: { + id: entryId, + userId: userId, + }, + }) +} diff --git a/src/server/queries/journal.ts b/src/server/queries/journal.ts new file mode 100644 index 0000000..8692f86 --- /dev/null +++ b/src/server/queries/journal.ts @@ -0,0 +1,27 @@ +import "server-only" + +import { getUserByClerkId } from "@/lib/auth" +import { db } from "@/lib/db" + +export async function getMyJournalEntries() { + const user = await getUserByClerkId() + + if (!user) { + throw new Error("Unauthorized") + } + + const entries = await db.journalEntry.findMany({ + where: { + userId: user?.id, + }, + select: { + id: true, + title: true, + createdAt: true, + }, + orderBy: { + updatedAt: "desc", + }, + }) + return entries +} diff --git a/src/server/queries/reminder.ts b/src/server/queries/reminder.ts new file mode 100644 index 0000000..3160b99 --- /dev/null +++ b/src/server/queries/reminder.ts @@ -0,0 +1,37 @@ +import "server-only" + +import { z } from "zod" + +import { getUserByClerkId } from "@/lib/auth" +import { db } from "@/lib/db" + +export async function getMyReminderSettings() { + try { + const user = await getUserByClerkId() + + if (!user) { + throw new Error("Unauthorized") + } + + const reminder = await db.reminder.findFirst({ + select: { + id: true, + frequency: true, + time: true, + active: true, + }, + where: { + userId: user.id, + }, + }) + return reminder + } catch (error: unknown) { + if (error instanceof z.ZodError) { + return { + error: error.issues, + code: 422, + } + } + throw new Error("Server error", error) + } +} From 4a14657d21cb7db4a47a9eeb41f3e1d7a623f169 Mon Sep 17 00:00:00 2001 From: Subham Bharadwaz Date: Mon, 13 May 2024 17:02:18 +0530 Subject: [PATCH 4/4] refactor: :recycle: Streamline Multiple Changes - Moved the components to their respective places - Implemented server-actions - Improved the overall codebase --- package.json | 12 +- pnpm-lock.yaml | 284 +++++++++--------- src/app/(editor)/editor/[entryId]/page.tsx | 14 +- .../_components/billing/billing-form.tsx | 13 +- src/app/(journal)/_components/editor.tsx | 20 +- .../_components}/entry-operations.tsx | 16 +- .../journal-entry-create-button.tsx | 30 +- .../(journal)/_components/journal-entry.tsx | 3 +- .../_components/settings/appearance-form.tsx | 8 +- .../_components/settings/reminder-form.tsx | 51 ++-- .../_components/settings/user-name-form.tsx | 18 +- src/app/(journal)/journal/billing/page.tsx | 2 +- src/app/(journal)/journal/page.tsx | 22 +- src/app/(journal)/journal/settings/page.tsx | 18 +- .../(marketing)/_components}/background.tsx | 0 src/app/(marketing)/layout.tsx | 3 +- .../api/journal/entries/[entryId]/route.ts | 92 ------ src/app/api/journal/entries/route.ts | 85 ------ src/app/api/reminder/route.ts | 90 ------ src/app/api/users/[userId]/route.ts | 49 --- src/app/api/users/stripe/route.ts | 60 ---- src/components/user-account-nav.tsx | 1 - src/lib/subscription.ts | 40 --- ...verify-current-user-has-access-to-entry.ts | 14 + 24 files changed, 248 insertions(+), 697 deletions(-) rename src/{components => app/(journal)/_components}/entry-operations.tsx (86%) rename src/{components/marketing => app/(marketing)/_components}/background.tsx (100%) delete mode 100644 src/app/api/journal/entries/[entryId]/route.ts delete mode 100644 src/app/api/journal/entries/route.ts delete mode 100644 src/app/api/reminder/route.ts delete mode 100644 src/app/api/users/[userId]/route.ts delete mode 100644 src/app/api/users/stripe/route.ts delete mode 100644 src/lib/subscription.ts create mode 100644 src/lib/verify-current-user-has-access-to-entry.ts diff --git a/package.json b/package.json index a337a0d..4f75e87 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "@editorjs/paragraph": "^2.11.4", "@editorjs/table": "^2.3.0", "@hookform/resolvers": "^3.3.4", - "@prisma/client": "^4.16.2", + "@prisma/client": "^5.13.0", "@radix-ui/react-accessible-icon": "^1.0.3", "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-aspect-ratio": "^1.0.3", @@ -82,7 +82,6 @@ "@uploadthing/react": "^5.7.0", "@vercel/analytics": "^1.2.2", "autoprefixer": "10.4.14", - "axios": "^1.6.8", "class-variance-authority": "^0.6.1", "clsx": "^1.2.1", "cmdk": "^0.2.1", @@ -95,7 +94,7 @@ "framer-motion": "^10.18.0", "lottie-react": "^2.4.0", "lucide-react": "^0.246.0", - "next": "13.4.8", + "next": "14.2.3", "next-contentlayer": "^0.3.4", "next-themes": "^0.2.1", "postcss": "8.4.24", @@ -110,13 +109,12 @@ "react-parallax-tilt": "^1.7.225", "react-textarea-autosize": "^8.5.3", "resend": "^0.17.2", + "server-only": "^0.0.1", "sharp": "^0.32.6", "stripe": "^12.18.0", "svix": "^1.24.0", "tailwind-merge": "^1.14.0", - "tailwindcss": "3.3.2", "tailwindcss-animate": "^1.0.7", - "typescript": "5.1.3", "uploadthing": "^5.7.4", "zod": "^3.23.8" }, @@ -133,6 +131,8 @@ "prettier": "^2.8.8", "prettier-plugin-tailwindcss": "^0.3.0", "pretty-quick": "^3.3.1", - "prisma": "^4.16.2" + "prisma": "^5.13.0", + "tailwindcss": "^3.4.1", + "typescript": "5.1.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1b18d75..5817d04 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,7 +16,7 @@ overrides: dependencies: "@clerk/nextjs": specifier: ^5.0.7 - version: 5.0.7(next@13.4.8)(react-dom@18.2.0)(react@18.2.0) + version: 5.0.7(next@14.2.3)(react-dom@18.2.0)(react@18.2.0) "@editorjs/checklist": specifier: ^1.6.0 version: 1.6.0 @@ -54,8 +54,8 @@ dependencies: specifier: ^3.3.4 version: 3.3.4(react-hook-form@7.51.4) "@prisma/client": - specifier: ^4.16.2 - version: 4.16.2(prisma@4.16.2) + specifier: ^5.13.0 + version: 5.13.0(prisma@5.13.0) "@radix-ui/react-accessible-icon": specifier: ^1.0.3 version: 1.0.3(@types/react-dom@18.2.6)(@types/react@18.2.13)(react-dom@18.2.0)(react@18.2.0) @@ -133,7 +133,7 @@ dependencies: version: 0.0.8(ts-node@10.9.2) "@sentry/nextjs": specifier: ^7.105.0 - version: 7.114.0(next@13.4.8)(react@18.2.0) + version: 7.114.0(next@14.2.3)(react@18.2.0) "@t3-oss/env-nextjs": specifier: ^0.4.1 version: 0.4.1(typescript@5.1.3)(zod@3.23.8) @@ -154,16 +154,13 @@ dependencies: version: 5.62.0(eslint@8.43.0)(typescript@5.1.3) "@uploadthing/react": specifier: ^5.7.0 - version: 5.7.0(next@13.4.8)(react@18.2.0)(uploadthing@5.7.4) + version: 5.7.0(next@14.2.3)(react@18.2.0)(uploadthing@5.7.4) "@vercel/analytics": specifier: ^1.2.2 - version: 1.2.2(next@13.4.8)(react@18.2.0) + version: 1.2.2(next@14.2.3)(react@18.2.0) autoprefixer: specifier: 10.4.14 version: 10.4.14(postcss@8.4.24) - axios: - specifier: ^1.6.8 - version: 1.6.8 class-variance-authority: specifier: ^0.6.1 version: 0.6.1 @@ -201,14 +198,14 @@ dependencies: specifier: ^0.246.0 version: 0.246.0(react@18.2.0) next: - specifier: 13.4.8 - version: 13.4.8(@babel/core@7.24.5)(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) + specifier: 14.2.3 + version: 14.2.3(@babel/core@7.24.5)(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) next-contentlayer: specifier: ^0.3.4 - version: 0.3.4(contentlayer@0.3.4)(esbuild@0.21.1)(next@13.4.8)(react-dom@18.2.0)(react@18.2.0) + version: 0.3.4(contentlayer@0.3.4)(esbuild@0.21.1)(next@14.2.3)(react-dom@18.2.0)(react@18.2.0) next-themes: specifier: ^0.2.1 - version: 0.2.1(next@13.4.8)(react-dom@18.2.0)(react@18.2.0) + version: 0.2.1(next@14.2.3)(react-dom@18.2.0)(react@18.2.0) postcss: specifier: 8.4.24 version: 8.4.24 @@ -245,6 +242,9 @@ dependencies: resend: specifier: ^0.17.2 version: 0.17.2 + server-only: + specifier: ^0.0.1 + version: 0.0.1 sharp: specifier: ^0.32.6 version: 0.32.6 @@ -257,15 +257,9 @@ dependencies: tailwind-merge: specifier: ^1.14.0 version: 1.14.0 - tailwindcss: - specifier: 3.3.2 - version: 3.3.2(ts-node@10.9.2) tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.3.2) - typescript: - specifier: 5.1.3 - version: 5.1.3 + version: 1.0.7(tailwindcss@3.4.3) uploadthing: specifier: ^5.7.4 version: 5.7.4 @@ -285,10 +279,10 @@ devDependencies: version: 4.2.1(prettier@2.8.8) "@tailwindcss/line-clamp": specifier: ^0.4.4 - version: 0.4.4(tailwindcss@3.3.2) + version: 0.4.4(tailwindcss@3.4.3) "@tailwindcss/typography": specifier: ^0.5.13 - version: 0.5.13(tailwindcss@3.3.2) + version: 0.5.13(tailwindcss@3.4.3) eslint-config-prettier: specifier: ^8.10.0 version: 8.10.0(eslint@8.43.0) @@ -297,7 +291,7 @@ devDependencies: version: 7.34.1(eslint@8.43.0) eslint-plugin-tailwindcss: specifier: ^3.15.1 - version: 3.15.1(tailwindcss@3.3.2) + version: 3.15.1(tailwindcss@3.4.3) husky: specifier: ^8.0.3 version: 8.0.3 @@ -311,8 +305,14 @@ devDependencies: specifier: ^3.3.1 version: 3.3.1(prettier@2.8.8) prisma: - specifier: ^4.16.2 - version: 4.16.2 + specifier: ^5.13.0 + version: 5.13.0 + tailwindcss: + specifier: ^3.4.1 + version: 3.4.3(ts-node@10.9.2) + typescript: + specifier: 5.1.3 + version: 5.1.3 packages: /@alloc/quick-lru@5.2.0: @@ -609,7 +609,7 @@ packages: tslib: 2.4.1 dev: false - /@clerk/nextjs@5.0.7(next@13.4.8)(react-dom@18.2.0)(react@18.2.0): + /@clerk/nextjs@5.0.7(next@14.2.3)(react-dom@18.2.0)(react@18.2.0): resolution: { integrity: sha512-HMUERoU52EnEVpG9IaFcRaEOXme8rzv40POkczt8MNHLJ7bQjf3oyc2L+o++w7lmusGNABbfTs1Aq6F2d9b2Gw==, @@ -624,7 +624,7 @@ packages: "@clerk/clerk-react": 5.0.4(react-dom@18.2.0)(react@18.2.0) "@clerk/shared": 2.0.2(react-dom@18.2.0)(react@18.2.0) crypto-js: 4.2.0 - next: 13.4.8(@babel/core@7.24.5)(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) + next: 14.2.3(@babel/core@7.24.5)(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) path-to-regexp: 6.2.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -2143,10 +2143,10 @@ packages: - supports-color dev: false - /@next/env@13.4.8: + /@next/env@14.2.3: resolution: { - integrity: sha512-twuSf1klb3k9wXI7IZhbZGtFCWvGD4wXTY2rmvzIgVhXhs7ISThrbNyutBx3jWIL8Y/Hk9+woytFz5QsgtcRKQ==, + integrity: sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA==, } dev: false @@ -2159,10 +2159,10 @@ packages: glob: 7.1.7 dev: false - /@next/swc-darwin-arm64@13.4.8: + /@next/swc-darwin-arm64@14.2.3: resolution: { - integrity: sha512-MSFplVM4dTWOuKAUv0XR9gY7AWtMSBu9os9f+kp+s5rWhM1I2CdR3obFttd6366nS/W/VZxbPM5oEIdlIa46zA==, + integrity: sha512-3pEYo/RaGqPP0YzwnlmPN2puaF2WMLM3apt5jLW2fFdXD9+pqcoTzRk+iZsf8ta7+quAe4Q6Ms0nR0SFGFdS1A==, } engines: { node: ">= 10" } cpu: [arm64] @@ -2171,10 +2171,10 @@ packages: dev: false optional: true - /@next/swc-darwin-x64@13.4.8: + /@next/swc-darwin-x64@14.2.3: resolution: { - integrity: sha512-Reox+UXgonon9P0WNDE6w85DGtyBqGitl/ryznOvn6TvfxEaZIpTgeu3ZrJLU9dHSMhiK7YAM793mE/Zii2/Qw==, + integrity: sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==, } engines: { node: ">= 10" } cpu: [x64] @@ -2183,10 +2183,10 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-gnu@13.4.8: + /@next/swc-linux-arm64-gnu@14.2.3: resolution: { - integrity: sha512-kdyzYvAYtqQVgzIKNN7e1rLU8aZv86FDSRqPlOkKZlvqudvTO0iohuTPmnEEDlECeBM6qRPShNffotDcU/R2KA==, + integrity: sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==, } engines: { node: ">= 10" } cpu: [arm64] @@ -2195,10 +2195,10 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-musl@13.4.8: + /@next/swc-linux-arm64-musl@14.2.3: resolution: { - integrity: sha512-oWxx4yRkUGcR81XwbI+T0zhZ3bDF6V1aVLpG+C7hSG50ULpV8gC39UxVO22/bv93ZlcfMY4zl8xkz9Klct6dpQ==, + integrity: sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==, } engines: { node: ">= 10" } cpu: [arm64] @@ -2207,10 +2207,10 @@ packages: dev: false optional: true - /@next/swc-linux-x64-gnu@13.4.8: + /@next/swc-linux-x64-gnu@14.2.3: resolution: { - integrity: sha512-anhtvuO6eE9YRhYnaEGTfbpH3L5gT/9qPFcNoi6xS432r/4DAtpJY8kNktqkTVevVIC/pVumqO8tV59PR3zbNg==, + integrity: sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==, } engines: { node: ">= 10" } cpu: [x64] @@ -2219,10 +2219,10 @@ packages: dev: false optional: true - /@next/swc-linux-x64-musl@13.4.8: + /@next/swc-linux-x64-musl@14.2.3: resolution: { - integrity: sha512-aR+J4wWfNgH1DwCCBNjan7Iumx0lLtn+2/rEYuhIrYLY4vnxqSVGz9u3fXcgUwo6Q9LT8NFkaqK1vPprdq+BXg==, + integrity: sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==, } engines: { node: ">= 10" } cpu: [x64] @@ -2231,10 +2231,10 @@ packages: dev: false optional: true - /@next/swc-win32-arm64-msvc@13.4.8: + /@next/swc-win32-arm64-msvc@14.2.3: resolution: { - integrity: sha512-OWBKIrJwQBTqrat0xhxEB/jcsjJR3+diD9nc/Y8F1mRdQzsn4bPsomgJyuqPVZs6Lz3K18qdIkvywmfSq75SsQ==, + integrity: sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==, } engines: { node: ">= 10" } cpu: [arm64] @@ -2243,10 +2243,10 @@ packages: dev: false optional: true - /@next/swc-win32-ia32-msvc@13.4.8: + /@next/swc-win32-ia32-msvc@14.2.3: resolution: { - integrity: sha512-agiPWGjUndXGTOn4ChbKipQXRA6/UPkywAWIkx7BhgGv48TiJfHTK6MGfBoL9tS6B4mtW39++uy0wFPnfD0JWg==, + integrity: sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==, } engines: { node: ">= 10" } cpu: [ia32] @@ -2255,10 +2255,10 @@ packages: dev: false optional: true - /@next/swc-win32-x64-msvc@13.4.8: + /@next/swc-win32-x64-msvc@14.2.3: resolution: { - integrity: sha512-UIRKoByVKbuR6SnFG4JM8EMFlJrfEGuUQ1ihxzEleWcNwRMMiVaCj1KyqfTOW8VTQhJ0u8P1Ngg6q1RwnIBTtw==, + integrity: sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==, } engines: { node: ">= 10" } cpu: [x64] @@ -2696,12 +2696,12 @@ packages: requiresBuild: true optional: true - /@prisma/client@4.16.2(prisma@4.16.2): + /@prisma/client@5.13.0(prisma@5.13.0): resolution: { - integrity: sha512-qCoEyxv1ZrQ4bKy39GnylE8Zq31IRmm8bNhNbZx7bF2cU5aiCCnSa93J2imF88MBjn7J9eUQneNxUQVJdl/rPQ==, + integrity: sha512-uYdfpPncbZ/syJyiYBwGZS8Gt1PTNoErNYMuqHDa2r30rNSFtgTA/LXsSk55R7pdRTMi5pHkeP9B14K6nHmwkg==, } - engines: { node: ">=14.17" } + engines: { node: ">=16.13" } requiresBuild: true peerDependencies: prisma: "*" @@ -2709,23 +2709,50 @@ packages: prisma: optional: true dependencies: - "@prisma/engines-version": 4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81 - prisma: 4.16.2 + prisma: 5.13.0 dev: false - /@prisma/engines-version@4.16.1-1.4bc8b6e1b66cb932731fb1bdbbc550d1e010de81: + /@prisma/debug@5.13.0: resolution: { - integrity: sha512-q617EUWfRIDTriWADZ4YiWRZXCa/WuhNgLTVd+HqWLffjMSPzyM5uOWoauX91wvQClSKZU4pzI4JJLQ9Kl62Qg==, + integrity: sha512-699iqlEvzyCj9ETrXhs8o8wQc/eVW+FigSsHpiskSFydhjVuwTJEfj/nIYqTaWFYuxiWQRfm3r01meuW97SZaQ==, + } + + /@prisma/engines-version@5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b: + resolution: + { + integrity: sha512-AyUuhahTINGn8auyqYdmxsN+qn0mw3eg+uhkp8zwknXYIqoT3bChG4RqNY/nfDkPvzWAPBa9mrDyBeOnWSgO6A==, } - dev: false - /@prisma/engines@4.16.2: + /@prisma/engines@5.13.0: resolution: { - integrity: sha512-vx1nxVvN4QeT/cepQce68deh/Turxy5Mr+4L4zClFuK1GlxN3+ivxfuv+ej/gvidWn1cE1uAhW7ALLNlYbRUAw==, + integrity: sha512-hIFLm4H1boj6CBZx55P4xKby9jgDTeDG0Jj3iXtwaaHmlD5JmiDkZhh8+DYWkTGchu+rRF36AVROLnk0oaqhHw==, } requiresBuild: true + dependencies: + "@prisma/debug": 5.13.0 + "@prisma/engines-version": 5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b + "@prisma/fetch-engine": 5.13.0 + "@prisma/get-platform": 5.13.0 + + /@prisma/fetch-engine@5.13.0: + resolution: + { + integrity: sha512-Yh4W+t6YKyqgcSEB3odBXt7QyVSm0OQlBSldQF2SNXtmOgMX8D7PF/fvH6E6qBCpjB/yeJLy/FfwfFijoHI6sA==, + } + dependencies: + "@prisma/debug": 5.13.0 + "@prisma/engines-version": 5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b + "@prisma/get-platform": 5.13.0 + + /@prisma/get-platform@5.13.0: + resolution: + { + integrity: sha512-B/WrQwYTzwr7qCLifQzYOmQhZcFmIFhR81xC45gweInSUn2hTEbfKUPd2keAog+y5WI5xLAFNJ3wkXplvSVkSw==, + } + dependencies: + "@prisma/debug": 5.13.0 /@protobufjs/aspromise@1.1.2: resolution: @@ -4787,7 +4814,7 @@ packages: localforage: 1.10.0 dev: false - /@sentry/nextjs@7.114.0(next@13.4.8)(react@18.2.0): + /@sentry/nextjs@7.114.0(next@14.2.3)(react@18.2.0): resolution: { integrity: sha512-QRqE+YTVG3btTPVhOfiq0XmHp0dG4A0C/R+ssR/pdfOBr4EfEEav0hlTlqvk9BV0u6naJ5TOvBZ6Fy41rkYYrQ==, @@ -4811,7 +4838,7 @@ packages: "@sentry/vercel-edge": 7.114.0 "@sentry/webpack-plugin": 1.21.0 chalk: 3.0.0 - next: 13.4.8(@babel/core@7.24.5)(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) + next: 14.2.3(@babel/core@7.24.5)(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 resolve: 1.22.8 rollup: 2.78.0 @@ -4918,12 +4945,20 @@ packages: } dev: false - /@swc/helpers@0.5.1: + /@swc/counter@0.1.3: resolution: { - integrity: sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==, + integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==, + } + dev: false + + /@swc/helpers@0.5.5: + resolution: + { + integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==, } dependencies: + "@swc/counter": 0.1.3 tslib: 2.6.2 dev: false @@ -4954,7 +4989,7 @@ packages: zod: 3.23.8 dev: false - /@tailwindcss/line-clamp@0.4.4(tailwindcss@3.3.2): + /@tailwindcss/line-clamp@0.4.4(tailwindcss@3.4.3): resolution: { integrity: sha512-5U6SY5z8N42VtrCrKlsTAA35gy2VSyYtHWCsg1H87NU1SXnEfekTVlrga9fzUDrrHcGi2Lb5KenUWb4lRQT5/g==, @@ -4962,10 +4997,10 @@ packages: peerDependencies: tailwindcss: ">=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1" dependencies: - tailwindcss: 3.3.2(ts-node@10.9.2) + tailwindcss: 3.4.3(ts-node@10.9.2) dev: true - /@tailwindcss/typography@0.5.13(tailwindcss@3.3.2): + /@tailwindcss/typography@0.5.13(tailwindcss@3.4.3): resolution: { integrity: sha512-ADGcJ8dX21dVVHIwTRgzrcunY6YY9uSlAHHGVKvkA+vLc5qLwEszvKts40lx7z0qc4clpjclwLeK5rVCV2P/uw==, @@ -4977,7 +5012,7 @@ packages: lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 postcss-selector-parser: 6.0.10 - tailwindcss: 3.3.2(ts-node@10.9.2) + tailwindcss: 3.4.3(ts-node@10.9.2) dev: true /@tanstack/query-core@4.36.1: @@ -5269,7 +5304,7 @@ packages: } dev: false - /@uploadthing/react@5.7.0(next@13.4.8)(react@18.2.0)(uploadthing@5.7.4): + /@uploadthing/react@5.7.0(next@14.2.3)(react@18.2.0)(uploadthing@5.7.4): resolution: { integrity: sha512-rSBzoC2eMRM2d6Mpis6RXfo1Y5JsV7oJanTmaVdltFLRxbO/4lwK1kvHM4qwDUvn01UpxBtcVayimMBXNJ0V8Q==, @@ -5285,7 +5320,7 @@ packages: "@uploadthing/shared": 5.2.7(@uploadthing/mime-types@0.2.9) attr-accept: 2.2.2 file-selector: 0.6.0 - next: 13.4.8(@babel/core@7.24.5)(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) + next: 14.2.3(@babel/core@7.24.5)(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 tailwind-merge: 1.14.0 uploadthing: 5.7.4 @@ -5307,7 +5342,7 @@ packages: "@uploadthing/mime-types": 0.2.9 dev: false - /@vercel/analytics@1.2.2(next@13.4.8)(react@18.2.0): + /@vercel/analytics@1.2.2(next@14.2.3)(react@18.2.0): resolution: { integrity: sha512-X0rctVWkQV1e5Y300ehVNqpOfSOufo7ieA5PIdna8yX/U7Vjz0GFsGf4qvAhxV02uQ2CVt7GYcrFfddXXK2Y4A==, @@ -5321,7 +5356,7 @@ packages: react: optional: true dependencies: - next: 13.4.8(@babel/core@7.24.5)(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) + next: 14.2.3(@babel/core@7.24.5)(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 server-only: 0.0.1 dev: false @@ -5774,19 +5809,6 @@ packages: - debug dev: false - /axios@1.6.8: - resolution: - { - integrity: sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==, - } - dependencies: - follow-redirects: 1.15.6 - form-data: 4.0.0 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - dev: false - /axobject-query@3.2.1: resolution: { @@ -7472,7 +7494,7 @@ packages: semver: 6.3.1 string.prototype.matchall: 4.0.11 - /eslint-plugin-tailwindcss@3.15.1(tailwindcss@3.3.2): + /eslint-plugin-tailwindcss@3.15.1(tailwindcss@3.4.3): resolution: { integrity: sha512-4RXRMIaMG07C2TBEW1k0VM4+dDazz1kxcZhkK4zirvmHGZTA4jnlSO2kq5mamuSPi+Wo17dh2SlC8IyFBuCd7Q==, @@ -7483,7 +7505,7 @@ packages: dependencies: fast-glob: 3.3.2 postcss: 8.4.24 - tailwindcss: 3.3.2(ts-node@10.9.2) + tailwindcss: 3.4.3(ts-node@10.9.2) dev: true /eslint-scope@7.2.2: @@ -10561,7 +10583,7 @@ packages: integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, } - /next-contentlayer@0.3.4(contentlayer@0.3.4)(esbuild@0.21.1)(next@13.4.8)(react-dom@18.2.0)(react@18.2.0): + /next-contentlayer@0.3.4(contentlayer@0.3.4)(esbuild@0.21.1)(next@14.2.3)(react-dom@18.2.0)(react@18.2.0): resolution: { integrity: sha512-UtUCwgAl159KwfhNaOwyiI7Lg6sdioyKMeh+E7jxx0CJ29JuXGxBEYmCI6+72NxFGIFZKx8lvttbbQhbnYWYSw==, @@ -10575,7 +10597,7 @@ packages: "@contentlayer/core": 0.3.4(esbuild@0.21.1) "@contentlayer/utils": 0.3.4 contentlayer: 0.3.4(esbuild@0.21.1) - next: 13.4.8(@babel/core@7.24.5)(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) + next: 14.2.3(@babel/core@7.24.5)(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) transitivePeerDependencies: @@ -10585,7 +10607,7 @@ packages: - supports-color dev: false - /next-themes@0.2.1(next@13.4.8)(react-dom@18.2.0)(react@18.2.0): + /next-themes@0.2.1(next@14.2.3)(react-dom@18.2.0)(react@18.2.0): resolution: { integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==, @@ -10595,53 +10617,52 @@ packages: react: "*" react-dom: "*" dependencies: - next: 13.4.8(@babel/core@7.24.5)(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) + next: 14.2.3(@babel/core@7.24.5)(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false - /next@13.4.8(@babel/core@7.24.5)(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0): + /next@14.2.3(@babel/core@7.24.5)(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0): resolution: { - integrity: sha512-lxUjndYKjZHGK3CWeN2RI+/6ni6EUvjiqGWXAYPxUfGIdFGQ5XoisrqAJ/dF74aP27buAfs8MKIbIMMdxjqSBg==, + integrity: sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==, } - engines: { node: ">=16.8.0" } + engines: { node: ">=18.17.0" } hasBin: true peerDependencies: "@opentelemetry/api": 1.4.1 - fibers: ">= 3.1.0" + "@playwright/test": ^1.41.2 react: ^18.2.0 react-dom: ^18.2.0 sass: ^1.3.0 peerDependenciesMeta: "@opentelemetry/api": optional: true - fibers: + "@playwright/test": optional: true sass: optional: true dependencies: - "@next/env": 13.4.8 + "@next/env": 14.2.3 "@opentelemetry/api": 1.4.1 - "@swc/helpers": 0.5.1 + "@swc/helpers": 0.5.5 busboy: 1.6.0 caniuse-lite: 1.0.30001617 - postcss: 8.4.14 + graceful-fs: 4.2.11 + postcss: 8.4.31 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) styled-jsx: 5.1.1(@babel/core@7.24.5)(react@18.2.0) - watchpack: 2.4.0 - zod: 3.21.4 optionalDependencies: - "@next/swc-darwin-arm64": 13.4.8 - "@next/swc-darwin-x64": 13.4.8 - "@next/swc-linux-arm64-gnu": 13.4.8 - "@next/swc-linux-arm64-musl": 13.4.8 - "@next/swc-linux-x64-gnu": 13.4.8 - "@next/swc-linux-x64-musl": 13.4.8 - "@next/swc-win32-arm64-msvc": 13.4.8 - "@next/swc-win32-ia32-msvc": 13.4.8 - "@next/swc-win32-x64-msvc": 13.4.8 + "@next/swc-darwin-arm64": 14.2.3 + "@next/swc-darwin-x64": 14.2.3 + "@next/swc-linux-arm64-gnu": 14.2.3 + "@next/swc-linux-arm64-musl": 14.2.3 + "@next/swc-linux-x64-gnu": 14.2.3 + "@next/swc-linux-x64-musl": 14.2.3 + "@next/swc-win32-arm64-msvc": 14.2.3 + "@next/swc-win32-ia32-msvc": 14.2.3 + "@next/swc-win32-x64-msvc": 14.2.3 transitivePeerDependencies: - "@babel/core" - babel-plugin-macros @@ -11331,10 +11352,10 @@ packages: integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==, } - /postcss@8.4.14: + /postcss@8.4.21: resolution: { - integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==, + integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==, } engines: { node: ^10 || ^12 || >=14 } dependencies: @@ -11343,28 +11364,28 @@ packages: source-map-js: 1.2.0 dev: false - /postcss@8.4.21: + /postcss@8.4.24: resolution: { - integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==, + integrity: sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==, } engines: { node: ^10 || ^12 || >=14 } dependencies: nanoid: 3.3.7 picocolors: 1.0.0 source-map-js: 1.2.0 - dev: false - /postcss@8.4.24: + /postcss@8.4.31: resolution: { - integrity: sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==, + integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==, } engines: { node: ^10 || ^12 || >=14 } dependencies: nanoid: 3.3.7 picocolors: 1.0.0 source-map-js: 1.2.0 + dev: false /posthog-js@1.131.3: resolution: @@ -11512,16 +11533,16 @@ packages: js-beautify: 1.15.1 dev: false - /prisma@4.16.2: + /prisma@5.13.0: resolution: { - integrity: sha512-SYCsBvDf0/7XSJyf2cHTLjLeTLVXYfqp7pG5eEVafFLeT0u/hLFz/9W196nDRGUOo1JfPatAEb+uEnTQImQC1g==, + integrity: sha512-kGtcJaElNRAdAGsCNykFSZ7dBKpL14Cbs+VaQ8cECxQlRPDjBlMHNFYeYt0SKovAVy2Y65JXQwB3A5+zIQwnTg==, } - engines: { node: ">=14.17" } + engines: { node: ">=16.13" } hasBin: true requiresBuild: true dependencies: - "@prisma/engines": 4.16.2 + "@prisma/engines": 5.13.0 /progress@2.0.3: resolution: @@ -12975,7 +12996,7 @@ packages: } dev: false - /tailwindcss-animate@1.0.7(tailwindcss@3.3.2): + /tailwindcss-animate@1.0.7(tailwindcss@3.4.3): resolution: { integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==, @@ -12983,7 +13004,7 @@ packages: peerDependencies: tailwindcss: ">=3.0.0 || insiders" dependencies: - tailwindcss: 3.3.2(ts-node@10.9.2) + tailwindcss: 3.4.3(ts-node@10.9.2) dev: false /tailwindcss@3.2.7(postcss@8.4.21)(ts-node@10.9.2): @@ -13023,10 +13044,10 @@ packages: - ts-node dev: false - /tailwindcss@3.3.2(ts-node@10.9.2): + /tailwindcss@3.4.3(ts-node@10.9.2): resolution: { - integrity: sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==, + integrity: sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==, } engines: { node: ">=14.0.0" } hasBin: true @@ -13051,7 +13072,6 @@ packages: postcss-load-config: 4.0.2(postcss@8.4.24)(ts-node@10.9.2) postcss-nested: 6.0.1(postcss@8.4.24) postcss-selector-parser: 6.0.16 - postcss-value-parser: 4.2.0 resolve: 1.22.8 sucrase: 3.35.0 transitivePeerDependencies: @@ -13836,17 +13856,6 @@ packages: vfile-message: 3.1.4 dev: false - /watchpack@2.4.0: - resolution: - { - integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==, - } - engines: { node: ">=10.13.0" } - dependencies: - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - dev: false - /wcwidth@1.0.1: resolution: { @@ -14104,13 +14113,6 @@ packages: } engines: { node: ">=10" } - /zod@3.21.4: - resolution: - { - integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==, - } - dev: false - /zod@3.23.8: resolution: { diff --git a/src/app/(editor)/editor/[entryId]/page.tsx b/src/app/(editor)/editor/[entryId]/page.tsx index f774e90..8977db9 100644 --- a/src/app/(editor)/editor/[entryId]/page.tsx +++ b/src/app/(editor)/editor/[entryId]/page.tsx @@ -1,19 +1,9 @@ import { notFound, redirect } from "next/navigation" -import { JournalEntry, User } from "@prisma/client" +import { getMyEntry } from "@/server/queries/editor" import { getUserByClerkId } from "@/lib/auth" -import { db } from "@/lib/db" import Editor from "@/app/(journal)/_components/editor" -async function getPostForUser(entryId: JournalEntry["id"], userId: User["id"]) { - return await db.journalEntry.findFirst({ - where: { - id: entryId, - userId: userId, - }, - }) -} - interface EditorPageProps { params: { entryId: string } } @@ -25,7 +15,7 @@ export default async function EditorPage({ params }: EditorPageProps) { redirect("/sign-in") } - const entry = await getPostForUser(params.entryId, user.id) + const entry = await getMyEntry(params.entryId, user.id) if (!entry) { notFound() diff --git a/src/app/(journal)/_components/billing/billing-form.tsx b/src/app/(journal)/_components/billing/billing-form.tsx index a426f7a..be1ef63 100644 --- a/src/app/(journal)/_components/billing/billing-form.tsx +++ b/src/app/(journal)/_components/billing/billing-form.tsx @@ -1,6 +1,7 @@ "use client" import * as React from "react" +import { stripeSubscription } from "@/server/actions/stripe" import { UserSubscriptionPlan } from "@/types" import { cn, formatDate } from "@/lib/utils" @@ -34,9 +35,10 @@ export function BillingForm({ setIsLoading(!isLoading) // Get a Stripe session URL. - const response = await fetch("/api/users/stripe") + const response = await stripeSubscription() + console.log(response) - if (!response?.ok) { + if (response?.error) { return toast({ title: "Something went wrong.", description: "Please refresh the page and try again.", @@ -47,9 +49,8 @@ export function BillingForm({ // Redirect to the Stripe session. // This could be a checkout page for initial upgrade. // Or portal to manage existing subscription. - const session = await response.json() - if (session) { - window.location.href = session.url + if (response) { + window.location.href = response?.url } } @@ -71,7 +72,7 @@ export function BillingForm({ disabled={isLoading} > {isLoading && ( - + )} {subscriptionPlan.isPro ? "Manage Subscription" : "Upgrade to PRO"} diff --git a/src/app/(journal)/_components/editor.tsx b/src/app/(journal)/_components/editor.tsx index fc0dd60..0f016eb 100644 --- a/src/app/(journal)/_components/editor.tsx +++ b/src/app/(journal)/_components/editor.tsx @@ -14,6 +14,8 @@ import { uploadFiles } from "@/lib/uploadthing" import "@/styles/editor.css" +import { editJournalEntry } from "@/server/actions/journal" + import { cn } from "@/lib/utils" import { entryPatchSchema } from "@/lib/validations/entry" import { buttonVariants } from "@/components/ui/button" @@ -146,20 +148,14 @@ const Editor: FC = ({ entry }) => { const blocks = await ref.current?.save() - const response = await fetch(`/api/journal/entries/${entry.id}`, { - method: "PATCH", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - title: data.title, - content: blocks, - }), - }) + const response = await editJournalEntry( + { params: { entryId: entry.id } }, + { title: data.title, content: blocks } + ) setIsSaving(false) - if (!response?.ok) { + if (response?.error) { return toast({ title: "Something went wrong.", description: "Your entry was not saved. Please try again.", @@ -167,8 +163,6 @@ const Editor: FC = ({ entry }) => { }) } - router.refresh() - return toast({ description: "Your entry has been saved.", }) diff --git a/src/components/entry-operations.tsx b/src/app/(journal)/_components/entry-operations.tsx similarity index 86% rename from src/components/entry-operations.tsx rename to src/app/(journal)/_components/entry-operations.tsx index ecaa7ed..07edb62 100644 --- a/src/components/entry-operations.tsx +++ b/src/app/(journal)/_components/entry-operations.tsx @@ -3,6 +3,7 @@ import * as React from "react" import Link from "next/link" import { useRouter } from "next/navigation" +import { deleteJournalEntry } from "@/server/actions/journal" import { JournalEntry } from "@prisma/client" import { @@ -26,11 +27,9 @@ import { toast } from "@/components/ui/use-toast" import { Icons } from "@/components/icons" async function deleteEntry(entryId: string) { - const response = await fetch(`/api/journal/entries/${entryId}`, { - method: "DELETE", - }) + const response = await deleteJournalEntry({ params: { entryId } }) - if (!response?.ok) { + if (response?.error) { toast({ title: "Something went wrong.", description: "Your Entry was not deleted. Please try again.", @@ -53,8 +52,8 @@ export function EntryOperations({ entry }: EntryOperationsProps) { return ( <> - - + + Open @@ -94,15 +93,14 @@ export function EntryOperations({ entry }: EntryOperationsProps) { if (deleted) { setIsDeleteLoading(false) setShowDeleteAlert(false) - router.refresh() } }} className="bg-red-600 focus:ring-red-600" > {isDeleteLoading ? ( - + ) : ( - + )} Delete diff --git a/src/app/(journal)/_components/journal-entry-create-button.tsx b/src/app/(journal)/_components/journal-entry-create-button.tsx index 69459db..cda28bf 100644 --- a/src/app/(journal)/_components/journal-entry-create-button.tsx +++ b/src/app/(journal)/_components/journal-entry-create-button.tsx @@ -2,9 +2,10 @@ import * as React from "react" import { useRouter } from "next/navigation" +import { createJournalEntry } from "@/server/actions/journal" import { cn } from "@/lib/utils" -import { Button, ButtonProps, buttonVariants } from "@/components/ui/button" +import { Button, ButtonProps } from "@/components/ui/button" import { toast } from "@/components/ui/use-toast" import { Icons } from "@/components/icons" @@ -21,20 +22,12 @@ const JournalEntryCreateButton: React.FC = ({ async function onClick() { setIsLoading(true) - const response = await fetch("/api/journal/entries", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - title: "Untitled Entry", - }), - }) + const entry = await createJournalEntry({ title: "Untitled Entry" }) setIsLoading(false) - if (!response?.ok) { - if (response.status === 402) { + if ("error" in entry) { + if (entry.code === 402) { return toast({ title: "Limit of 3 entries reached.", description: "Please upgrade to the PRO plan.", @@ -49,12 +42,9 @@ const JournalEntryCreateButton: React.FC = ({ }) } - const entry = await response.json() - - // This forces a cache invalidation. - router.refresh() - - router.push(`/editor/${entry.id}`) + if (entry) { + router.push(`/editor/${entry.id}`) + } } return ( @@ -70,9 +60,9 @@ const JournalEntryCreateButton: React.FC = ({ {...props} > {isLoading ? ( - + ) : ( - + )} New Entry diff --git a/src/app/(journal)/_components/journal-entry.tsx b/src/app/(journal)/_components/journal-entry.tsx index cd8de0a..2fc2c42 100644 --- a/src/app/(journal)/_components/journal-entry.tsx +++ b/src/app/(journal)/_components/journal-entry.tsx @@ -4,7 +4,8 @@ import { JournalEntry } from "@prisma/client" import { formatDate } from "@/lib/utils" import { Skeleton } from "@/components/ui/skeleton" -import { EntryOperations } from "@/components/entry-operations" + +import { EntryOperations } from "../_components/entry-operations" interface JournalEntryProps { entry: Pick diff --git a/src/app/(journal)/_components/settings/appearance-form.tsx b/src/app/(journal)/_components/settings/appearance-form.tsx index 3ba2d5d..253da36 100644 --- a/src/app/(journal)/_components/settings/appearance-form.tsx +++ b/src/app/(journal)/_components/settings/appearance-form.tsx @@ -70,11 +70,11 @@ export function AppearanceForm() {
-
+
-
+
@@ -96,11 +96,11 @@ export function AppearanceForm() {
-
+
-
+
diff --git a/src/app/(journal)/_components/settings/reminder-form.tsx b/src/app/(journal)/_components/settings/reminder-form.tsx index d8c55de..f440e63 100644 --- a/src/app/(journal)/_components/settings/reminder-form.tsx +++ b/src/app/(journal)/_components/settings/reminder-form.tsx @@ -2,12 +2,13 @@ import * as React from "react" import { useRouter } from "next/navigation" +import { updateReminder } from "@/server/actions/reminder" import { UserSubscriptionPlan } from "@/types" import { zodResolver } from "@hookform/resolvers/zod" import { Reminder } from "@prisma/client" -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" -import axios from "axios" +import { useMutation, useQueryClient } from "@tanstack/react-query" import { useForm } from "react-hook-form" +import { ZodIssue } from "zod" import { cn } from "@/lib/utils" import { @@ -15,7 +16,6 @@ import { ReminderFormValues, } from "@/lib/validations/reminder" import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" import { toast } from "@/components/ui/use-toast" import { Icons } from "@/components/icons" @@ -37,50 +37,41 @@ import { } from "../../../../components/ui/select" import { Switch } from "../../../../components/ui/switch" +type ReminderError = { + error: ZodIssue[] + code: number +} interface ReminderFormProps extends React.HTMLAttributes { subscriptionPlan: UserSubscriptionPlan + reminderSettings: Reminder } export function ReminderForm({ subscriptionPlan, className, + reminderSettings, ...props }: ReminderFormProps) { const router = useRouter() const queryClient = useQueryClient() const [isSaving, setIsSaving] = React.useState(false) - - const { data: reminder, isLoading } = useQuery({ - queryKey: ["reminder"], - queryFn: async () => { - const res = await axios.get( - `${process.env.NEXT_PUBLIC_APP_URL}/api/reminder` - ) - return await res.data - }, - }) + const [isLoading, setIsLoading] = React.useState(false) const form = useForm({ resolver: zodResolver(reminderFormSchema), - defaultValues: async () => { - const res = await axios.get( - `${process.env.NEXT_PUBLIC_APP_URL}/api/reminder` - ) - const data = await res.data - return { - active: data?.active, - frequency: data?.frequency, - } + defaultValues: { + active: reminderSettings?.active, + frequency: reminderSettings?.frequency, }, }) const updateReminderHandler = async (data: ReminderFormValues) => { - const res = await axios.patch( - `${process.env.NEXT_PUBLIC_APP_URL}/api/reminder`, - data - ) - return await res.data + const reminder = await updateReminder({ + active: data.active, + frequency: data.frequency, + }) + return reminder } const updateReminderMutation = useMutation({ @@ -147,9 +138,9 @@ export function ReminderForm({ Frequency