diff --git a/src/components/marketing/pricing/actions/update-user-name.ts b/src/components/marketing/pricing/actions/update-user-name.ts deleted file mode 100644 index c3f1bc5..0000000 --- a/src/components/marketing/pricing/actions/update-user-name.ts +++ /dev/null @@ -1,38 +0,0 @@ -"use server"; - -import { auth } from "@/auth"; -import { db } from "@/lib/db"; -import { userNameSchema } from "@/components/marketing/pricing/lib/validations/user"; -import { revalidatePath } from "next/cache"; - -export type FormData = { - name: string; -}; - -export async function updateUserName(userId: string, data: FormData) { - try { - const session = await auth() - - if (!session?.user || session?.user.id !== userId) { - throw new Error("Unauthorized"); - } - - const { name } = userNameSchema.parse(data); - - // Update the user name. - await db.user.update({ - where: { - id: userId, - }, - data: { - username: name, - }, - }) - - revalidatePath('/dashboard/settings'); - return { status: "success" }; - } catch (error) { - // console.log(error) - return { status: "error" } - } -} \ No newline at end of file diff --git a/src/components/marketing/pricing/actions/update-user-role.ts b/src/components/marketing/pricing/actions/update-user-role.ts deleted file mode 100644 index 9e49baf..0000000 --- a/src/components/marketing/pricing/actions/update-user-role.ts +++ /dev/null @@ -1,40 +0,0 @@ -"use server"; - -import { revalidatePath } from "next/cache"; -import { auth } from "@/auth"; -import { UserRole } from "@/generated/prisma/browser"; - -import { prisma } from "@/components/marketing/pricing/lib/db"; -import { userRoleSchema } from "@/components/marketing/pricing/lib/validations/user"; - -export type FormData = { - role: UserRole; -}; - -export async function updateUserRole(userId: string, data: FormData) { - try { - const session = await auth(); - - if (!session?.user || session?.user.id !== userId) { - throw new Error("Unauthorized"); - } - - const { role } = userRoleSchema.parse(data); - - // Update the user role. - await prisma.user.update({ - where: { - id: userId, - }, - data: { - role: role, - }, - }); - - revalidatePath("/dashboard/settings"); - return { status: "success" }; - } catch (error) { - // console.log(error) - return { status: "error" }; - } -} diff --git a/src/components/marketing/pricing/billing-info.tsx b/src/components/marketing/pricing/billing-info.tsx deleted file mode 100644 index 4f2ed4d..0000000 --- a/src/components/marketing/pricing/billing-info.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import Link from "next/link"; -import * as React from "react"; - -import { CustomerPortalButton } from "@/components/platform/dashboard/billing/customer-portal-button"; -import { buttonVariants } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { cn, formatDate } from "@/components/marketing/pricing/lib/utils"; -import { UserSubscriptionPlan } from "@/components/marketing/pricing/types"; - -interface BillingInfoProps extends React.HTMLAttributes { - userSubscriptionPlan: UserSubscriptionPlan; -} - -export function BillingInfo({ userSubscriptionPlan }: BillingInfoProps) { - const { - title, - description, - stripeCustomerId, - isPaid, - isCanceled, - stripeCurrentPeriodEnd, - } = userSubscriptionPlan; - - return ( - - - Subscription Plan - - You are currently on the {title} plan. - - - {description} - - {isPaid ? ( -

- {isCanceled - ? "Your plan will be canceled on " - : "Your plan renews on "} - {formatDate(stripeCurrentPeriodEnd)}. -

- ) : null} - - {isPaid && stripeCustomerId ? ( - - ) : ( - - Choose a plan - - )} -
-
- ); -} diff --git a/src/components/marketing/pricing/config/docs.ts b/src/components/marketing/pricing/config/docs.ts deleted file mode 100644 index 2d13136..0000000 --- a/src/components/marketing/pricing/config/docs.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { DocsConfig } from "types"; - -export const docsConfig: DocsConfig = { - mainNav: [ - { - title: "Documentation", - href: "/docs", - }, - { - title: "Guides", - href: "/guides", - }, - ], - sidebarNav: [ - { - title: "Getting Started", - items: [ - { - title: "Introduction", - href: "/docs", - }, - { - title: "Installation", - href: "/docs/installation", - }, - ], - }, - { - title: "Configuration", - items: [ - { - title: "Authentification", - href: "/docs/configuration/authentification", - }, - { - title: "Blog", - href: "/docs/configuration/blog", - }, - { - title: "Components", - href: "/docs/configuration/components", - }, - { - title: "Config files", - href: "/docs/configuration/config-files", - }, - { - title: "Database", - href: "/docs/configuration/database", - }, - { - title: "Email", - href: "/docs/configuration/email", - }, - { - title: "Layouts", - href: "/docs/configuration/layouts", - }, - { - title: "Markdown files", - href: "/docs/configuration/markdown-files", - }, - { - title: "Subscriptions", - href: "/docs/configuration/subscriptions", - }, - ], - }, - ], -}; diff --git a/src/components/marketing/pricing/config/landing.ts b/src/components/marketing/pricing/config/landing.ts deleted file mode 100644 index 7727fee..0000000 --- a/src/components/marketing/pricing/config/landing.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { FeatureLdg, InfoLdg, TestimonialType } from "types"; - -export const infos: InfoLdg[] = [ - { - title: "Empower your projects", - description: - "Unlock the full potential of your projects with our open-source SaaS platform. Collaborate seamlessly, innovate effortlessly, and scale limitlessly.", - image: "/_static/illustrations/work-from-home.jpg", - list: [ - { - title: "Collaborative", - description: "Work together with your team members in real-time.", - icon: "laptop", - }, - { - title: "Innovative", - description: "Stay ahead of the curve with access constant updates.", - icon: "settings", - }, - { - title: "Scalable", - description: - "Our platform offers the scalability needed to adapt to your needs.", - icon: "search", - }, - ], - }, - { - title: "Seamless Integration", - description: - "Integrate our open-source SaaS seamlessly into your existing workflows. Effortlessly connect with your favorite tools and services for a streamlined experience.", - image: "/_static/illustrations/work-from-home.jpg", - list: [ - { - title: "Flexible", - description: - "Customize your integrations to fit your unique requirements.", - icon: "laptop", - }, - { - title: "Efficient", - description: "Streamline your processes and reducing manual effort.", - icon: "search", - }, - { - title: "Reliable", - description: - "Rely on our robust infrastructure and comprehensive documentation.", - icon: "settings", - }, - ], - }, -]; - -export const features: FeatureLdg[] = [ - { - title: "Feature 1", - description: - "Amet praesentium deserunt ex commodi tempore fuga voluptatem. Sit, sapiente.", - link: "/", - icon: "nextjs", - }, - { - title: "Feature 2", - description: - "Amet praesentium deserunt ex commodi tempore fuga voluptatem. Sit, sapiente.", - link: "/", - icon: "google", - }, - { - title: "Feature 3", - description: - "Amet praesentium deserunt ex commodi tempore fuga voluptatem. Sit, sapiente.", - link: "/", - icon: "gitHub", - }, - { - title: "Feature 4", - description: - "Amet praesentium deserunt ex commodi tempore fuga voluptatem. Sit, sapiente.", - link: "/", - icon: "laptop", - }, - { - title: "Feature 5", - description: - "Amet praesentium deserunt ex commodi tempore fuga voluptatem. Sit, sapiente.", - link: "/", - icon: "user", - }, - { - title: "Feature 6", - description: - "Amet praesentium deserunt ex commodi tempore fuga voluptatem. Sit, sapiente.", - link: "/", - icon: "copy", - }, -]; - -export const testimonials: TestimonialType[] = [ - { - name: "John Doe", - job: "Full Stack Developer", - image: "https://randomuser.me/api/portraits/men/1.jpg", - review: - "The next-saas-stripe-starter repo has truly revolutionized my development workflow. With its comprehensive features and seamless integration with Stripe, I've been able to build and deploy projects faster than ever before. The documentation is clear and concise, making it easy to navigate through the setup process. I highly recommend next-saas-stripe-starter to any developer.", - }, - { - name: "Alice Smith", - job: "UI/UX Designer", - image: "https://randomuser.me/api/portraits/women/2.jpg", - review: - "Thanks to next-saas-stripe-starter, I've been able to create modern and attractive user interfaces in record time. The starter kit provides a solid foundation for building sleek and intuitive designs, allowing me to focus more on the creative aspects of my work.", - }, - { - name: "David Johnson", - job: "DevOps Engineer", - image: "https://randomuser.me/api/portraits/men/3.jpg", - review: - "Thanks to next-saas-stripe-starter, I was able to streamline the entire process and get payments up and running in no time. ", - }, - { - name: "Michael Wilson", - job: "Project Manager", - image: "https://randomuser.me/api/portraits/men/5.jpg", - review: - "I'm impressed by the quality of code and clear documentation of next-saas-stripe-starter. Kudos to the team!", - }, - { - name: "Sophia Garcia", - job: "Data Analyst", - image: "https://randomuser.me/api/portraits/women/6.jpg", - review: - "next-saas-stripe-starter provided me with the tools I needed to efficiently manage user data. Thank you so much!", - }, - { - name: "Emily Brown", - job: "Marketing Manager", - image: "https://randomuser.me/api/portraits/women/4.jpg", - review: - "next-saas-stripe-starter has been an invaluable asset in my role as a marketing manager. With its seamless integration with Stripe, I've been able to launch targeted marketing campaigns with built-in payment functionality, allowing us to monetize our products and services more effectively.", - }, - { - name: "Jason Stan", - job: "Web Designer", - image: "https://randomuser.me/api/portraits/men/9.jpg", - review: - "Thanks to next-saas-stripe-starter, I've been able to create modern and attractive user interfaces in record time. The starter kit provides a solid foundation for building sleek and intuitive designs, allowing me to focus more on the creative aspects of my work.", - }, -]; diff --git a/src/components/marketing/pricing/config/marketing.ts b/src/components/marketing/pricing/config/marketing.ts deleted file mode 100644 index fd1c55b..0000000 --- a/src/components/marketing/pricing/config/marketing.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { MarketingConfig } from "types" - -export const marketingConfig: MarketingConfig = { - mainNav: [ - { - title: "Pricing", - href: "/pricing", - }, - { - title: "Blog", - href: "/blog", - }, - { - title: "Documentation", - href: "/docs", - }, - ], -} diff --git a/src/components/marketing/pricing/config/site.ts b/src/components/marketing/pricing/config/site.ts deleted file mode 100644 index f24a340..0000000 --- a/src/components/marketing/pricing/config/site.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { SidebarNavItem, SiteConfig } from "types"; -import { env } from "@/env.mjs"; - -const site_url = env.NEXT_PUBLIC_APP_URL; - -export const siteConfig: SiteConfig = { - name: "SaaS Starter", - description: - "Get your project off to an explosive start with SaaS Starter! Harness the power of Next.js 14, Prisma, Neon, Auth.js v5, Resend, React Email, Shadcn/ui and Stripe to build your next big thing.", - url: site_url, - ogImage: `${site_url}/_static/og.jpg`, - links: { - twitter: "https://twitter.com/miickasmt", - github: "https://github.com/mickasmt/next-saas-stripe-starter", - }, - mailSupport: "support@saas-starter.com", -}; - -export const footerLinks: SidebarNavItem[] = [ - { - title: "Company", - items: [ - { title: "About", href: "#" }, - { title: "Enterprise", href: "#" }, - { title: "Terms", href: "/terms" }, - { title: "Privacy", href: "/privacy" }, - ], - }, - { - title: "Product", - items: [ - { title: "Security", href: "#" }, - { title: "Customization", href: "#" }, - { title: "Customers", href: "#" }, - { title: "Changelog", href: "#" }, - ], - }, - { - title: "Docs", - items: [ - { title: "Introduction", href: "#" }, - { title: "Installation", href: "#" }, - { title: "Components", href: "#" }, - { title: "Code Blocks", href: "#" }, - ], - }, -]; diff --git a/src/components/marketing/pricing/content.tsx b/src/components/marketing/pricing/content.tsx index 6816c77..6236e07 100644 --- a/src/components/marketing/pricing/content.tsx +++ b/src/components/marketing/pricing/content.tsx @@ -1,15 +1,10 @@ import { currentUser } from "@/components/auth/auth"; import { getUserSubscriptionPlan } from "@/components/marketing/pricing/lib/subscription"; -import { Callout } from "@/components/marketing/pricing/shared/callout"; -import { cn, constructMetadata } from "@/components/marketing/pricing/lib/utils"; -import { ComparePlans } from "@/components/marketing/pricing/compare-plans"; import { PricingCards } from "@/components/marketing/pricing/pricing-cards"; -import { PricingFaq } from "@/components/marketing/pricing/pricing-faq"; +import { ComparePlans } from "@/components/marketing/pricing/compare-plans"; import PricingHeader from "./pricing-header"; import PricingFAQs from "./pricing-faqs"; import EnterpriseSection from "./enterprise-section"; -import Link from "next/link"; -import { buttonVariants } from "@/components/ui/button"; import PricingLoaderOverlay from "./loader-overlay"; diff --git a/src/components/marketing/pricing/emails/magic-link-email.tsx b/src/components/marketing/pricing/emails/magic-link-email.tsx deleted file mode 100644 index 494538b..0000000 --- a/src/components/marketing/pricing/emails/magic-link-email.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { - Body, - Button, - Container, - Head, - Hr, - Html, - Preview, - Section, - Tailwind, - Text, -} from "@react-email/components"; - -import { Icons } from "../shared/icons"; - -type MagicLinkEmailProps = { - actionUrl: string; - firstName: string; - mailType: "login" | "register"; - siteName: string; -}; - -export const MagicLinkEmail = ({ - firstName = "", - actionUrl, - mailType, - siteName, -}: MagicLinkEmailProps) => ( - - - - The sales intelligence platform that helps you uncover qualified leads. - - - - - - Hi {firstName}, - - Welcome to {siteName} ! Click the link below to{" "} - {mailType === "login" ? "sign in to" : "activate"} your account. - -
- -
- - This link expires in 24 hours and can only be used once. - - {mailType === "login" ? ( - - If you did not try to log into your account, you can safely ignore - it. - - ) : null} -
- - 123 Code Street, Suite 404, Devtown, CA 98765 - -
- -
- -); diff --git a/src/components/marketing/pricing/forms/newsletter-form.tsx b/src/components/marketing/pricing/forms/newsletter-form.tsx deleted file mode 100644 index 4b82374..0000000 --- a/src/components/marketing/pricing/forms/newsletter-form.tsx +++ /dev/null @@ -1,75 +0,0 @@ -"use client"; - -import { zodResolver } from "@hookform/resolvers/zod"; -import { useForm } from "react-hook-form"; -import { z } from "zod"; - -import { Button } from "@/components/ui/button"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { Input } from "@/components/ui/input"; -import { toast } from "sonner"; - -const FormSchema = z.object({ - email: z.string().email({ - message: "Enter a valid email.", - }), -}); - -export function NewsletterForm() { - const form = useForm>({ - resolver: zodResolver(FormSchema), - defaultValues: { - email: "", - }, - }); - - function onSubmit(data: z.infer) { - form.reset(); - toast( -
-
You submitted the following values:
-
-          {JSON.stringify(data, null, 2)}
-        
-
- ); - } - - return ( -
- - ( - - Subscribe to our newsletter - - - - - - )} - /> - - - - ); -} diff --git a/src/components/marketing/pricing/forms/user-auth-form.tsx b/src/components/marketing/pricing/forms/user-auth-form.tsx deleted file mode 100644 index 3114915..0000000 --- a/src/components/marketing/pricing/forms/user-auth-form.tsx +++ /dev/null @@ -1,118 +0,0 @@ -"use client"; - -import * as React from "react"; -import { useSearchParams } from "next/navigation"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { signIn } from "next-auth/react"; -import { useForm } from "react-hook-form"; -import * as z from "zod"; - -import { cn } from "@/lib/utils"; -import { userAuthSchema } from "../lib/validations/auth"; -import { buttonVariants } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { toast } from "sonner"; -import { Icons } from "../shared/icons"; - -interface UserAuthFormProps extends React.HTMLAttributes { - type?: string; -} - -type FormData = z.infer; - -export function UserAuthForm({ className, type, ...props }: UserAuthFormProps) { - const { - register, - handleSubmit, - formState: { errors }, - } = useForm({ - resolver: zodResolver(userAuthSchema), - }); - const [isLoading, setIsLoading] = React.useState(false); - const [isGoogleLoading, setIsGoogleLoading] = React.useState(false); - const searchParams = useSearchParams(); - - async function onSubmit(data: FormData) { - setIsLoading(true); - - const signInResult = await signIn("resend", { - email: data.email.toLowerCase(), - redirect: false, - callbackUrl: searchParams?.get("from") || "/dashboard", - }); - - setIsLoading(false); - - if (!signInResult?.ok) { - return toast.error("Something went wrong.", { - description: "Your sign in request failed. Please try again." - }); - } - - return toast.success("Check your email", { - description: "We sent you a login link. Be sure to check your spam too.", - }); - } - - return ( -
-
-
-
- - - {errors?.email && ( -

- {errors.email.message} -

- )} -
- -
-
-
-
- -
-
- - Or continue with - -
-
- -
- ); -} diff --git a/src/components/marketing/pricing/forms/user-name.action.ts b/src/components/marketing/pricing/forms/user-name.action.ts deleted file mode 100644 index 5cfc16e..0000000 --- a/src/components/marketing/pricing/forms/user-name.action.ts +++ /dev/null @@ -1,19 +0,0 @@ -"use server"; - -import { revalidatePath } from "next/cache"; -import { z } from "zod"; -import { db } from "@/lib/db"; - -const userNameSchema = z.object({ name: z.string().min(1).max(32) }); -export type FormData = z.infer; - -export async function updateUserName(userId: string, data: FormData) { - const parsed = userNameSchema.safeParse(data); - if (!parsed.success) { - return { status: "error" as const }; - } - await db.user.update({ where: { id: userId }, data: { username: parsed.data.name } }); - revalidatePath("/dashboard/settings"); - return { status: "success" as const }; -} - diff --git a/src/components/marketing/pricing/forms/user-role-form.tsx b/src/components/marketing/pricing/forms/user-role-form.tsx deleted file mode 100644 index 42e6e8b..0000000 --- a/src/components/marketing/pricing/forms/user-role-form.tsx +++ /dev/null @@ -1,134 +0,0 @@ -"use client"; - -import { useState, useTransition } from "react"; -import { updateUserRole, type FormData } from "./user-role.action"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { User, UserRole } from "@/generated/prisma/browser"; -import { useSession } from "next-auth/react"; -import { useForm } from "react-hook-form"; -import { toast } from "sonner"; -import { z } from "zod"; - -const userRoleSchema = z.object({ role: z.nativeEnum(UserRole) }); -import { Button } from "@/components/ui/button"; -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { SectionColumns } from "@/components/platform/dashboard/section-columns"; -import { Icons } from "@/components/marketing/pricing/shared/icons"; - -interface UserNameFormProps { - user: Pick; -} - -export function UserRoleForm({ user }: UserNameFormProps) { - const { update } = useSession(); - const [updated, setUpdated] = useState(false); - const [isPending, startTransition] = useTransition(); - const updateUserRoleWithId = updateUserRole.bind(null, user.id); - - const roles = Object.values(UserRole); - const [role, setRole] = useState(user.role); - - const form = useForm({ - resolver: zodResolver(userRoleSchema), - values: { - role: role, - }, - }); - - const onSubmit = (data: z.infer) => { - startTransition(async () => { - const { status } = await updateUserRoleWithId(data); - - if (status !== "success") { - toast.error("Something went wrong.", { - description: "Your role was not updated. Please try again.", - }); - } else { - await update(); - setUpdated(false); - toast.success("Your role has been updated."); - } - }); - }; - - return ( -
- - -
- ( - - Role - - - - )} - /> - -
-
-

- Remove this field on real production -

-
-
-
- - ); -} \ No newline at end of file diff --git a/src/components/marketing/pricing/forms/user-role.action.ts b/src/components/marketing/pricing/forms/user-role.action.ts deleted file mode 100644 index be490c9..0000000 --- a/src/components/marketing/pricing/forms/user-role.action.ts +++ /dev/null @@ -1,22 +0,0 @@ -"use server"; - -import { revalidatePath } from "next/cache"; -import { UserRole } from "@/generated/prisma/browser"; -import { z } from "zod"; - -import { db } from "@/lib/db"; - -const userRoleSchema = z.object({ role: z.nativeEnum(UserRole) }); -export type FormData = z.infer; - -export async function updateUserRole(userId: string, data: FormData) { - const parsed = userRoleSchema.safeParse(data); - if (!parsed.success) { - return { status: "error" as const }; - } - - await db.user.update({ where: { id: userId }, data: { role: parsed.data.role } }); - revalidatePath("/dashboard/settings"); - return { status: "success" as const }; -} - diff --git a/src/components/marketing/pricing/lib/email.ts b/src/components/marketing/pricing/lib/email.ts deleted file mode 100644 index f7102a4..0000000 --- a/src/components/marketing/pricing/lib/email.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { MagicLinkEmail } from "@/components/marketing/pricing/emails/magic-link-email"; -import { EmailConfig } from "next-auth/providers/email"; -import { Resend } from "resend"; - -import { env } from "@/env.mjs"; -import { siteConfig } from "@/components/marketing/pricing/config/site"; - -import { getUserByEmail } from "./user"; - -export const resend = new Resend(env.RESEND_API_KEY); - -export const sendVerificationRequest: EmailConfig["sendVerificationRequest"] = - async ({ identifier, url, provider }) => { - const user = await getUserByEmail(identifier); - if (!user) return; - - const userVerified = user?.emailVerified ? true : false; - const authSubject = userVerified - ? `Sign-in link for ${siteConfig.name}` - : "Activate your account"; - - try { - const { data, error } = await resend.emails.send({ - from: env.EMAIL_FROM ?? "no-reply@example.com", - to: - process.env.NODE_ENV === "development" - ? "delivered@resend.dev" - : identifier, - subject: authSubject, - react: MagicLinkEmail({ - firstName: (user.username ?? "") as string, - actionUrl: url, - mailType: userVerified ? "login" : "register", - siteName: siteConfig.name, - }), - // Set this to prevent Gmail from threading emails. - // More info: https://resend.com/changelog/custom-email-headers - headers: { - "X-Entity-Ref-ID": new Date().getTime() + "", - }, - }); - - if (error || !data) { - throw new Error(error?.message); - } - - // console.log(data) - } catch (error) { - throw new Error("Failed to send verification email."); - } - }; diff --git a/src/components/marketing/pricing/lib/get-tier-id.ts b/src/components/marketing/pricing/lib/get-tier-id.ts deleted file mode 100644 index 7556365..0000000 --- a/src/components/marketing/pricing/lib/get-tier-id.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { db } from "@/lib/db"; - -export async function getTierIdFromStripePrice(stripePriceId: string): Promise { - // Find the subscription tier that matches the Stripe price ID - const tier = await db.subscriptionTier.findFirst({ - where: { - OR: [ - { monthlyPriceStripeId: stripePriceId }, - { yearlyPriceStripeId: stripePriceId } - ] - }, - select: { id: true } - }); - - if (!tier) { - throw new Error(`No subscription tier found for Stripe price ID: ${stripePriceId}`); - } - - return tier.id; -} diff --git a/src/components/marketing/pricing/lib/user.ts b/src/components/marketing/pricing/lib/user.ts deleted file mode 100644 index bcea88c..0000000 --- a/src/components/marketing/pricing/lib/user.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { db } from "@/lib/db"; - -export const getUserByEmail = async (email: string) => { - try { - const user = await db.user.findFirst({ - where: { - email: email, - }, - select: { - username: true, - emailVerified: true, - }, - }); - - return user; - } catch { - return null; - } -}; - -export const getUserById = async (id: string) => { - try { - const user = await db.user.findUnique({ where: { id } }); - - return user; - } catch { - return null; - } -}; \ No newline at end of file diff --git a/src/components/marketing/pricing/lib/utils.ts b/src/components/marketing/pricing/lib/utils.ts index c3bad33..39069e4 100644 --- a/src/components/marketing/pricing/lib/utils.ts +++ b/src/components/marketing/pricing/lib/utils.ts @@ -1,74 +1,12 @@ -import { Metadata } from "next"; import { clsx, type ClassValue } from "clsx"; import { twMerge } from "tailwind-merge"; import { env } from "@/env.mjs"; -import { siteConfig } from "@/components/marketing/pricing/config/site"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } -export function constructMetadata({ - title = siteConfig.name, - description = siteConfig.description, - image = siteConfig.ogImage, - icons = "/favicon.ico", - noIndex = false, -}: { - title?: string; - description?: string; - image?: string; - icons?: string; - noIndex?: boolean; -} = {}): Metadata { - return { - title, - description, - keywords: [ - "Next.js", - "React", - "Prisma", - "Neon", - "Auth.js", - "shadcn ui", - "Resend", - "React Email", - "Stripe", - ], - authors: [ - { - name: "mickasmt", - }, - ], - creator: "mickasmt", - openGraph: { - type: "website", - locale: "en_US", - url: siteConfig.url, - title, - description, - siteName: title, - }, - twitter: { - card: "summary_large_image", - title, - description, - images: [image], - creator: "@miickasmt", - }, - icons, - metadataBase: new URL(siteConfig.url), - manifest: `${siteConfig.url}/site.webmanifest`, - ...(noIndex && { - robots: { - index: false, - follow: false, - }, - }), - }; -} - export function formatDate(input: string | number): string { const date = new Date(input); return date.toLocaleDateString("en-US", { @@ -151,27 +89,5 @@ export const truncate = (str: string, length: number) => { return `${str.slice(0, length)}...`; }; -export const getBlurDataURL = async (url: string | null) => { - if (!url) { - return "data:image/webp;base64,AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; - } - - if (url.startsWith("/_static/")) { - url = `${siteConfig.url}${url}`; - } - - try { - const response = await fetch( - `https://wsrv.nl/?url=${url}&w=50&h=50&blur=5`, - ); - const buffer = await response.arrayBuffer(); - const base64 = Buffer.from(buffer).toString("base64"); - - return `data:image/png;base64,${base64}`; - } catch (error) { - return "data:image/webp;base64,AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; - } -}; - export const placeholderBlurhash = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAoJJREFUWEfFl4lu4zAMRO3cx/9/au6reMaOdkxTTl0grQFCRoqaT+SQotq2bV9N8rRt28xms87m83l553eZ/9vr9Wpkz+ezkT0ej+6dv1X81AFw7M4FBACPVn2c1Z3zLgDeJwHgeLFYdAARYioAEAKJEG2WAjl3gCwNYymQQ9b7/V4spmIAwO6Wy2VnAMikBWlDURBELf8CuN1uHQSrPwMAHK5WqwFELQ01AIXdAa7XawfAb3p6AOwK5+v1ugAoEq4FRSFLgavfQ49jAGQpAE5wjgGCeRrGdBArwHOPcwFcLpcGU1X0IsBuN5tNgYhaiFFwHTiAwq8I+O5xfj6fOz38K+X/fYAdb7fbAgFAjIJ6Aav3AYlQ6nfnDoDz0+lUxNiLALvf7XaDNGQ6GANQBKR85V27B4D3QQRw7hGIYlQKWGM79hSweyCUe1blXhEAogfABwHAXAcqSYkxCtHLUK3XBajSc4Dj8dilAeiSAgD2+30BAEKV4GKcAuDqB4TdYwBgPQByCgApUBoE4EJUGvxUjF3Q69/zLw3g/HA45ABKgdIQu+JPIyDnisCfAxAFNFM0EFNQ64gfS0EUoQP8ighrZSjn3oziZEQpauyKbfjbZchHUL/3AS/Dd30gAkxuRACgfO+EWQW8qwI1o+wseNuKcQiESjALvwNoMI0TcRzD4lFcPYwIM+JTF5x6HOs8yI7jeB5oKhpMRFH9UwaSCDB2Jmg4rc6E2TT0biIaG0rQhNqyhpHBcayTTSXH6vcDL7/sdqRK8LkwTsU499E8vRcAojHcZ4AxABdilgrp4lsXk8oVqgwh7+6H3phqd8J0Kk4vbx/+sZqCD/vNLya/5dT9fAH8g1WdNGgwbQAAAABJRU5ErkJggg=="; diff --git a/src/components/marketing/pricing/lib/validations/auth.ts b/src/components/marketing/pricing/lib/validations/auth.ts deleted file mode 100644 index b91e85a..0000000 --- a/src/components/marketing/pricing/lib/validations/auth.ts +++ /dev/null @@ -1,5 +0,0 @@ -import * as z from "zod" - -export const userAuthSchema = z.object({ - email: z.string().email(), -}) diff --git a/src/components/marketing/pricing/lib/validations/user.ts b/src/components/marketing/pricing/lib/validations/user.ts deleted file mode 100644 index ae5f610..0000000 --- a/src/components/marketing/pricing/lib/validations/user.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { UserRole } from "@/generated/prisma/browser"; -import * as z from "zod"; - -export const userNameSchema = z.object({ - name: z.string().min(3).max(32), -}); - -export const userRoleSchema = z.object({ - role: z.nativeEnum(UserRole), -}); diff --git a/src/components/marketing/pricing/modals/delete-account-modal.tsx b/src/components/marketing/pricing/modals/delete-account-modal.tsx deleted file mode 100644 index 2f6358e..0000000 --- a/src/components/marketing/pricing/modals/delete-account-modal.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import { - Dispatch, - SetStateAction, - useCallback, - useMemo, - useState, -} from "react"; -import { signOut, useSession } from "next-auth/react"; -import { toast } from "sonner"; -import { deleteCurrentUser } from "@/components/invoice/actions/user"; - -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Modal } from "@/components/atom/modal"; -import { UserAvatar } from "@/components/marketing/pricing/shared/user-avatar"; - -function DeleteAccountModal({ - showDeleteAccountModal, - setShowDeleteAccountModal, -}: { - showDeleteAccountModal: boolean; - setShowDeleteAccountModal: Dispatch>; -}) { - const { data: session } = useSession(); - const [deleting, setDeleting] = useState(false); - - async function deleteAccount() { - setDeleting(true); - const result = await deleteCurrentUser(); - if (result.success) { - await new Promise((resolve) => - setTimeout(() => { - signOut({ callbackUrl: `${window.location.origin}/` }); - resolve(null); - }, 500), - ); - } else { - setDeleting(false); - throw result.error || "Failed to delete account"; - } - } - - return ( - -
- -

Delete Account

-

- Warning: This will permanently delete your account and your - active subscription! -

- - {/* TODO: Use getUserSubscriptionPlan(session.user.id) to display the user's subscription if he have a paid plan */} -
- -
{ - e.preventDefault(); - toast.promise(deleteAccount(), { - loading: "Deleting account...", - success: "Account deleted successfully!", - error: (err) => err, - }); - }} - className="flex flex-col space-y-6 bg-accent px-4 py-8 text-left sm:px-16" - > -
- - -
- - -
-
- ); -} - -export function useDeleteAccountModal() { - const [showDeleteAccountModal, setShowDeleteAccountModal] = useState(false); - - const DeleteAccountModalCallback = useCallback(() => { - return ( - - ); - }, [showDeleteAccountModal, setShowDeleteAccountModal]); - - return useMemo( - () => ({ - setShowDeleteAccountModal, - DeleteAccountModal: DeleteAccountModalCallback, - }), - [setShowDeleteAccountModal, DeleteAccountModalCallback], - ); -} diff --git a/src/components/marketing/pricing/modals/providers.tsx b/src/components/marketing/pricing/modals/providers.tsx deleted file mode 100644 index 89f0ba7..0000000 --- a/src/components/marketing/pricing/modals/providers.tsx +++ /dev/null @@ -1,26 +0,0 @@ -"use client"; - -import { createContext, Dispatch, ReactNode, SetStateAction } from "react"; - -import { useSignInModal } from "@/components/marketing/pricing/modals/sign-in-modal"; - -export const ModalContext = createContext<{ - setShowSignInModal: Dispatch>; -}>({ - setShowSignInModal: () => {}, -}); - -export default function ModalProvider({ children }: { children: ReactNode }) { - const { SignInModal, setShowSignInModal } = useSignInModal(); - - return ( - - - {children} - - ); -} diff --git a/src/components/marketing/pricing/modals/sign-in-modal.tsx b/src/components/marketing/pricing/modals/sign-in-modal.tsx deleted file mode 100644 index 3e17fad..0000000 --- a/src/components/marketing/pricing/modals/sign-in-modal.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { signIn } from "next-auth/react"; -import { - Dispatch, - SetStateAction, - useCallback, - useMemo, - useState, -} from "react"; - -import { Icons } from "@/components/marketing/pricing/shared/icons"; -import { Button } from "@/components/ui/button"; -import { Modal } from "@/components/atom/modal"; -import { siteConfig } from "@/components/marketing/pricing/config/site"; - -function SignInModal({ - showSignInModal, - setShowSignInModal, -}: { - showSignInModal: boolean; - setShowSignInModal: Dispatch>; -}) { - const [signInClicked, setSignInClicked] = useState(false); - - return ( - -
-
- - - -

Sign In

-

- This is strictly for demo purposes - only your email and profile - picture will be stored. -

-
- -
- -
-
-
- ); -} - -export function useSignInModal() { - const [showSignInModal, setShowSignInModal] = useState(false); - - const SignInModalCallback = useCallback(() => { - return ( - - ); - }, [showSignInModal, setShowSignInModal]); - - return useMemo( - () => ({ - setShowSignInModal, - SignInModal: SignInModalCallback, - }), - [setShowSignInModal, SignInModalCallback], - ); -} diff --git a/src/components/marketing/pricing/pricing-faq.tsx b/src/components/marketing/pricing/pricing-faq.tsx deleted file mode 100644 index b5ecdf0..0000000 --- a/src/components/marketing/pricing/pricing-faq.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, -} from "@/components/ui/accordion"; - -import { HeaderSection } from "../../atom/header-section"; - -const pricingFaqData = [ - { - id: "item-1", - question: "What is the cost of the free plan?", - answer: - "Our free plan is completely free, with no monthly or annual charges. It's a great way to get started and explore our basic features.", - }, - { - id: "item-2", - question: "How much does the Basic Monthly plan cost?", - answer: - "The Basic Monthly plan is priced at $15 per month. It provides access to our core features and is billed on a monthly basis.", - }, - { - id: "item-3", - question: "What is the price of the Pro Monthly plan?", - answer: - "The Pro Monthly plan is available for $25 per month. It offers advanced features and is billed on a monthly basis for added flexibility.", - }, - { - id: "item-4", - question: "Do you offer any annual subscription plans?", - answer: - "Yes, we offer annual subscription plans for even more savings. The Basic Annual plan is $144 per year, and the Pro Annual plan is $300 per year.", - }, - { - id: "item-5", - question: "Is there a trial period for the paid plans?", - answer: - "We offer a 14-day free trial for both the Pro Monthly and Pro Annual plans. It's a great way to experience all the features before committing to a paid subscription.", - }, -]; - -export function PricingFaq() { - return ( -
- - - - {pricingFaqData.map((faqItem) => ( - - {faqItem.question} - - {faqItem.answer} - - - ))} - -
- ); -} diff --git a/src/components/marketing/pricing/sections/bentogrid.tsx b/src/components/marketing/pricing/sections/bentogrid.tsx deleted file mode 100644 index e0296a3..0000000 --- a/src/components/marketing/pricing/sections/bentogrid.tsx +++ /dev/null @@ -1,367 +0,0 @@ -import { OptimizedImage } from '@/components/ui/optimized-image'; - -import MaxWidthWrapper from "../shared/max-width-wrapper"; - -export default function BentoGrid() { - return ( -
- -
- {/* First card */} -
-
-
- - - - - 100% - -
-

- Customizable -

-
-
- - {/* Second card */} -
-
-
- - - - - - - - - - - - - - - - -
-
-

- Secure by default -

-

- Provident fugit and vero voluptate. magnam magni doloribus - dolores voluptates a sapiente nisi. -

-
-
-
- - {/* Third card */} -
-
-
- - - - - - - - - - - - - - - - - - - - - -
-
-

- Faster than light -

-

- Provident fugit vero voluptate. magnam magni doloribus dolores - voluptates inventore nisi. -

-
-
-
- - {/* Second row */} -
-
-
-
- - - -
-
-

- Faster than light -

-

- Provident fugit vero voluptate. Voluptates a sapiente - inventore nisi. -

-
-
-
-
- - - -
- - - - - - - - - - -
-
-
- -
-
-
-
- - - - - - -
-
-

- Keep your loved ones safe -

-

- Voluptate. magnam magni doloribus dolores voluptates a - sapiente. -

-
-
-
-
-
- - Glodie - -
- -
-
-
-
- -
- - M. Irung - -
-
- - B. Ng - -
- -
-
-
-
-
-
-
-
-
- ); -} diff --git a/src/components/marketing/pricing/sections/features.tsx b/src/components/marketing/pricing/sections/features.tsx deleted file mode 100644 index 797f651..0000000 --- a/src/components/marketing/pricing/sections/features.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import Link from "next/link"; - -import { features } from "../config/landing"; -import { Button } from "@/components/ui/button"; -import { HeaderSection } from "@/components/atom/header-section"; -import { Icons } from "../shared/icons"; -import MaxWidthWrapper from "../shared/max-width-wrapper"; - -export default function Features() { - return ( -
-
- - - -
- {features.map((feature) => { - const Icon = Icons[feature.icon || "nextjs"]; - return ( -
- - ); - })} -
- -
-
- ); -} diff --git a/src/components/marketing/pricing/sections/hero-landing.tsx b/src/components/marketing/pricing/sections/hero-landing.tsx deleted file mode 100644 index c8b0ace..0000000 --- a/src/components/marketing/pricing/sections/hero-landing.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import Link from "next/link"; - -import { env } from "@/env.mjs"; -import { siteConfig } from "../config/site"; -import { cn, nFormatter } from "@/lib/utils"; -import { buttonVariants } from "@/components/ui/button"; -import { Icons } from "../shared/icons"; - -export default async function HeroLanding() { - const { stargazers_count: stars } = await fetch( - "https://api.github.com/repos/mickasmt/next-saas-stripe-starter", - { - ...(env.GITHUB_OAUTH_TOKEN && { - headers: { - Authorization: `Bearer ${process.env.GITHUB_OAUTH_TOKEN}`, - "Content-Type": "application/json", - }, - }), - // data will revalidate every hour - next: { revalidate: 3600 }, - }, - ) - .then((res) => res.json()) - .catch((e) => console.log(e)); - - return ( -
-
- - 🎉 - Introducing  Next Auth - Roles Template on - - -

- Kick off with a bang with{" "} - - SaaS Starter - -

- -

- Build your next project using Next.js 14, Prisma, Neon, Auth.js v5, - Resend, React Email, Shadcn/ui, Stripe. -

- -
- - Go Pricing - - - - -

- Star on GitHub{" "} - {nFormatter(stars)} -

- -
-
-
- ); -} diff --git a/src/components/marketing/pricing/sections/info-landing.tsx b/src/components/marketing/pricing/sections/info-landing.tsx deleted file mode 100644 index 6986a8f..0000000 --- a/src/components/marketing/pricing/sections/info-landing.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { OptimizedImage } from '@/components/ui/optimized-image'; -import { InfoLdg } from "../types"; - -import { cn } from "@/lib/utils"; -import { Icons } from "../shared/icons"; -import MaxWidthWrapper from "../shared/max-width-wrapper"; - -interface InfoLandingProps { - data: InfoLdg; - reverse?: boolean; -} - -export default function InfoLanding({ - data, - reverse = false, -}: InfoLandingProps) { - return ( -
- -
-

- {data.title} -

-

- {data.description} -

-
- {data.list.map((item, index) => { - const Icon = Icons[item.icon || "arrowRight"]; - return ( -
-
- - {item.title} -
-
- {item.description} -
-
- ); - })} -
-
-
-
- -
-
-
-
- ); -} diff --git a/src/components/marketing/pricing/sections/testimonials.tsx b/src/components/marketing/pricing/sections/testimonials.tsx deleted file mode 100644 index 722e6ad..0000000 --- a/src/components/marketing/pricing/sections/testimonials.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { OptimizedImage } from '@/components/ui/optimized-image'; - -import { testimonials } from "../config/landing"; -import { HeaderSection } from "@/components/atom/header-section"; - -export default function Testimonials() { - return ( -
-
- - -
- {testimonials.map((item) => ( -
-
-
-
-
- - - -
-

- {item.name} -

-

- {item.job} -

-
-
- {item.review} -
-
-
-
- ))} -
-
-
- ); -} diff --git a/src/components/marketing/pricing/shared/user-avatar.tsx b/src/components/marketing/pricing/shared/user-avatar.tsx deleted file mode 100644 index 666581a..0000000 --- a/src/components/marketing/pricing/shared/user-avatar.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { User } from "@/generated/prisma/browser" -import { AvatarProps } from "@radix-ui/react-avatar" - -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" -import { Icons } from "@/components/marketing/pricing/shared/icons" - -interface UserAvatarProps extends AvatarProps { - user: Pick -} - -export function UserAvatar({ user, ...props }: UserAvatarProps) { - return ( - - {user.image ? ( - - ) : ( - - {user.username ?? ""} - - - )} - - ) -}