├── .env.example ├── .eslintrc.json ├── .gitignore ├── README.md ├── app ├── (auth) │ ├── (routes) │ │ └── sign-in │ │ │ └── page.tsx │ └── layout.tsx ├── (dashboard) │ ├── (routes) │ │ ├── bg-remove │ │ │ └── page.tsx │ │ ├── billing │ │ │ └── page.tsx │ │ ├── dashboard │ │ │ └── page.tsx │ │ ├── face-swap │ │ │ ├── constants.ts │ │ │ └── page.tsx │ │ ├── headshot [NOT_USING] │ │ │ ├── constants.ts │ │ │ └── page.tsx │ │ ├── qrgen │ │ │ ├── constants.ts │ │ │ └── page.tsx │ │ ├── real-art │ │ │ ├── constants.ts │ │ │ └── page.tsx │ │ ├── settings │ │ │ └── page.tsx │ │ └── upscale │ │ │ ├── constants.ts │ │ │ └── page.tsx │ └── layout.tsx ├── (landing) │ ├── layout.tsx │ └── page.tsx ├── api │ ├── auth │ │ └── [...nextauth] │ │ │ └── route.ts │ ├── bg-remove │ │ ├── [id] │ │ │ └── route.ts │ │ └── route.ts │ ├── face-swap │ │ ├── [id] │ │ │ └── route.ts │ │ └── route.ts │ ├── headshot │ │ ├── [id] │ │ │ └── route.ts │ │ └── route.ts │ ├── qrgen │ │ ├── [id] │ │ │ └── route.ts │ │ └── route.ts │ ├── real-art │ │ ├── [id] │ │ │ └── route.ts │ │ └── route.ts │ ├── stripe │ │ └── route.ts │ ├── upscale │ │ ├── [id] │ │ │ └── route.ts │ │ └── route.ts │ ├── user │ │ └── [userId] │ │ │ └── route.ts │ └── webhook │ │ └── route.ts ├── favicon.ico ├── globals.css └── layout.tsx ├── components.json ├── components ├── billing.tsx ├── custom-icons.tsx ├── dashboard-card.tsx ├── dropzone.tsx ├── email │ ├── activation.tsx │ └── new-user.tsx ├── free-counter.tsx ├── heading.tsx ├── image-compare-slider.tsx ├── landing │ ├── headshot-landing.tsx │ ├── landing-footer.tsx │ ├── landing-hero.tsx │ ├── landing-nav.tsx │ ├── landing-section-second.tsx │ ├── landing-section-three.tsx │ ├── landing-section.tsx │ ├── qrcode-landing.tsx │ ├── removebg-landing.tsx │ └── typewrite-landing.tsx ├── loaders.tsx ├── mobile-sidebar.tsx ├── modal-provider.tsx ├── navbar.tsx ├── page-card.tsx ├── pro-modal.tsx ├── shell.tsx ├── sidebar.tsx ├── theme-provider.tsx ├── theme-toggle.tsx ├── toaster-provider.tsx ├── ui │ ├── avatar.tsx │ ├── button.tsx │ ├── card.tsx │ ├── dialog.tsx │ ├── dropdown-menu.tsx │ ├── form.tsx │ ├── input.tsx │ ├── label.tsx │ ├── progress.tsx │ ├── select.tsx │ ├── separator.tsx │ └── sheet.tsx ├── user-account-nav.tsx ├── user-auth-form.tsx ├── user-avatar.tsx ├── user-settings-form.tsx └── user-store-provider.tsx ├── constants.ts ├── lib ├── api-limit.ts ├── auth.ts ├── cloudinary.ts ├── db.ts ├── mail.ts ├── session.ts ├── stripe.ts ├── subscription.ts ├── utils.ts └── validations │ ├── auth.ts │ ├── dropImage.ts │ └── user.ts ├── middleware.ts ├── next.config.js ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── prettier.config.js ├── prisma └── schema.prisma ├── public ├── avatar_fallback.jpg ├── dashboard │ ├── bg-remove-dashboard.png │ ├── face-swap-dashboard.png │ ├── headshot-dashboard.png │ ├── qr-dashboard.png │ ├── real-art-dashboard.png │ └── upscale-dashboard.png ├── headshot-res.png ├── headshot-src.jpg ├── logo3.png ├── pro-dark.png ├── pro.png ├── qrlanding.png ├── real-art-res.png ├── real-art-src.jpg ├── removebg-res.png ├── removebg-src.png ├── test.gif ├── thumbnail.png └── twist_line_edit.png ├── store ├── promodal-store.ts └── store.ts ├── tailwind.config.js ├── tailwind.config.ts ├── tsconfig.json └── types ├── index.d.ts └── next-auth.d.ts /.env.example: -------------------------------------------------------------------------------- 1 | 2 | #From Google API 3 | GOOGLE_CLIENT_ID= 4 | GOOGLE_CLIENT_SECRET= 5 | 6 | NEXTAUTH_URL=http://localhost:3000 7 | NEXTAUTH_SECRET= Random String 8 | 9 | NEXT_PUBLIC_APP_URL=http://localhost:3000 10 | 11 | #From Planetscale SQL 12 | DATABASE_URL= Your SQL DB URL 13 | 14 | #From Replicate 15 | REPLICATE_API_TOKEN= 16 | 17 | #From Your SMTP Provider 18 | EMAIL_SERVER_USER= 19 | EMAIL_SERVER_PASSWORD= 20 | EMAIL_SERVER_HOST= 21 | EMAIL_SERVER_PORT= 22 | EMAIL_FROM=noreply@pixiemist 23 | 24 | #From Cloudinary 25 | CLOUDINARY_CLOUD_NAME= 26 | CLOUDINARY_API_KEY= 27 | CLOUDINARY_API_SECRET= 28 | 29 | 30 | #From Stripe 31 | STRIPE_API_KEY= 32 | STRIPE_PRO_PRICE_ID= 33 | STRIPE_WEBHOOK_SECRET= -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "next/core-web-vitals" 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | *.env 30 | 31 | 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | next-env.d.ts 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pixiemist 2 | 3 | A SaaS application for image editing using AI. 4 | 5 | ![Project Image](https://github.com/Sanjay-Ganapathi/Pixiemist/blob/main/public/thumbnail.png) 6 | 7 | ## Technologies 8 | 9 | - ✨ Next js 13 `App Router` 10 | - ⚒ Models from **Replicate** 11 | - 💎 Styled using **Tailwind CSS** 12 | - 🎨 Components and Theme using **Shadcn/ui** 13 | - 🔒 Authentication using **NextAuth.js** 14 | - 🐻 State Management using **Zustand** 15 | - 📊 ORM using **Prisma** 16 | - 🧧 Database - SQL on **PlanetScale** 17 | - 💳 Subscriptions using **Stripe** 18 | - 🔧 Form Validations using **Zod** 19 | - 📷 Image Storage using **Cloudinary** 20 | - 📤 Drag n Drop using **React-dropzone** 21 | - 🌀 Written in **TypeScript** 22 | 23 | ## Running Locally 24 | 25 | 1. Install dependencies using pnpm: 26 | 27 | ```sh 28 | pnpm install 29 | ``` 30 | 31 | 2. Copy `.env.example` to `.env` and update the variables according to the need. 32 | 33 | ```sh 34 | cp .env.example .env 35 | ``` 36 | 37 | 3. Start the development server: 38 | 39 | ```sh 40 | pnpm dev 41 | ``` 42 | 43 |
44 |
45 | 46 | _Note_: Sometimes the generations can take upto 3 to 5 mins because of Replicate's Cold Start. [Learn More](https://replicate.com/docs/how-does-replicate-work#cold-boots) 47 | -------------------------------------------------------------------------------- /app/(auth)/(routes)/sign-in/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from "next"; 2 | import Image from "next/image"; 3 | 4 | import { UserAuthForm } from "@/components/user-auth-form"; 5 | 6 | export const metadata: Metadata = { 7 | title: "Login", 8 | description: "Login to your account", 9 | }; 10 | 11 | export default function RegisterPage() { 12 | return ( 13 |
14 |
15 |
16 | Landing 23 |
24 |
25 |
26 | Logo 27 |
28 | Pixiemist 29 |
30 |
31 |
32 |

33 | “These tools have saved me countless hours of work and 34 | helped me deliver stunning designs to my clients faster than ever 35 | before.” 36 |

37 |
Muthuvel Pandian
38 |
39 |
40 |
41 |
42 |
43 |
44 |

45 | Welcome to Pixiemist 46 |

47 |

48 | Enter your email below to sign in to your account 49 |

50 |
51 | 52 |
53 |
54 |
55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /app/(auth)/layout.tsx: -------------------------------------------------------------------------------- 1 | export default function AuthLayout({children} : {children:React.ReactNode}){ 2 | return
3 | {children} 4 |
5 | } -------------------------------------------------------------------------------- /app/(dashboard)/(routes)/bg-remove/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import axios from "axios"; 3 | import toast from "react-hot-toast"; 4 | import { useEffect, useRef, useState } from "react"; 5 | import { useForm } from "react-hook-form"; 6 | import { zodResolver } from "@hookform/resolvers/zod"; 7 | import { useRouter } from "next/navigation"; 8 | import { z } from "zod"; 9 | 10 | import { FileWithPreview, OutputImgSlider } from "@/types"; 11 | import { Heading } from "@/components/heading"; 12 | import { HeadingShell } from "@/components/shell"; 13 | import { Loader } from "@/components/loaders"; 14 | import { Form, FormControl, FormItem } from "@/components/ui/form"; 15 | import { dropImageSchema } from "@/lib/validations/dropImage"; 16 | import { Button } from "@/components/ui/button"; 17 | import { Dropzone } from "@/components/dropzone"; 18 | import { PageCard } from "@/components/page-card"; 19 | import { ImageCompareSlider } from "@/components/image-compare-slider"; 20 | import { sleep } from "@/lib/utils"; 21 | import { usePromodal } from "@/store/promodal-store"; 22 | 23 | type Input = z.infer; 24 | 25 | const BackgroundRemovePage = () => { 26 | const [file, setFile] = useState([]); 27 | const [outputSlider, setOutputSlider] = useState({ 28 | previewURL: "", 29 | outputURL: "", 30 | }); 31 | 32 | const proModal = usePromodal(); 33 | 34 | const router = useRouter(); 35 | 36 | const bottomOfPanelRef = useRef(null); 37 | 38 | const form = useForm({ 39 | resolver: zodResolver(dropImageSchema), 40 | }); 41 | 42 | const isLoading = form.formState.isSubmitting; 43 | 44 | const onSubmit = async (data: Input) => { 45 | try { 46 | if ( 47 | !data?.image || 48 | !Array.isArray(data.image) || 49 | data.image.length <= 0 50 | ) { 51 | return toast.error("No Image is Selected"); 52 | } 53 | 54 | setOutputSlider({ previewURL: "", outputURL: "" }); 55 | const formData = new FormData(); 56 | formData.append("image", data.image[0]); 57 | 58 | let response = await axios.post("/api/bg-remove", formData); 59 | 60 | let { generation_id, status } = response.data; 61 | console.log(generation_id, status); 62 | 63 | while (status !== "succeeded" && status !== "failed") { 64 | await sleep(1000); 65 | response = await axios.get("api/bg-remove/" + generation_id); 66 | status = response.data.status; 67 | console.log(status); 68 | } 69 | 70 | if (status === "failed") { 71 | return toast.error("Server Error.Please try after some time"); 72 | } 73 | 74 | console.log(response.data); 75 | 76 | setOutputSlider({ 77 | previewURL: file[0]?.preview, 78 | outputURL: response.data.outputURL, 79 | }); 80 | 81 | form.reset(); 82 | } catch (err: any) { 83 | console.log("[CLIENT_BGREMOVE_ERROR"); 84 | if (err?.response?.status === 403) { 85 | proModal.onOpen(); 86 | } else toast.error("Internal Server Error."); 87 | } finally { 88 | router.refresh(); 89 | } 90 | }; 91 | 92 | useEffect(() => { 93 | if (bottomOfPanelRef.current) { 94 | bottomOfPanelRef.current.scrollIntoView({ behavior: "smooth" }); 95 | } 96 | }, [outputSlider]); 97 | 98 | return ( 99 | <> 100 | 101 | 105 | 106 | 107 |
108 | 109 | 110 | 111 | 120 | 121 | 122 | 123 |
124 | 132 |
133 |
134 | 135 | 136 |
137 | {/* Preview */} 138 | {file[0]?.preview && ( 139 | URL.revokeObjectURL(file[0]?.preview)} 144 | onClick={() => setFile([])} 145 | /> 146 | )} 147 | 148 | {/* Output */} 149 | {isLoading ? ( 150 | 151 | ) : ( 152 | outputSlider.outputURL && ( 153 | 158 | ) 159 | )} 160 |
161 |
162 | 163 | ); 164 | }; 165 | 166 | export default BackgroundRemovePage; 167 | -------------------------------------------------------------------------------- /app/(dashboard)/(routes)/billing/page.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { redirect } from "next/navigation"; 4 | import { Metadata } from "next"; 5 | 6 | import { Heading } from "@/components/heading"; 7 | import { HeadingShell } from "@/components/shell"; 8 | import { getCurrentUser } from "@/lib/session"; 9 | import { getUserSubscription } from "@/lib/subscription"; 10 | import { stripe } from "@/lib/stripe"; 11 | import { BillingCard } from "@/components/billing"; 12 | 13 | export const metadata: Metadata = { 14 | title: "Billing", 15 | description: "Handle your payment and subscription details.", 16 | }; 17 | 18 | export default async function BillingPage() { 19 | const user = await getCurrentUser(); 20 | if (!user) { 21 | redirect("/sign-in"); 22 | } 23 | const subscriptionPlan = await getUserSubscription(user.id); 24 | 25 | if (subscriptionPlan instanceof Error) { 26 | redirect("/sign-in"); 27 | } 28 | let isCancelled = false; 29 | if (subscriptionPlan.isPro && subscriptionPlan.stripeSubscriptionId) { 30 | const stripePlan = await stripe.subscriptions.retrieve( 31 | subscriptionPlan.stripeSubscriptionId, 32 | ); 33 | isCancelled = stripePlan.cancel_at_period_end; 34 | } 35 | 36 | return ( 37 | <> 38 | 39 | 43 | 44 |
45 | 50 |
51 | 52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /app/(dashboard)/(routes)/dashboard/page.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | import { Metadata } from "next"; 4 | 5 | import { DashboardCard } from "@/components/dashboard-card"; 6 | import { tools } from "@/constants"; 7 | 8 | export const metadata: Metadata = { 9 | title: "Dashboard", 10 | }; 11 | 12 | const DashboardPage = () => { 13 | return ( 14 |
15 | {tools.map((tool) => ( 16 | 17 | 24 | 25 | ))} 26 |
27 | ); 28 | }; 29 | 30 | export default DashboardPage; 31 | -------------------------------------------------------------------------------- /app/(dashboard)/(routes)/face-swap/constants.ts: -------------------------------------------------------------------------------- 1 | import * as z from "zod"; 2 | 3 | import { dropImageSchema } from "@/lib/validations/dropImage"; 4 | 5 | export const formSchema = z.object({ 6 | sourceImage: dropImageSchema.shape.image, 7 | targetFace: dropImageSchema.shape.image, 8 | }); 9 | -------------------------------------------------------------------------------- /app/(dashboard)/(routes)/face-swap/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import axios from "axios"; 4 | import toast from "react-hot-toast"; 5 | import { useEffect, useRef, useState } from "react"; 6 | import { set, useForm } from "react-hook-form"; 7 | import { zodResolver } from "@hookform/resolvers/zod"; 8 | import { useRouter } from "next/navigation"; 9 | import { z } from "zod"; 10 | 11 | import { Heading } from "@/components/heading"; 12 | import { HeadingShell } from "@/components/shell"; 13 | import { Loader } from "@/components/loaders"; 14 | import { Form, FormControl, FormItem, FormLabel } from "@/components/ui/form"; 15 | import { Button } from "@/components/ui/button"; 16 | import { Dropzone } from "@/components/dropzone"; 17 | import { FileWithPreview, OutputImgSlider } from "@/types"; 18 | import { sleep } from "@/lib/utils"; 19 | import { usePromodal } from "@/store/promodal-store"; 20 | import { formSchema } from "./constants"; 21 | import { ChevronsLeft, ChevronsUp } from "lucide-react"; 22 | import { ImageCompareSlider } from "@/components/image-compare-slider"; 23 | 24 | type Input = z.infer; 25 | 26 | const FaceSwapPage = () => { 27 | const [sourceImage, setSourceImage] = useState([]); 28 | const [targetFace, setTargetFace] = useState([]); 29 | 30 | const [outputSlider, setOutputSlider] = useState({ 31 | previewURL: "", 32 | outputURL: "", 33 | }); 34 | 35 | const proModal = usePromodal(); 36 | 37 | const router = useRouter(); 38 | 39 | const bottomOfPanelRef = useRef(null); 40 | 41 | const form = useForm({ 42 | resolver: zodResolver(formSchema), 43 | }); 44 | 45 | const isLoading = form.formState.isSubmitting; 46 | 47 | const onSubmit = async (data: Input) => { 48 | try { 49 | console.log(data); 50 | 51 | if ( 52 | !data?.sourceImage || 53 | !Array.isArray(data.sourceImage) || 54 | data.sourceImage.length <= 0 55 | ) { 56 | return toast.error("No Source Image is Selected"); 57 | } 58 | 59 | if ( 60 | !data?.targetFace || 61 | !Array.isArray(data.targetFace) || 62 | data.targetFace.length <= 0 63 | ) { 64 | return toast.error("No Target Face Image is Selected"); 65 | } 66 | 67 | setOutputSlider({ previewURL: "", outputURL: "" }); 68 | const formData = new FormData(); 69 | formData.append("sourceImage", data.sourceImage[0]); 70 | formData.append("targetFace", data.targetFace[0]); 71 | 72 | let response = await axios.post("api/face-swap", formData); 73 | 74 | let { generation_id, status } = response.data; 75 | console.log(generation_id, status); 76 | 77 | while (status !== "succeeded" && status !== "failed") { 78 | await sleep(1000); 79 | response = await axios.get("api/face-swap/" + generation_id); 80 | status = response.data.status; 81 | console.log(status); 82 | } 83 | 84 | if (status === "failed") { 85 | if (response.data.outputURL == "No Face Found") { 86 | return toast.error( 87 | "No face found. Please try again with a different image", 88 | ); 89 | } 90 | 91 | return toast.error("Server Error.Please try after some time"); 92 | } 93 | 94 | console.log(response.data); 95 | 96 | if (response.data.outputURL == "No Face Found") { 97 | return toast.error( 98 | "No face found. Please try again with a different image", 99 | ); 100 | } else { 101 | setOutputSlider({ 102 | previewURL: sourceImage[0]?.preview, 103 | outputURL: response.data.outputURL, 104 | }); 105 | 106 | setSourceImage([]); 107 | setTargetFace([]); 108 | 109 | form.reset(); 110 | } 111 | } catch (err: any) { 112 | console.log(err); 113 | if (err?.response?.status === 403) { 114 | proModal.onOpen(); 115 | } else toast.error("Internal Server Error."); 116 | } finally { 117 | router.refresh(); 118 | } 119 | }; 120 | 121 | useEffect(() => { 122 | if (bottomOfPanelRef.current) { 123 | bottomOfPanelRef.current.scrollIntoView({ behavior: "smooth" }); 124 | } 125 | }, [outputSlider]); 126 | 127 | return ( 128 | <> 129 | 130 | 131 | 132 | 133 |
134 | 135 |
136 | 137 | Reference Image 138 | 139 | 151 | 152 | 153 | 154 | 155 | 160 | 161 | 162 | Your Face Image 163 | 164 | 176 | 177 | 178 |
179 |
180 | 188 |
189 |
190 | 191 | 192 |
193 | {isLoading ? ( 194 | 195 | ) : ( 196 | outputSlider.outputURL && ( 197 | 202 | ) 203 | )} 204 |
205 |
206 | 207 | ); 208 | }; 209 | 210 | export default FaceSwapPage; 211 | -------------------------------------------------------------------------------- /app/(dashboard)/(routes)/headshot [NOT_USING]/constants.ts: -------------------------------------------------------------------------------- 1 | import * as z from "zod"; 2 | 3 | import { dropImageSchema } from "@/lib/validations/dropImage"; 4 | 5 | export const formSchema = z.object({ 6 | image: dropImageSchema.shape.image, 7 | gender: z.string({ 8 | required_error: "Gender is required", 9 | }), 10 | pose: z.string({ 11 | required_error: "Pose is required", 12 | }), 13 | }); 14 | 15 | export const genderOptions = [ 16 | { value: "man", label: "Male" }, 17 | { value: "woman", label: "Female" }, 18 | { value: "unisex", label: "Unisex" }, 19 | ]; 20 | 21 | export const poseOptions = [ 22 | { value: "random", label: "Random Pose" }, 23 | { value: "1", label: "Pose 1" }, 24 | { value: "2", label: "Pose 2" }, 25 | { value: "3", label: "Pose 3" }, 26 | { value: "4", label: "Pose 4" }, 27 | { value: "5", label: "Pose 5" }, 28 | { value: "6", label: "Pose 6" }, 29 | { value: "7", label: "Pose 7" }, 30 | { value: "8", label: "Pose 8" }, 31 | { value: "9", label: "Pose 9" }, 32 | ]; 33 | -------------------------------------------------------------------------------- /app/(dashboard)/(routes)/headshot [NOT_USING]/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import axios from "axios"; 3 | import toast from "react-hot-toast"; 4 | import { useEffect, useRef, useState } from "react"; 5 | import { useForm } from "react-hook-form"; 6 | import { zodResolver } from "@hookform/resolvers/zod"; 7 | import { useRouter } from "next/navigation"; 8 | import { z } from "zod"; 9 | 10 | import { Heading } from "@/components/heading"; 11 | import { HeadingShell } from "@/components/shell"; 12 | import { Loader } from "@/components/loaders"; 13 | import { Form, FormControl, FormField, FormItem } from "@/components/ui/form"; 14 | import { Button } from "@/components/ui/button"; 15 | import { Dropzone } from "@/components/dropzone"; 16 | import { PageCard } from "@/components/page-card"; 17 | import { FileWithPreview } from "@/types"; 18 | import { sleep } from "@/lib/utils"; 19 | import { usePromodal } from "@/store/promodal-store"; 20 | import { 21 | Select, 22 | SelectContent, 23 | SelectItem, 24 | SelectTrigger, 25 | SelectValue, 26 | } from "@/components/ui/select"; 27 | import { formSchema, poseOptions, genderOptions } from "./constants"; 28 | 29 | type Input = z.infer; 30 | 31 | const HeadShotPage = () => { 32 | const [file, setFile] = useState([]); 33 | const [outputImg, setOutputImg] = useState(); 34 | 35 | const proModal = usePromodal(); 36 | const router = useRouter(); 37 | const bottomOfPanelRef = useRef(null); 38 | 39 | const form = useForm({ 40 | resolver: zodResolver(formSchema), 41 | defaultValues: { 42 | gender: "man", 43 | pose: "random", 44 | }, 45 | }); 46 | 47 | const isLoading = form.formState.isSubmitting; 48 | 49 | useEffect(() => { 50 | if (bottomOfPanelRef.current) { 51 | bottomOfPanelRef.current.scrollIntoView({ behavior: "smooth" }); 52 | } 53 | }, [outputImg]); 54 | 55 | const onSubmit = async (data: Input) => { 56 | console.log("Submitted"); 57 | console.log(data); 58 | try { 59 | if ( 60 | !data?.image || 61 | !Array.isArray(data.image) || 62 | data.image.length <= 0 63 | ) { 64 | return toast.error("No Image is Selected"); 65 | } 66 | 67 | setOutputImg(""); 68 | const formData = new FormData(); 69 | formData.append("image", data.image[0]); 70 | formData.append("gender", data.gender); 71 | formData.append("pose", data.pose); 72 | 73 | let response = await axios.post("/api/headshot", formData); 74 | 75 | let { generation_id, status } = response.data; 76 | console.log(generation_id, status); 77 | 78 | while (status !== "succeeded" && status !== "failed") { 79 | await sleep(1000); 80 | response = await axios.get("api/headshot/" + generation_id); 81 | status = response.data.status; 82 | console.log(status); 83 | } 84 | if (status === "failed") { 85 | console.log("Failed", response.data); 86 | if (response.data.outputURL == "No face found") { 87 | return toast.error( 88 | "No face found. Please try again with a different image", 89 | ); 90 | } 91 | 92 | return toast.error("Server Error.Please try after some time"); 93 | } 94 | 95 | setOutputImg(response.data.outputURL); 96 | 97 | form.reset(); 98 | } catch (err: any) { 99 | console.log("[CLIENT_ERROR_FRONTEND_HEADSHOT]"); 100 | console.log(err); 101 | if (err?.response?.status === 403) { 102 | proModal.onOpen(); 103 | } else toast.error("Internal Server Error."); 104 | } finally { 105 | router.refresh(); 106 | } 107 | }; 108 | 109 | return ( 110 | <> 111 | 112 | 116 | 117 | 118 |
119 | 120 | 121 | 122 | 131 | 132 | 133 | 134 |
135 | ( 139 | 140 | 162 | 163 | )} 164 | /> 165 | 166 | ( 170 | 171 | 193 | 194 | )} 195 | /> 196 | 197 | 205 |
206 |
207 | 208 |
209 | {/* Preview */} 210 | {file[0]?.preview && ( 211 | URL.revokeObjectURL(file[0]?.preview)} 216 | onClick={() => setFile([])} 217 | /> 218 | )} 219 | {isLoading ? ( 220 | 221 | ) : ( 222 | outputImg && ( 223 | 224 | ) 225 | )} 226 |
227 |
228 | 229 | ); 230 | }; 231 | 232 | export default HeadShotPage; 233 | -------------------------------------------------------------------------------- /app/(dashboard)/(routes)/qrgen/constants.ts: -------------------------------------------------------------------------------- 1 | import * as z from "zod"; 2 | 3 | export const formSchema = z.object({ 4 | prompt: z.string().min(1, { 5 | message: "Proompt is required", 6 | }), 7 | content: z.string().min(1), 8 | model: z.string().min(1), 9 | amount: z.string().min(1), 10 | }); 11 | 12 | export const amountOptions = [ 13 | { 14 | value: "1", 15 | label: "1 Image", 16 | }, 17 | { 18 | value: "2", 19 | label: "2 Images", 20 | }, 21 | { 22 | value: "3", 23 | label: "3 Images", 24 | }, 25 | { 26 | value: "4", 27 | label: "4 Images", 28 | }, 29 | ]; 30 | 31 | export const modelOptions = [ 32 | { 33 | value: "1", 34 | label: "Model 1: High Accuracy", 35 | }, 36 | { 37 | value: "2", 38 | label: "Model 2: High Style", 39 | }, 40 | ]; 41 | -------------------------------------------------------------------------------- /app/(dashboard)/(routes)/qrgen/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useRef, useState } from "react"; 4 | 5 | import { Heading } from "@/components/heading"; 6 | import { HeadingShell } from "@/components/shell"; 7 | import { formSchema } from "./constants"; 8 | import * as z from "zod"; 9 | import { Form, FormControl, FormField, FormItem } from "@/components/ui/form"; 10 | import { useForm } from "react-hook-form"; 11 | import { zodResolver } from "@hookform/resolvers/zod"; 12 | import { Input } from "@/components/ui/input"; 13 | import { 14 | Select, 15 | SelectContent, 16 | SelectItem, 17 | SelectTrigger, 18 | SelectValue, 19 | } from "@/components/ui/select"; 20 | import { modelOptions, amountOptions } from "./constants"; 21 | import { Button } from "@/components/ui/button"; 22 | import { Loader } from "@/components/loaders"; 23 | import { PageCard } from "@/components/page-card"; 24 | import axios from "axios"; 25 | import toast from "react-hot-toast"; 26 | import { sleep } from "@/lib/utils"; 27 | import { useRouter } from "next/navigation"; 28 | import { usePromodal } from "@/store/promodal-store"; 29 | 30 | type Input = z.infer; 31 | 32 | const QRGenerationPage = () => { 33 | const [images, setImages] = useState([]); 34 | 35 | const proModal = usePromodal(); 36 | const router = useRouter(); 37 | 38 | const form = useForm({ 39 | resolver: zodResolver(formSchema), 40 | defaultValues: { 41 | prompt: "", 42 | content: "", 43 | model: "1", 44 | amount: "1", 45 | }, 46 | }); 47 | 48 | const isLoading = form.formState.isSubmitting; 49 | 50 | const bottomOfPanelRef = useRef(null); 51 | 52 | const onSubmit = async (data: Input) => { 53 | try { 54 | setImages([]); 55 | let response = await axios.post("/api/qrgen", data); 56 | let { generation_id, status } = response.data; 57 | console.log(generation_id, status); 58 | 59 | while (status !== "succeeded" && status !== "failed") { 60 | await sleep(1000); 61 | response = await axios.get("api/qrgen/" + generation_id); 62 | status = response.data.status; 63 | console.log(status); 64 | } 65 | if (status === "failed") { 66 | return toast.error("Server Error.Please try after some time"); 67 | } 68 | 69 | console.log(response.data); 70 | setImages(response.data.outputURL); 71 | 72 | form.reset(); 73 | } catch (err: any) { 74 | console.log("[QR_CODE_CLIENT_ERROR]"); 75 | if (err?.response?.status === 403) { 76 | proModal.onOpen(); 77 | } else toast.error("Internal Server Error."); 78 | } finally { 79 | router.refresh(); 80 | } 81 | }; 82 | 83 | return ( 84 | <> 85 | 86 | 90 | 91 | 92 |
93 |
94 | 98 | ( 101 | 102 | 103 | 109 | 110 | 111 | )} 112 | /> 113 | 114 | ( 117 | 118 | 119 | 125 | 126 | 127 | )} 128 | /> 129 | 130 | ( 134 | 135 | 154 | 155 | )} 156 | /> 157 | 158 | ( 162 | 163 | 182 | 183 | )} 184 | /> 185 | 186 | 194 | 195 | 196 | 197 | {isLoading && } 198 | 199 | {images.length > 0 && ( 200 |
201 | {images.map((image) => ( 202 | 208 | ))} 209 |
210 | )} 211 | 212 |
213 |
214 | 215 | ); 216 | }; 217 | 218 | export default QRGenerationPage; 219 | -------------------------------------------------------------------------------- /app/(dashboard)/(routes)/real-art/constants.ts: -------------------------------------------------------------------------------- 1 | import * as z from "zod"; 2 | 3 | import { dropImageSchema } from "@/lib/validations/dropImage"; 4 | 5 | export const formSchema = z.object({ 6 | image: dropImageSchema.shape.image, 7 | prompt: z.string().min(1, { 8 | message: "Prompt is required", 9 | }), 10 | }); 11 | -------------------------------------------------------------------------------- /app/(dashboard)/(routes)/real-art/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import axios from "axios"; 4 | import toast from "react-hot-toast"; 5 | import { useRef, useState, useEffect } from "react"; 6 | import { useForm } from "react-hook-form"; 7 | import { zodResolver } from "@hookform/resolvers/zod"; 8 | import { useRouter } from "next/navigation"; 9 | import { z } from "zod"; 10 | 11 | import { Heading } from "@/components/heading"; 12 | import { HeadingShell } from "@/components/shell"; 13 | import { Loader } from "@/components/loaders"; 14 | import { Form, FormControl, FormField, FormItem } from "@/components/ui/form"; 15 | import { formSchema } from "./constants"; 16 | import { Button } from "@/components/ui/button"; 17 | import { Dropzone } from "@/components/dropzone"; 18 | import { PageCard } from "@/components/page-card"; 19 | import { FileWithPreview } from "@/types"; 20 | import { sleep } from "@/lib/utils"; 21 | import { usePromodal } from "@/store/promodal-store"; 22 | import { Input } from "@/components/ui/input"; 23 | 24 | type Input = z.infer; 25 | 26 | const RealArtPage = () => { 27 | const [file, setFile] = useState([]); 28 | const [outputImg, setOutputImg] = useState(); 29 | 30 | const proModal = usePromodal(); 31 | const router = useRouter(); 32 | const bottomOfPanelRef = useRef(null); 33 | 34 | const form = useForm({ 35 | resolver: zodResolver(formSchema), 36 | defaultValues: { 37 | prompt: "", 38 | }, 39 | }); 40 | 41 | const isLoading = form.formState.isSubmitting; 42 | 43 | useEffect(() => { 44 | if (bottomOfPanelRef.current) { 45 | bottomOfPanelRef.current.scrollIntoView({ behavior: "smooth" }); 46 | } 47 | }, [outputImg]); 48 | 49 | const onSubmit = async (data: Input) => { 50 | console.log("Submitted"); 51 | console.log(data); 52 | try { 53 | if ( 54 | !data?.image || 55 | !Array.isArray(data.image) || 56 | data.image.length <= 0 57 | ) { 58 | return toast.error("No Image is Selected"); 59 | } 60 | 61 | setOutputImg(""); 62 | const formData = new FormData(); 63 | formData.append("image", data.image[0]); 64 | formData.append("prompt", data.prompt); 65 | 66 | let response = await axios.post("/api/real-art", formData); 67 | 68 | let { generation_id, status } = response.data; 69 | console.log(generation_id, status); 70 | 71 | while (status !== "succeeded" && status !== "failed") { 72 | await sleep(1000); 73 | response = await axios.get("api/real-art/" + generation_id); 74 | status = response.data.status; 75 | console.log(status); 76 | } 77 | 78 | if (status === "failed") { 79 | return toast.error("Server Error.Please try after some time"); 80 | } 81 | 82 | setOutputImg(response.data.outputURL); 83 | 84 | form.reset(); 85 | } catch (err: any) { 86 | console.log("[CLIENT_ERROR_REAL_ART]"); 87 | if (err?.response?.status === 403) { 88 | proModal.onOpen(); 89 | } else toast.error("Internal Server Error."); 90 | } finally { 91 | router.refresh(); 92 | } 93 | }; 94 | 95 | return ( 96 | <> 97 | 98 | 102 | 103 | 104 |
105 | 106 | 107 | 108 | 117 | 118 | 119 | 120 |
121 | ( 125 | 126 | 127 | 133 | 134 | 135 | )} 136 | /> 137 | 138 | 146 |
147 |
148 | 149 |
150 | {/* Preview */} 151 | {file[0]?.preview && ( 152 | URL.revokeObjectURL(file[0]?.preview)} 157 | onClick={() => setFile([])} 158 | /> 159 | )} 160 | {isLoading ? ( 161 | 162 | ) : ( 163 | outputImg && ( 164 | 165 | ) 166 | )} 167 |
168 |
169 | 170 | ); 171 | }; 172 | 173 | export default RealArtPage; 174 | -------------------------------------------------------------------------------- /app/(dashboard)/(routes)/settings/page.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { redirect } from "next/navigation"; 4 | import { Metadata } from "next"; 5 | 6 | import { Heading } from "@/components/heading"; 7 | import { HeadingShell } from "@/components/shell"; 8 | import { getCurrentUser } from "@/lib/session"; 9 | import { UserSettingsForm } from "@/components/user-settings-form"; 10 | 11 | export const metadata: Metadata = { 12 | title: "Settings", 13 | description: "Manage account settings.", 14 | }; 15 | 16 | const Settings = async () => { 17 | const user = await getCurrentUser(); 18 | if (!user) { 19 | redirect("/sign-in"); 20 | } 21 | return ( 22 | <> 23 | 24 | 25 | 26 |
27 | 31 |
32 | 33 | ); 34 | }; 35 | export default Settings; 36 | -------------------------------------------------------------------------------- /app/(dashboard)/(routes)/upscale/constants.ts: -------------------------------------------------------------------------------- 1 | import * as z from "zod"; 2 | 3 | import { dropImageSchema } from "@/lib/validations/dropImage"; 4 | 5 | export const formSchema = z.object({ 6 | image: dropImageSchema.shape.image, 7 | scale: z.string({ 8 | required_error: "Scale is required", 9 | }), 10 | }); 11 | 12 | export const scaleOptions = [ 13 | { value: "2", label: "2x" }, 14 | { value: "3", label: "3x" }, 15 | { value: "4", label: "4x" }, 16 | ]; 17 | -------------------------------------------------------------------------------- /app/(dashboard)/(routes)/upscale/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import axios from "axios"; 4 | import toast from "react-hot-toast"; 5 | import { useRef, useState, useEffect } from "react"; 6 | import { useForm } from "react-hook-form"; 7 | import { zodResolver } from "@hookform/resolvers/zod"; 8 | import { useRouter } from "next/navigation"; 9 | import { z } from "zod"; 10 | 11 | import { Heading } from "@/components/heading"; 12 | import { HeadingShell } from "@/components/shell"; 13 | import { Loader } from "@/components/loaders"; 14 | import { Form, FormControl, FormField, FormItem } from "@/components/ui/form"; 15 | import { formSchema, scaleOptions } from "./constants"; 16 | import { Button } from "@/components/ui/button"; 17 | import { Dropzone } from "@/components/dropzone"; 18 | import { PageCard } from "@/components/page-card"; 19 | import { FileWithPreview } from "@/types"; 20 | import { 21 | Select, 22 | SelectContent, 23 | SelectItem, 24 | SelectTrigger, 25 | SelectValue, 26 | } from "@/components/ui/select"; 27 | import { sleep } from "@/lib/utils"; 28 | import { usePromodal } from "@/store/promodal-store"; 29 | 30 | type Input = z.infer; 31 | 32 | const ImageUpscalePage = () => { 33 | const [file, setFile] = useState([]); 34 | const [outputImg, setOutputImg] = useState(); 35 | 36 | const proModal = usePromodal(); 37 | const router = useRouter(); 38 | const bottomOfPanelRef = useRef(null); 39 | 40 | const form = useForm({ 41 | resolver: zodResolver(formSchema), 42 | defaultValues: { 43 | scale: "2", 44 | }, 45 | }); 46 | 47 | const isLoading = form.formState.isSubmitting; 48 | 49 | useEffect(() => { 50 | if (bottomOfPanelRef.current) { 51 | bottomOfPanelRef.current.scrollIntoView({ behavior: "smooth" }); 52 | } 53 | }, [outputImg]); 54 | 55 | const onSubmit = async (data: Input) => { 56 | console.log("Submitted"); 57 | try { 58 | if ( 59 | !data?.image || 60 | !Array.isArray(data.image) || 61 | data.image.length <= 0 62 | ) { 63 | return toast.error("No Image is Selected"); 64 | } 65 | 66 | setOutputImg(""); 67 | const formData = new FormData(); 68 | formData.append("image", data.image[0]); 69 | formData.append("scale", data.scale); 70 | 71 | let response = await axios.post("/api/upscale", formData); 72 | 73 | let { generation_id, status } = response.data; 74 | console.log(generation_id, status); 75 | 76 | while (status !== "succeeded" && status !== "failed") { 77 | await sleep(1000); 78 | response = await axios.get("api/upscale/" + generation_id); 79 | status = response.data.status; 80 | console.log(status); 81 | } 82 | 83 | if (status === "failed") { 84 | return toast.error("Server Error.Please try after some time"); 85 | } 86 | 87 | setOutputImg(response.data.outputURL); 88 | 89 | form.reset(); 90 | } catch (err: any) { 91 | console.log("[CLIENT_ERROR_FRONTEND_UPSCALE]"); 92 | if (err?.response?.status === 403) { 93 | proModal.onOpen(); 94 | } else toast.error("Internal Server Error."); 95 | } finally { 96 | router.refresh(); 97 | } 98 | }; 99 | 100 | return ( 101 | <> 102 | 103 | 104 | 105 | 106 |
107 | 108 | 109 | 110 | 119 | 120 | 121 | 122 |
123 | ( 127 | 128 | 150 | 151 | )} 152 | /> 153 | 154 | 162 |
163 |
164 | 165 |
166 | {/* Preview */} 167 | {file[0]?.preview && ( 168 | URL.revokeObjectURL(file[0]?.preview)} 173 | onClick={() => setFile([])} 174 | /> 175 | )} 176 | {isLoading ? ( 177 | 178 | ) : ( 179 | outputImg && ( 180 | 181 | ) 182 | )} 183 |
184 |
185 | 186 | ); 187 | }; 188 | 189 | export default ImageUpscalePage; 190 | -------------------------------------------------------------------------------- /app/(dashboard)/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { redirect } from "next/navigation"; 3 | 4 | import { Sidebar } from "@/components/sidebar"; 5 | import { getCurrentUser } from "@/lib/session"; 6 | import { Navbar } from "@/components/navbar"; 7 | import UserStoreProvider from "@/components/user-store-provider"; 8 | import { getCreationCount } from "@/lib/api-limit"; 9 | import { getUserSubscription } from "@/lib/subscription"; 10 | 11 | export default async function DashboardLayout({ 12 | children, 13 | }: { 14 | children: React.ReactNode; 15 | }) { 16 | const user = await getCurrentUser(); 17 | 18 | if (!user) { 19 | redirect("/sign-in"); 20 | } 21 | const creationCount = await getCreationCount(); 22 | const subscriptionPlan = await getUserSubscription(user.id); 23 | 24 | if (subscriptionPlan instanceof Error) { 25 | redirect("/sign-in"); 26 | } 27 | 28 | return ( 29 |
30 | 37 | 38 |
39 | 40 |
41 |
42 | 43 | {children} 44 |
45 |
46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /app/(landing)/layout.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | const LandingLayout = ({ children }: { children: React.ReactNode }) => { 4 | return ( 5 |
6 | landing 14 |
{children}
15 |
16 | ); 17 | }; 18 | export default LandingLayout; 19 | -------------------------------------------------------------------------------- /app/(landing)/page.tsx: -------------------------------------------------------------------------------- 1 | import { ChevronDown } from "lucide-react"; 2 | import { getCurrentUser } from "@/lib/session"; 3 | import { LandingHero } from "@/components/landing/landing-hero"; 4 | import { LandingNav } from "@/components/landing/landing-nav"; 5 | import { LandingSection } from "@/components/landing/landing-section"; 6 | import { LandingSectionSecond } from "@/components/landing/landing-section-second"; 7 | import { LandingSectionThree } from "@/components/landing/landing-section-three"; 8 | import { LandingFooter } from "@/components/landing/landing-footer"; 9 | 10 | export default async function Landing() { 11 | const user = await getCurrentUser(); 12 | 13 | return ( 14 | <> 15 |
16 |
17 | 18 | 19 | 20 |
21 |
22 | 23 |
24 |
25 | 26 |
27 |
28 |
29 | 30 |
31 |
32 |
33 | 34 |
35 |
36 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import { authOptions } from "@/lib/auth" 2 | import NextAuth from "next-auth" 3 | 4 | const handler = NextAuth(authOptions) 5 | 6 | export { handler as GET, handler as POST } -------------------------------------------------------------------------------- /app/api/bg-remove/[id]/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import Replicate from "replicate"; 3 | 4 | import { getCurrentUser } from "@/lib/session"; 5 | import prismadb from "@/lib/db"; 6 | import { uploadImage } from "@/lib/cloudinary"; 7 | 8 | const replicate = new Replicate({ 9 | auth: process.env.REPLICATE_API_TOKEN!, 10 | }); 11 | 12 | export async function GET( 13 | req: NextRequest, 14 | { params }: { params: { id: string } }, 15 | ) { 16 | const user = await getCurrentUser(); 17 | 18 | if (!user) { 19 | return new NextResponse("Unauthorized", { status: 401 }); 20 | } 21 | 22 | const prediction = await replicate.predictions.get(params.id); 23 | 24 | if (prediction?.status !== "succeeded" && prediction?.status !== "failed") { 25 | return NextResponse.json({ status: prediction.status, outputURL: "" }); 26 | } else { 27 | if (prediction?.status === "succeeded") { 28 | const cloudinary_resp = await uploadImage(prediction.output as string); 29 | 30 | await prismadb.creation.create({ 31 | data: { 32 | imageUrl: cloudinary_resp.secure_url, 33 | domain: "bg-remove", 34 | userId: user.id, 35 | }, 36 | }); 37 | 38 | return NextResponse.json( 39 | { 40 | status: prediction.status, 41 | outputURL: cloudinary_resp.secure_url, 42 | }, 43 | { status: 200 }, 44 | ); 45 | } else 46 | return NextResponse.json({ status: prediction.status, outputURL: "" }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/api/bg-remove/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import Replicate from "replicate"; 3 | 4 | import { getCurrentUser } from "@/lib/session"; 5 | import { checkApiLimit } from "@/lib/api-limit"; 6 | import { getUserSubscription } from "@/lib/subscription"; 7 | 8 | const replicate = new Replicate({ 9 | auth: process.env.REPLICATE_API_TOKEN!, 10 | }); 11 | 12 | export async function POST(req: NextRequest) { 13 | try { 14 | const user = await getCurrentUser(); 15 | 16 | if (!user) { 17 | return new NextResponse("Unauthorized", { status: 401 }); 18 | } 19 | 20 | const formData = await req.formData(); 21 | const file = formData.get("image") as Blob; 22 | 23 | if (!file) { 24 | return new NextResponse("No file found", { status: 400 }); 25 | } 26 | 27 | const freeTrial = await checkApiLimit(); 28 | const subscriptionPlan = await getUserSubscription(user.id); 29 | if (subscriptionPlan instanceof Error) { 30 | return new NextResponse("Unauthorized", { status: 401 }); 31 | } 32 | 33 | if (!freeTrial && !subscriptionPlan.isPro) { 34 | return new NextResponse("Free Tier Ended", { status: 403 }); 35 | } 36 | 37 | const bytes = await file.arrayBuffer(); 38 | const buffer = Buffer.from(bytes); 39 | const dataURI = `data:${file.type};base64,${buffer.toString("base64")}`; 40 | 41 | const modelURL = 42 | "fb8af171cfa1616ddcf1242c093f9c46bcada5ad4cf6f2fbe8b81b330ec5c003"; 43 | const output = await replicate.predictions.create({ 44 | version: modelURL, 45 | input: { 46 | image: dataURI, 47 | }, 48 | }); 49 | 50 | const resp_data = { 51 | generation_id: output.id, 52 | status: output.status, 53 | }; 54 | 55 | return NextResponse.json(resp_data, { status: 200 }); 56 | } catch (err: any) { 57 | console.log("[BGREMOVE_API_ERROR]"); 58 | console.log(err); 59 | return new NextResponse(err, { status: 500 }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/api/face-swap/[id]/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import Replicate from "replicate"; 3 | 4 | import { getCurrentUser } from "@/lib/session"; 5 | import prismadb from "@/lib/db"; 6 | import { uploadImage } from "@/lib/cloudinary"; 7 | 8 | const replicate = new Replicate({ 9 | auth: process.env.REPLICATE_API_TOKEN!, 10 | }); 11 | 12 | export async function GET( 13 | req: NextRequest, 14 | { params }: { params: { id: string } }, 15 | ) { 16 | const user = await getCurrentUser(); 17 | if (!user) { 18 | return new NextResponse("Unauthorized", { status: 401 }); 19 | } 20 | 21 | const prediction = await replicate.predictions.get(params.id); 22 | 23 | if (prediction?.status !== "succeeded" && prediction?.status !== "failed") { 24 | return NextResponse.json({ status: prediction.status, outputURL: "" }); 25 | } else { 26 | if (prediction?.status === "succeeded") { 27 | const prediction_url = prediction.output[0]; 28 | 29 | if (!prediction_url) 30 | return NextResponse.json({ 31 | status: prediction.status, 32 | outputURL: "No Face Found", 33 | }); 34 | else { 35 | const cloudinary_resp = await uploadImage(prediction_url as string); 36 | 37 | await prismadb.creation.create({ 38 | data: { 39 | imageUrl: cloudinary_resp.secure_url, 40 | domain: "face-swap", 41 | userId: user.id, 42 | }, 43 | }); 44 | 45 | return NextResponse.json( 46 | { 47 | status: prediction.status, 48 | outputURL: cloudinary_resp.secure_url, 49 | }, 50 | { status: 200 }, 51 | ); 52 | } 53 | } else { 54 | console.log(prediction); 55 | if (prediction?.error == "'NoneType' object has no attribute 'kps'") { 56 | return NextResponse.json({ 57 | status: prediction.status, 58 | outputURL: "No Face Found", 59 | }); 60 | } 61 | 62 | return NextResponse.json({ status: prediction.status, outputURL: "" }); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/api/face-swap/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import Replicate from "replicate"; 3 | 4 | import { getCurrentUser } from "@/lib/session"; 5 | import { checkApiLimit } from "@/lib/api-limit"; 6 | import { getUserSubscription } from "@/lib/subscription"; 7 | 8 | const replicate = new Replicate({ 9 | auth: process.env.REPLICATE_API_TOKEN!, 10 | }); 11 | 12 | export async function POST(req: NextRequest) { 13 | try { 14 | const user = await getCurrentUser(); 15 | 16 | if (!user) { 17 | return new NextResponse("Unauthorized", { status: 401 }); 18 | } 19 | 20 | const formData = await req.formData(); 21 | const sourceImage = formData.get("sourceImage") as Blob; 22 | const targetFace = formData.get("targetFace") as Blob; 23 | 24 | if (!sourceImage) { 25 | return new NextResponse("No Source Image Found", { status: 400 }); 26 | } 27 | 28 | if (!targetFace) { 29 | return new NextResponse("No Target Face Found", { status: 400 }); 30 | } 31 | 32 | const freeTrial = await checkApiLimit(); 33 | const subscriptionPlan = await getUserSubscription(user.id); 34 | 35 | if (subscriptionPlan instanceof Error) { 36 | return new NextResponse("Unauthorized", { status: 401 }); 37 | } 38 | 39 | if (!freeTrial && !subscriptionPlan.isPro) { 40 | return new NextResponse("Free Tier Ended", { status: 403 }); 41 | } 42 | 43 | const sourceImageBytes = await sourceImage.arrayBuffer(); 44 | const sourceImageBuffer = Buffer.from(sourceImageBytes); 45 | const sourceImageURI = `data:${ 46 | sourceImage.type 47 | };base64,${sourceImageBuffer.toString("base64")}`; 48 | 49 | const targetFaceBytes = await targetFace.arrayBuffer(); 50 | const targetFaceBuffer = Buffer.from(targetFaceBytes); 51 | const targetFaceURI = `data:${ 52 | targetFace.type 53 | };base64,${targetFaceBuffer.toString("base64")}`; 54 | 55 | const modelURL = 56 | "8c1e100ecabb3151cf1e6c62879b6de7a4b84602de464ed249b6cff0b86211d8"; 57 | 58 | const output = await replicate.predictions.create({ 59 | version: modelURL, 60 | input: { 61 | target: sourceImageURI, 62 | source: targetFaceURI, 63 | enhance_face: true, 64 | }, 65 | }); 66 | 67 | const resp_data = { 68 | generation_id: output.id, 69 | status: output.status, 70 | }; 71 | 72 | return NextResponse.json(resp_data); 73 | } catch (err: any) { 74 | console.log("[FACE_SWAP_API_ERROR]"); 75 | console.log(err); 76 | return new NextResponse(err, { status: 500 }); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/api/headshot/[id]/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import Replicate from "replicate"; 3 | 4 | import { getCurrentUser } from "@/lib/session"; 5 | import prismadb from "@/lib/db"; 6 | import { uploadImage } from "@/lib/cloudinary"; 7 | 8 | const replicate = new Replicate({ 9 | auth: process.env.REPLICATE_API_TOKEN!, 10 | }); 11 | 12 | export async function GET( 13 | req: NextRequest, 14 | { params }: { params: { id: string } }, 15 | ) { 16 | const user = await getCurrentUser(); 17 | 18 | if (!user) { 19 | return new NextResponse("Unauthorized", { status: 401 }); 20 | } 21 | 22 | const prediction = await replicate.predictions.get(params.id); 23 | 24 | if (prediction?.status !== "succeeded" && prediction?.status !== "failed") { 25 | return NextResponse.json({ status: prediction.status, outputURL: "" }); 26 | } else { 27 | if (prediction?.status === "succeeded") { 28 | const cloudinary_resp = await uploadImage(prediction.output as string); 29 | 30 | await prismadb.creation.create({ 31 | data: { 32 | imageUrl: cloudinary_resp.secure_url, 33 | domain: "headshot", 34 | userId: user.id, 35 | }, 36 | }); 37 | 38 | return NextResponse.json( 39 | { 40 | status: prediction.status, 41 | outputURL: cloudinary_resp.secure_url, 42 | }, 43 | { status: 200 }, 44 | ); 45 | } else { 46 | if ( 47 | prediction?.error == 48 | "'NoneType' object has no attribute 'normed_embedding'" 49 | ) 50 | return NextResponse.json({ 51 | status: prediction.status, 52 | outputURL: "No face found", 53 | }); 54 | else { 55 | return NextResponse.json({ status: prediction.status, outputURL: "" }); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/api/headshot/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import Replicate from "replicate"; 3 | 4 | import { getCurrentUser } from "@/lib/session"; 5 | import { checkApiLimit } from "@/lib/api-limit"; 6 | import { getUserSubscription } from "@/lib/subscription"; 7 | 8 | const replicate = new Replicate({ 9 | auth: process.env.REPLICATE_API_TOKEN!, 10 | }); 11 | 12 | export async function POST(req: NextRequest) { 13 | try { 14 | const user = await getCurrentUser(); 15 | 16 | if (!user) { 17 | return new NextResponse("Unauthorized", { status: 401 }); 18 | } 19 | 20 | const formData = await req.formData(); 21 | const file = formData.get("image") as Blob; 22 | const gender = formData.get("gender") as string; 23 | const pose = formData.get("pose") as string; 24 | 25 | if (!file) { 26 | return new NextResponse("No file found", { status: 400 }); 27 | } 28 | 29 | if (!gender) { 30 | return new NextResponse("No gender found", { status: 400 }); 31 | } 32 | 33 | if (!pose) { 34 | return new NextResponse("No pose found", { status: 400 }); 35 | } 36 | const freeTrial = await checkApiLimit(); 37 | const subscriptionPlan = await getUserSubscription(user.id); 38 | if (subscriptionPlan instanceof Error) { 39 | return new NextResponse("Unauthorized", { status: 401 }); 40 | } 41 | if (!freeTrial && !subscriptionPlan.isPro) { 42 | return new NextResponse("Free Tier Ended", { status: 403 }); 43 | } 44 | 45 | const bytes = await file.arrayBuffer(); 46 | const buffer = Buffer.from(bytes); 47 | const dataURI = `data:${file.type};base64,${buffer.toString("base64")}`; 48 | 49 | const modelURL = 50 | "377cf09e230c0d599c2022aa315a56bbe588e625f8f517fc07086e6f286e62d5"; 51 | 52 | const output = await replicate.predictions.create({ 53 | version: modelURL, 54 | input: { 55 | image: dataURI, 56 | gender: gender, 57 | pose: pose, 58 | seed: -1, 59 | }, 60 | }); 61 | 62 | const resp_data = { 63 | generation_id: output.id, 64 | status: output.status, 65 | }; 66 | 67 | return NextResponse.json(resp_data); 68 | } catch (err: any) { 69 | console.log("[HEADSHOT_API_ERROR]"); 70 | console.log(err); 71 | return new NextResponse(err, { status: 500 }); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/api/qrgen/[id]/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import Replicate from "replicate"; 3 | 4 | import { getCurrentUser } from "@/lib/session"; 5 | import prismadb from "@/lib/db"; 6 | import { uploadMultipleImages } from "@/lib/cloudinary"; 7 | 8 | const replicate = new Replicate({ 9 | auth: process.env.REPLICATE_API_TOKEN!, 10 | }); 11 | 12 | export async function GET( 13 | req: NextRequest, 14 | { params }: { params: { id: string } }, 15 | ) { 16 | const user = await getCurrentUser(); 17 | 18 | 19 | if (!user) { 20 | return new NextResponse("Unauthorized", { status: 401 }); 21 | } 22 | const prediction = await replicate.predictions.get(params.id); 23 | if (prediction?.status !== "succeeded" && prediction?.status !== "failed") { 24 | return NextResponse.json({ status: prediction.status, outputURL: "" }); 25 | } else { 26 | if (prediction?.status === "succeeded") { 27 | const cloudinary_resp = await uploadMultipleImages( 28 | prediction.output as string[], 29 | ); 30 | 31 | await prismadb.creation.createMany({ 32 | data: cloudinary_resp?.map((imageUrl) => ({ 33 | imageUrl, 34 | domain: "qr-code", 35 | userId: user.id, 36 | })), 37 | }); 38 | 39 | return NextResponse.json( 40 | { 41 | status: prediction.status, 42 | outputURL: cloudinary_resp, 43 | }, 44 | { status: 200 }, 45 | ); 46 | } else 47 | return NextResponse.json({ status: prediction.status, outputURL: "" }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/api/qrgen/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import Replicate from "replicate"; 3 | 4 | import { getCurrentUser } from "@/lib/session"; 5 | import { checkApiLimit } from "@/lib/api-limit"; 6 | import { getUserSubscription } from "@/lib/subscription"; 7 | 8 | const replicate = new Replicate({ 9 | auth: process.env.REPLICATE_API_TOKEN!, 10 | }); 11 | 12 | const QR_CONDITIONAL_SCALE = 1.3; 13 | const GUIDANCE_SCALE = 9; 14 | 15 | export async function POST(req: NextRequest) { 16 | try { 17 | const user = await getCurrentUser(); 18 | 19 | if (!user) { 20 | return new NextResponse("Unauthorized", { status: 401 }); 21 | } 22 | const body = await req.json(); 23 | const { prompt, content, model, amount } = body; 24 | 25 | if (!prompt) { 26 | return new NextResponse("Prompt is required", { status: 400 }); 27 | } 28 | 29 | if (!content) { 30 | return new NextResponse("Content is required", { status: 400 }); 31 | } 32 | 33 | if (!model) { 34 | return new NextResponse("Model is required", { status: 400 }); 35 | } 36 | if (!amount) { 37 | return new NextResponse("Amount is required", { status: 400 }); 38 | } 39 | 40 | const freeTrial = await checkApiLimit(); 41 | const subscriptionPlan = await getUserSubscription(user.id); 42 | if (subscriptionPlan instanceof Error) { 43 | return new NextResponse("Unauthorized", { status: 401 }); 44 | } 45 | 46 | if (!freeTrial && !subscriptionPlan.isPro) { 47 | return new NextResponse("Free Tier Ended", { status: 403 }); 48 | } 49 | 50 | const modelURL = 51 | model === "1" 52 | ? "9cdabf8f8a991351960c7ce2105de2909514b40bd27ac202dba57935b07d29d4" 53 | : "628e604e13cf63d8ec58bd4d238474e8986b054bc5e1326e50995fdbc851c557"; 54 | 55 | const req_options = 56 | model == "1" 57 | ? { 58 | qr_code_content: content, 59 | prompt: prompt, 60 | batch_size: parseInt(amount, 10), 61 | controlnet_conditioning_scale: QR_CONDITIONAL_SCALE, 62 | guidance_scale: GUIDANCE_SCALE, 63 | } 64 | : { 65 | url: content, 66 | prompt: prompt, 67 | num_outputs: parseInt(amount, 10), 68 | qr_conditioning_scale: QR_CONDITIONAL_SCALE, 69 | guidance_scale: GUIDANCE_SCALE, 70 | }; 71 | 72 | const output = await replicate.predictions.create({ 73 | version: modelURL, 74 | input: req_options, 75 | }); 76 | 77 | // const replicate_res = string().array().parse(output); 78 | 79 | // const cloudinary_resp = await uploadMultipleImages(replicate_res); 80 | 81 | // await prismadb.creation.createMany({ 82 | // data: cloudinary_resp?.map((imageUrl) => ({ 83 | // imageUrl, 84 | // domain: "qr-code", 85 | // userId: user.id, 86 | // })), 87 | // }); 88 | 89 | const resp_data = { 90 | generation_id: output.id, 91 | status: output.status, 92 | }; 93 | 94 | return NextResponse.json(resp_data, { status: 200 }); 95 | } catch (err) { 96 | console.log("[QR_GEN_BACKEND_ERROR]"); 97 | console.log(err); 98 | return NextResponse.json(err, { status: 500 }); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /app/api/real-art/[id]/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import Replicate from "replicate"; 3 | 4 | import { getCurrentUser } from "@/lib/session"; 5 | import prismadb from "@/lib/db"; 6 | import { uploadImage } from "@/lib/cloudinary"; 7 | 8 | const replicate = new Replicate({ 9 | auth: process.env.REPLICATE_API_TOKEN!, 10 | }); 11 | 12 | export async function GET( 13 | req: NextRequest, 14 | { params }: { params: { id: string } }, 15 | ) { 16 | const user = await getCurrentUser(); 17 | if (!user) { 18 | return new NextResponse("Unauthorized", { status: 401 }); 19 | } 20 | 21 | const prediction = await replicate.predictions.get(params.id); 22 | 23 | if (prediction?.status !== "succeeded" && prediction?.status !== "failed") { 24 | return NextResponse.json({ status: prediction.status, outputURL: "" }); 25 | } else { 26 | if (prediction?.status === "succeeded") { 27 | const cloudinary_resp = await uploadImage(prediction.output as string); 28 | 29 | await prismadb.creation.create({ 30 | data: { 31 | imageUrl: cloudinary_resp.secure_url, 32 | domain: "real-art", 33 | userId: user.id, 34 | }, 35 | }); 36 | 37 | return NextResponse.json( 38 | { 39 | status: prediction.status, 40 | outputURL: cloudinary_resp.secure_url, 41 | }, 42 | { status: 200 }, 43 | ); 44 | } else 45 | return NextResponse.json({ status: prediction.status, outputURL: "" }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/api/real-art/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import Replicate from "replicate"; 3 | 4 | import { getCurrentUser } from "@/lib/session"; 5 | import { checkApiLimit } from "@/lib/api-limit"; 6 | import { getUserSubscription } from "@/lib/subscription"; 7 | 8 | const NEGATIVE_PROMPT = 9 | "(deformed iris, deformed pupils, semi-realistic, cgi, 3d, render, sketch, cartoon, drawing, anime, mutated hands and fingers:1.4), (deformed, distorted, disfigured:1.3), poorly drawn, bad anatomy, wrong anatomy, extra limb, missing limb, floating limbs, disconnected limbs, mutation, mutated, ugly, disgusting, amputation, human anatomy"; 10 | const STRENGTH = 0.71; 11 | 12 | const replicate = new Replicate({ 13 | auth: process.env.REPLICATE_API_TOKEN!, 14 | }); 15 | 16 | export async function POST(req: NextRequest) { 17 | try { 18 | const user = await getCurrentUser(); 19 | 20 | if (!user) { 21 | return new NextResponse("Unauthorized", { status: 401 }); 22 | } 23 | 24 | const formData = await req.formData(); 25 | const file = formData.get("image") as Blob; 26 | const prompt = formData.get("prompt") as string; 27 | 28 | if (!file) { 29 | return new NextResponse("No file found", { status: 400 }); 30 | } 31 | 32 | if (!prompt) { 33 | return new NextResponse("No prompt found", { status: 400 }); 34 | } 35 | 36 | const freeTrial = await checkApiLimit(); 37 | const subscriptionPlan = await getUserSubscription(user.id); 38 | if (subscriptionPlan instanceof Error) { 39 | return new NextResponse("Unauthorized", { status: 401 }); 40 | } 41 | 42 | if (!freeTrial && !subscriptionPlan.isPro) { 43 | return new NextResponse("Free Tier Ended", { status: 403 }); 44 | } 45 | 46 | const bytes = await file.arrayBuffer(); 47 | const buffer = Buffer.from(bytes); 48 | const dataURI = `data:${file.type};base64,${buffer.toString("base64")}`; 49 | 50 | const modelURL = 51 | "82bbb4595458d6be142450fc6d8c4d79c936b92bd184dd2d6dd71d0796159819"; 52 | 53 | const output = await replicate.predictions.create({ 54 | version: modelURL, 55 | input: { 56 | image: dataURI, 57 | prompt: prompt, 58 | negative_prompt: NEGATIVE_PROMPT, 59 | strength: STRENGTH, 60 | }, 61 | }); 62 | 63 | const resp_data = { 64 | generation_id: output.id, 65 | status: output.status, 66 | }; 67 | 68 | return NextResponse.json(resp_data); 69 | } catch (err: any) { 70 | console.log("[REAL_ART_API_ERROR]"); 71 | console.log(err); 72 | return new NextResponse(err, { status: 500 }); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/api/stripe/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | 3 | import { getCurrentUser } from "@/lib/session"; 4 | import { stripe } from "@/lib/stripe"; 5 | import { absoluteUrl } from "@/lib/utils"; 6 | import { getUserSubscription } from "@/lib/subscription"; 7 | 8 | const billingUrl = absoluteUrl("/billing"); 9 | 10 | export async function GET(req: Request) { 11 | try { 12 | const user = await getCurrentUser(); 13 | 14 | if (!user || !user.email) { 15 | return new NextResponse("Unauthorized", { status: 401 }); 16 | } 17 | 18 | const subscriptionPlan = await getUserSubscription(user.id); 19 | 20 | if (subscriptionPlan instanceof Error) { 21 | return new NextResponse("Unauthorized", { status: 401 }); 22 | } 23 | 24 | // User on Pro Plan 25 | if (subscriptionPlan.isPro && subscriptionPlan.stripeCustomerId) { 26 | const stripeSession = await stripe.billingPortal.sessions.create({ 27 | customer: subscriptionPlan.stripeCustomerId, 28 | return_url: billingUrl, 29 | }); 30 | 31 | return new Response(JSON.stringify({ url: stripeSession.url })); 32 | } 33 | 34 | // User on Free Plan so create checkout session 35 | const stripeSession = await stripe.checkout.sessions.create({ 36 | success_url: billingUrl, 37 | cancel_url: billingUrl, 38 | payment_method_types: ["card"], 39 | mode: "subscription", 40 | billing_address_collection: "auto", 41 | customer_email: user.email, 42 | line_items: [ 43 | { 44 | price: process.env.STRIPE_PRO_PRICE_ID, 45 | quantity: 1, 46 | }, 47 | ], 48 | metadata: { 49 | userId: user.id, 50 | }, 51 | }); 52 | return new Response(JSON.stringify({ url: stripeSession.url })); 53 | } catch (e) { 54 | console.log("[STRIPE_API_ERROR]", e); 55 | return new NextResponse("Payment error", { status: 500 }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/api/upscale/[id]/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import Replicate from "replicate"; 3 | 4 | import { getCurrentUser } from "@/lib/session"; 5 | import prismadb from "@/lib/db"; 6 | import { uploadImage } from "@/lib/cloudinary"; 7 | 8 | const replicate = new Replicate({ 9 | auth: process.env.REPLICATE_API_TOKEN!, 10 | }); 11 | 12 | export async function GET( 13 | req: NextRequest, 14 | { params }: { params: { id: string } }, 15 | ) { 16 | const user = await getCurrentUser(); 17 | 18 | if (!user) { 19 | return new NextResponse("Unauthorized", { status: 401 }); 20 | } 21 | 22 | const prediction = await replicate.predictions.get(params.id); 23 | 24 | if (prediction?.status !== "succeeded" && prediction?.status !== "failed") { 25 | return NextResponse.json({ status: prediction.status, outputURL: "" }); 26 | } else { 27 | if (prediction?.status === "succeeded") { 28 | // Max size to cloudinary is 10MB [Free plan] 29 | // const cloudinary_resp = await uploadImage(replicate_res); 30 | // const cloudinary_resp = await uploadImage(prediction.output as string); 31 | 32 | await prismadb.creation.create({ 33 | data: { 34 | imageUrl: null, 35 | domain: "bg-remove", 36 | userId: user.id, 37 | }, 38 | }); 39 | 40 | return NextResponse.json( 41 | { 42 | status: prediction.status, 43 | // outputURL: cloudinary_resp.secure_url, 44 | outputURL: prediction.output, 45 | }, 46 | { status: 200 }, 47 | ); 48 | } else 49 | return NextResponse.json({ status: prediction.status, outputURL: "" }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/api/upscale/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import Replicate from "replicate"; 3 | 4 | import { getCurrentUser } from "@/lib/session"; 5 | import { checkApiLimit } from "@/lib/api-limit"; 6 | import { getUserSubscription } from "@/lib/subscription"; 7 | 8 | const replicate = new Replicate({ 9 | auth: process.env.REPLICATE_API_TOKEN!, 10 | }); 11 | 12 | export async function POST(req: NextRequest) { 13 | try { 14 | const user = await getCurrentUser(); 15 | 16 | if (!user) { 17 | return new NextResponse("Unauthorized", { status: 401 }); 18 | } 19 | 20 | const formData = await req.formData(); 21 | const file = formData.get("image") as Blob; 22 | const scale = formData.get("scale") as string; 23 | 24 | if (!file) { 25 | return new NextResponse("No file found", { status: 400 }); 26 | } 27 | 28 | if (!scale) { 29 | return new NextResponse("No scale found", { status: 400 }); 30 | } 31 | 32 | const freeTrial = await checkApiLimit(); 33 | const subscriptionPlan = await getUserSubscription(user.id); 34 | if (subscriptionPlan instanceof Error) { 35 | return new NextResponse("Unauthorized", { status: 401 }); 36 | } 37 | 38 | if (!freeTrial && !subscriptionPlan.isPro) { 39 | return new NextResponse("Free Tier Ended", { status: 403 }); 40 | } 41 | 42 | const bytes = await file.arrayBuffer(); 43 | const buffer = Buffer.from(bytes); 44 | const dataURI = `data:${file.type};base64,${buffer.toString("base64")}`; 45 | 46 | const modelURL = 47 | "499940604f95b416c3939423df5c64a5c95cfd32b464d755dacfe2192a2de7ef"; 48 | 49 | const output = await replicate.predictions.create({ 50 | version: modelURL, 51 | input: { 52 | image: dataURI, 53 | scale: parseInt(scale, 10), 54 | }, 55 | }); 56 | 57 | const resp_data = { 58 | generation_id: output.id, 59 | status: output.status, 60 | }; 61 | 62 | return NextResponse.json(resp_data); 63 | } catch (err: any) { 64 | console.log("[UPSCALE_API_ERROR]"); 65 | console.log(err); 66 | return new NextResponse(err, { status: 500 }); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/api/user/[userId]/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | 3 | import { getCurrentUser } from "@/lib/session"; 4 | import prismadb from "@/lib/db"; 5 | import { userSettingSchema } from "@/lib/validations/user"; 6 | 7 | export async function PATCH( 8 | req: NextRequest, 9 | { params }: { params: { userId: string } }, 10 | ) { 11 | const user = await getCurrentUser(); 12 | 13 | if (!user) { 14 | return new NextResponse("Unauthorized", { status: 401 }); 15 | } 16 | 17 | const body = await req.json(); 18 | 19 | const data = userSettingSchema.parse(body); 20 | 21 | await prismadb.user.update({ 22 | where: { 23 | id: user.id, 24 | }, 25 | data: { 26 | name: data.username, 27 | }, 28 | }); 29 | 30 | return new NextResponse("Success", { status: 200 }); 31 | } 32 | -------------------------------------------------------------------------------- /app/api/webhook/route.ts: -------------------------------------------------------------------------------- 1 | import { headers } from "next/headers"; 2 | import Stripe from "stripe"; 3 | 4 | import prismadb from "@/lib/db"; 5 | import { stripe } from "@/lib/stripe"; 6 | import { NextResponse } from "next/server"; 7 | 8 | export async function POST(req: Request) { 9 | const body = await req.text(); 10 | const signature = headers().get("Stripe-Signature") as string; 11 | 12 | let event: Stripe.Event; 13 | 14 | try { 15 | event = stripe.webhooks.constructEvent( 16 | body, 17 | signature, 18 | process.env.STRIPE_WEBHOOK_SECRET!, 19 | ); 20 | } catch (err: any) { 21 | return new NextResponse("WEBHOOK ERROR:" + err.message, { status: 400 }); 22 | } 23 | 24 | const session = event.data.object as Stripe.Checkout.Session; 25 | 26 | if (event.type === "checkout.session.completed") { 27 | const subscription = await stripe.subscriptions.retrieve( 28 | session.subscription as string, 29 | ); 30 | 31 | if (!session?.metadata?.userId) { 32 | return new NextResponse("No user id in session", { status: 400 }); 33 | } 34 | 35 | await prismadb.user.update({ 36 | where: { 37 | id: session?.metadata?.userId, 38 | }, 39 | data: { 40 | stripeSubscriptionId: subscription.id, 41 | stripeCustomerId: subscription.customer as string, 42 | stripePriceId: subscription.items.data[0].price.id, 43 | stripeCurrentPeriodEnd: new Date( 44 | subscription.current_period_end * 1000, 45 | ), 46 | }, 47 | }); 48 | } 49 | 50 | if (event.type === "invoice.payment_succeeded") { 51 | const subscription = await stripe.subscriptions.retrieve( 52 | session.subscription as string, 53 | ); 54 | 55 | await prismadb.user.update({ 56 | where: { 57 | stripeSubscriptionId: subscription.id, 58 | }, 59 | data: { 60 | stripePriceId: subscription.items.data[0].price.id, 61 | stripeCurrentPeriodEnd: new Date( 62 | subscription.current_period_end * 1000, 63 | ), 64 | }, 65 | }); 66 | } 67 | return new NextResponse(null, { status: 200 }); 68 | } 69 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sanjay-Ganapathi/Pixiemist/7dee2dbec0579e45c2d825117677a590d69419b6/app/favicon.ico -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html,body,:root{ 6 | height: 100%; 7 | } 8 | 9 | 10 | 11 | @layer base { 12 | :root { 13 | --background: 0 0% 100%; 14 | --foreground: 224 71.4% 4.1%; 15 | --card: 0 0% 100%; 16 | --card-foreground: 224 71.4% 4.1%; 17 | --popover: 0 0% 100%; 18 | --popover-foreground: 224 71.4% 4.1%; 19 | --primary: 262.1 83.3% 57.8%; 20 | --primary-foreground: 210 20% 98%; 21 | --secondary: 220 14.3% 95.9%; 22 | --secondary-foreground: 220.9 39.3% 11%; 23 | --muted: 220 14.3% 95.9%; 24 | --muted-foreground: 220 8.9% 46.1%; 25 | --accent: 220 14.3% 95.9%; 26 | --accent-foreground: 220.9 39.3% 11%; 27 | --destructive: 0 84.2% 60.2%; 28 | --destructive-foreground: 210 20% 98%; 29 | --border: 220 13% 91%; 30 | --input: 220 13% 91%; 31 | --ring: 262.1 83.3% 57.8%; 32 | --radius: 0.5rem; 33 | } 34 | 35 | .dark { 36 | --background: 224 71.4% 4.1%; 37 | --foreground: 210 20% 98%; 38 | --card: 224 71.4% 4.1%; 39 | --card-foreground: 210 20% 98%; 40 | --popover: 224 71.4% 4.1%; 41 | --popover-foreground: 210 20% 98%; 42 | --primary: 263.4 70% 50.4%; 43 | --primary-foreground: 210 20% 98%; 44 | --secondary: 215 27.9% 16.9%; 45 | --secondary-foreground: 210 20% 98%; 46 | --muted: 215 27.9% 16.9%; 47 | --muted-foreground: 217.9 10.6% 64.9%; 48 | --accent: 215 27.9% 16.9%; 49 | --accent-foreground: 210 20% 98%; 50 | --destructive: 0 62.8% 30.6%; 51 | --destructive-foreground: 210 20% 98%; 52 | --border: 215 27.9% 16.9%; 53 | --input: 215 27.9% 16.9%; 54 | --ring: 263.4 70% 50.4%; 55 | } 56 | } 57 | 58 | 59 | @layer base { 60 | * { 61 | @apply border-border; 62 | } 63 | body { 64 | @apply bg-background text-foreground; 65 | } 66 | } -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "./globals.css"; 2 | import type { Metadata } from "next"; 3 | import { Inter } from "next/font/google"; 4 | 5 | import { ToasterProvider } from "@/components/toaster-provider"; 6 | import { ModalProvider } from "@/components/modal-provider"; 7 | import { ThemeProvider } from "@/components/theme-provider"; 8 | 9 | const inter = Inter({ 10 | subsets: ["latin"], 11 | variable: "--font-inter", 12 | display: "swap", 13 | }); 14 | 15 | export const metadata: Metadata = { 16 | title: "Pixiemist", 17 | }; 18 | 19 | export default function RootLayout({ 20 | children, 21 | }: { 22 | children: React.ReactNode; 23 | }) { 24 | return ( 25 | 26 | 27 | 33 | 34 | 35 | {children} 36 | 37 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /components/billing.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useState } from "react"; 4 | 5 | import { UserSubscriptionPlan } from "@/types"; 6 | import { cn, formatDate } from "@/lib/utils"; 7 | import { 8 | Card, 9 | CardContent, 10 | CardDescription, 11 | CardFooter, 12 | CardHeader, 13 | CardTitle, 14 | } from "@/components/ui/card"; 15 | import { Button } from "./ui/button"; 16 | import { Loader } from "lucide-react"; 17 | import toast from "react-hot-toast"; 18 | import axios from "axios"; 19 | 20 | interface BillingCardProps extends React.HTMLAttributes { 21 | subscriptionPlan: UserSubscriptionPlan; 22 | isCancelled: boolean; 23 | } 24 | 25 | export const BillingCard = ({ 26 | subscriptionPlan, 27 | isCancelled, 28 | className, 29 | }: BillingCardProps) => { 30 | const [isLoading, setIsLoading] = useState(false); 31 | 32 | const handleSubmit = async () => { 33 | console.log("Submitted"); 34 | try { 35 | setIsLoading(true); 36 | 37 | const response = await axios.get("/api/stripe"); 38 | window.location.href = response.data.url; 39 | } catch (err) { 40 | toast.error("Something went wrong.Please try again."); 41 | } finally { 42 | setIsLoading(false); 43 | } 44 | }; 45 | 46 | return ( 47 |
48 | 49 | 50 | Subscription Plan 51 | 52 | You are currently on{" "} 53 | {subscriptionPlan.isPro ? "Pro" : "Free"} plan. 54 | 55 | 56 | 57 | {subscriptionPlan.isPro 58 | ? "You can enjoy unlimited creations." 59 | : "The Free Plan allows upto 10 creations. Consider upgrading to Pro for unlimited creations."} 60 | 61 | 62 | 63 | 72 | {subscriptionPlan.isPro ? ( 73 |

74 | {isCancelled 75 | ? "Your plan will expire on " 76 | : "Your plan will renew on "} 77 | {formatDate(subscriptionPlan.stripeCurrentPeriodEnd!)}. 78 |

79 | ) : ( 80 | <> 81 | )} 82 |
83 |
84 |
85 | ); 86 | }; 87 | -------------------------------------------------------------------------------- /components/custom-icons.tsx: -------------------------------------------------------------------------------- 1 | import { LucideProps } from "lucide-react"; 2 | const GoogleIcon = ({ ...props }: LucideProps) => ( 3 | 30 | ); 31 | 32 | const LogoIcon = ({ ...props }: LucideProps) =>( 33 | 44 | 48 | 49 | ) 50 | 51 | export { GoogleIcon,LogoIcon }; 52 | -------------------------------------------------------------------------------- /components/dashboard-card.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Image from "next/image"; 3 | 4 | import { 5 | Card, 6 | CardFooter, 7 | CardTitle, 8 | CardDescription, 9 | } from "@/components/ui/card"; 10 | 11 | interface DashboardCardProps { 12 | img: string; 13 | heading: string; 14 | description: string; 15 | } 16 | 17 | export const DashboardCard = ({ 18 | img, 19 | heading, 20 | description, 21 | }: DashboardCardProps) => { 22 | return ( 23 | 24 | {description} 31 | 32 | {heading} 33 | {description} 34 | 35 | 36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /components/dropzone.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useState } from "react"; 2 | import { Upload } from "lucide-react"; 3 | import { FileWithPreview } from "@/types"; 4 | 5 | import { 6 | useDropzone, 7 | FileWithPath, 8 | FileRejection, 9 | Accept, 10 | } from "react-dropzone"; 11 | import { 12 | FieldPath, 13 | FieldValues, 14 | Path, 15 | PathValue, 16 | UseFormSetValue, 17 | } from "react-hook-form"; 18 | import toast from "react-hot-toast"; 19 | import { formatBytes } from "@/lib/utils"; 20 | import { PageCard } from "./page-card"; 21 | 22 | interface DropzoneProps< 23 | TFieldValues extends FieldValues = FieldValues, 24 | TName extends FieldPath = FieldPath, 25 | > extends React.HTMLAttributes { 26 | accept?: Accept; 27 | maxSize?: number; 28 | name: TName; 29 | setValue: UseFormSetValue; 30 | maxFiles?: number; 31 | file: FileWithPreview[]; 32 | setFile: React.Dispatch>; 33 | disabled: boolean; 34 | icon?: boolean; 35 | caption?: string; 36 | ifAlwaysVisible?: boolean; 37 | } 38 | 39 | export const Dropzone = ({ 40 | accept = { 41 | "image/jpeg": [], 42 | "image/png": [], 43 | }, 44 | maxFiles = 1, 45 | name, 46 | maxSize = 1024 * 1024 * 2, 47 | setValue, 48 | file, 49 | setFile, 50 | className, 51 | disabled, 52 | icon = true, 53 | caption = "Drop your Image here or Click to Select Image", 54 | ifAlwaysVisible = true, 55 | }: DropzoneProps) => { 56 | const onDrop = useCallback( 57 | (acceptedFiles: FileWithPath[], rejectedFiles: FileRejection[]) => { 58 | acceptedFiles.forEach((file) => { 59 | setFile((prevFiles) => [ 60 | ...acceptedFiles.map((file) => 61 | Object.assign(file, { preview: URL.createObjectURL(file) }), 62 | ), 63 | ]); 64 | }); 65 | 66 | if (rejectedFiles.length > 0) { 67 | if (rejectedFiles.length > 1) { 68 | toast.error("Only one file is allowed"); 69 | } else { 70 | rejectedFiles.forEach(({ errors }) => { 71 | if (errors[0]?.code === "file-too-large") { 72 | toast.error( 73 | `File is too large. Max size is ${formatBytes(maxSize)}`, 74 | ); 75 | } else errors[0]?.message && toast.error(errors[0].message); 76 | }); 77 | } 78 | 79 | return; 80 | } 81 | }, 82 | [maxSize, setFile], 83 | ); 84 | 85 | useEffect(() => { 86 | setValue(name, file as PathValue>); 87 | 88 | // eslint-disable-next-line react-hooks/exhaustive-deps 89 | }, [file]); 90 | 91 | const { getRootProps, getInputProps, isDragActive } = useDropzone({ 92 | onDrop, 93 | maxSize, 94 | maxFiles, 95 | accept, 96 | disabled, 97 | }); 98 | useEffect(() => { 99 | return () => { 100 | if (!file) return; 101 | file.forEach((f) => URL.revokeObjectURL(f.preview)); 102 | }; 103 | // eslint-disable-next-line react-hooks/exhaustive-deps 104 | }, []); 105 | 106 | const renderDropZoneUI = () => ( 107 |
108 |
109 | {icon && } 110 | {isDragActive ?

Drop the files here ...

:

{caption}

} 111 |
112 |
113 | ); 114 | 115 | return ( 116 | <> 117 |
118 | 119 | {ifAlwaysVisible && renderDropZoneUI()} 120 | 121 | {!ifAlwaysVisible && 122 | (file.length > 0 ? ( 123 | <> 124 | { 129 | setFile([]); 130 | }} 131 | /> 132 | 133 | ) : ( 134 | renderDropZoneUI() 135 | ))} 136 |
137 | 138 | ); 139 | }; 140 | -------------------------------------------------------------------------------- /components/email/activation.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Container, 4 | Head, 5 | Heading, 6 | Html, 7 | Link, 8 | Preview, 9 | Text, 10 | } from "@react-email/components"; 11 | import * as React from "react"; 12 | 13 | interface ActivationLinkProps { 14 | url?: string; 15 | } 16 | 17 | export const ActivationLink = ({ url }: ActivationLinkProps) => ( 18 | 19 | 20 | Pixiemist magic link for registration 21 | 22 | 23 | Hey!! Welcome Back to Pixiemist 24 | 33 | Click here to log in with this magic link 34 | 35 | 43 | This link expires in 24 hours and can only be used once. 44 | 45 | 53 | If you didn't try to signin, you can safely ignore this email. 54 | 55 | 56 | 57 | 58 | ); 59 | 60 | const main = { 61 | backgroundColor: "#ffffff", 62 | }; 63 | 64 | const container = { 65 | paddingLeft: "12px", 66 | paddingRight: "12px", 67 | margin: "0 auto", 68 | }; 69 | 70 | const h1 = { 71 | color: "#333", 72 | fontFamily: 73 | "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif", 74 | fontSize: "24px", 75 | fontWeight: "bold", 76 | margin: "40px 0", 77 | padding: "0", 78 | }; 79 | 80 | const link = { 81 | color: "#2754C5", 82 | fontFamily: 83 | "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif", 84 | fontSize: "14px", 85 | textDecoration: "underline", 86 | }; 87 | 88 | const text = { 89 | color: "#333", 90 | fontFamily: 91 | "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif", 92 | fontSize: "14px", 93 | margin: "24px 0", 94 | }; 95 | -------------------------------------------------------------------------------- /components/email/new-user.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Container, 4 | Head, 5 | Heading, 6 | Html, 7 | Link, 8 | Preview, 9 | Text, 10 | } from "@react-email/components"; 11 | import * as React from "react"; 12 | 13 | interface NewUserEmailProps { 14 | url?: string; 15 | } 16 | 17 | export const NewUserEmail = ({ url }: NewUserEmailProps) => ( 18 | 19 | 20 | Pixiemist magic link for registration 21 | 22 | 23 | Welcome to Pixiemist✨ 24 | 33 | Click here to log in with this magic link 34 | 35 | 43 | This link expires in 24 hours and can only be used once. 44 | 45 | 53 | If you didn't try to signin, you can safely ignore this email. 54 | 55 | 56 | 57 | 58 | ); 59 | 60 | const main = { 61 | backgroundColor: "#ffffff", 62 | }; 63 | 64 | const container = { 65 | paddingLeft: "12px", 66 | paddingRight: "12px", 67 | margin: "0 auto", 68 | }; 69 | 70 | const h1 = { 71 | color: "#333", 72 | fontFamily: 73 | "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif", 74 | fontSize: "24px", 75 | fontWeight: "bold", 76 | margin: "40px 0", 77 | padding: "0", 78 | }; 79 | 80 | const link = { 81 | color: "#2754C5", 82 | fontFamily: 83 | "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif", 84 | fontSize: "14px", 85 | textDecoration: "underline", 86 | }; 87 | 88 | const text = { 89 | color: "#333", 90 | fontFamily: 91 | "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif", 92 | fontSize: "14px", 93 | margin: "24px 0", 94 | }; 95 | -------------------------------------------------------------------------------- /components/free-counter.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { MAX_FREE_COUNT } from "@/constants"; 4 | import { Progress } from "@/components/ui/progress"; 5 | import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; 6 | import { useEffect, useState } from "react"; 7 | import { usePromodal } from "@/store/promodal-store"; 8 | import { Button } from "@/components/ui/button"; 9 | 10 | interface FreeCounterProps { 11 | creationCount: number; 12 | isPro: boolean; 13 | } 14 | 15 | export const FreeCounter = ({ creationCount, isPro }: FreeCounterProps) => { 16 | const [mounted, setMounted] = useState(false); 17 | const proModal = usePromodal(); 18 | useEffect(() => { 19 | setMounted(true); 20 | }, []); 21 | 22 | if (!mounted) return null; 23 | 24 | return isPro ? ( 25 | <> 26 | ) : ( 27 |
28 | 29 | 30 | 31 | {creationCount}/{MAX_FREE_COUNT} Free Creations 32 | 33 | 34 | 35 | 39 | 46 | 47 | 48 |
49 | ); 50 | }; 51 | -------------------------------------------------------------------------------- /components/heading.tsx: -------------------------------------------------------------------------------- 1 | interface HeadingProps { 2 | heading: string; 3 | subHeading?: string; 4 | children?: React.ReactNode; 5 | } 6 | 7 | export const Heading = ({ heading, subHeading, children }: HeadingProps) => { 8 | return ( 9 |
10 |
11 |

12 | {heading} 13 |

14 | {subHeading && ( 15 |

{subHeading}

16 | )} 17 |
18 | {children} 19 |
20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /components/image-compare-slider.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Image from "next/image"; 3 | import { ReactCompareSlider } from "react-compare-slider"; 4 | 5 | import { Card } from "@/components/ui/card"; 6 | import { ChevronDownCircle } from "lucide-react"; 7 | 8 | type ImageCompareSliderProps = { 9 | imageOne: string; 10 | imageTwo: string; 11 | downloadBtn?: boolean; 12 | downloadImg?: "first" | "second"; 13 | }; 14 | 15 | export const ImageCompareSlider = ({ 16 | imageOne, 17 | imageTwo, 18 | downloadBtn = false, 19 | downloadImg = "second", 20 | }: ImageCompareSliderProps) => { 21 | return ( 22 | 23 | 26 | } 27 | itemTwo={ 28 | imageTwo 29 | } 30 | /> 31 | 32 | {downloadBtn && ( 33 | { 37 | if (downloadImg === "first") { 38 | window.open(imageOne, "_blank"); 39 | } else { 40 | window.open(imageTwo, "_blank"); 41 | } 42 | }} 43 | /> 44 | )} 45 | 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /components/landing/headshot-landing.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import { DM_Serif_Display } from "next/font/google"; 3 | import { cn } from "@/lib/utils"; 4 | import { ChevronsDown, ChevronsRight } from "lucide-react"; 5 | 6 | const font = DM_Serif_Display({ weight: "400", subsets: ["latin"] }); 7 | 8 | export const HeadShotLanding = () => { 9 | return ( 10 |
11 |
12 |

13 | Generate Realistic Image from Painting Portrait. 14 |

15 |
16 |
17 | Portrait Painting 24 | 25 | 26 | Realistic Portrait 33 |
34 |
35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /components/landing/landing-footer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Separator } from "../ui/separator"; 3 | import Link from "next/link"; 4 | 5 | export const LandingFooter = () => { 6 | return ( 7 | <> 8 |
9 |
10 |

Pixiemist

11 |
12 | Made by{" "} 13 | Sanjay Ganapathi 14 |
15 |
16 | 17 |

Copyright © {new Date().getFullYear()} Pixiemist

18 |
19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /components/landing/landing-hero.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | import { Instrument_Sans, Ubuntu } from "next/font/google"; 5 | import { TypewriterLandingComponent } from "./typewrite-landing"; 6 | import { Button } from "@/components/ui/button"; 7 | import { User } from "next-auth"; 8 | 9 | const font = Instrument_Sans({ 10 | weight: "600", 11 | subsets: ["latin"], 12 | }); 13 | const font_2 = Ubuntu({ weight: "700", subsets: ["latin"] }); 14 | 15 | type LandingHeroProps = { 16 | user?: User; 17 | }; 18 | 19 | export const LandingHero = ({ user }: LandingHeroProps) => { 20 | return ( 21 |
22 |
28 |

Unlock a world of creative possibilities with

29 |
35 | 36 |
37 |
38 |
39 | 40 | 46 | 47 | 48 |
49 | No Credit Card required 50 |
51 |
52 |
53 | ); 54 | }; 55 | -------------------------------------------------------------------------------- /components/landing/landing-nav.tsx: -------------------------------------------------------------------------------- 1 | import { Montserrat } from "next/font/google"; 2 | import Image from "next/image"; 3 | import Link from "next/link"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | import { Button } from "@/components/ui/button"; 7 | import { User } from "next-auth"; 8 | 9 | const font = Montserrat({ weight: "600", subsets: ["latin"] }); 10 | 11 | type LandingNavProps = { 12 | user?: User; 13 | }; 14 | 15 | export const LandingNav = ({ user }: LandingNavProps) => { 16 | return ( 17 | 37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /components/landing/landing-section-second.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { HeadShotLanding } from "./headshot-landing"; 3 | export const LandingSectionSecond = () => { 4 | return ( 5 |
6 |
7 | 8 |
9 |
10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /components/landing/landing-section-three.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { RemoveBgLanding } from "./removebg-landing"; 3 | import { DM_Serif_Display } from "next/font/google"; 4 | import { cn } from "@/lib/utils"; 5 | 6 | const font = DM_Serif_Display({ weight: "400", subsets: ["latin"] }); 7 | 8 | export const LandingSectionThree = () => { 9 | return ( 10 |
11 |
12 | 13 |
19 | And many more ... 20 |
21 |
22 |
23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /components/landing/landing-section.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { QRCodeLanding } from "./qrcode-landing"; 3 | 4 | export const LandingSection = () => { 5 | return ( 6 |
7 |
8 |
9 | 10 | Power Up 11 | {" "} 12 | With AI! 13 |
14 |

15 | Use AI to simplify your workflow. Save Time and Effort. 16 |

17 |
18 |
19 | 20 |
21 |
22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /components/landing/qrcode-landing.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Input } from "../ui/input"; 3 | import Image from "next/image"; 4 | import { DM_Serif_Display } from "next/font/google"; 5 | import { cn } from "@/lib/utils"; 6 | 7 | const font = DM_Serif_Display({ weight: "400", subsets: ["latin"] }); 8 | 9 | export const QRCodeLanding = () => { 10 | return ( 11 |
12 |
13 |

14 | Generate Stylised QR Codes from your prompts. 15 |

16 |
17 | 18 |
19 | 25 | QR Code 32 |
33 |
34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /components/landing/removebg-landing.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import { DM_Serif_Display } from "next/font/google"; 3 | import { cn } from "@/lib/utils"; 4 | import { ChevronsDown, ChevronsRight } from "lucide-react"; 5 | 6 | const font = DM_Serif_Display({ weight: "400", subsets: ["latin"] }); 7 | 8 | export const RemoveBgLanding = () => { 9 | return ( 10 |
11 |
12 |

13 | Remove Background from your Image. 14 |

15 |
16 |
17 | Remove Bg Source 24 | 25 | 26 | Remove Bg Result 33 |
34 |
35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /components/landing/typewrite-landing.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import Typewriter from "typewriter-effect"; 3 | 4 | export const TypewriterLandingComponent = () => { 5 | return ( 6 | { 8 | typewriter.typeString("Pixiemist.").start(); 9 | }} 10 | /> 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /components/loaders.tsx: -------------------------------------------------------------------------------- 1 | import { NewtonsCradle } from "@uiball/loaders"; 2 | import { cn } from "@/lib/utils"; 3 | const Loader = ({ className }: { className?: string }) => { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | }; 10 | 11 | export { Loader }; 12 | -------------------------------------------------------------------------------- /components/mobile-sidebar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { PanelRight } from "lucide-react"; 4 | import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"; 5 | import { Sidebar } from "@/components/sidebar"; 6 | import { useState, useEffect } from "react"; 7 | 8 | interface MobileSidebarProps { 9 | creationCount: number; 10 | isPro: boolean; 11 | } 12 | 13 | export const MobileSidebar = ({ creationCount, isPro }: MobileSidebarProps) => { 14 | const [isMounted, setIsMounted] = useState(false); 15 | 16 | useEffect(() => { 17 | setIsMounted(true); 18 | }, []); 19 | if (!isMounted) return null; 20 | 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /components/modal-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState, useEffect } from "react"; 4 | import { ProModal } from "@/components/pro-modal"; 5 | 6 | export const ModalProvider = () => { 7 | const [isMounted, setIsMounted] = useState(false); 8 | 9 | useEffect(() => { 10 | setIsMounted(true); 11 | }, []); 12 | 13 | if (!isMounted) return null; 14 | 15 | return ; 16 | }; 17 | -------------------------------------------------------------------------------- /components/navbar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { MobileSidebar } from "@/components/mobile-sidebar"; 4 | import { UserAccountNav } from "@/components/user-account-nav"; 5 | import { getCreationCount } from "@/lib/api-limit"; 6 | import { ModeToggle } from "@/components/theme-toggle"; 7 | 8 | export const Navbar = async ({ isPro }: { isPro: boolean }) => { 9 | const creationCount = await getCreationCount(); 10 | 11 | return ( 12 |
13 | 14 |
15 | 16 | 17 |
18 |
19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /components/page-card.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import React from "react"; 3 | import { OnLoad } from "next/dist/shared/lib/get-img-props"; 4 | 5 | import { Card } from "@/components/ui/card"; 6 | import { ChevronDownCircle, XCircle } from "lucide-react"; 7 | 8 | interface PageCardProps { 9 | src: string; 10 | alt: string; 11 | onLoad?: OnLoad; 12 | closeBtn?: boolean; 13 | onClick?: () => void; 14 | downloadBtn?: boolean; 15 | } 16 | 17 | export const PageCard = ({ 18 | src, 19 | alt, 20 | onLoad, 21 | closeBtn = false, 22 | onClick, 23 | downloadBtn = false, 24 | }: PageCardProps) => { 25 | return ( 26 | 27 | {alt} 28 | 29 | {closeBtn && ( 30 | 34 | )} 35 | 36 | {downloadBtn && ( 37 | { 41 | window.open(src, "_blank"); 42 | }} 43 | /> 44 | )} 45 | 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /components/pro-modal.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import axios from "axios"; 4 | import Image from "next/image"; 5 | import { useState } from "react"; 6 | 7 | import { usePromodal } from "@/store/promodal-store"; 8 | import { Button } from "@/components/ui/button"; 9 | import { Dialog, DialogContent } from "@/components/ui/dialog"; 10 | import toast from "react-hot-toast"; 11 | import { useTheme } from "next-themes"; 12 | 13 | export const ProModal = () => { 14 | const proModal = usePromodal(); 15 | const [loading, setLoading] = useState(false); 16 | 17 | const { resolvedTheme } = useTheme(); 18 | 19 | const onSubscribe = async () => { 20 | try { 21 | setLoading(true); 22 | 23 | const response = await axios.get("/api/stripe"); 24 | window.location.href = response.data.url; 25 | } catch (err) { 26 | toast.error("Pro Subscription Failed"); 27 | } finally { 28 | setLoading(false); 29 | } 30 | }; 31 | 32 | return ( 33 | <> 34 | 35 | 36 |

37 | Upgrade to PRO 38 |

39 | {resolvedTheme === "dark" ? ( 40 | pro 47 | ) : ( 48 | pro 55 | )} 56 |

57 | Enjoy unlimited creations and unlock the full potential! 58 |

59 | 62 |
63 |
64 | 65 | ); 66 | }; 67 | -------------------------------------------------------------------------------- /components/shell.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | interface HeadingShellProps extends React.HTMLAttributes {} 6 | 7 | export const HeadingShell = ({ 8 | children, 9 | className, 10 | ...props 11 | }: HeadingShellProps) => { 12 | return ( 13 |
14 | {children} 15 |
16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /components/sidebar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Montserrat } from "next/font/google"; 4 | import Link from "next/link"; 5 | import Image from "next/image"; 6 | import { usePathname } from "next/navigation"; 7 | 8 | import { routes } from "@/constants"; 9 | import { cn } from "@/lib/utils"; 10 | import { useUserStore } from "@/store/store"; 11 | import { FreeCounter } from "./free-counter"; 12 | import { UserAvatar } from "./user-avatar"; 13 | 14 | const montserrat = Montserrat({ weight: "600", subsets: ["latin"] }); 15 | 16 | interface SidebarProps { 17 | creationCount: number; 18 | isPro: boolean; 19 | } 20 | 21 | export const Sidebar = ({ creationCount, isPro }: SidebarProps) => { 22 | const pathname = usePathname(); 23 | 24 | const user = useUserStore((state) => state.user); 25 | 26 | return ( 27 |
28 |
29 | 30 |
31 | Logo 32 |
33 |

34 | Pixiemist 35 |

36 | 37 |
38 |
39 | 46 |
47 | 48 |

49 | {user.name || user.email} 50 |

51 |
52 |
53 | {routes.map((route) => ( 54 | 64 |
{route.label}
65 | 66 | ))} 67 |
68 |
69 | 70 |
71 | ); 72 | }; 73 | -------------------------------------------------------------------------------- /components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { useEffect, useState } from "react"; 5 | import { ThemeProvider as NextThemesProvider } from "next-themes"; 6 | import { type ThemeProviderProps } from "next-themes/dist/types"; 7 | 8 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 9 | const [isMounted, setIsMounted] = useState(false); 10 | 11 | useEffect(() => { 12 | setIsMounted(true); 13 | }, []); 14 | if (!isMounted) return null; 15 | 16 | return {children}; 17 | } 18 | -------------------------------------------------------------------------------- /components/theme-toggle.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { MoonStar, Sun } from "lucide-react"; 5 | import { useTheme } from "next-themes"; 6 | 7 | import { Button } from "@/components/ui/button"; 8 | import { 9 | DropdownMenu, 10 | DropdownMenuContent, 11 | DropdownMenuItem, 12 | DropdownMenuTrigger, 13 | } from "@/components/ui/dropdown-menu"; 14 | 15 | export function ModeToggle() { 16 | const { setTheme } = useTheme(); 17 | 18 | return ( 19 | 20 | 21 | 26 | 27 | 28 | setTheme("light")}> 29 | Light 30 | 31 | setTheme("dark")}> 32 | Dark 33 | 34 | setTheme("system")}> 35 | System 36 | 37 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /components/toaster-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect, useState } from "react"; 4 | import { Toaster } from "react-hot-toast"; 5 | export const ToasterProvider = () => { 6 | const [isMounted, setIsMounted] = useState(false); 7 | 8 | useEffect(() => { 9 | setIsMounted(true); 10 | }, []); 11 | 12 | if (!isMounted) return null; 13 | 14 | return ; 15 | }; 16 | -------------------------------------------------------------------------------- /components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AvatarPrimitive from "@radix-ui/react-avatar" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Avatar = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | )) 21 | Avatar.displayName = AvatarPrimitive.Root.displayName 22 | 23 | const AvatarImage = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => ( 27 | 32 | )) 33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName 34 | 35 | const AvatarFallback = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | )) 48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName 49 | 50 | export { Avatar, AvatarImage, AvatarFallback } 51 | -------------------------------------------------------------------------------- /components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | } 34 | ) 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button" 45 | return ( 46 | 51 | ) 52 | } 53 | ) 54 | Button.displayName = "Button" 55 | 56 | export { Button, buttonVariants } 57 | -------------------------------------------------------------------------------- /components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLParagraphElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |

44 | )) 45 | CardTitle.displayName = "CardTitle" 46 | 47 | const CardDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |

56 | )) 57 | CardDescription.displayName = "CardDescription" 58 | 59 | const CardContent = React.forwardRef< 60 | HTMLDivElement, 61 | React.HTMLAttributes 62 | >(({ className, ...props }, ref) => ( 63 |

64 | )) 65 | CardContent.displayName = "CardContent" 66 | 67 | const CardFooter = React.forwardRef< 68 | HTMLDivElement, 69 | React.HTMLAttributes 70 | >(({ className, ...props }, ref) => ( 71 |
76 | )) 77 | CardFooter.displayName = "CardFooter" 78 | 79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 80 | -------------------------------------------------------------------------------- /components/ui/dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as DialogPrimitive from "@radix-ui/react-dialog" 5 | import { X } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Dialog = DialogPrimitive.Root 10 | 11 | const DialogTrigger = DialogPrimitive.Trigger 12 | 13 | const DialogPortal = ({ 14 | className, 15 | ...props 16 | }: DialogPrimitive.DialogPortalProps) => ( 17 | 18 | ) 19 | DialogPortal.displayName = DialogPrimitive.Portal.displayName 20 | 21 | const DialogOverlay = React.forwardRef< 22 | React.ElementRef, 23 | React.ComponentPropsWithoutRef 24 | >(({ className, ...props }, ref) => ( 25 | 33 | )) 34 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName 35 | 36 | const DialogContent = React.forwardRef< 37 | React.ElementRef, 38 | React.ComponentPropsWithoutRef 39 | >(({ className, children, ...props }, ref) => ( 40 | 41 | 42 | 50 | {children} 51 | 52 | 53 | Close 54 | 55 | 56 | 57 | )) 58 | DialogContent.displayName = DialogPrimitive.Content.displayName 59 | 60 | const DialogHeader = ({ 61 | className, 62 | ...props 63 | }: React.HTMLAttributes) => ( 64 |
71 | ) 72 | DialogHeader.displayName = "DialogHeader" 73 | 74 | const DialogFooter = ({ 75 | className, 76 | ...props 77 | }: React.HTMLAttributes) => ( 78 |
85 | ) 86 | DialogFooter.displayName = "DialogFooter" 87 | 88 | const DialogTitle = React.forwardRef< 89 | React.ElementRef, 90 | React.ComponentPropsWithoutRef 91 | >(({ className, ...props }, ref) => ( 92 | 100 | )) 101 | DialogTitle.displayName = DialogPrimitive.Title.displayName 102 | 103 | const DialogDescription = React.forwardRef< 104 | React.ElementRef, 105 | React.ComponentPropsWithoutRef 106 | >(({ className, ...props }, ref) => ( 107 | 112 | )) 113 | DialogDescription.displayName = DialogPrimitive.Description.displayName 114 | 115 | export { 116 | Dialog, 117 | DialogTrigger, 118 | DialogContent, 119 | DialogHeader, 120 | DialogFooter, 121 | DialogTitle, 122 | DialogDescription, 123 | } 124 | -------------------------------------------------------------------------------- /components/ui/dropdown-menu.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; 5 | import { Check, ChevronRight, Circle } from "lucide-react"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | 9 | const DropdownMenu = DropdownMenuPrimitive.Root; 10 | 11 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; 12 | 13 | const DropdownMenuGroup = DropdownMenuPrimitive.Group; 14 | 15 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal; 16 | 17 | const DropdownMenuSub = DropdownMenuPrimitive.Sub; 18 | 19 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; 20 | 21 | const DropdownMenuSubTrigger = React.forwardRef< 22 | React.ElementRef, 23 | React.ComponentPropsWithoutRef & { 24 | inset?: boolean; 25 | } 26 | >(({ className, inset, children, ...props }, ref) => ( 27 | 36 | {children} 37 | 38 | 39 | )); 40 | DropdownMenuSubTrigger.displayName = 41 | DropdownMenuPrimitive.SubTrigger.displayName; 42 | 43 | const DropdownMenuSubContent = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef 46 | >(({ className, ...props }, ref) => ( 47 | 55 | )); 56 | DropdownMenuSubContent.displayName = 57 | DropdownMenuPrimitive.SubContent.displayName; 58 | 59 | const DropdownMenuContent = React.forwardRef< 60 | React.ElementRef, 61 | React.ComponentPropsWithoutRef 62 | >(({ className, sideOffset = 4, ...props }, ref) => ( 63 | 64 | 73 | 74 | )); 75 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; 76 | 77 | const DropdownMenuItem = React.forwardRef< 78 | React.ElementRef, 79 | React.ComponentPropsWithoutRef & { 80 | inset?: boolean; 81 | } 82 | >(({ className, inset, ...props }, ref) => ( 83 | 92 | )); 93 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; 94 | 95 | const DropdownMenuCheckboxItem = React.forwardRef< 96 | React.ElementRef, 97 | React.ComponentPropsWithoutRef 98 | >(({ className, children, checked, ...props }, ref) => ( 99 | 108 | 109 | 110 | 111 | 112 | 113 | {children} 114 | 115 | )); 116 | DropdownMenuCheckboxItem.displayName = 117 | DropdownMenuPrimitive.CheckboxItem.displayName; 118 | 119 | const DropdownMenuRadioItem = React.forwardRef< 120 | React.ElementRef, 121 | React.ComponentPropsWithoutRef 122 | >(({ className, children, ...props }, ref) => ( 123 | 131 | 132 | 133 | 134 | 135 | 136 | {children} 137 | 138 | )); 139 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; 140 | 141 | const DropdownMenuLabel = React.forwardRef< 142 | React.ElementRef, 143 | React.ComponentPropsWithoutRef & { 144 | inset?: boolean; 145 | } 146 | >(({ className, inset, ...props }, ref) => ( 147 | 156 | )); 157 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; 158 | 159 | const DropdownMenuSeparator = React.forwardRef< 160 | React.ElementRef, 161 | React.ComponentPropsWithoutRef 162 | >(({ className, ...props }, ref) => ( 163 | 168 | )); 169 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; 170 | 171 | const DropdownMenuShortcut = ({ 172 | className, 173 | ...props 174 | }: React.HTMLAttributes) => { 175 | return ( 176 | 180 | ); 181 | }; 182 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; 183 | 184 | export { 185 | DropdownMenu, 186 | DropdownMenuTrigger, 187 | DropdownMenuContent, 188 | DropdownMenuItem, 189 | DropdownMenuCheckboxItem, 190 | DropdownMenuRadioItem, 191 | DropdownMenuLabel, 192 | DropdownMenuSeparator, 193 | DropdownMenuShortcut, 194 | DropdownMenuGroup, 195 | DropdownMenuPortal, 196 | DropdownMenuSub, 197 | DropdownMenuSubContent, 198 | DropdownMenuSubTrigger, 199 | DropdownMenuRadioGroup, 200 | }; 201 | -------------------------------------------------------------------------------- /components/ui/form.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as LabelPrimitive from "@radix-ui/react-label" 3 | import { Slot } from "@radix-ui/react-slot" 4 | import { 5 | Controller, 6 | ControllerProps, 7 | FieldPath, 8 | FieldValues, 9 | FormProvider, 10 | useFormContext, 11 | } from "react-hook-form" 12 | 13 | import { cn } from "@/lib/utils" 14 | import { Label } from "@/components/ui/label" 15 | 16 | const Form = FormProvider 17 | 18 | type FormFieldContextValue< 19 | TFieldValues extends FieldValues = FieldValues, 20 | TName extends FieldPath = FieldPath 21 | > = { 22 | name: TName 23 | } 24 | 25 | const FormFieldContext = React.createContext( 26 | {} as FormFieldContextValue 27 | ) 28 | 29 | const FormField = < 30 | TFieldValues extends FieldValues = FieldValues, 31 | TName extends FieldPath = FieldPath 32 | >({ 33 | ...props 34 | }: ControllerProps) => { 35 | return ( 36 | 37 | 38 | 39 | ) 40 | } 41 | 42 | const useFormField = () => { 43 | const fieldContext = React.useContext(FormFieldContext) 44 | const itemContext = React.useContext(FormItemContext) 45 | const { getFieldState, formState } = useFormContext() 46 | 47 | const fieldState = getFieldState(fieldContext.name, formState) 48 | 49 | if (!fieldContext) { 50 | throw new Error("useFormField should be used within ") 51 | } 52 | 53 | const { id } = itemContext 54 | 55 | return { 56 | id, 57 | name: fieldContext.name, 58 | formItemId: `${id}-form-item`, 59 | formDescriptionId: `${id}-form-item-description`, 60 | formMessageId: `${id}-form-item-message`, 61 | ...fieldState, 62 | } 63 | } 64 | 65 | type FormItemContextValue = { 66 | id: string 67 | } 68 | 69 | const FormItemContext = React.createContext( 70 | {} as FormItemContextValue 71 | ) 72 | 73 | const FormItem = React.forwardRef< 74 | HTMLDivElement, 75 | React.HTMLAttributes 76 | >(({ className, ...props }, ref) => { 77 | const id = React.useId() 78 | 79 | return ( 80 | 81 |
82 | 83 | ) 84 | }) 85 | FormItem.displayName = "FormItem" 86 | 87 | const FormLabel = React.forwardRef< 88 | React.ElementRef, 89 | React.ComponentPropsWithoutRef 90 | >(({ className, ...props }, ref) => { 91 | const { error, formItemId } = useFormField() 92 | 93 | return ( 94 |