├── .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 | 
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 |
23 |
24 |
25 |
26 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
21 |
24 |
27 |
28 |
31 |
32 |
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 |
69 | {isLoading && }
70 | {subscriptionPlan.isPro ? "Manage Subscription" : "Upgrade to Pro"}
71 |
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 |
13 |
17 |
21 |
25 |
29 |
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 |
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 |
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 | proModal.onOpen()}
41 | className="mt-2 w-full rounded-md border-2 border-primary text-[12px] font-bold uppercase tracking-wider transition-colors hover:bg-primary hover:text-white"
42 | variant="outline"
43 | >
44 | Upgrade
45 |
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 |
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 |
24 |
25 |
26 |
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 |
44 | Get Started – It's Free!
45 |
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 |
18 |
19 |
20 |
21 |
22 |
23 | Pixiemist
24 |
25 |
26 |
27 |
28 |
32 | Get Started
33 |
34 |
35 |
36 |
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 |
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 |
24 |
25 |
26 |
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 |
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 |
47 | ) : (
48 |
55 | )}
56 |
57 | Enjoy unlimited creations and unlock the full potential!
58 |
59 |
60 | Subscribe
61 |
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 |
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 |
22 |
23 |
24 | Toggle theme
25 |
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 |
100 | )
101 | })
102 | FormLabel.displayName = "FormLabel"
103 |
104 | const FormControl = React.forwardRef<
105 | React.ElementRef,
106 | React.ComponentPropsWithoutRef
107 | >(({ ...props }, ref) => {
108 | const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
109 |
110 | return (
111 |
122 | )
123 | })
124 | FormControl.displayName = "FormControl"
125 |
126 | const FormDescription = React.forwardRef<
127 | HTMLParagraphElement,
128 | React.HTMLAttributes
129 | >(({ className, ...props }, ref) => {
130 | const { formDescriptionId } = useFormField()
131 |
132 | return (
133 |
139 | )
140 | })
141 | FormDescription.displayName = "FormDescription"
142 |
143 | const FormMessage = React.forwardRef<
144 | HTMLParagraphElement,
145 | React.HTMLAttributes
146 | >(({ className, children, ...props }, ref) => {
147 | const { error, formMessageId } = useFormField()
148 | const body = error ? String(error?.message) : children
149 |
150 | if (!body) {
151 | return null
152 | }
153 |
154 | return (
155 |
161 | {body}
162 |
163 | )
164 | })
165 | FormMessage.displayName = "FormMessage"
166 |
167 | export {
168 | useFormField,
169 | Form,
170 | FormItem,
171 | FormLabel,
172 | FormControl,
173 | FormDescription,
174 | FormMessage,
175 | FormField,
176 | }
177 |
--------------------------------------------------------------------------------
/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/components/ui/progress.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as ProgressPrimitive from "@radix-ui/react-progress";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const Progress = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, value, ...props }, ref) => (
12 |
20 |
25 |
26 | ));
27 | Progress.displayName = ProgressPrimitive.Root.displayName;
28 |
29 | export { Progress };
30 |
--------------------------------------------------------------------------------
/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as SelectPrimitive from "@radix-ui/react-select";
5 | import { Check, ChevronDown } from "lucide-react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const Select = SelectPrimitive.Root;
10 |
11 | const SelectGroup = SelectPrimitive.Group;
12 |
13 | const SelectValue = SelectPrimitive.Value;
14 |
15 | const SelectTrigger = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, children, ...props }, ref) => (
19 |
27 | {children}
28 |
29 |
30 |
31 |
32 | ));
33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
34 |
35 | const SelectContent = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, children, position = "popper", ...props }, ref) => (
39 |
40 |
51 |
58 | {children}
59 |
60 |
61 |
62 | ));
63 | SelectContent.displayName = SelectPrimitive.Content.displayName;
64 |
65 | const SelectLabel = React.forwardRef<
66 | React.ElementRef,
67 | React.ComponentPropsWithoutRef
68 | >(({ className, ...props }, ref) => (
69 |
74 | ));
75 | SelectLabel.displayName = SelectPrimitive.Label.displayName;
76 |
77 | const SelectItem = React.forwardRef<
78 | React.ElementRef,
79 | React.ComponentPropsWithoutRef
80 | >(({ className, children, ...props }, ref) => (
81 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | {children}
96 |
97 | ));
98 | SelectItem.displayName = SelectPrimitive.Item.displayName;
99 |
100 | const SelectSeparator = React.forwardRef<
101 | React.ElementRef,
102 | React.ComponentPropsWithoutRef
103 | >(({ className, ...props }, ref) => (
104 |
109 | ));
110 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
111 |
112 | export {
113 | Select,
114 | SelectGroup,
115 | SelectValue,
116 | SelectTrigger,
117 | SelectContent,
118 | SelectLabel,
119 | SelectItem,
120 | SelectSeparator,
121 | };
122 |
--------------------------------------------------------------------------------
/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref
15 | ) => (
16 |
27 | )
28 | )
29 | Separator.displayName = SeparatorPrimitive.Root.displayName
30 |
31 | export { Separator }
32 |
--------------------------------------------------------------------------------
/components/ui/sheet.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as SheetPrimitive from "@radix-ui/react-dialog";
5 | import { cva, type VariantProps } from "class-variance-authority";
6 | import { X } from "lucide-react";
7 |
8 | import { cn } from "@/lib/utils";
9 |
10 | const Sheet = SheetPrimitive.Root;
11 |
12 | const SheetTrigger = SheetPrimitive.Trigger;
13 |
14 | const SheetClose = SheetPrimitive.Close;
15 |
16 | const SheetPortal = ({
17 | className,
18 | ...props
19 | }: SheetPrimitive.DialogPortalProps) => (
20 |
21 | );
22 | SheetPortal.displayName = SheetPrimitive.Portal.displayName;
23 |
24 | const SheetOverlay = React.forwardRef<
25 | React.ElementRef,
26 | React.ComponentPropsWithoutRef
27 | >(({ className, ...props }, ref) => (
28 |
36 | ));
37 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
38 |
39 | const sheetVariants = cva(
40 | "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
41 | {
42 | variants: {
43 | side: {
44 | top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
45 | bottom:
46 | "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
47 | left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
48 | right:
49 | "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
50 | },
51 | },
52 | defaultVariants: {
53 | side: "right",
54 | },
55 | },
56 | );
57 |
58 | interface SheetContentProps
59 | extends React.ComponentPropsWithoutRef,
60 | VariantProps {}
61 |
62 | const SheetContent = React.forwardRef<
63 | React.ElementRef,
64 | SheetContentProps
65 | >(({ side = "right", className, children, ...props }, ref) => (
66 |
67 |
68 |
73 | {children}
74 |
75 |
76 | Close
77 |
78 |
79 |
80 | ));
81 | SheetContent.displayName = SheetPrimitive.Content.displayName;
82 |
83 | const SheetHeader = ({
84 | className,
85 | ...props
86 | }: React.HTMLAttributes) => (
87 |
94 | );
95 | SheetHeader.displayName = "SheetHeader";
96 |
97 | const SheetFooter = ({
98 | className,
99 | ...props
100 | }: React.HTMLAttributes) => (
101 |
108 | );
109 | SheetFooter.displayName = "SheetFooter";
110 |
111 | const SheetTitle = React.forwardRef<
112 | React.ElementRef,
113 | React.ComponentPropsWithoutRef
114 | >(({ className, ...props }, ref) => (
115 |
120 | ));
121 | SheetTitle.displayName = SheetPrimitive.Title.displayName;
122 |
123 | const SheetDescription = React.forwardRef<
124 | React.ElementRef,
125 | React.ComponentPropsWithoutRef
126 | >(({ className, ...props }, ref) => (
127 |
132 | ));
133 | SheetDescription.displayName = SheetPrimitive.Description.displayName;
134 |
135 | export {
136 | Sheet,
137 | SheetTrigger,
138 | SheetClose,
139 | SheetContent,
140 | SheetHeader,
141 | SheetFooter,
142 | SheetTitle,
143 | SheetDescription,
144 | };
145 |
--------------------------------------------------------------------------------
/components/user-account-nav.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import Link from "next/link";
4 | import { signOut } from "next-auth/react";
5 |
6 | import {
7 | DropdownMenu,
8 | DropdownMenuContent,
9 | DropdownMenuItem,
10 | DropdownMenuLabel,
11 | DropdownMenuSeparator,
12 | DropdownMenuTrigger,
13 | } from "@/components/ui/dropdown-menu";
14 | import { UserAvatar } from "@/components/user-avatar";
15 | import { LogOut } from "lucide-react";
16 | import { useUserStore } from "@/store/store";
17 |
18 | export const UserAccountNav = () => {
19 | const { user, clearUser } = useUserStore((state) => ({
20 | user: state.user,
21 | clearUser: state.clearUser,
22 | }));
23 |
24 | const handleSignOut = async (e: React.MouseEvent) => {
25 | e.preventDefault();
26 |
27 | await signOut({
28 | callbackUrl: `${window.location.origin}/`,
29 | });
30 | clearUser();
31 | };
32 |
33 | return (
34 |
35 |
36 |
42 |
43 |
44 |
45 | {user.name || user.email || "MysteriousOne"}
46 |
47 |
48 |
49 | Dashboard
50 |
51 |
52 | Subscription
53 |
54 |
55 | Settings
56 |
57 |
58 |
59 |
60 | Sign Out
61 |
62 |
63 |
64 | );
65 | };
66 |
--------------------------------------------------------------------------------
/components/user-auth-form.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { zodResolver } from "@hookform/resolvers/zod";
4 | import { signIn } from "next-auth/react";
5 | import { useForm } from "react-hook-form";
6 | import * as z from "zod";
7 | import { HTMLAttributes, useState } from "react";
8 |
9 | import { cn } from "@/lib/utils";
10 | import { userAuthSchema } from "@/lib/validations/auth";
11 | import { Input } from "@/components/ui/input";
12 | import { Button } from "@/components/ui/button";
13 | import { Form, FormControl, FormField, FormItem } from "@/components/ui/form";
14 | import { Loader2Icon } from "lucide-react";
15 | import { GoogleIcon } from "@/components/custom-icons";
16 | import { toast } from "react-hot-toast";
17 |
18 | interface UserAuthFormProps extends HTMLAttributes {}
19 |
20 | type Input = z.infer;
21 |
22 | export function UserAuthForm({ className, ...props }: UserAuthFormProps) {
23 | const [isGoogleLoading, setIsGoogleLoading] = useState(false);
24 |
25 | const form = useForm ({
26 | resolver: zodResolver(userAuthSchema),
27 | defaultValues: {
28 | email: "",
29 | },
30 | });
31 |
32 | const {
33 | handleSubmit,
34 | formState: { errors, isSubmitting: isLoading },
35 | } = form;
36 |
37 | async function onSubmit(data: Input) {
38 | console.log(data);
39 |
40 | const signInResult = await signIn("email", {
41 | email: data.email.toLowerCase(),
42 | redirect: false,
43 | callbackUrl: "/dashboard",
44 | });
45 |
46 | if (!signInResult?.ok) {
47 | console.log("[EMAIL VERIFICATION FRONTEND ERROR]");
48 | return toast.error("Your sign in request failed. Please try again.");
49 | }
50 | console.log("[EMAIL VERIFICATION FRONTEND SUCCESS]");
51 | return toast.success(
52 | "Please check your email or spam for verification instructions.",
53 | );
54 | }
55 |
56 | return (
57 |
58 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | Or continue with
101 |
102 |
103 |
104 |
{
109 | setIsGoogleLoading(true);
110 | signIn("google", {
111 | callbackUrl: "/dashboard",
112 | }).then(() => setIsGoogleLoading(false));
113 | }}
114 | >
115 | {isGoogleLoading ? (
116 |
117 | ) : (
118 |
119 | )}{" "}
120 | Google
121 |
122 |
123 | );
124 | }
125 |
--------------------------------------------------------------------------------
/components/user-avatar.tsx:
--------------------------------------------------------------------------------
1 | import { User } from "@prisma/client";
2 | import { Avatar, AvatarImage } from "@/components/ui/avatar";
3 |
4 | interface UserAvatarProps extends React.HTMLProps {
5 | user: Pick;
6 | }
7 |
8 | export const UserAvatar = ({ user, className, ...props }: UserAvatarProps) => {
9 | return (
10 |
11 |
15 |
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/components/user-settings-form.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { useRouter } from "next/navigation";
5 | import { zodResolver } from "@hookform/resolvers/zod";
6 | import { User } from "@prisma/client";
7 | import { useForm } from "react-hook-form";
8 | import * as z from "zod";
9 |
10 | import { cn } from "@/lib/utils";
11 | import { userSettingSchema } from "@/lib/validations/user";
12 | import { Input } from "@/components/ui/input";
13 | import {
14 | Form,
15 | FormControl,
16 | FormField,
17 | FormItem,
18 | FormLabel,
19 | } from "@/components/ui/form";
20 | import {
21 | Card,
22 | CardContent,
23 | CardDescription,
24 | CardFooter,
25 | CardHeader,
26 | CardTitle,
27 | } from "@/components/ui/card";
28 | import toast from "react-hot-toast";
29 | import { Button } from "./ui/button";
30 | import { Loader } from "lucide-react";
31 | import axios from "axios";
32 |
33 | interface UserSettingsFormProps extends React.HTMLAttributes {
34 | user: Pick;
35 | }
36 |
37 | type Input = z.infer;
38 |
39 | export function UserSettingsForm({
40 | user,
41 | className,
42 | ...props
43 | }: UserSettingsFormProps) {
44 | const router = useRouter();
45 |
46 | const form = useForm ({
47 | resolver: zodResolver(userSettingSchema),
48 | defaultValues: {
49 | username: "",
50 | },
51 | });
52 |
53 | const { isSubmitting, errors } = form.formState;
54 |
55 | const onSubmit = async (data: Input) => {
56 | try {
57 | const dataToSend = {
58 | username: data.username,
59 | };
60 | const resp = await axios.patch(`/api/user/${user.id}`, dataToSend);
61 | console.log(resp);
62 | form.reset();
63 | } catch (error) {
64 | console.log("[Settings_FRONT_END_ERR]", error);
65 | return toast.error("Something went wrong.Please try again later");
66 | } finally {
67 | toast.success("Profile Updated Successfully");
68 |
69 | router.refresh();
70 | }
71 | };
72 |
73 | return (
74 |
125 |
126 | );
127 | }
128 |
--------------------------------------------------------------------------------
/components/user-store-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { User } from "next-auth";
3 |
4 | import { useUserStore } from "@/store/store";
5 | import { useEffect } from "react";
6 |
7 | type UserStoreProviderProps = {
8 | user: Pick;
9 | };
10 |
11 | export default function UserStoreProvider({ user }: UserStoreProviderProps) {
12 | const setUser = useUserStore((state) => state.setUser);
13 | useEffect(() => {
14 |
15 | setUser({
16 | name: user.name,
17 | email: user.email,
18 | image: user.image,
19 | });
20 | },[setUser, user.email, user.image, user.name])
21 | return <>>;
22 | }
23 |
--------------------------------------------------------------------------------
/constants.ts:
--------------------------------------------------------------------------------
1 | export const MAX_FREE_COUNT = 10;
2 |
3 | export const routes = [
4 | {
5 | label: "Dashboard",
6 | path: "/dashboard",
7 | description: "Dashboard",
8 | },
9 |
10 | {
11 | label: "Background Remove",
12 | path: "/bg-remove",
13 | description: "Remove Background from your Image",
14 | image: "/dashboard/bg-remove-dashboard.png",
15 | },
16 | {
17 | label: "Image Upscale",
18 | path: "/upscale",
19 | description: "Upscale your Image",
20 | image: "/dashboard/upscale-dashboard.png",
21 | },
22 | {
23 | label: "QR Code Generator",
24 | path: "/qrgen",
25 | description: "Generate Stylised QR Code from Link/Text",
26 | image: "/dashboard/qr-dashboard.png",
27 | },
28 | {
29 | label: "Artistic Realism",
30 | path: "/real-art",
31 | description: "Bringing Your Art to Life",
32 | image: "/dashboard/real-art-dashboard.png",
33 | },
34 | {
35 | label: "Face Swap",
36 | path: "/face-swap",
37 | description: "Your Face, Their Body ",
38 | image: "/dashboard/face-swap-dashboard.png",
39 | },
40 | {
41 | label: "Settings",
42 | path: "/settings",
43 | description: "Settings",
44 | },
45 | ];
46 |
47 | export const tools = routes.filter(
48 | (route) => route.label !== "Settings" && route.label !== "Dashboard",
49 | );
50 |
--------------------------------------------------------------------------------
/lib/api-limit.ts:
--------------------------------------------------------------------------------
1 | import { getCurrentUser } from "@/lib/session";
2 | import { MAX_FREE_COUNT } from "@/constants";
3 | import prismadb from "@/lib/db";
4 |
5 | export const checkApiLimit = async () => {
6 | const user = await getCurrentUser();
7 |
8 | if (!user) return;
9 |
10 | const count = await prismadb.creation.count({
11 | where: {
12 | userId: user.id,
13 | },
14 | });
15 |
16 | if (count && count >= MAX_FREE_COUNT) {
17 | return false;
18 | } else {
19 | return true;
20 | }
21 | };
22 |
23 | export const getCreationCount = async () => {
24 | const user = await getCurrentUser();
25 |
26 | if (!user) return 0;
27 |
28 | const count = await prismadb.creation.count({
29 | where: {
30 | userId: user.id,
31 | },
32 | });
33 |
34 | if (count) return count;
35 | else return 0;
36 | };
37 |
--------------------------------------------------------------------------------
/lib/auth.ts:
--------------------------------------------------------------------------------
1 | import { PrismaAdapter } from "@next-auth/prisma-adapter";
2 | import { NextAuthOptions } from "next-auth";
3 | import EmailProvider from "next-auth/providers/email";
4 | import GoogleProvider from "next-auth/providers/google";
5 |
6 | import prismadb from "@/lib/db";
7 | import { transporter } from "@/lib/mail";
8 | import { render } from "@react-email/render";
9 | import { NewUserEmail } from "@/components/email/new-user";
10 | import { ActivationLink } from "@/components/email/activation";
11 |
12 | export const authOptions: NextAuthOptions = {
13 | adapter: PrismaAdapter(prismadb),
14 | session: {
15 | strategy: "jwt",
16 | },
17 | pages: {
18 | signIn: "/sign-in",
19 | },
20 | secret: process.env.NEXTAUTH_SECRET!,
21 | providers: [
22 | GoogleProvider({
23 | clientId: process.env.GOOGLE_CLIENT_ID!,
24 | clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
25 | }),
26 | EmailProvider({
27 | from: process.env.EMAIL_FROM,
28 | sendVerificationRequest: async ({ identifier, url, provider }) => {
29 | const user = await prismadb.user.findUnique({
30 | where: {
31 | email: identifier,
32 | },
33 | select: {
34 | emailVerified: true,
35 | },
36 | });
37 |
38 | const html = !user?.emailVerified
39 | ? render(NewUserEmail({ url }))
40 | : render(ActivationLink({ url }));
41 |
42 | const mailOptions = {
43 | from: provider.from as string,
44 | to: identifier,
45 | subject: "Verification of Email",
46 | html,
47 | };
48 | try {
49 | await transporter.sendMail(mailOptions);
50 | console.log("Message Sent");
51 | } catch (err) {
52 | console.log("ERROR while sending");
53 | }
54 | },
55 | }),
56 | ],
57 | callbacks: {
58 | // runs after jwt callback
59 | async session({ token, session }) {
60 | if (token) {
61 | session.user.id = token.id;
62 | session.user.name = token.name;
63 | session.user.email = token.email;
64 | session.user.image = token.picture;
65 | }
66 |
67 | return session;
68 | },
69 | async jwt({ token, user }) {
70 | const dbUser = await prismadb.user.findFirst({
71 | where: {
72 | email: token.email,
73 | },
74 | });
75 |
76 | if (!dbUser) {
77 | if (user) {
78 | token.id = user?.id;
79 | }
80 | return token;
81 | }
82 |
83 | return {
84 | id: dbUser.id,
85 | name: dbUser.name,
86 | email: dbUser.email,
87 | picture: dbUser.image,
88 | };
89 | },
90 | },
91 | };
92 |
--------------------------------------------------------------------------------
/lib/cloudinary.ts:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | import { v2 as cloudinary } from "cloudinary";
4 |
5 | cloudinary.config({
6 | cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
7 | api_key: process.env.CLOUDINARY_API_KEY,
8 | api_secret: process.env.CLOUDINARY_API_SECRET,
9 | });
10 |
11 | const uploadImage = async (imageURL: string) => {
12 | const output = await cloudinary.uploader.upload(
13 | imageURL,
14 | { folder: "Images", resource_type: "image" },
15 |
16 | (error, result) => {
17 | if (error) {
18 | console.log("[CLOUDINARY_UPLOAD_IMG_ERROR]");
19 | console.log(error);
20 | } else {
21 | console.log("[CLOUDINARY_UPLOAD_IMG_SUCCESS]");
22 | }
23 | },
24 | );
25 |
26 | return output;
27 | };
28 |
29 | const uploadMultipleImages = async (imageURLs: string[]) => {
30 | const output = imageURLs.map((imageURL) => uploadImage(imageURL));
31 |
32 | try {
33 | const results = await Promise.all(output);
34 | console.log("[CLOUDINARY_UPLOAD_MULTIPLE_IMAGES_SUCCESS]");
35 |
36 | const final = results.map((result) => result.secure_url);
37 |
38 | return final;
39 | } catch (err) {
40 | console.log("[CLOUDINARY_UPLOAD_MULTIPLE_IMAGES_ERROR]");
41 | console.log(err);
42 | return [];
43 | }
44 | };
45 |
46 | export { uploadImage, uploadMultipleImages };
47 |
--------------------------------------------------------------------------------
/lib/db.ts:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from "@prisma/client";
2 |
3 | declare global {
4 | var prisma:PrismaClient | undefined;
5 | }
6 |
7 | const prismadb = globalThis.prisma || new PrismaClient();
8 |
9 | if(process.env.NODE_ENV !== "production") globalThis.prisma = prismadb;
10 |
11 | export default prismadb;
12 |
13 |
--------------------------------------------------------------------------------
/lib/mail.ts:
--------------------------------------------------------------------------------
1 | import nodemailer, { TransportOptions } from "nodemailer";
2 |
3 |
4 | const hostOptions = {
5 | host: process.env.EMAIL_SERVER_HOST,
6 | port: process.env.EMAIL_SERVER_PORT,
7 | auth: {
8 | user: process.env.EMAIL_SERVER_USER,
9 | pass: process.env.EMAIL_SERVER_PASSWORD,
10 | },
11 | };
12 |
13 | const transporter = nodemailer.createTransport(
14 | hostOptions as TransportOptions
15 | );
16 |
17 | export {transporter} ;
--------------------------------------------------------------------------------
/lib/session.ts:
--------------------------------------------------------------------------------
1 | import { authOptions } from "@/lib/auth";
2 | import { getServerSession } from "next-auth";
3 |
4 | export async function getCurrentUser() {
5 | const session = await getServerSession(authOptions);
6 |
7 | return session?.user;
8 | }
9 |
--------------------------------------------------------------------------------
/lib/stripe.ts:
--------------------------------------------------------------------------------
1 | import Stripe from "stripe";
2 |
3 | export const stripe = new Stripe(process.env.STRIPE_API_KEY!, {
4 | apiVersion: "2023-08-16",
5 | typescript: true,
6 | });
7 |
--------------------------------------------------------------------------------
/lib/subscription.ts:
--------------------------------------------------------------------------------
1 | import prismadb from "@/lib/db";
2 | import { UserSubscriptionPlan } from "@/types";
3 |
4 | const GRACE_PERIOD_MS = 86_400_000;
5 |
6 | export async function getUserSubscription(
7 | userId: string,
8 | ): Promise {
9 | const user = await prismadb.user.findUnique({
10 | where: {
11 | id: userId,
12 | },
13 | select: {
14 | stripeSubscriptionId: true,
15 | stripeCustomerId: true,
16 | stripePriceId: true,
17 | stripeCurrentPeriodEnd: true,
18 | },
19 | });
20 |
21 | if (!user) {
22 | return new Error("User not found");
23 | }
24 |
25 | const isPro =
26 | user.stripePriceId &&
27 | user.stripeCurrentPeriodEnd?.getTime()! + GRACE_PERIOD_MS > Date.now();
28 |
29 | return {
30 | ...user,
31 | isPro: !!isPro,
32 | };
33 | }
34 |
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
8 | export function formatBytes(
9 | bytes: number,
10 | decimals = 0,
11 | sizeType: "accurate" | "normal" = "normal",
12 | ) {
13 | const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
14 | const accurateSizes = ["Bytes", "KiB", "MiB", "GiB", "TiB"];
15 | if (bytes === 0) return "0 Byte";
16 | const i = Math.floor(Math.log(bytes) / Math.log(1024));
17 | return `${(bytes / Math.pow(1024, i)).toFixed(decimals)} ${
18 | sizeType === "accurate" ? accurateSizes[i] ?? "Bytest" : sizes[i] ?? "Bytes"
19 | }`;
20 | }
21 |
22 | export function formatDate(inputDate: string | number | Date) {
23 | const date = new Date(inputDate);
24 | return date.toLocaleDateString("en-US", {
25 | year: "numeric",
26 | month: "long",
27 | day: "numeric",
28 | });
29 | }
30 |
31 | export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
32 |
33 | export function absoluteUrl(path: string) {
34 | return `${process.env.NEXT_PUBLIC_APP_URL}${path}`;
35 | }
36 |
--------------------------------------------------------------------------------
/lib/validations/auth.ts:
--------------------------------------------------------------------------------
1 | import * as z from "zod"
2 |
3 | export const userAuthSchema = z.object({
4 | email: z.string().email(),
5 | })
--------------------------------------------------------------------------------
/lib/validations/dropImage.ts:
--------------------------------------------------------------------------------
1 | import * as z from "zod";
2 |
3 | export const dropImageSchema = z.object({
4 | image: z.unknown().refine((val) => {
5 | if (!Array.isArray(val)) return false;
6 | if (val.some((file) => !(file instanceof File))) return false;
7 | return true;
8 | }, "Must be an array of File"),
9 | });
10 |
--------------------------------------------------------------------------------
/lib/validations/user.ts:
--------------------------------------------------------------------------------
1 | import * as z from "zod";
2 |
3 | export const userSettingSchema = z.object({
4 | username: z.string().min(3).max(30),
5 | });
6 |
--------------------------------------------------------------------------------
/middleware.ts:
--------------------------------------------------------------------------------
1 | import { getToken } from "next-auth/jwt";
2 | import { withAuth } from "next-auth/middleware";
3 | import { NextResponse } from "next/server";
4 |
5 | export default withAuth(
6 | async function middleware(req) {
7 | if (req.nextUrl.pathname === "/") {
8 | return NextResponse.next();
9 | }
10 |
11 | const token = await getToken({ req });
12 | const isAuth = !!token;
13 |
14 | const isAuthPage = req.nextUrl.pathname.startsWith("/sign-in");
15 |
16 | if (isAuthPage) {
17 | if (isAuth) {
18 | return NextResponse.redirect(new URL("/dashboard", req.url));
19 | }
20 | return null;
21 | }
22 |
23 | if (!isAuth) {
24 | let from = req.nextUrl.pathname;
25 | if (req.nextUrl.search) {
26 | from += req.nextUrl.search;
27 | }
28 | return NextResponse.redirect(
29 | new URL(`/sign-in?from=${encodeURIComponent(from)}`, req.url),
30 | );
31 | }
32 | },
33 | {
34 | callbacks: {
35 | async authorized() {
36 | return true;
37 | },
38 | },
39 | },
40 | );
41 |
42 | export const config = {
43 | matcher: ["/((?!api|_next/static|_next/image|.*\\..*|favicon.ico).*)"],
44 | };
45 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | images: {
4 | domains: [
5 | "images.unsplash.com",
6 | "source.unsplash.com",
7 | "pbxt.replicate.delivery",
8 | "replicate.delivery",
9 | "res.cloudinary.com",
10 | ],
11 | },
12 | compiler: {
13 | styledComponents: true,
14 | },
15 | };
16 |
17 | module.exports = nextConfig;
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ai-tools",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "postinstall": "prisma generate"
11 | },
12 | "dependencies": {
13 | "@hookform/resolvers": "^3.3.1",
14 | "@next-auth/prisma-adapter": "^1.0.7",
15 | "@prisma/client": "^5.1.1",
16 | "@radix-ui/react-avatar": "^1.0.3",
17 | "@radix-ui/react-dialog": "^1.0.4",
18 | "@radix-ui/react-dropdown-menu": "^2.0.5",
19 | "@radix-ui/react-label": "^2.0.2",
20 | "@radix-ui/react-progress": "^1.0.3",
21 | "@radix-ui/react-select": "^1.2.2",
22 | "@radix-ui/react-separator": "^1.0.3",
23 | "@radix-ui/react-slot": "^1.0.2",
24 | "@react-email/components": "^0.0.7",
25 | "@react-email/render": "^0.0.7",
26 | "@types/node": "20.4.9",
27 | "@types/nodemailer": "^6.4.9",
28 | "@types/react": "18.2.20",
29 | "@types/react-dom": "18.2.7",
30 | "@uiball/loaders": "^1.3.0",
31 | "autoprefixer": "10.4.14",
32 | "axios": "^1.5.0",
33 | "class-variance-authority": "^0.7.0",
34 | "cloudinary": "^1.40.0",
35 | "clsx": "^2.0.0",
36 | "eslint": "8.46.0",
37 | "eslint-config-next": "13.4.13",
38 | "lucide-react": "^0.263.1",
39 | "next": "13.5.1",
40 | "next-auth": "^4.22.5",
41 | "next-themes": "^0.2.1",
42 | "nodemailer": "^6.9.5",
43 | "postcss": "8.4.27",
44 | "react": "18.2.0",
45 | "react-compare-slider": "^2.2.0",
46 | "react-dom": "18.2.0",
47 | "react-dropzone": "^14.2.3",
48 | "react-email": "^1.9.4",
49 | "react-hook-form": "^7.45.4",
50 | "react-hot-toast": "^2.4.1",
51 | "replicate": "^0.16.1",
52 | "stripe": "^13.5.0",
53 | "tailwind-merge": "^1.14.0",
54 | "tailwindcss": "3.3.3",
55 | "tailwindcss-animate": "^1.0.6",
56 | "typescript": "5.1.6",
57 | "typewriter-effect": "^2.21.0",
58 | "zod": "^3.22.2",
59 | "zustand": "^4.4.1"
60 | },
61 | "devDependencies": {
62 | "prettier": "^3.0.2",
63 | "prettier-plugin-tailwindcss": "^0.5.3",
64 | "prisma": "^5.1.1"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ["prettier-plugin-tailwindcss"],
3 | tailwindConfig: "./tailwind.config.js",
4 | };
5 |
--------------------------------------------------------------------------------
/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | // This is your Prisma schema file,
2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema
3 |
4 | generator client {
5 | provider = "prisma-client-js"
6 | }
7 |
8 | datasource db {
9 | provider = "mysql"
10 | url = env("DATABASE_URL")
11 | relationMode = "prisma"
12 | }
13 |
14 | model Account {
15 | id String @id @default(cuid())
16 | userId String
17 | type String
18 | provider String
19 | providerAccountId String
20 | refresh_token String? @db.Text
21 | access_token String? @db.Text
22 | expires_at Int?
23 | token_type String?
24 | scope String?
25 | id_token String? @db.Text
26 | session_state String?
27 | createdAt DateTime @default(now()) @map(name: "created_at")
28 | updatedAt DateTime @default(now()) @map(name: "updated_at")
29 |
30 | user User @relation(fields: [userId], references: [id], onDelete: Cascade)
31 |
32 | @@unique([provider, providerAccountId])
33 | @@index([userId])
34 | @@map(name: "accounts")
35 | }
36 |
37 | model Session {
38 | id String @id @default(cuid())
39 | sessionToken String @unique
40 | userId String
41 | expires DateTime
42 | user User @relation(fields: [userId], references: [id], onDelete: Cascade)
43 |
44 | @@index([userId])
45 | @@map(name: "sessions")
46 | }
47 |
48 | model User {
49 | id String @id @default(cuid())
50 | name String?
51 | email String? @unique
52 | emailVerified DateTime?
53 | image String?
54 | createdAt DateTime @default(now()) @map(name: "created_at")
55 | updatedAt DateTime @default(now()) @map(name: "updated_at")
56 |
57 | accounts Account[]
58 | sessions Session[]
59 | creations Creation[]
60 |
61 | stripeCustomerId String? @unique @map(name: "stripe_customer_id")
62 | stripeSubscriptionId String? @unique @map(name: "stripe_subscription_id")
63 | stripePriceId String? @map(name: "stripe_price_id")
64 | stripeCurrentPeriodEnd DateTime? @map(name: "stripe_current_period_end")
65 |
66 | @@map(name: "users")
67 | }
68 |
69 | model VerificationToken {
70 | identifier String
71 | token String @unique
72 | expires DateTime
73 |
74 | @@unique([identifier, token])
75 | @@map(name: "verification_tokens")
76 | }
77 |
78 | model Creation {
79 | id String @id @default(cuid())
80 | imageUrl String? @unique
81 | domain String
82 | createdAt DateTime @default(now()) @map(name: "created_at")
83 | userId String
84 |
85 | user User @relation(fields: [userId], references: [id])
86 |
87 | @@index([userId])
88 | @@map(name: "creation")
89 | }
90 |
--------------------------------------------------------------------------------
/public/avatar_fallback.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanjay-Ganapathi/Pixiemist/7dee2dbec0579e45c2d825117677a590d69419b6/public/avatar_fallback.jpg
--------------------------------------------------------------------------------
/public/dashboard/bg-remove-dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanjay-Ganapathi/Pixiemist/7dee2dbec0579e45c2d825117677a590d69419b6/public/dashboard/bg-remove-dashboard.png
--------------------------------------------------------------------------------
/public/dashboard/face-swap-dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanjay-Ganapathi/Pixiemist/7dee2dbec0579e45c2d825117677a590d69419b6/public/dashboard/face-swap-dashboard.png
--------------------------------------------------------------------------------
/public/dashboard/headshot-dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanjay-Ganapathi/Pixiemist/7dee2dbec0579e45c2d825117677a590d69419b6/public/dashboard/headshot-dashboard.png
--------------------------------------------------------------------------------
/public/dashboard/qr-dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanjay-Ganapathi/Pixiemist/7dee2dbec0579e45c2d825117677a590d69419b6/public/dashboard/qr-dashboard.png
--------------------------------------------------------------------------------
/public/dashboard/real-art-dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanjay-Ganapathi/Pixiemist/7dee2dbec0579e45c2d825117677a590d69419b6/public/dashboard/real-art-dashboard.png
--------------------------------------------------------------------------------
/public/dashboard/upscale-dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanjay-Ganapathi/Pixiemist/7dee2dbec0579e45c2d825117677a590d69419b6/public/dashboard/upscale-dashboard.png
--------------------------------------------------------------------------------
/public/headshot-res.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanjay-Ganapathi/Pixiemist/7dee2dbec0579e45c2d825117677a590d69419b6/public/headshot-res.png
--------------------------------------------------------------------------------
/public/headshot-src.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanjay-Ganapathi/Pixiemist/7dee2dbec0579e45c2d825117677a590d69419b6/public/headshot-src.jpg
--------------------------------------------------------------------------------
/public/logo3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanjay-Ganapathi/Pixiemist/7dee2dbec0579e45c2d825117677a590d69419b6/public/logo3.png
--------------------------------------------------------------------------------
/public/pro-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanjay-Ganapathi/Pixiemist/7dee2dbec0579e45c2d825117677a590d69419b6/public/pro-dark.png
--------------------------------------------------------------------------------
/public/pro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanjay-Ganapathi/Pixiemist/7dee2dbec0579e45c2d825117677a590d69419b6/public/pro.png
--------------------------------------------------------------------------------
/public/qrlanding.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanjay-Ganapathi/Pixiemist/7dee2dbec0579e45c2d825117677a590d69419b6/public/qrlanding.png
--------------------------------------------------------------------------------
/public/real-art-res.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanjay-Ganapathi/Pixiemist/7dee2dbec0579e45c2d825117677a590d69419b6/public/real-art-res.png
--------------------------------------------------------------------------------
/public/real-art-src.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanjay-Ganapathi/Pixiemist/7dee2dbec0579e45c2d825117677a590d69419b6/public/real-art-src.jpg
--------------------------------------------------------------------------------
/public/removebg-res.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanjay-Ganapathi/Pixiemist/7dee2dbec0579e45c2d825117677a590d69419b6/public/removebg-res.png
--------------------------------------------------------------------------------
/public/removebg-src.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanjay-Ganapathi/Pixiemist/7dee2dbec0579e45c2d825117677a590d69419b6/public/removebg-src.png
--------------------------------------------------------------------------------
/public/test.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanjay-Ganapathi/Pixiemist/7dee2dbec0579e45c2d825117677a590d69419b6/public/test.gif
--------------------------------------------------------------------------------
/public/thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanjay-Ganapathi/Pixiemist/7dee2dbec0579e45c2d825117677a590d69419b6/public/thumbnail.png
--------------------------------------------------------------------------------
/public/twist_line_edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sanjay-Ganapathi/Pixiemist/7dee2dbec0579e45c2d825117677a590d69419b6/public/twist_line_edit.png
--------------------------------------------------------------------------------
/store/promodal-store.ts:
--------------------------------------------------------------------------------
1 | import { create } from "zustand";
2 |
3 | interface usePromodalStore {
4 | isOpen: boolean;
5 | onOpen: () => void;
6 | onClose: () => void;
7 | }
8 |
9 | export const usePromodal = create((set) => ({
10 | isOpen: false,
11 | onOpen: () => set({ isOpen: true }),
12 | onClose: () => set({ isOpen: false }),
13 | }));
14 |
--------------------------------------------------------------------------------
/store/store.ts:
--------------------------------------------------------------------------------
1 | import { create } from "zustand";
2 |
3 | import { User } from "next-auth";
4 |
5 | type UserStore = Pick;
6 |
7 | interface useUserStore {
8 | user: UserStore;
9 | setUser: (newUser: UserStore) => void;
10 | clearUser: () => void;
11 | }
12 | const initialUser: UserStore = {};
13 |
14 | export const useUserStore = create((set) => ({
15 | user: initialUser,
16 | setUser: (newUser) => set({ user: newUser }),
17 | clearUser: () => set({ user: initialUser }),
18 | }));
19 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | darkMode: ["class"],
4 | content: [
5 | "./pages/**/*.{ts,tsx}",
6 | "./components/**/*.{ts,tsx}",
7 | "./app/**/*.{ts,tsx}",
8 | "./src/**/*.{ts,tsx}",
9 | ],
10 | theme: {
11 | container: {
12 | center: true,
13 | padding: "2rem",
14 | screens: {
15 | "2xl": "1400px",
16 | },
17 | },
18 | extend: {
19 | colors: {
20 | border: "hsl(var(--border))",
21 | input: "hsl(var(--input))",
22 | ring: "hsl(var(--ring))",
23 | background: "hsl(var(--background))",
24 | foreground: "hsl(var(--foreground))",
25 | primary: {
26 | DEFAULT: "hsl(var(--primary))",
27 | foreground: "hsl(var(--primary-foreground))",
28 | },
29 | secondary: {
30 | DEFAULT: "hsl(var(--secondary))",
31 | foreground: "hsl(var(--secondary-foreground))",
32 | },
33 | destructive: {
34 | DEFAULT: "hsl(var(--destructive))",
35 | foreground: "hsl(var(--destructive-foreground))",
36 | },
37 | muted: {
38 | DEFAULT: "hsl(var(--muted))",
39 | foreground: "hsl(var(--muted-foreground))",
40 | },
41 | accent: {
42 | DEFAULT: "hsl(var(--accent))",
43 | foreground: "hsl(var(--accent-foreground))",
44 | },
45 | popover: {
46 | DEFAULT: "hsl(var(--popover))",
47 | foreground: "hsl(var(--popover-foreground))",
48 | },
49 | card: {
50 | DEFAULT: "hsl(var(--card))",
51 | foreground: "hsl(var(--card-foreground))",
52 | },
53 | },
54 | borderRadius: {
55 | lg: "var(--radius)",
56 | md: "calc(var(--radius) - 2px)",
57 | sm: "calc(var(--radius) - 4px)",
58 | },
59 | keyframes: {
60 | "accordion-down": {
61 | from: { height: 0 },
62 | to: { height: "var(--radix-accordion-content-height)" },
63 | },
64 | "accordion-up": {
65 | from: { height: "var(--radix-accordion-content-height)" },
66 | to: { height: 0 },
67 | },
68 | "fade-in": {
69 | from: {
70 | opacity: "0",
71 | },
72 | to: {
73 | opacity: "1",
74 | },
75 | },
76 | "fade-in-up": {
77 | "0%": {
78 | opacity: "0",
79 | transform: "translateY(50px)",
80 | },
81 | "100%": {
82 | opacity: "1",
83 | transform: "translateY(0)",
84 | },
85 | },
86 | },
87 | animation: {
88 | "accordion-down": "accordion-down 0.2s ease-out",
89 | "accordion-up": "accordion-up 0.2s ease-out",
90 | "fade-in-up": "fade-in-up 0.5s ease-in-out",
91 | },
92 | },
93 | },
94 | plugins: [require("tailwindcss-animate")],
95 | };
96 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | const config: Config = {
4 | content: [
5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}",
6 | "./components/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./app/**/*.{js,ts,jsx,tsx,mdx}",
8 | ],
9 | theme: {
10 | extend: {
11 | backgroundImage: {
12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
13 | "gradient-conic":
14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
15 | },
16 | },
17 | },
18 | plugins: [],
19 | };
20 | export default config;
21 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "bundler",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "plugins": [
18 | {
19 | "name": "next"
20 |
21 | }
22 | ],
23 | "paths": {
24 | "@/*": ["./*"]
25 | }
26 | },
27 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
28 | "exclude": ["node_modules"]
29 | }
30 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | import { User } from "@prisma/client";
2 |
3 | export interface FileWithPreview extends File {
4 | preview: string;
5 | }
6 |
7 | export interface OutputImgSlider {
8 | previewURL: string;
9 | outputURL: string;
10 | }
11 |
12 | export type UserSubscriptionPlan = Pick<
13 | User,
14 | | "stripeCustomerId"
15 | | "stripeSubscriptionId"
16 | | "stripePriceId"
17 | | "stripeCurrentPeriodEnd"
18 | > & {
19 | isPro: boolean;
20 | };
21 |
--------------------------------------------------------------------------------
/types/next-auth.d.ts:
--------------------------------------------------------------------------------
1 | import { User } from "next-auth"
2 | import { JWT } from "next-auth/jwt"
3 |
4 | type UserId = string
5 |
6 | declare module "next-auth/jwt" {
7 | interface JWT {
8 | id: UserId
9 | }
10 | }
11 |
12 | declare module "next-auth" {
13 | interface Session {
14 | user: User & {
15 | id: UserId
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------