├── public ├── coin.png ├── logo.png ├── favicon.ico ├── thumbnail.png ├── 100_credits.png ├── 10_credits.png └── 50_credits.png ├── postcss.config.cjs ├── prettier.config.cjs ├── src ├── aws-config.mjs ├── pages │ ├── sign-in │ │ └── [[...index]].tsx │ ├── sign-up │ │ └── [[...index]].tsx │ ├── api │ │ ├── trpc │ │ │ └── [trpc].ts │ │ └── stripe-webhook.ts │ ├── _app.tsx │ ├── cookies.tsx │ ├── buy.tsx │ ├── index.tsx │ ├── history.tsx │ ├── terms-of-service.tsx │ ├── generate.tsx │ └── privacy-policy.tsx ├── middleware.ts ├── server │ ├── db.ts │ └── api │ │ ├── routers │ │ ├── suggestedPrompts.ts │ │ ├── stripeUser.ts │ │ ├── checkout.ts │ │ └── images.ts │ │ ├── root.ts │ │ └── trpc.ts ├── styles │ └── globals.css ├── components │ ├── footer.tsx │ ├── loading.tsx │ └── navbar.tsx ├── utils │ └── api.ts └── env.mjs ├── tailwind.config.ts ├── .gitignore ├── next.config.mjs ├── tsconfig.json ├── README.md ├── .eslintrc.cjs ├── prisma └── schema.prisma └── package.json /public/coin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Danielpark1239/Imagen/HEAD/public/coin.png -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Danielpark1239/Imagen/HEAD/public/logo.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Danielpark1239/Imagen/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Danielpark1239/Imagen/HEAD/public/thumbnail.png -------------------------------------------------------------------------------- /public/100_credits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Danielpark1239/Imagen/HEAD/public/100_credits.png -------------------------------------------------------------------------------- /public/10_credits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Danielpark1239/Imagen/HEAD/public/10_credits.png -------------------------------------------------------------------------------- /public/50_credits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Danielpark1239/Imagen/HEAD/public/50_credits.png -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | 8 | module.exports = config; 9 | -------------------------------------------------------------------------------- /prettier.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import("prettier").Config} */ 2 | const config = { 3 | plugins: [require.resolve("prettier-plugin-tailwindcss")], 4 | semi: false, 5 | }; 6 | 7 | module.exports = config; 8 | -------------------------------------------------------------------------------- /src/aws-config.mjs: -------------------------------------------------------------------------------- 1 | import AWS from "aws-sdk" 2 | 3 | AWS.config.update({ 4 | accessKeyId: process.env.AWS_ACCESS_KEY_ID, 5 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, 6 | }) 7 | 8 | export const s3 = new AWS.S3() 9 | -------------------------------------------------------------------------------- /src/pages/sign-in/[[...index]].tsx: -------------------------------------------------------------------------------- 1 | import { SignIn } from "@clerk/nextjs" 2 | 3 | export default function Page() { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /src/pages/sign-up/[[...index]].tsx: -------------------------------------------------------------------------------- 1 | import { SignUp } from "@clerk/nextjs" 2 | 3 | export default function Page() { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { authMiddleware } from "@clerk/nextjs"; 2 | 3 | export default authMiddleware({ 4 | publicRoutes: ["/", "/(api|trpc)(.*)"] 5 | }); 6 | 7 | export const config = { 8 | matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"], 9 | }; -------------------------------------------------------------------------------- /src/server/db.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | import { env } from "~/env.mjs"; 3 | 4 | const globalForPrisma = globalThis as unknown as { 5 | prisma: PrismaClient | undefined; 6 | }; 7 | 8 | export const prisma = 9 | globalForPrisma.prisma ?? 10 | new PrismaClient({ 11 | log: 12 | env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"], 13 | }); 14 | 15 | if (env.NODE_ENV !== "production") globalForPrisma.prisma = prisma; 16 | -------------------------------------------------------------------------------- /src/server/api/routers/suggestedPrompts.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createTRPCRouter, 3 | publicProcedure, 4 | } from "~/server/api/trpc" 5 | 6 | export const suggestedPromptsRouter = createTRPCRouter({ 7 | getRandom: publicProcedure.query(async ({ ctx }) => { 8 | const promptCount = await ctx.prisma.suggestedPrompt.count() - 1 9 | const randomPrompt = await ctx.prisma.suggestedPrompt.findMany({ 10 | take: 1, 11 | skip: Math.floor(Math.random() * promptCount) 12 | }) 13 | return randomPrompt[0] 14 | }), 15 | }) 16 | -------------------------------------------------------------------------------- /src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | @apply text-slate-100 bg-slate-200; 7 | } 8 | 9 | .shadow-top { 10 | --tw-shadow: 0 -10px 15px 3px rgb(0 0 0 / 0.1), 0 -4px 6px 4px rgb(0 0 0 / 0.1); 11 | --tw-shadow-colored: 0 -10px 15px 3px var(--tw-shadow-color), 0 -4px 6px 4px var(--tw-shadow-color); 12 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 13 | } 14 | 15 | textarea:focus, input:focus{ 16 | @apply outline-none 17 | } -------------------------------------------------------------------------------- /src/pages/api/trpc/[trpc].ts: -------------------------------------------------------------------------------- 1 | import { appRouter } from "~/server/api/root" 2 | import { createTRPCContext } from "~/server/api/trpc" 3 | import { createNextApiHandler } from "@trpc/server/adapters/next" 4 | import { env } from "~/env.mjs" 5 | 6 | // export API handler 7 | export default createNextApiHandler({ 8 | router: appRouter, 9 | createContext: createTRPCContext, 10 | onError: 11 | env.NODE_ENV === "development" 12 | ? ({ path, error }) => { 13 | console.error( 14 | `❌ tRPC failed on ${path ?? ""}: ${error.message}`, 15 | ); 16 | } 17 | : undefined, 18 | }) -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import { type Config } from "tailwindcss"; 2 | 3 | export default { 4 | content: ["./src/**/*.{js,ts,jsx,tsx}"], 5 | theme: { 6 | extend: {}, 7 | screens: { 8 | 'xs': '350px', 9 | 'xss': '390px', 10 | 's': '430px', 11 | 'ss': '500px', 12 | sm: '640px', 13 | md: '768px', 14 | ml: '900px', 15 | lg: '1024px', 16 | xl: '1280px', 17 | '2xl': '1560px', 18 | }, 19 | borderWidth: { 20 | '0': '0', 21 | '1': '1px', 22 | '2': '2px', 23 | '3': '3px', 24 | '4': '4px', 25 | }, 26 | }, 27 | plugins: [], 28 | } satisfies Config; 29 | -------------------------------------------------------------------------------- /.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 | # database 12 | /prisma/db.sqlite 13 | /prisma/db.sqlite-journal 14 | 15 | # next.js 16 | /.next/ 17 | /out/ 18 | next-env.d.ts 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # local env files 34 | # do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables 35 | .env 36 | .env*.local 37 | 38 | # vercel 39 | .vercel 40 | 41 | # typescript 42 | *.tsbuildinfo 43 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { type AppType } from "next/app"; 2 | import { api } from "~/utils/api"; 3 | import "~/styles/globals.css"; 4 | import { ClerkProvider } from "@clerk/nextjs"; 5 | import { Toaster } from "react-hot-toast" 6 | import Head from "next/head" 7 | 8 | const MyApp: AppType = ({ Component, pageProps }) => { 9 | return ( 10 | 11 | 12 | Imagen 13 | 17 | 18 | 19 | 20 | 21 | 22 | ) 23 | } 24 | 25 | export default api.withTRPC(MyApp); 26 | -------------------------------------------------------------------------------- /src/server/api/root.ts: -------------------------------------------------------------------------------- 1 | import { imagesRouter } from "~/server/api/routers/images" 2 | import { suggestedPromptsRouter } from "~/server/api/routers/suggestedPrompts" 3 | import { checkoutRouter } from "~/server/api/routers/checkout"; 4 | import { stripeUserRouter } from "~/server/api/routers/stripeUser"; 5 | import { createTRPCRouter } from "~/server/api/trpc" 6 | 7 | /** 8 | * This is the primary router for your server. 9 | * 10 | * All routers added in /api/routers should be manually added here. 11 | */ 12 | export const appRouter = createTRPCRouter({ 13 | images: imagesRouter, 14 | suggestedPrompts: suggestedPromptsRouter, 15 | checkout: checkoutRouter, 16 | stripeUser: stripeUserRouter 17 | }); 18 | 19 | // export type definition of API 20 | export type AppRouter = typeof appRouter; 21 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful 3 | * for Docker builds. 4 | */ 5 | await import("./src/env.mjs"); 6 | 7 | /** @type {import("next").NextConfig} */ 8 | const config = { 9 | reactStrictMode: true, 10 | images: { 11 | domains: [ 12 | "images.clerk.dev", 13 | "oaidalleapiprodscus.blob.core.windows.net", 14 | "imagen-images.s3.us-east-1.amazonaws.com", 15 | ], 16 | }, 17 | 18 | /** 19 | * If you have `experimental: { appDir: true }` set, then you must comment the below `i18n` config 20 | * out. 21 | * 22 | * @see https://github.com/vercel/next.js/issues/41980 23 | */ 24 | i18n: { 25 | locales: ["en"], 26 | defaultLocale: "en", 27 | }, 28 | } 29 | export default config; 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "checkJs": true, 7 | "skipLibCheck": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve", 17 | "incremental": true, 18 | "noUncheckedIndexedAccess": true, 19 | "baseUrl": ".", 20 | "paths": { 21 | "~/*": ["./src/*"] 22 | } 23 | }, 24 | "include": [ 25 | ".eslintrc.cjs", 26 | "next-env.d.ts", 27 | "**/*.ts", 28 | "**/*.tsx", 29 | "**/*.cjs", 30 | "**/*.mjs" 31 | ], 32 | "exclude": ["node_modules"] 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Imagen 2 | 3 | Imagen is a SaaS project that allows users to create AI-generated images through OpenAI’s DALLE model. 4 | 5 | It's currently hosted at [ima-gen.vercel.app](https://ima-gen.vercel.app), and is deployed using Vercel, AWS S3, Planetscale, Stripe, and Clerk. 6 | 7 | This is a [T3 Stack](https://create.t3.gg/) project bootstrapped with `create-t3-app`. 8 | 9 | 10 | # Want to run it yourself? 11 | (assuming you have the env vars) 12 | ``` 13 | yarn 14 | yarn run dev 15 | yarn run stripe:listen 16 | ``` 17 | 18 | # Screenshots 19 | ![imagen-home](https://github.com/Danielpark1239/Imagen/assets/90424009/44b453da-3b56-4e7c-bda0-3cc958a71a04) 20 | ![imagen-home2](https://github.com/Danielpark1239/Imagen/assets/90424009/b77cf50b-1078-4a93-8cbe-9e25f458275c) 21 | ![imagen-generate](https://github.com/Danielpark1239/Imagen/assets/90424009/530903e9-a9f1-4db9-8f32-080a500dc901) 22 | ![imagen-history](https://github.com/Danielpark1239/Imagen/assets/90424009/4ecb0d65-4a88-42a9-8185-bc3808271b5a) 23 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const path = require("path"); 3 | 4 | /** @type {import("eslint").Linter.Config} */ 5 | const config = { 6 | overrides: [ 7 | { 8 | extends: [ 9 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 10 | ], 11 | files: ["*.ts", "*.tsx"], 12 | parserOptions: { 13 | project: path.join(__dirname, "tsconfig.json"), 14 | }, 15 | }, 16 | ], 17 | parser: "@typescript-eslint/parser", 18 | parserOptions: { 19 | project: path.join(__dirname, "tsconfig.json"), 20 | }, 21 | plugins: ["@typescript-eslint"], 22 | extends: ["next/core-web-vitals", "plugin:@typescript-eslint/recommended"], 23 | rules: { 24 | "@typescript-eslint/consistent-type-imports": [ 25 | "warn", 26 | { 27 | prefer: "type-imports", 28 | fixStyle: "inline-type-imports", 29 | }, 30 | ], 31 | "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }], 32 | "react/no-unescaped-entities": "off", 33 | "@typescript-eslint/no-misused-promises": [ 34 | "error", 35 | { "checksVoidReturn": false } 36 | ], 37 | }, 38 | }; 39 | 40 | module.exports = config; 41 | -------------------------------------------------------------------------------- /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 | previewFeatures = ["jsonProtocol"] 7 | } 8 | 9 | datasource db { 10 | provider = "mysql" 11 | url = env("DATABASE_URL") 12 | relationMode = "prisma" 13 | } 14 | 15 | model Image { 16 | id String @id @default(cuid()) 17 | createdAt DateTime @default(now()) 18 | prompt String @db.Text 19 | authorId String 20 | url String @db.Text 21 | hidden Boolean @default(false) 22 | @@index([authorId, createdAt(sort: Desc)]) 23 | } 24 | 25 | model SuggestedPrompt { 26 | id String @id @default(cuid()) 27 | text String @db.Text 28 | } 29 | 30 | model StripeUser { 31 | id String @id @default(cuid()) 32 | clerkID String @unique 33 | credits Int 34 | } 35 | 36 | model StripeEvent { 37 | id String @id @unique 38 | api_version String? 39 | data Json 40 | request Json? 41 | type String 42 | object String 43 | account String? 44 | created DateTime 45 | livemode Boolean 46 | pending_webhooks Int 47 | } -------------------------------------------------------------------------------- /src/server/api/routers/stripeUser.ts: -------------------------------------------------------------------------------- 1 | import { TRPCError } from "@trpc/server"; 2 | import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc" 3 | 4 | export const stripeUserRouter = createTRPCRouter({ 5 | getCredits: protectedProcedure.query(async ({ ctx }) => { 6 | const user = await ctx.prisma.stripeUser.findFirst({ 7 | where: { 8 | clerkID: ctx.userId 9 | }, 10 | }) 11 | // If user doesn't exist, create one with 3 credits 12 | if (!user) { 13 | await ctx.prisma.stripeUser.create({ 14 | data: { 15 | clerkID: ctx.userId, 16 | credits: 3 17 | } 18 | }) 19 | return 1 20 | } 21 | return user.credits 22 | }), 23 | 24 | decrementCredits: protectedProcedure.mutation(async ({ ctx }) => { 25 | const user = await ctx.prisma.stripeUser.findFirst({ 26 | where: { 27 | clerkID: ctx.userId 28 | } 29 | }) 30 | if (!user || !user.credits) { 31 | throw new TRPCError({ 32 | code: "NOT_FOUND", 33 | message: "Stripe user not found" 34 | }) 35 | } 36 | if (user.credits <= 0) { 37 | throw new TRPCError({ 38 | code: "BAD_REQUEST", 39 | message: "User has no credits" 40 | }) 41 | } 42 | await ctx.prisma.stripeUser.update({ 43 | where: { 44 | clerkID: ctx.userId 45 | }, 46 | data: { 47 | credits: { 48 | decrement: 1 49 | } 50 | } 51 | }) 52 | }) 53 | }) -------------------------------------------------------------------------------- /src/components/footer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Link from "next/link" 3 | 4 | const Footer: React.FC = () => { 5 | return ( 6 |
7 |
8 | 12 | Imagen by Daniel Park 13 | 14 |
15 |
16 | 20 | Terms of Service 21 | 22 | 26 | Privacy Policy 27 | 28 | 32 | Cookie Policy 33 | 34 |
35 |
36 | ) 37 | } 38 | 39 | export default Footer 40 | -------------------------------------------------------------------------------- /src/components/loading.tsx: -------------------------------------------------------------------------------- 1 | export const LoadingSpinner = (props: { size?: number }) => { 2 | return ( 3 |
4 | 22 | Loading... 23 |
24 | ); 25 | }; 26 | 27 | export const LoadingPage = () => { 28 | return ( 29 |
30 | 31 |
32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ai-image-generator", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "next build", 7 | "dev": "next dev", 8 | "postinstall": "prisma generate", 9 | "lint": "next lint", 10 | "start": "next start", 11 | "stripe:listen": "stripe listen --forward-to localhost:3000/api/stripe-webhook --latest" 12 | }, 13 | "dependencies": { 14 | "@clerk/nextjs": "^4.19.0", 15 | "@headlessui/react": "^1.7.15", 16 | "@heroicons/react": "^2.0.18", 17 | "@prisma/client": "^4.14.0", 18 | "@stripe/stripe-js": "^1.54.0", 19 | "@t3-oss/env-nextjs": "^0.3.1", 20 | "@tanstack/react-query": "^4.29.7", 21 | "@trpc/client": "^10.26.0", 22 | "@trpc/next": "^10.26.0", 23 | "@trpc/react-query": "^10.26.0", 24 | "@trpc/server": "^10.26.0", 25 | "aws-sdk": "^2.1392.0", 26 | "axios": "^1.4.0", 27 | "dayjs": "^1.11.8", 28 | "micro": "^10.0.1", 29 | "next": "^13.4.5", 30 | "react": "18.2.0", 31 | "react-dom": "18.2.0", 32 | "react-hot-toast": "^2.4.1", 33 | "stripe": "^12.9.0", 34 | "superjson": "1.12.2", 35 | "zod": "^3.21.4" 36 | }, 37 | "devDependencies": { 38 | "@types/eslint": "^8.37.0", 39 | "@types/node": "^18.16.0", 40 | "@types/prettier": "^2.7.2", 41 | "@types/react": "^18.2.6", 42 | "@types/react-dom": "^18.2.4", 43 | "@typescript-eslint/eslint-plugin": "^5.59.6", 44 | "@typescript-eslint/parser": "^5.59.6", 45 | "autoprefixer": "^10.4.14", 46 | "eslint": "^8.40.0", 47 | "eslint-config-next": "^13.4.2", 48 | "postcss": "^8.4.21", 49 | "prettier": "^2.8.8", 50 | "prettier-plugin-tailwindcss": "^0.2.8", 51 | "prisma": "^4.14.0", 52 | "tailwindcss": "^3.3.0", 53 | "typescript": "^5.0.4" 54 | }, 55 | "ct3aMetadata": { 56 | "initVersion": "7.13.1" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/pages/api/stripe-webhook.ts: -------------------------------------------------------------------------------- 1 | import type { NextApiRequest, NextApiResponse } from "next" 2 | import Stripe from "stripe" 3 | import { buffer } from "micro" 4 | import { env } from "~/env.mjs" 5 | import { prisma } from "~/server/db" 6 | 7 | const stripe = new Stripe(env.STRIPE_SECRET_KEY, { 8 | apiVersion: "2022-11-15", 9 | }); 10 | 11 | export const config = { 12 | api: { 13 | bodyParser: false, 14 | }, 15 | }; 16 | 17 | const webhook = async (req: NextApiRequest, res: NextApiResponse) => { 18 | if (!process.env.STRIPE_WEBHOOK_SECRET) { 19 | throw new Error("Missing Stripe Web Hook Secret") 20 | } 21 | if (req.method === "POST") { 22 | const buf = await buffer(req); 23 | const sig = req.headers["stripe-signature"] as string; 24 | 25 | let event; 26 | 27 | try { 28 | event = stripe.webhooks.constructEvent( 29 | buf, 30 | sig, 31 | process.env.STRIPE_WEBHOOK_SECRET 32 | ); 33 | } catch (err) { 34 | let message = "Unknown Error"; 35 | if (err instanceof Error) message = err.message; 36 | res.status(400).send(`Webhook Error: ${message}`); 37 | return; 38 | } 39 | 40 | switch (event.type) { 41 | case "checkout.session.completed": 42 | const completedEvent = event.data.object as { 43 | id: string; 44 | metadata: { 45 | userId: string; 46 | credits: number; 47 | }; 48 | }; 49 | 50 | await prisma.stripeUser.update({ 51 | where: { 52 | clerkID: completedEvent.metadata.userId, 53 | }, 54 | data: { 55 | credits: { 56 | increment: Number(completedEvent.metadata.credits), 57 | }, 58 | }, 59 | }) 60 | break 61 | default: 62 | console.log(`Unhandled event type ${event.type}`); 63 | } 64 | 65 | res.json({ received: true }); 66 | } else { 67 | res.setHeader("Allow", "POST"); 68 | res.status(405).end("Method Not Allowed"); 69 | } 70 | }; 71 | 72 | export default webhook; -------------------------------------------------------------------------------- /src/utils/api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the client-side entrypoint for your tRPC API. It is used to create the `api` object which 3 | * contains the Next.js App-wrapper, as well as your type-safe React Query hooks. 4 | * 5 | * We also create a few inference helpers for input and output types. 6 | */ 7 | import { httpBatchLink, loggerLink } from "@trpc/client"; 8 | import { createTRPCNext } from "@trpc/next"; 9 | import { type inferRouterInputs, type inferRouterOutputs } from "@trpc/server"; 10 | import superjson from "superjson"; 11 | import { type AppRouter } from "~/server/api/root"; 12 | 13 | const getBaseUrl = () => { 14 | if (typeof window !== "undefined") return ""; // browser should use relative url 15 | if (process.env.HOST) return `https://${process.env.HOST}`; // SSR should use vercel url 16 | return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost 17 | }; 18 | 19 | /** A set of type-safe react-query hooks for your tRPC API. */ 20 | export const api = createTRPCNext({ 21 | config() { 22 | return { 23 | /** 24 | * Transformer used for data de-serialization from the server. 25 | * 26 | * @see https://trpc.io/docs/data-transformers 27 | */ 28 | transformer: superjson, 29 | 30 | /** 31 | * Links used to determine request flow from client to server. 32 | * 33 | * @see https://trpc.io/docs/links 34 | */ 35 | links: [ 36 | loggerLink({ 37 | enabled: (opts) => 38 | process.env.NODE_ENV === "development" || 39 | (opts.direction === "down" && opts.result instanceof Error), 40 | }), 41 | httpBatchLink({ 42 | url: `${getBaseUrl()}/api/trpc`, 43 | }), 44 | ], 45 | }; 46 | }, 47 | /** 48 | * Whether tRPC should await queries when server rendering pages. 49 | * 50 | * @see https://trpc.io/docs/nextjs#ssr-boolean-default-false 51 | */ 52 | ssr: false, 53 | }); 54 | 55 | /** 56 | * Inference helper for inputs. 57 | * 58 | * @example type HelloInput = RouterInputs['example']['hello'] 59 | */ 60 | export type RouterInputs = inferRouterInputs; 61 | 62 | /** 63 | * Inference helper for outputs. 64 | * 65 | * @example type HelloOutput = RouterOutputs['example']['hello'] 66 | */ 67 | export type RouterOutputs = inferRouterOutputs; 68 | -------------------------------------------------------------------------------- /src/server/api/routers/checkout.ts: -------------------------------------------------------------------------------- 1 | import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc" 2 | import Stripe from "stripe"; 3 | import { TRPCError } from "@trpc/server"; 4 | 5 | if (!process.env.STRIPE_SECRET_KEY) { 6 | throw new Error("Missing Stripe Secret Key") 7 | } 8 | const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { 9 | apiVersion: "2022-11-15", 10 | }); 11 | 12 | export const checkoutRouter = createTRPCRouter({ 13 | createCheckout10: protectedProcedure.mutation(async ({ ctx }) => { 14 | if (!process.env.HOST) { 15 | throw new Error("Missing Host") 16 | } 17 | const checkoutSession = await stripe.checkout.sessions.create({ 18 | payment_method_types: ["card", "us_bank_account"], 19 | metadata: { 20 | userId: ctx.userId, 21 | credits: 10, 22 | }, 23 | line_items: [{ price: process.env.STRIPE_10CREDIT_PRICE, quantity: 1 }], 24 | mode: "payment", 25 | success_url: `${process.env.HOST}/`, 26 | cancel_url: `${process.env.HOST}/buy`, 27 | }) 28 | if (!checkoutSession) { 29 | throw new TRPCError({ 30 | code: "INTERNAL_SERVER_ERROR", 31 | 'message': 'Could not create checkout session' 32 | }) 33 | } 34 | return checkoutSession.url 35 | }), 36 | createCheckout50: protectedProcedure.mutation(async ({ ctx }) => { 37 | if (!process.env.HOST) { 38 | throw new Error("Missing Host") 39 | } 40 | const checkoutSession = await stripe.checkout.sessions.create({ 41 | payment_method_types: ["card", "us_bank_account"], 42 | metadata: { 43 | userId: ctx.userId, 44 | credits: 50, 45 | }, 46 | line_items: [{ price: process.env.STRIPE_50CREDIT_PRICE, quantity: 1 }], 47 | mode: "payment", 48 | success_url: `${process.env.HOST}/`, 49 | cancel_url: `${process.env.HOST}/buy`, 50 | }) 51 | if (!checkoutSession) { 52 | throw new TRPCError({ 53 | code: "INTERNAL_SERVER_ERROR", 54 | 'message': 'Could not create checkout session' 55 | }) 56 | } 57 | return checkoutSession.url 58 | }), 59 | createCheckout100: protectedProcedure.mutation(async ({ ctx }) => { 60 | if (!process.env.HOST) { 61 | throw new Error("Missing Host") 62 | } 63 | const checkoutSession = await stripe.checkout.sessions.create({ 64 | payment_method_types: ["card", "us_bank_account"], 65 | metadata: { 66 | userId: ctx.userId, 67 | credits: 100, 68 | }, 69 | line_items: [{ price: process.env.STRIPE_100CREDIT_PRICE, quantity: 1 }], 70 | mode: "payment", 71 | success_url: `${process.env.HOST}/`, 72 | cancel_url: `${process.env.HOST}/buy`, 73 | }) 74 | if (!checkoutSession) { 75 | throw new TRPCError({ 76 | code: "INTERNAL_SERVER_ERROR", 77 | 'message': 'Could not create checkout session' 78 | }) 79 | } 80 | return checkoutSession.url 81 | }), 82 | }); -------------------------------------------------------------------------------- /src/env.mjs: -------------------------------------------------------------------------------- 1 | import { createEnv } from "@t3-oss/env-nextjs"; 2 | import { z } from "zod"; 3 | 4 | export const env = createEnv({ 5 | /** 6 | * Specify your server-side environment variables schema here. This way you can ensure the app 7 | * isn't built with invalid env vars. 8 | */ 9 | server: { 10 | DATABASE_URL: z.string().url(), 11 | NODE_ENV: z.enum(["development", "test", "production"]), 12 | // Add `.min(1) on ID and SECRET if you want to make sure they're not empty 13 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1), 14 | CLERK_SECRET_KEY: z.string().min(1), 15 | NEXT_PUBLIC_CLERK_SIGN_IN_URL: z.string().min(1), 16 | NEXT_PUBLIC_CLERK_SIGN_UP_URL: z.string().min(1), 17 | NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL: z.string().min(1), 18 | NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL: z.string().min(1), 19 | OPENAI_API_KEY: z.string().min(1), 20 | AWS_ACCESS_KEY_ID: z.string().min(1), 21 | AWS_SECRET_ACCESS_KEY: z.string().min(1), 22 | NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: z.string().min(1), 23 | STRIPE_SECRET_KEY: z.string().min(1), 24 | STRIPE_WEBHOOK_SECRET: z.string().min(1), 25 | STRIPE_10CREDIT_PRICE: z.string().min(1), 26 | STRIPE_50CREDIT_PRICE: z.string().min(1), 27 | STRIPE_100CREDIT_PRICE: z.string().min(1), 28 | HOST: z.string().min(1), 29 | }, 30 | 31 | /** 32 | * Specify your client-side environment variables schema here. This way you can ensure the app 33 | * isn't built with invalid env vars. To expose them to the client, prefix them with 34 | * `NEXT_PUBLIC_`. 35 | */ 36 | client: { 37 | // NEXT_PUBLIC_CLIENTVAR: z.string().min(1), 38 | }, 39 | 40 | /** 41 | * You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g. 42 | * middlewares) or client-side so we need to destruct manually. 43 | */ 44 | runtimeEnv: { 45 | DATABASE_URL: process.env.DATABASE_URL, 46 | NODE_ENV: process.env.NODE_ENV, 47 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: 48 | process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, 49 | CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY, 50 | NEXT_PUBLIC_CLERK_SIGN_IN_URL: process.env.CLERK_SECRET_KEY, 51 | NEXT_PUBLIC_CLERK_SIGN_UP_URL: process.env.NEXT_PUBLIC_CLERK_SIGN_UP_URL, 52 | NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL: 53 | process.env.NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL, 54 | NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL: 55 | process.env.NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL, 56 | OPENAI_API_KEY: process.env.OPENAI_API_KEY, 57 | AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID, 58 | AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY, 59 | NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, 60 | STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY, 61 | STRIPE_WEBHOOK_SECRET: process.env.STRIPE_WEBHOOK_SECRET, 62 | STRIPE_10CREDIT_PRICE: process.env.STRIPE_10CREDIT_PRICE, 63 | STRIPE_50CREDIT_PRICE: process.env.STRIPE_50CREDIT_PRICE, 64 | STRIPE_100CREDIT_PRICE: process.env.STRIPE_100CREDIT_PRICE, 65 | HOST: process.env.HOST, 66 | }, 67 | /** 68 | * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. 69 | * This is especially useful for Docker builds. 70 | */ 71 | skipValidation: !!process.env.SKIP_ENV_VALIDATION, 72 | }) 73 | -------------------------------------------------------------------------------- /src/pages/cookies.tsx: -------------------------------------------------------------------------------- 1 | import { type NextPage } from "next" 2 | import Head from "next/head" 3 | import Navbar from "../components/navbar" 4 | import Footer from "../components/footer" 5 | 6 | const Cookies: NextPage = () => { 7 | return ( 8 | <> 9 | 10 | Imagen 11 | 12 |
13 | 14 |
15 |

Cookie Policy

16 |

17 | Cookies are small text files that are stored on your device when you 18 | visit a website. We use cookies to provide a better user experience, 19 | analyze how users interact with our website, and to personalize 20 | content and ads. 21 |

22 |

23 | Below you can manage your cookie preferences. Please note that by 24 | disabling certain cookies, you may limit your ability to use certain 25 | features of our website. 26 |

27 |

Cookies we use

28 |

29 | We use cookies to provide a better experience on our website and to 30 | understand how visitors interact with our content. Here are the 31 | cookies we use: 32 |

33 |

Stripe (required)

34 |

35 | We use stripe as our payment gateway which allows websites to 36 | process online payments securely and easily. When a user makes a 37 | payment on your website using Stripe, their payment information 38 | (such as their credit card details) needs to be stored temporarily 39 | while the payment is being processed. Please review the{" "} 40 | 41 | https://stripe.com/cookie-settings 42 | {" "} 43 | settings to configure your stripe cookies. 44 |

45 |

46 | To enable this, Stripe uses cookies to store information about the 47 | user's session, such as their session ID and the status of their 48 | payment. These cookies are necessary for the payment process to work 49 | properly, and they are stored on the user's browser until the 50 | payment process is complete. 51 |

52 |

53 | In addition to payment-related cookies, Stripe may also use other 54 | cookies on your website to improve performance, analyze how users 55 | interact with the website, and provide relevant advertising. 56 | However, these additional cookies are optional and you can choose to 57 | disable them if you prefer. Stripe's use of cookies is subject to 58 | their own privacy policy, which you should review if you have any 59 | specific concerns. 60 |

61 |

62 | Authentication (required) 63 |

64 |

65 | We use Clerk to authenticate users on our application. Clerk is a 66 | service which allows users to authenticate using various third party 67 | services, such as Google, which makes it easy to sign up and start 68 | using our application. 69 |

70 |
71 |
72 |
73 | 74 | ) 75 | } 76 | 77 | export default Cookies 78 | -------------------------------------------------------------------------------- /src/server/api/trpc.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * YOU PROBABLY DON'T NEED TO EDIT THIS FILE, UNLESS: 3 | * 1. You want to modify request context (see Part 1). 4 | * 2. You want to create a new middleware or type of procedure (see Part 3). 5 | * 6 | * TL;DR - This is where all the tRPC server stuff is created and plugged in. The pieces you will 7 | * need to use are documented accordingly near the end. 8 | */ 9 | import { type CreateNextContextOptions } from "@trpc/server/adapters/next"; 10 | import { prisma } from "~/server/db"; 11 | import { initTRPC, TRPCError } from "@trpc/server"; 12 | import superjson from "superjson"; 13 | import { getAuth } from "@clerk/nextjs/server"; 14 | import { ZodError } from "zod"; 15 | 16 | /** 17 | * 1. CONTEXT 18 | * 19 | * This section defines the "contexts" that are available in the backend API. 20 | * 21 | * These allow you to access things when processing a request, like the database, the session, etc. 22 | */ 23 | 24 | /** 25 | * This is the actual context you will use in your router. It will be used to process every request 26 | * that goes through your tRPC endpoint. 27 | * 28 | * @see https://trpc.io/docs/context 29 | */ 30 | export const createTRPCContext = (opts: CreateNextContextOptions) => { 31 | const { req } = opts; 32 | const sesh = getAuth(req); 33 | 34 | const userId = sesh.userId; 35 | 36 | return { 37 | prisma, 38 | userId, 39 | }; 40 | }; 41 | 42 | /** 43 | * 2. INITIALIZATION 44 | * 45 | * This is where the tRPC API is initialized, connecting the context and transformer. We also parse 46 | * ZodErrors so that you get typesafety on the frontend if your procedure fails due to validation 47 | * errors on the backend. 48 | */ 49 | 50 | const t = initTRPC.context().create({ 51 | transformer: superjson, 52 | errorFormatter({ shape, error }) { 53 | return { 54 | ...shape, 55 | data: { 56 | ...shape.data, 57 | zodError: 58 | error.cause instanceof ZodError ? error.cause.flatten() : null, 59 | }, 60 | }; 61 | }, 62 | }); 63 | 64 | /** 65 | * 3. ROUTER & PROCEDURE (THE IMPORTANT BIT) 66 | * 67 | * These are the pieces you use to build your tRPC API. You should import these a lot in the 68 | * "/src/server/api/routers" directory. 69 | */ 70 | 71 | /** 72 | * This is how you create new routers and sub-routers in your tRPC API. 73 | * 74 | * @see https://trpc.io/docs/router 75 | */ 76 | export const createTRPCRouter = t.router; 77 | 78 | /** 79 | * Public (unauthenticated) procedure 80 | * 81 | * This is the base piece you use to build new queries and mutations on your tRPC API. It does not 82 | * guarantee that a user querying is authorized, but you can still access user session data if they 83 | * are logged in. 84 | */ 85 | export const publicProcedure = t.procedure; 86 | 87 | const enforceUserIsAuthed = t.middleware(async ({ ctx, next }) => { 88 | if (!ctx.userId) { 89 | throw new TRPCError({ 90 | code: "UNAUTHORIZED", 91 | message: "You are not authorized to view this page. Please log in." 92 | }); 93 | } 94 | 95 | return next({ 96 | ctx: { 97 | userId: ctx.userId, 98 | }, 99 | }); 100 | }); 101 | 102 | /** 103 | * Protected (authenticated) procedure 104 | * 105 | * If you want a query or mutation to ONLY be accessible to logged in users, use this. It verifies 106 | * the session is valid and guarantees `ctx.session.user` is not null. 107 | * 108 | * @see https://trpc.io/docs/procedures 109 | */ 110 | export const protectedProcedure = t.procedure.use(enforceUserIsAuthed); 111 | -------------------------------------------------------------------------------- /src/components/navbar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import Link from "next/link" 3 | import Image from "next/image" 4 | import { UserButton, useUser } from "@clerk/nextjs" 5 | import { api } from "~/utils/api" 6 | import { LoadingSpinner } from "~/components/loading" 7 | import coin from "../../public/coin.png" 8 | 9 | const Navbar: React.FC = () => { 10 | const { isSignedIn } = useUser() 11 | 12 | type CreditsDisplayProps = { 13 | isSignedIn: boolean | undefined 14 | } 15 | 16 | const CreditsDisplay = ({ isSignedIn }: CreditsDisplayProps) => { 17 | if (!isSignedIn) { 18 | return null 19 | } 20 | const { data: credits, isLoading: creditsLoading } = 21 | api.stripeUser.getCredits.useQuery() 22 | 23 | if (creditsLoading) { 24 | return ( 25 |
26 |
27 |
28 | 29 |

x

30 |
31 | Credits 38 |
39 | 44 | Buy Credits 45 | 46 |
47 | ) 48 | } 49 | 50 | return ( 51 |
52 |
53 |
54 | {credits} 55 |

x

56 |
57 | credits 64 |
65 | 70 | Buy Credits 71 | 72 |
73 | ) 74 | } 75 | 76 | return ( 77 | 128 | ) 129 | } 130 | 131 | export default Navbar 132 | -------------------------------------------------------------------------------- /src/pages/buy.tsx: -------------------------------------------------------------------------------- 1 | import { type NextPage } from "next" 2 | import Head from "next/head" 3 | import Image from "next/image" 4 | import { useRouter } from "next/router" 5 | import { useState } from "react" 6 | import Navbar from "../components/navbar" 7 | import Footer from "../components/footer" 8 | import { api } from "~/utils/api" 9 | import { LoadingSpinner } from "~/components/loading" 10 | import credits10 from "../../public/10_credits.png" 11 | import credits50 from "../../public/50_credits.png" 12 | import credits100 from "../../public/100_credits.png" 13 | 14 | const Home: NextPage = () => { 15 | api.images.getAll.useQuery() // Start fetching asap 16 | const { push } = useRouter() 17 | const { mutateAsync: createCheckout10 } = 18 | api.checkout.createCheckout10.useMutation() 19 | const { mutateAsync: createCheckout50 } = 20 | api.checkout.createCheckout50.useMutation() 21 | const { mutateAsync: createCheckout100 } = 22 | api.checkout.createCheckout100.useMutation() 23 | const [checkout10Loading, setCheckout10Loading] = useState(false) 24 | const [checkout50Loading, setCheckout50Loading] = useState(false) 25 | const [checkout100Loading, setCheckout100Loading] = useState(false) 26 | 27 | return ( 28 | <> 29 | 30 | Imagen 31 | 32 |
33 | 34 |
35 |

36 | Buy Credits 37 |

38 |

39 | Each image requires one credit to generate. Buy them here! 40 |

41 |
42 |
43 | 10 credits 50 |

10 Credits

51 | {checkout10Loading && } 52 | {!checkout10Loading && ( 53 | 66 | )} 67 |
68 |
69 | 50 credits 76 |

50 Credits

77 | {checkout50Loading && } 78 | {!checkout50Loading && ( 79 | 92 | )} 93 |
94 |
95 | 100 credits 102 |

100 Credits

103 | {checkout100Loading && } 104 | {!checkout100Loading && ( 105 | 118 | )} 119 |
120 |
121 |
122 |
123 |
124 | 125 | ) 126 | } 127 | 128 | export default Home 129 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { type NextPage } from "next" 2 | import { Fragment, useState } from "react" 3 | import Head from "next/head" 4 | import Image from "next/image" 5 | import Link from "next/link" 6 | import Navbar from "../components/navbar" 7 | import Footer from "../components/footer" 8 | import { api } from "~/utils/api" 9 | import { LoadingSpinner } from "~/components/loading" 10 | import type { Image as PrismaImage } from "@prisma/client" 11 | import { Dialog, Transition } from "@headlessui/react" 12 | import thumbnail from "../../public/thumbnail.png" 13 | 14 | type HomeImageDisplayProps = { 15 | image: PrismaImage 16 | } 17 | 18 | const HomeImageDisplay = ({ image }: HomeImageDisplayProps) => { 19 | const [imageModalOpen, setImageModalOpen] = useState(false) 20 | const [hovered, setHovered] = useState(false) 21 | 22 | return ( 23 |
setHovered(true)} 27 | onMouseLeave={() => setHovered(false)} 28 | > 29 | Image 40 |
46 | 53 |

{image.prompt}

54 |
55 | 56 | { 60 | setHovered(false) 61 | setImageModalOpen(false) 62 | }} 63 | > 64 | 73 |
74 | 75 |
76 |
77 | 86 | 87 |
88 | Generated image 96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | ) 105 | } 106 | 107 | const LatestImages = () => { 108 | const { data, isLoading: postsLoading } = api.images.getAll.useQuery() 109 | 110 | if (postsLoading) 111 | return ( 112 |
113 | 114 |
115 | ) 116 | 117 | if (!data) return
Something went wrong
118 | 119 | return ( 120 |
121 | {data?.map((image) => ( 122 | 123 | ))} 124 |
125 | ) 126 | } 127 | 128 | const Home: NextPage = () => { 129 | api.images.getAll.useQuery() // Start fetching asap 130 | 131 | return ( 132 | <> 133 | 134 | Imagen 135 | 136 |
137 | 138 |
139 |

140 | Imagen brings your 141 | imagination to life. 142 |

143 |
144 |
145 |
146 | Empower your creative endeavors using the power of generative 147 | AI. Streamline your workflow and lower your costs with our 148 | state-of-the-art solution. It's never been easier to 149 | generate stunning images with the click of a button! 150 |
151 |
152 | 157 | Get started 158 | 159 |
160 |
161 |
162 | Thumbnail 170 |
171 |
172 |
173 |

174 | Latest community-generated images 175 |

176 | 177 |
178 |
179 |
180 |
181 | 182 | ) 183 | } 184 | 185 | export default Home 186 | -------------------------------------------------------------------------------- /src/server/api/routers/images.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | import { 3 | createTRPCRouter, 4 | publicProcedure, 5 | protectedProcedure, 6 | } from "~/server/api/trpc" 7 | import { TRPCError } from "@trpc/server" 8 | import axios, { type AxiosError } from "axios" 9 | import { s3 } from "../../../aws-config.mjs" 10 | 11 | type DalleResponse = { 12 | 'data': { 13 | 'data': [ 14 | { 15 | 'url': string 16 | } 17 | ] 18 | } 19 | } 20 | 21 | type ImageResponse = { 22 | 'data': Buffer 23 | } 24 | 25 | export const imagesRouter = createTRPCRouter({ 26 | // Get user's most recently generated image 27 | getLatest: protectedProcedure.query(async ({ ctx }) => { 28 | const userId = ctx.userId 29 | const image = await ctx.prisma.image.findFirst({ 30 | where: { 31 | authorId: { 32 | equals: userId 33 | } 34 | }, 35 | orderBy: [ 36 | {createdAt: "desc"} 37 | ] 38 | }) 39 | return image 40 | }), 41 | // Get 100 most recently generated images in reverse chronological order 42 | getAll: publicProcedure.query(async ({ ctx }) => { 43 | const images = await ctx.prisma.image.findMany({ 44 | where: { 45 | hidden: { 46 | equals: false 47 | } 48 | }, 49 | take: 100, 50 | orderBy: [ 51 | {createdAt: "desc"} 52 | ] 53 | }) 54 | return images 55 | }), 56 | 57 | // Get all of a user's images in reverse chronological order 58 | getAllUser: protectedProcedure.query(async ({ ctx }) => { 59 | const userId = ctx.userId 60 | const images = await ctx.prisma.image.findMany({ 61 | where: { 62 | authorId: { 63 | equals: userId 64 | } 65 | }, 66 | orderBy: [ 67 | {createdAt: "desc"} 68 | ] 69 | }) 70 | return images 71 | }), 72 | 73 | // Generate a new image for a user 74 | create: protectedProcedure 75 | .input( 76 | z.object({ 77 | prompt: z.string() 78 | .min(1, {message: "Prompt cannot be empty"}) 79 | }) 80 | ) 81 | .mutation(async ({ ctx, input }) => { 82 | const authorId = ctx.userId 83 | const prompt = input.prompt 84 | // For testing purposes (return latest generated image) 85 | // const image = await ctx.prisma.image.findFirst({ 86 | // take: 1, 87 | // orderBy: [ 88 | // {createdAt: "desc"} 89 | // ] 90 | // }) 91 | // return image 92 | // }), 93 | 94 | // Check if user has a positive token balance 95 | const stripeUser = await ctx.prisma.stripeUser.findFirst({ 96 | where: { 97 | clerkID: ctx.userId 98 | } 99 | }) 100 | if (!stripeUser || !stripeUser.credits || stripeUser?.credits <= 0) { 101 | throw new TRPCError({ 102 | code: "BAD_REQUEST", 103 | message: "You don't have enough credits to generate an image. Please buy more credits." 104 | }) 105 | } 106 | 107 | if (!process.env.OPENAI_API_KEY) { 108 | throw new TRPCError({ 109 | code: "INTERNAL_SERVER_ERROR", 110 | message: "OpenAI API key not found" 111 | }) 112 | } 113 | 114 | // Define async func to upload to S3 bucket 115 | type uploadParams = { 116 | Bucket: string, 117 | Key: string, 118 | Body: Buffer 119 | } 120 | const uploadImage = (params: uploadParams) => { 121 | return new Promise((resolve, reject) => { 122 | s3.putObject(params, (err, data) => { 123 | if (err) { 124 | reject(err); 125 | } else { 126 | resolve(data); 127 | } 128 | }); 129 | }); 130 | }; 131 | 132 | try { 133 | // Make call to DALL-E 134 | const dalleResponse: DalleResponse = await axios.post( 135 | "https://api.openai.com/v1/images/generations", 136 | { 137 | "prompt": prompt, 138 | "n": 1, 139 | "size": "1024x1024" 140 | }, 141 | { 142 | headers: { 143 | "Content-Type": "application/json", 144 | Authorization: `Bearer ${process.env.OPENAI_API_KEY}` 145 | } 146 | } 147 | ) 148 | const imageUrl = dalleResponse['data']['data'][0]['url'] 149 | const imageResponse: ImageResponse = await axios.get(imageUrl, { responseType: 'arraybuffer' }) 150 | if (!imageResponse) { 151 | throw new TRPCError({ 152 | code: "INTERNAL_SERVER_ERROR", 153 | message: "Error downloading image from DALL-E" 154 | }) 155 | } 156 | 157 | // Create a unique key for image 158 | const timeStamp = Date.now() 159 | const randomString = Math.random().toString(36).substring(2, 15) 160 | const s3Key = `${timeStamp}-${randomString}.jpg` 161 | 162 | // Await S3 upload 163 | await uploadImage({ 164 | Bucket: 'imagen-images', 165 | Key: s3Key, 166 | Body: imageResponse.data, 167 | }) 168 | const s3Url = `https://imagen-images.s3.us-east-1.amazonaws.com/${s3Key}` 169 | console.log(s3Url) 170 | 171 | // Create db record and return 172 | const image = await ctx.prisma.image.create({ 173 | data: { 174 | authorId: authorId, 175 | prompt: prompt, 176 | url: s3Url 177 | } 178 | }) 179 | return image 180 | } catch {(error: AxiosError | Error) => { 181 | console.log(error) 182 | if (axios.isAxiosError(error)) { 183 | if (error.response) { 184 | console.log(error.response.status) 185 | console.log(error.response.data) 186 | throw new TRPCError({ 187 | code: "BAD_REQUEST", 188 | message: "Something went wrong while generating the image. Please try again." 189 | }) 190 | } else { 191 | console.log(error.message) 192 | throw new TRPCError({ 193 | code: "BAD_REQUEST", 194 | message: error.message 195 | }) 196 | } 197 | } else { 198 | console.log("Error generating image", error) 199 | throw new TRPCError({ 200 | code: "BAD_REQUEST", 201 | message: error.message 202 | }) 203 | } 204 | }} 205 | }), 206 | 207 | // Delete an image given its id 208 | delete: protectedProcedure 209 | .input( 210 | z.object({ 211 | id: z.string() 212 | .min(1, {message: "id cannot be empty"}) 213 | }) 214 | ) 215 | .mutation(async ({ ctx, input }) => { 216 | await ctx.prisma.image.delete({ 217 | where: { 218 | id: input.id 219 | } 220 | }) 221 | }), 222 | 223 | // Show an image given its id 224 | show: protectedProcedure 225 | .input( 226 | z.object({ 227 | id: z.string() 228 | .min(1, {message: "id cannot be empty"}) 229 | }) 230 | ) 231 | .mutation(async ({ ctx, input }) => { 232 | const image = await ctx.prisma.image.update({ 233 | where: { 234 | id: input.id 235 | }, 236 | data: { 237 | hidden: false 238 | } 239 | }) 240 | return image 241 | } 242 | ), 243 | 244 | // Hide an image given its id 245 | hide: protectedProcedure 246 | .input( 247 | z.object({ 248 | id: z.string() 249 | .min(1, {message: "id cannot be empty"}) 250 | }) 251 | ) 252 | .mutation(async ({ ctx, input }) => { 253 | const image = await ctx.prisma.image.update({ 254 | where: { 255 | id: input.id 256 | }, 257 | data: { 258 | hidden: true 259 | } 260 | }) 261 | return image 262 | } 263 | ) 264 | }) 265 | -------------------------------------------------------------------------------- /src/pages/history.tsx: -------------------------------------------------------------------------------- 1 | import { type NextPage } from "next" 2 | import dayjs from "dayjs" 3 | import relativeTime from "dayjs/plugin/relativeTime" 4 | import Head from "next/head" 5 | import Link from "next/link" 6 | import Image from "next/image" 7 | import { useUser } from "@clerk/nextjs" 8 | import { Dialog, Transition } from "@headlessui/react" 9 | import { Fragment, useRef, useState } from "react" 10 | import { ExclamationTriangleIcon } from "@heroicons/react/24/outline" 11 | import type { RouterOutputs } from "~/utils/api" 12 | import { LoadingPage } from "~/components/loading" 13 | import Navbar from "../components/navbar" 14 | import Footer from "../components/footer" 15 | import { api } from "~/utils/api" 16 | import { toast } from "react-hot-toast" 17 | 18 | dayjs.extend(relativeTime) 19 | 20 | type Image = RouterOutputs["images"]["getAllUser"][number] 21 | const ImageView = (image: Image) => { 22 | const [deleteModalOpen, setDeleteModalOpen] = useState(false) 23 | const [imageModalOpen, setImageModalOpen] = useState(false) 24 | const cancelButtonRef = useRef(null) 25 | 26 | const { user } = useUser() 27 | if (!user) { 28 | return null 29 | } 30 | const ctx = api.useContext() 31 | const { mutate: mutateDelete } = api.images.delete.useMutation({ 32 | onSuccess: () => { 33 | void ctx.images.invalidate() 34 | toast.success("Image successfully deleted!") 35 | }, 36 | onError: (e) => { 37 | // TRPC Error 38 | console.log(e) 39 | if (e.message) { 40 | toast.error(e.message) 41 | } else { 42 | // Zod Error 43 | console.log(e.data?.zodError) 44 | const errorMessage = e.data?.zodError?.fieldErrors.prompt 45 | if (errorMessage && errorMessage[0]) { 46 | toast.error(errorMessage[0]) 47 | } else { 48 | toast.error(e.message) 49 | } 50 | } 51 | }, 52 | }) 53 | const { mutate: mutateHide } = api.images.hide.useMutation({ 54 | onSuccess: () => { 55 | void ctx.images.invalidate() 56 | toast.success("Image successfully hidden from the front page.") 57 | }, 58 | onError: (e) => { 59 | // TRPC Error 60 | console.log(e) 61 | if (e.message) { 62 | toast.error(e.message) 63 | } else { 64 | // Zod Error 65 | console.log(e.data?.zodError) 66 | const errorMessage = e.data?.zodError?.fieldErrors.prompt 67 | if (errorMessage && errorMessage[0]) { 68 | toast.error(errorMessage[0]) 69 | } else { 70 | toast.error(e.message) 71 | } 72 | } 73 | }, 74 | }) 75 | const { mutate: mutateShow } = api.images.show.useMutation({ 76 | onSuccess: () => { 77 | void ctx.images.invalidate() 78 | toast.success("Image successfully shown on the front page.") 79 | }, 80 | onError: (e) => { 81 | // TRPC Error 82 | console.log(e) 83 | if (e.message) { 84 | toast.error(e.message) 85 | } else { 86 | // Zod Error 87 | console.log(e.data?.zodError) 88 | const errorMessage = e.data?.zodError?.fieldErrors.prompt 89 | if (errorMessage && errorMessage[0]) { 90 | toast.error(errorMessage[0]) 91 | } else { 92 | toast.error(e.message) 93 | } 94 | } 95 | }, 96 | }) 97 | return ( 98 | <> 99 |
100 | Generated image setImageModalOpen(true)} 108 | /> 109 |
110 |
111 | {`Generated ${dayjs( 112 | image.createdAt 113 | ).fromNow()}`} 114 |
115 | 116 | {image.prompt} 117 | 118 |
119 | 124 | Download 125 | 126 | 133 | { image.hidden && ( 134 | 142 | ) 143 | } 144 | { !image.hidden && ( 145 | 153 | ) 154 | } 155 |
156 |
157 |
158 | 159 | setDeleteModalOpen(false)} 164 | > 165 | 174 |
175 | 176 | 177 |
178 |
179 | 188 | 189 |
190 |
191 |
192 |
197 |
198 | 202 | Delete image 203 | 204 |
205 |

206 | Are you sure you want to delete this image? Image 207 | data will be permanently removed. This action cannot 208 | be undone. 209 |

210 |
211 |
212 |
213 |
214 |
215 | 225 | 233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 | 241 | setImageModalOpen(false)} 245 | > 246 | 255 |
256 | 257 | 258 |
259 |
260 | 269 | 270 |
271 | Generated image 279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 | 287 | ) 288 | } 289 | 290 | const Feed = () => { 291 | const { data, isLoading: postsLoading } = api.images.getAllUser.useQuery() 292 | if (postsLoading) return 293 | if (!data) return
Something went wrong
294 | 295 | return ( 296 |
297 |

298 | You have{" "} 299 | {data ? data.length : 0}{" "} 300 | generated images 301 |

302 |
303 | 308 | Generate a new image 309 | 310 |
311 | {!!data.length && ( 312 | <> 313 | {data?.map((fullImage) => ( 314 | 315 | ))} 316 | 317 | )} 318 | {!!data.length && data.length >= 3 && ( 319 |
320 | 325 | Generate new images 326 | 327 |
328 | )} 329 |
330 | ) 331 | } 332 | 333 | const HistoryPage: NextPage = () => { 334 | const { isLoaded: userLoaded } = useUser() 335 | api.images.getAllUser.useQuery() // pre-fetch 336 | if (!userLoaded) return
337 | return ( 338 | <> 339 | 340 | Imagen 341 | 342 |
343 | 344 |
345 | 346 |
347 |
348 |
349 | 350 | ) 351 | } 352 | 353 | export default HistoryPage 354 | -------------------------------------------------------------------------------- /src/pages/terms-of-service.tsx: -------------------------------------------------------------------------------- 1 | import { type NextPage } from "next" 2 | import Head from "next/head" 3 | import Navbar from "../components/navbar" 4 | import Footer from "../components/footer" 5 | 6 | const TermsOfService: NextPage = () => { 7 | return ( 8 | <> 9 | 10 | Imagen 11 | 12 |
13 | 14 |
15 |

Terms of Service

16 |

Last updated: June 12, 2023

17 |

18 | Please read these terms and conditions carefully before using Our 19 | Service. 20 |

21 |

22 | Interpretation and Definitions 23 |

24 |

Interpretation

25 |

26 | The words of which the initial letter is capitalized have meanings 27 | defined under the following conditions. The following definitions 28 | shall have the same meaning regardless of whether they appear in 29 | singular or in plural. 30 |

31 |

Definitions

32 |

For the purposes of these Terms and Conditions:

33 |
    34 |
  • 35 |

    36 | Affiliate means an entity that controls, is 37 | controlled by or is under common control with a party, where 38 | "control" means ownership of 50% or more of the shares, equity 39 | interest or other securities entitled to vote for election of 40 | directors or other managing authority. 41 |

    42 |
  • 43 |
  • 44 |

    45 | Country refers to: United States 46 |

    47 |
  • 48 |
  • 49 |

    50 | Company (referred to as either "the Company", 51 | "We", "Us" or "Our" in this Agreement) refers to Imagen. 52 |

    53 |
  • 54 |
  • 55 |

    56 | Device means any device that can access the 57 | Service such as a computer, a cellphone or a digital tablet. 58 |

    59 |
  • 60 |
  • 61 |

    62 | Service refers to the Website. 63 |

    64 |
  • 65 |
  • 66 |

    67 | Terms and Conditions (also referred as "Terms") 68 | mean these Terms and Conditions that form the entire agreement 69 | between You and the Company regarding the use of the Service. 70 |

    71 |
  • 72 |
  • 73 |

    74 | Third-party Social Media Service means any 75 | services or content (including data, information, products or 76 | services) provided by a third-party that may be displayed, 77 | included or made available by the Service. 78 |

    79 |
  • 80 |
  • 81 |

    82 | Website refers to Imagen, accessible from{" "} 83 | 84 | imagen-danielpark1239.vercel.app 85 | 86 |

    87 |
  • 88 |
  • 89 |

    90 | You means the individual accessing or using the 91 | Service, or the company, or other legal entity on behalf of 92 | which such individual is accessing or using the Service, as 93 | applicable. 94 |

    95 |
  • 96 |
97 |

Acknowledgment

98 |

99 | These are the Terms and Conditions governing the use of this Service 100 | and the agreement that operates between You and the Company. These 101 | Terms and Conditions set out the rights and obligations of all users 102 | regarding the use of the Service. 103 |

104 |

105 | Your access to and use of the Service is conditioned on Your 106 | acceptance of and compliance with these Terms and Conditions. These 107 | Terms and Conditions apply to all visitors, users and others who 108 | access or use the Service. 109 |

110 |

111 | By accessing or using the Service You agree to be bound by these 112 | Terms and Conditions. If You disagree with any part of these Terms 113 | and Conditions then You may not access the Service. 114 |

115 |

116 | Your access to and use of the Service is also conditioned on Your 117 | acceptance of and compliance with the Privacy Policy of the Company. 118 | Our Privacy Policy describes Our policies and procedures on the 119 | collection, use and disclosure of Your personal information when You 120 | use the Application or the Website and tells You about Your privacy 121 | rights and how the law protects You. Please read Our Privacy Policy 122 | carefully before using Our Service. 123 |

124 |

Links to Other Websites

125 |

126 | Our Service may contain links to third-party web sites or services 127 | that are not owned or controlled by the Company. 128 |

129 |

130 | The Company has no control over, and assumes no responsibility for, 131 | the content, privacy policies, or practices of any third party web 132 | sites or services. You further acknowledge and agree that the 133 | Company shall not be responsible or liable, directly or indirectly, 134 | for any damage or loss caused or alleged to be caused by or in 135 | connection with the use of or reliance on any such content, goods or 136 | services available on or through any such web sites or services. 137 |

138 |

139 | We strongly advise You to read the terms and conditions and privacy 140 | policies of any third-party web sites or services that You visit. 141 |

142 |

Icon Generation

143 |

144 | We can use your icons to promote our service. This includes, but is 145 | not limited to: online ads, physical ads, social media posts, and 146 | anywhere inside this application. 147 |

148 |

Termination

149 |

150 | We may terminate or suspend Your access immediately, without prior 151 | notice or liability, for any reason whatsoever, including without 152 | limitation if You breach these Terms and Conditions. 153 |

154 |

155 | Upon termination, Your right to use the Service will cease 156 | immediately. 157 |

158 |

Limitation of Liability

159 |

160 | Our service uses AI provided by OpenAI to generate icons. Please 161 | review copyright laws for your country and state in regards to AI 162 | generated art or images. By using our services, you agree that our 163 | company and service assumes no responsibility for the images 164 | generated. Nor do we assume responsibility for damages or loss 165 | caused by these images. 166 |

167 |

168 | Notwithstanding any damages that You might incur, the entire 169 | liability of the Company and any of its suppliers under any 170 | provision of this Terms and Your exclusive remedy for all of the 171 | foregoing shall be limited to the amount actually paid by You 172 | through the Service or 100 USD if You haven't purchased anything 173 | through the Service. 174 |

175 |

176 | To the maximum extent permitted by applicable law, in no event shall 177 | the Company or its suppliers be liable for any special, incidental, 178 | indirect, or consequential damages whatsoever (including, but not 179 | limited to, damages for loss of profits, loss of data or other 180 | information, for business interruption, for personal injury, loss of 181 | privacy arising out of or in any way related to the use of or 182 | inability to use the Service, third-party software and/or 183 | third-party hardware used with the Service, or otherwise in 184 | connection with any provision of this Terms), even if the Company or 185 | any supplier has been advised of the possibility of such damages and 186 | even if the remedy fails of its essential purpose. 187 |

188 |

189 | Some states do not allow the exclusion of implied warranties or 190 | limitation of liability for incidental or consequential damages, 191 | which means that some of the above limitations may not apply. In 192 | these states, each party's liability will be limited to the greatest 193 | extent permitted by law. 194 |

195 |

196 | "AS IS" and "AS AVAILABLE" Disclaimer 197 |

198 |

199 | The Service is provided to You "AS IS" and "AS AVAILABLE" and with 200 | all faults and defects without warranty of any kind. To the maximum 201 | extent permitted under applicable law, the Company, on its own 202 | behalf and on behalf of its Affiliates and its and their respective 203 | licensors and service providers, expressly disclaims all warranties, 204 | whether express, implied, statutory or otherwise, with respect to 205 | the Service, including all implied warranties of merchantability, 206 | fitness for a particular purpose, title and non-infringement, and 207 | warranties that may arise out of course of dealing, course of 208 | performance, usage or trade practice. Without limitation to the 209 | foregoing, the Company provides no warranty or undertaking, and 210 | makes no representation of any kind that the Service will meet Your 211 | requirements, achieve any intended results, be compatible or work 212 | with any other software, applications, systems or services, operate 213 | without interruption, meet any performance or reliability standards 214 | or be error free or that any errors or defects can or will be 215 | corrected. 216 |

217 |

218 | Without limiting the foregoing, neither the Company nor any of the 219 | company's provider makes any representation or warranty of any kind, 220 | express or implied: (i) as to the operation or availability of the 221 | Service, or the information, content, and materials or products 222 | included thereon; (ii) that the Service will be uninterrupted or 223 | error-free; (iii) as to the accuracy, reliability, or currency of 224 | any information or content provided through the Service; or (iv) 225 | that the Service, its servers, the content, or e-mails sent from or 226 | on behalf of the Company are free of viruses, scripts, trojan 227 | horses, worms, malware, timebombs or other harmful components. 228 |

229 |

230 | Some jurisdictions do not allow the exclusion of certain types of 231 | warranties or limitations on applicable statutory rights of a 232 | consumer, so some or all of the above exclusions and limitations may 233 | not apply to You. But in such a case the exclusions and limitations 234 | set forth in this section shall be applied to the greatest extent 235 | enforceable under applicable law. 236 |

237 |

Governing Law

238 |

239 | The laws of the Country, excluding its conflicts of law rules, shall 240 | govern this Terms and Your use of the Service. Your use of the 241 | Application may also be subject to other local, state, national, or 242 | international laws. 243 |

244 |

Disputes Resolution

245 |

246 | If You have any concern or dispute about the Service, You agree to 247 | first try to resolve the dispute informally by contacting the 248 | Company. 249 |

250 |

For European Union (EU) Users

251 |

252 | If You are a European Union consumer, you will benefit from any 253 | mandatory provisions of the law of the country in which you are 254 | resident in. 255 |

256 |

257 | United States Legal Compliance 258 |

259 |

260 | You represent and warrant that (i) You are not located in a country 261 | that is subject to the United States government embargo, or that has 262 | been designated by the United States government as a "terrorist 263 | supporting" country, and (ii) You are not listed on any United 264 | States government list of prohibited or restricted parties. 265 |

266 |

Severability and Waiver

267 |

Severability

268 |

269 | If any provision of these Terms is held to be unenforceable or 270 | invalid, such provision will be changed and interpreted to 271 | accomplish the objectives of such provision to the greatest extent 272 | possible under applicable law and the remaining provisions will 273 | continue in full force and effect. 274 |

275 |

Waiver

276 |

277 | Except as provided herein, the failure to exercise a right or to 278 | require performance of an obligation under these Terms shall not 279 | effect a party's ability to exercise such right or require such 280 | performance at any time thereafter nor shall the waiver of a breach 281 | constitute a waiver of any subsequent breach. 282 |

283 |

Translation Interpretation

284 |

285 | These Terms and Conditions may have been translated if We have made 286 | them available to You on our Service. You agree that the original 287 | English text shall prevail in the case of a dispute. 288 |

289 |

290 | Changes to These Terms and Conditions 291 |

292 |

293 | We reserve the right, at Our sole discretion, to modify or replace 294 | these Terms at any time. If a revision is material We will make 295 | reasonable efforts to provide at least 30 days' notice prior to any 296 | new terms taking effect. What constitutes a material change will be 297 | determined at Our sole discretion. 298 |

299 |

300 | By continuing to access or use Our Service after those revisions 301 | become effective, You agree to be bound by the revised terms. If You 302 | do not agree to the new terms, in whole or in part, please stop 303 | using the website and the Service. 304 |

305 |

Contact Us

306 |

307 | If you have any questions about these Terms and Conditions, You can 308 | contact us: 309 |

310 |
    311 |
  • By email: danielpark1239@gmail.com
  • 312 |
313 |
314 |
315 |
316 | 317 | ) 318 | } 319 | 320 | export default TermsOfService 321 | -------------------------------------------------------------------------------- /src/pages/generate.tsx: -------------------------------------------------------------------------------- 1 | import { type NextPage } from "next" 2 | import Image from "next/image" 3 | import Head from "next/head" 4 | import Link from "next/link" 5 | import { useUser } from "@clerk/nextjs" 6 | import { Dialog, Transition } from "@headlessui/react" 7 | import { Fragment, useRef, useState } from "react" 8 | import { ExclamationTriangleIcon } from "@heroicons/react/24/outline" 9 | import dayjs from "dayjs" 10 | import relativeTime from "dayjs/plugin/relativeTime" 11 | import { api } from "~/utils/api" 12 | import { LoadingSpinner } from "~/components/loading" 13 | import { toast } from "react-hot-toast" 14 | import Navbar from "~/components/navbar" 15 | import Footer from "~/components/footer" 16 | import type { Image as PrismaImage } from "@prisma/client" 17 | import coin from "../../public/coin.png" 18 | 19 | dayjs.extend(relativeTime) 20 | 21 | const CreateImageWizard = () => { 22 | const [input, setInput] = useState("") 23 | const [deleteModalOpen, setDeleteModalOpen] = useState(false) 24 | const [imageModalOpen, setImageModalOpen] = useState(false) 25 | const [createdImage, setCreatedImage] = useState(undefined) 26 | const cancelButtonRef = useRef(null) 27 | 28 | const { user } = useUser() 29 | if (!user) { 30 | return null 31 | } 32 | const ctx = api.useContext() 33 | const { mutate: createMutate, isLoading: isGenerating } = 34 | api.images.create.useMutation({ 35 | onSuccess: (createdData) => { 36 | if (createdData === undefined) { 37 | toast.error("Something went wrong. Adjust your prompt and try again.") 38 | } else { 39 | toast.success("Image successfully generated!") 40 | decrementCredits() 41 | } 42 | setInput("") 43 | setCreatedImage(createdData) 44 | void ctx.images.invalidate() 45 | }, 46 | onError: (e) => { 47 | // TRPC Error 48 | console.log(e) 49 | if (e.message) { 50 | toast.error(e.message) 51 | } else { 52 | // Zod Error 53 | console.log(e.data?.zodError) 54 | const errorMessage = e.data?.zodError?.fieldErrors.prompt 55 | if (errorMessage && errorMessage[0]) { 56 | toast.error(errorMessage[0]) 57 | } else { 58 | toast.error(e.message) 59 | } 60 | } 61 | }, 62 | }) 63 | const { mutate: deleteMutate } = api.images.delete.useMutation({ 64 | onSuccess: () => { 65 | void ctx.images.invalidate() 66 | toast.success("Image successfully deleted!") 67 | }, 68 | onError: (e) => { 69 | // TRPC Error 70 | console.log(e) 71 | if (e.message) { 72 | toast.error(e.message) 73 | } else { 74 | // Zod Error 75 | console.log(e.data?.zodError) 76 | const errorMessage = e.data?.zodError?.fieldErrors.prompt 77 | if (errorMessage && errorMessage[0]) { 78 | toast.error(errorMessage[0]) 79 | } else { 80 | toast.error(e.message) 81 | } 82 | } 83 | }, 84 | }) 85 | const { mutate: decrementCredits } = api.stripeUser.decrementCredits.useMutation({ 86 | onSuccess: () => { 87 | void ctx.stripeUser.invalidate() 88 | }, 89 | onError: (e) => { 90 | // TRPC Error 91 | console.log(e) 92 | if (e.message) { 93 | toast.error(e.message) 94 | } else { 95 | // Zod Error 96 | console.log(e.data?.zodError) 97 | const errorMessage = e.data?.zodError?.fieldErrors.prompt 98 | if (errorMessage && errorMessage[0]) { 99 | toast.error(errorMessage[0]) 100 | } else { 101 | toast.error(e.message) 102 | } 103 | } 104 | }, 105 | }) 106 | const { 107 | data: suggestedPrompt, 108 | isLoading, 109 | refetch, 110 | } = api.suggestedPrompts.getRandom.useQuery() 111 | const { mutate: mutateHide } = api.images.hide.useMutation({ 112 | onSuccess: (mutatedData) => { 113 | setCreatedImage(mutatedData) 114 | void ctx.images.invalidate() 115 | toast.success("Image successfully hidden from the front page.") 116 | }, 117 | onError: (e) => { 118 | // TRPC Error 119 | console.log(e) 120 | if (e.message) { 121 | toast.error(e.message) 122 | } else { 123 | // Zod Error 124 | console.log(e.data?.zodError) 125 | const errorMessage = e.data?.zodError?.fieldErrors.prompt 126 | if (errorMessage && errorMessage[0]) { 127 | toast.error(errorMessage[0]) 128 | } else { 129 | toast.error(e.message) 130 | } 131 | } 132 | }, 133 | }) 134 | const { mutate: mutateShow } = api.images.show.useMutation({ 135 | onSuccess: (mutatedData) => { 136 | setCreatedImage(mutatedData) 137 | void ctx.images.invalidate() 138 | toast.success("Image successfully shown on the front page.") 139 | }, 140 | onError: (e) => { 141 | // TRPC Error 142 | console.log(e) 143 | if (e.message) { 144 | toast.error(e.message) 145 | } else { 146 | // Zod Error 147 | console.log(e.data?.zodError) 148 | const errorMessage = e.data?.zodError?.fieldErrors.prompt 149 | if (errorMessage && errorMessage[0]) { 150 | toast.error(errorMessage[0]) 151 | } else { 152 | toast.error(e.message) 153 | } 154 | } 155 | }, 156 | }) 157 | 158 | return ( 159 |
160 |

161 | Generate an image 162 |

163 |
164 |

Instructions for best use:

165 |
    166 |
  • Be as detailed as possible.
  • 167 |
  • 168 | Mention the style of image you want, such as cartoon, painting, 169 | photo, 3d render, Unreal Engine, 8k, etc. 170 |
  • 171 |
  • 172 | Be specific about the individual elements, background, and colors 173 | you want in your image. 174 |
  • 175 |
  • 176 | Try to avoid overly complicated/specific prompts and images with 177 | multiple human subjects, as they may lead to distortions. 178 |
  • 179 |
  • 180 | Note: Some long/complex prompts cause an error by taking over 10 seconds to generate, leading to an 181 | API timeout. If this happens, please try again with a different prompt while we work on a fix. 182 |
  • 183 |
184 |
185 |
186 |
187 |

188 | Want a sample prompt? 189 |

190 | 203 |
204 |
205 | 213 | 221 | {!isGenerating ? ( 222 |
223 | 230 | credits 237 |
238 | ) : ( 239 |
240 | 241 |
242 | )} 243 |
244 |
245 |
246 | {createdImage && ( 247 | <> 248 |
249 | Generated image setImageModalOpen(true)} 257 | /> 258 |
259 |
260 | {`Generated ${dayjs( 261 | createdImage.createdAt 262 | ).fromNow()}`} 263 |
264 | 265 | {createdImage.prompt} 266 | 267 |
268 | 273 | Download 274 | 275 | 282 | {createdImage.hidden && ( 283 | 291 | )} 292 | {!createdImage.hidden && ( 293 | 301 | )} 302 |
303 |
304 |
305 | 306 | setDeleteModalOpen(false)} 311 | > 312 | 321 |
322 | 323 |
324 |
325 | 334 | 335 |
336 |
337 |
338 |
343 |
344 | 348 | Delete image 349 | 350 |
351 |

352 | Are you sure you want to delete this image? 353 | Image data will be permanently removed. This 354 | action cannot be undone. 355 |

356 |
357 |
358 |
359 |
360 |
361 | 372 | 380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 | 388 | setImageModalOpen(false)} 392 | > 393 | 402 |
403 | 404 | 405 |
406 |
407 | 416 | 417 |
418 | Generated image 426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 | 434 | )} 435 |
436 | 441 | Image History 442 | 443 |
444 |
445 |
446 | ) 447 | } 448 | 449 | const Generate: NextPage = () => { 450 | const { isLoaded: userLoaded } = useUser() 451 | api.suggestedPrompts.getRandom.useQuery() // Start fetching asap 452 | 453 | if (!userLoaded) return
454 | 455 | return ( 456 | <> 457 | 458 | Imagen 459 | 460 |
461 | 462 |
463 | 464 |
465 |
466 |
467 | 468 | ) 469 | } 470 | 471 | export default Generate 472 | -------------------------------------------------------------------------------- /src/pages/privacy-policy.tsx: -------------------------------------------------------------------------------- 1 | import { type NextPage } from "next" 2 | import Head from "next/head" 3 | import Navbar from "../components/navbar" 4 | import Footer from "../components/footer" 5 | 6 | const PrivacyPolicy: NextPage = () => { 7 | return ( 8 | <> 9 | 10 | Imagen 11 | 12 |
13 | 14 |
15 |

Privacy Policy

16 |

Last updated: June 12, 2023

17 |

18 | This Privacy Policy describes Our policies and procedures on the 19 | collection, use and disclosure of Your information when You use the 20 | Service and tells You about Your privacy rights and how the law 21 | protects You. 22 |

23 |

24 | We use Your Personal data to provide and improve the Service. By 25 | using the Service, You agree to the collection and use of 26 | information in accordance with this Privacy Policy. 27 |

28 |

29 | Interpretation and Definitions 30 |

31 |

Interpretation

32 |

33 | The words of which the initial letter is capitalized have meanings 34 | defined under the following conditions. The following definitions 35 | shall have the same meaning regardless of whether they appear in 36 | singular or in plural. 37 |

38 |

Definitions

39 |

For the purposes of this Privacy Policy:

40 |
    41 |
  • 42 |

    43 | Account means a unique account created for You 44 | to access our Service or parts of our Service. 45 |

    46 |
  • 47 |
  • 48 |

    49 | Affiliate means an entity that controls, is 50 | controlled by or is under common control with a party, where 51 | "control" means ownership of 50% or more of the shares, equity 52 | interest or other securities entitled to vote for election of 53 | directors or other managing authority. 54 |

    55 |
  • 56 |
  • 57 |

    58 | Company (referred to as either "the Company", 59 | "We", "Us" or "Our" in this Agreement) refers to Imagen. 60 |

    61 |
  • 62 |
  • 63 |

    64 | Cookies are small files that are placed on Your 65 | computer, mobile device or any other device by a website, 66 | containing the details of Your browsing history on that website 67 | among its many uses. 68 |

    69 |
  • 70 |
  • 71 |

    72 | Country refers to: United States 73 |

    74 |
  • 75 |
  • 76 |

    77 | Device means any device that can access the 78 | Service such as a computer, a cellphone or a digital tablet. 79 |

    80 |
  • 81 |
  • 82 |

    83 | Personal Data is any information that relates 84 | to an identified or identifiable individual. 85 |

    86 |
  • 87 |
  • 88 |

    89 | Service refers to the Website. 90 |

    91 |
  • 92 |
  • 93 |

    94 | Service Provider means any natural or legal 95 | person who processes the data on behalf of the Company. It 96 | refers to third-party companies or individuals employed by the 97 | Company to facilitate the Service, to provide the Service on 98 | behalf of the Company, to perform services related to the 99 | Service or to assist the Company in analyzing how the Service is 100 | used. 101 |

    102 |
  • 103 |
  • 104 |

    105 | Third-party Social Media Service refers to any 106 | website or any social network website through which a User can 107 | log in or create an account to use the Service. 108 |

    109 |
  • 110 |
  • 111 |

    112 | Usage Data refers to data collected 113 | automatically, either generated by the use of the Service or 114 | from the Service infrastructure itself (for example, the 115 | duration of a page visit). 116 |

    117 |
  • 118 |
  • 119 |

    120 | Website refers to Imagen, accessible 121 | from{"imagen-danielpark1239.vercel.app"} 122 | 123 | 124 | 125 |

    126 |
  • 127 |
  • 128 |

    129 | You means the individual accessing or using the 130 | Service, or the company, or other legal entity on behalf of 131 | which such individual is accessing or using the Service, as 132 | applicable. 133 |

    134 |
  • 135 |
136 |

Collecting and Using Your Personal Data

137 |

Types of Data Collected

138 |

Personal Data

139 |

140 | While using Our Service, We may ask You to provide Us with certain 141 | personally identifiable information that can be used to contact or 142 | identify You. Personally identifiable information may include, but 143 | is not limited to: 144 |

145 |
    146 |
  • 147 |

    Email address

    148 |
  • 149 |
  • 150 |

    First name and last name

    151 |
  • 152 |
153 |

Information from Third-Party Social Media Services

154 |

155 | The Company allows You to create an account and log in to use the 156 | Service through the following Third-party Social Media Services: 157 |

158 |
    159 |
  • Google
  • 160 |
  • GitHub
  • 161 |
162 |

163 | If You decide to register through or otherwise grant us access to a 164 | Third-Party Social Media Service, We may collect Personal data that 165 | is already associated with Your Third-Party Social Media Service's 166 | account, such as Your name, Your email address, Your activities or 167 | Your contact list associated with that account. 168 |

169 |

170 | You may also have the option of sharing additional information with 171 | the Company through Your Third-Party Social Media Service's account. 172 | If You choose to provide such information and Personal Data, during 173 | registration or otherwise, You are giving the Company permission to 174 | use, share, and store it in a manner consistent with this Privacy 175 | Policy. 176 |

177 |

Tracking Technologies and Cookies

178 |

179 | We use Cookies and similar tracking technologies to track the 180 | activity on Our Service and store certain information. Tracking 181 | technologies used are beacons, tags, and scripts to collect and 182 | track information and to improve and analyze Our Service. The 183 | technologies We use may include: 184 |

185 |
    186 |
  • 187 | Cookies or Browser Cookies. A cookie is a small 188 | file placed on Your Device. You can instruct Your browser to 189 | refuse all Cookies or to indicate when a Cookie is being sent. 190 | However, if You do not accept Cookies, You may not be able to use 191 | some parts of our Service. Unless you have adjusted Your browser 192 | setting so that it will refuse Cookies, our Service may use 193 | Cookies. 194 |
  • 195 |
  • 196 | Web Beacons. Certain sections of our Service and 197 | our emails may contain small electronic files known as web beacons 198 | (also referred to as clear gifs, pixel tags, and single-pixel 199 | gifs) that permit the Company, for example, to count users who 200 | have visited those pages or opened an email and for other related 201 | website statistics (for example, recording the popularity of a 202 | certain section and verifying system and server integrity). 203 |
  • 204 |
205 |

206 | Cookies can be "Persistent" or "Session" Cookies. Persistent Cookies 207 | remain on Your personal computer or mobile device when You go 208 | offline, while Session Cookies are deleted as soon as You close Your 209 | web browser. 210 |

211 |

212 | We use both Session and Persistent Cookies for the purposes set out 213 | below: 214 |

215 |
    216 |
  • 217 |

    218 | Necessary / Essential Cookies 219 |

    220 |

    Type: Session Cookies

    221 |

    Administered by: Us

    222 |

    223 | Purpose: These Cookies are essential to provide You with 224 | services available through the Website and to enable You to use 225 | some of its features. They help to authenticate users and 226 | prevent fraudulent use of user accounts. Without these Cookies, 227 | the services that You have asked for cannot be provided, and We 228 | only use these Cookies to provide You with those services. 229 |

    230 |
  • 231 |
  • 232 |

    233 | Cookies Policy / Notice Acceptance Cookies 234 |

    235 |

    Type: Persistent Cookies

    236 |

    Administered by: Us

    237 |

    238 | Purpose: These Cookies identify if users have accepted the use 239 | of cookies on the Website. 240 |

    241 |
  • 242 |
  • 243 |

    244 | Functionality Cookies 245 |

    246 |

    Type: Persistent Cookies

    247 |

    Administered by: Us

    248 |

    249 | Purpose: These Cookies allow us to remember choices You make 250 | when You use the Website, such as remembering your login details 251 | or language preference. The purpose of these Cookies is to 252 | provide You with a more personal experience and to avoid You 253 | having to re-enter your preferences every time You use the 254 | Website. 255 |

    256 |
  • 257 |
258 |

259 | For more information about the cookies we use and your choices 260 | regarding cookies, please visit our Cookies Policy or the Cookies 261 | section of our Privacy Policy. 262 |

263 |

Use of Your Personal Data

264 |

The Company may use Personal Data for the following purposes:

265 |
    266 |
  • 267 |

    268 | To provide and maintain our Service, including 269 | to monitor the usage of our Service. 270 |

    271 |
  • 272 |
  • 273 |

    274 | To manage Your Account: to manage Your 275 | registration as a user of the Service. The Personal Data You 276 | provide can give You access to different functionalities of the 277 | Service that are available to You as a registered user. 278 |

    279 |
  • 280 |
  • 281 |

    282 | For the performance of a contract: the 283 | development, compliance and undertaking of the purchase contract 284 | for the products, items or services You have purchased or of any 285 | other contract with Us through the Service. 286 |

    287 |
  • 288 |
  • 289 |

    290 | To contact You: To contact You by email, 291 | telephone calls, SMS, or other equivalent forms of electronic 292 | communication, such as a mobile application's push notifications 293 | regarding updates or informative communications related to the 294 | functionalities, products or contracted services, including the 295 | security updates, when necessary or reasonable for their 296 | implementation. 297 |

    298 |
  • 299 |
  • 300 |

    301 | To provide You with news, special offers and 302 | general information about other goods, services and events which 303 | we offer that are similar to those that you have already 304 | purchased or enquired about unless You have opted not to receive 305 | such information. 306 |

    307 |
  • 308 |
  • 309 |

    310 | To manage Your requests: To attend and manage 311 | Your requests to Us. 312 |

    313 |
  • 314 |
  • 315 |

    316 | For business transfers: We may use Your 317 | information to evaluate or conduct a merger, divestiture, 318 | restructuring, reorganization, dissolution, or other sale or 319 | transfer of some or all of Our assets, whether as a going 320 | concern or as part of bankruptcy, liquidation, or similar 321 | proceeding, in which Personal Data held by Us about our Service 322 | users is among the assets transferred. 323 |

    324 |
  • 325 |
  • 326 |

    327 | For other purposes: We may use Your information 328 | for other purposes, such as data analysis, identifying usage 329 | trends, determining the effectiveness of our promotional 330 | campaigns and to evaluate and improve our Service, products, 331 | services, marketing and your experience. 332 |

    333 |
  • 334 |
335 |

336 | We may share Your personal information in the following situations: 337 |

338 |
    339 |
  • 340 | With Service Providers: We may share Your 341 | personal information with Service Providers to monitor and analyze 342 | the use of our Service, to contact You. 343 |
  • 344 |
  • 345 | For business transfers: We may share or transfer 346 | Your personal information in connection with, or during 347 | negotiations of, any merger, sale of Company assets, financing, or 348 | acquisition of all or a portion of Our business to another 349 | company. 350 |
  • 351 |
  • 352 | With Affiliates: We may share Your information 353 | with Our affiliates, in which case we will require those 354 | affiliates to honor this Privacy Policy. Affiliates include Our 355 | parent company and any other subsidiaries, joint venture partners 356 | or other companies that We control or that are under common 357 | control with Us. 358 |
  • 359 |
  • 360 | With business partners: We may share Your 361 | information with Our business partners to offer You certain 362 | products, services or promotions. 363 |
  • 364 |
  • 365 | With other users: when You share personal 366 | information or otherwise interact in the public areas with other 367 | users, such information may be viewed by all users and may be 368 | publicly distributed outside. If You interact with other users or 369 | register through a Third-Party Social Media Service, Your contacts 370 | on the Third-Party Social Media Service may see Your name, 371 | profile, pictures and description of Your activity. Similarly, 372 | other users will be able to view descriptions of Your activity, 373 | communicate with You and view Your profile. 374 |
  • 375 |
  • 376 | With Your consent: We may disclose Your personal 377 | information for any other purpose with Your consent. 378 |
  • 379 |
380 |

Retention of Your Personal Data

381 |

382 | The Company will retain Your Personal Data only for as long as is 383 | necessary for the purposes set out in this Privacy Policy. We will 384 | retain and use Your Personal Data to the extent necessary to comply 385 | with our legal obligations (for example, if we are required to 386 | retain your data to comply with applicable laws), resolve disputes, 387 | and enforce our legal agreements and policies. 388 |

389 |

390 | The Company will also retain Usage Data for internal analysis 391 | purposes. Usage Data is generally retained for a shorter period of 392 | time, except when this data is used to strengthen the security or to 393 | improve the functionality of Our Service, or We are legally 394 | obligated to retain this data for longer time periods. 395 |

396 |

Transfer of Your Personal Data

397 |

398 | Your information, including Personal Data, is processed at the 399 | Company's operating offices and in any other places where the 400 | parties involved in the processing are located. It means that this 401 | information may be transferred to — and maintained on — computers 402 | located outside of Your state, province, country or other 403 | governmental jurisdiction where the data protection laws may differ 404 | than those from Your jurisdiction. 405 |

406 |

407 | Your consent to this Privacy Policy followed by Your submission of 408 | such information represents Your agreement to that transfer. 409 |

410 |

411 | The Company will take all steps reasonably necessary to ensure that 412 | Your data is treated securely and in accordance with this Privacy 413 | Policy and no transfer of Your Personal Data will take place to an 414 | organization or a country unless there are adequate controls in 415 | place including the security of Your data and other personal 416 | information. 417 |

418 |

Delete Your Personal Data

419 |

420 | You have the right to delete or request that We assist in deleting 421 | the Personal Data that We have collected about You. 422 |

423 |

424 | Our Service may give You the ability to delete certain information 425 | about You from within the Service. 426 |

427 |

428 | You may update, amend, or delete Your information at any time by 429 | signing in to Your Account, if you have one, and visiting the 430 | account settings section that allows you to manage Your personal 431 | information. You may also contact Us to request access to, correct, 432 | or delete any personal information that You have provided to Us. 433 |

434 |

435 | Please note, however, that We may need to retain certain information 436 | when we have a legal obligation or lawful basis to do so. 437 |

438 |

Disclosure of Your Personal Data

439 |

Business Transactions

440 |

441 | If the Company is involved in a merger, acquisition or asset sale, 442 | Your Personal Data may be transferred. We will provide notice before 443 | Your Personal Data is transferred and becomes subject to a different 444 | Privacy Policy. 445 |

446 |

Law enforcement

447 |

448 | Under certain circumstances, the Company may be required to disclose 449 | Your Personal Data if required to do so by law or in response to 450 | valid requests by public authorities (e.g. a court or a government 451 | agency). 452 |

453 |

Other legal requirements

454 |

455 | The Company may disclose Your Personal Data in the good faith belief 456 | that such action is necessary to: 457 |

458 |
    459 |
  • Comply with a legal obligation
  • 460 |
  • Protect and defend the rights or property of the Company
  • 461 |
  • 462 | Prevent or investigate possible wrongdoing in connection with the 463 | Service 464 |
  • 465 |
  • 466 | Protect the personal safety of Users of the Service or the public 467 |
  • 468 |
  • Protect against legal liability
  • 469 |
470 |

Security of Your Personal Data

471 |

472 | The security of Your Personal Data is important to Us, but remember 473 | that no method of transmission over the Internet, or method of 474 | electronic storage is 100% secure. While We strive to use 475 | commercially acceptable means to protect Your Personal Data, We 476 | cannot guarantee its absolute security. 477 |

478 |

Children's Privacy

479 |

480 | Our Service does not address anyone under the age of 13. We do not 481 | knowingly collect personally identifiable information from anyone 482 | under the age of 13. If You are a parent or guardian and You are 483 | aware that Your child has provided Us with Personal Data, please 484 | contact Us. If We become aware that We have collected Personal Data 485 | from anyone under the age of 13 without verification of parental 486 | consent, We take steps to remove that information from Our servers. 487 |

488 |

489 | If We need to rely on consent as a legal basis for processing Your 490 | information and Your country requires consent from a parent, We may 491 | require Your parent's consent before We collect and use that 492 | information. 493 |

494 |

Links to Other Websites

495 |

496 | Our Service may contain links to other websites that are not 497 | operated by Us. If You click on a third party link, You will be 498 | directed to that third party's site. We strongly advise You to 499 | review the Privacy Policy of every site You visit. 500 |

501 |

502 | We have no control over and assume no responsibility for the 503 | content, privacy policies or practices of any third party sites or 504 | services. 505 |

506 |

Changes to this Privacy Policy

507 |

508 | We may update Our Privacy Policy from time to time. We will notify 509 | You of any changes by posting the new Privacy Policy on this page. 510 |

511 |

512 | We will let You know via email and/or a prominent notice on Our 513 | Service, prior to the change becoming effective and update the "Last 514 | updated" date at the top of this Privacy Policy. 515 |

516 |

517 | You are advised to review this Privacy Policy periodically for any 518 | changes. Changes to this Privacy Policy are effective when they are 519 | posted on this page. 520 |

521 |

Contact Us

522 |

523 | If you have any questions about this Privacy Policy, You can contact 524 | us: 525 |

526 |
    527 |
  • By email: danielpark1239@gmail.com
  • 528 |
529 |
530 |
532 | 533 | ) 534 | } 535 | 536 | export default PrivacyPolicy 537 | --------------------------------------------------------------------------------