├── .env.example
├── .eslintrc.json
├── .gitignore
├── .husky
└── pre-commit
├── .prettierignore
├── .prettierrc
├── README.md
├── app
├── (home)
│ ├── layout.tsx
│ └── page.tsx
├── action.ts
├── blog
│ └── [id]
│ │ └── page.tsx
├── favicon.ico
├── globals.css
└── layout.tsx
├── components.json
├── components
├── all-blogs.tsx
├── generate.tsx
├── nav
│ ├── footer.tsx
│ ├── index.ts
│ └── navbar.tsx
└── ui
│ ├── button.tsx
│ ├── card.tsx
│ ├── input.tsx
│ └── sonner.tsx
├── lib
├── supabase.ts
└── utils.ts
├── middleware.ts
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── brand-logo.svg
└── brand.png
├── tailwind.config.ts
└── tsconfig.json
/.env.example:
--------------------------------------------------------------------------------
1 | SUPABASE_URL=
2 | SUPABASE_SERVICE_KEY=
3 | OPENAI_API_KEY=
4 | CLERK_SECRET_KEY=
5 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.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 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | # env files
30 | .env*.local
31 | .env
32 |
33 | # vercel
34 | .vercel
35 |
36 | # typescript
37 | *.tsbuildinfo
38 | next-env.d.ts
39 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx lint-staged
5 | npm run build
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .next
3 | yarn.lock
4 | package-lock.json
5 | public
6 | .history
7 | .env
8 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "avoid",
3 | "jsxSingleQuote": false,
4 | "proseWrap": "always",
5 | "printWidth": 80,
6 | "trailingComma": "es5",
7 | "tabWidth": 4,
8 | "semi": true,
9 | "singleQuote": false,
10 | "plugins": ["prettier-plugin-tailwindcss"]
11 | }
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AI-Powered Blog Generator
2 |
3 |
4 | ### Project Overview
5 | This project is an AI-powered blog generator that allows users to create blog posts with just a single click. It utilizes advanced AI algorithms for generating plagiarism-free content and fetching relevant images to accompany the text.
6 |
7 | ### Features
8 | 1. **One-Click Blog Generation**: Users can generate fully-formed blog posts with just a single click, saving time and effort.
9 | 2. **Plagiarism-Free Content**: The content generated by the AI is guaranteed to be original and plagiarism-free.
10 | 3. **Image Integration**: Relevant images are fetched automatically to complement the generated content, enhancing the visual appeal of the blog post.
11 |
12 | ## Built with
13 |
14 | - [Next.js](https://nextjs.org/)
15 | - [Tailwind CSS](https://tailwindcss.com/)
16 | - [shadcn/ui](https://ui.shadcn.com/)
17 | - [Clerk](https://clerk.com/)
18 | - [Supabase](https://supabase.com/)
19 |
20 | ## Feature Requests
21 |
22 | To request a feature open a [GitHub issue](https://github.com/anayatkhan1/GenieBlog/issues).
23 |
24 | ## Contribution Guidelines
25 |
26 | Thank you for considering contributing to our AI-powered blog generator project! Please follow these guidelines to ensure smooth collaboration:
27 |
28 | 1. Fork the repository to your GitHub account.
29 | 2. Clone the forked repository to your local machine:
30 |
31 | ```bash
32 | git clone https://github.com/your-name/GenieBlog.git
33 | ```
34 |
35 | 3. Create a new branch for your changes:
36 |
37 | ```bash
38 | git checkout -b feature/your-feature-name
39 | ```
40 |
41 | 4. Make your changes and ensure they adhere to the project's coding style and guidelines.
42 | 5. Test your changes thoroughly to avoid introducing bugs.
43 | 6. Commit your changes with clear and descriptive commit messages:
44 |
45 | ```bash
46 | git commit -m 'Add your descriptive commit message'
47 | ```
48 |
49 | 7. Push your changes to your forked repository:
50 |
51 | ```bash
52 | git push origin feature/your-feature-name
53 | ```
54 |
55 | 8. Open a pull request against the `main` branch of the original repository.
56 | 9. Provide a clear and concise description of your changes in the pull request, along with any relevant information.
57 | 10. Ensure your pull request passes all checks and tests before requesting a review.
58 |
59 | ## Setting Up Environment Variables
60 |
61 | To run the project locally, you need to set up the following environment variables:
62 |
63 | - `SUPABASE_URL`: URL of your Supabase project.
64 | - `SUPABASE_SERVICE_KEY`: Service key for accessing your Supabase project.
65 | - `OPENAI_API_KEY`: API key for accessing the OpenAI platform.
66 | - `CLERK_SECRET_KEY`: Secret key for Clerk authentication.
67 | - `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY`: Publishable key for Clerk authentication.
68 |
69 | You can set these environment variables by creating a `.env.local or .env` file in the root directory of the project and adding the variables with their respective values:
70 |
71 | Ensure not to share sensitive information like API keys publicly.
72 |
73 | Once you've set up the environment variables, you can run the project locally without any issues. If you encounter any problems, feel free to reach out for assistance.
74 |
--------------------------------------------------------------------------------
/app/(home)/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Navbar, Footer } from "@/components/nav";
3 |
4 | export const metadata: Metadata = {
5 | title: "Genie Blog",
6 | description: "Generated Blog content with Powerful AI tool",
7 | };
8 |
9 | export default function HomeLayout({
10 | children,
11 | }: {
12 | children: React.ReactNode;
13 | }) {
14 | return (
15 | <>
16 | {/** Background Snippet */}
17 |
20 |
21 |
22 | {children}
23 | {/* */}
24 |
25 | >
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/app/(home)/page.tsx:
--------------------------------------------------------------------------------
1 | import AllBlogs from "@/components/all-blogs";
2 | import Generate from "@/components/generate";
3 |
4 | export const maxDuration = 10;
5 | const Home = async () => {
6 | return (
7 |
13 | );
14 | };
15 |
16 | export default Home;
17 |
--------------------------------------------------------------------------------
/app/action.ts:
--------------------------------------------------------------------------------
1 | "use server";
2 |
3 | import OpenAI from "openai";
4 | import { auth } from "@clerk/nextjs";
5 | import { supabase } from "@/lib/supabase";
6 | import { decode } from "base64-arraybuffer";
7 | import { revalidatePath } from "next/cache";
8 | import { redirect } from "next/navigation";
9 |
10 | const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
11 |
12 | export async function createCompletion(prompt: string) {
13 | if (!prompt) {
14 | return { error: "Ohho! Prompt is required" };
15 | }
16 |
17 | const { userId } = auth();
18 | if (!userId) {
19 | return { error: "User is not logged in" };
20 | }
21 |
22 | // generate blog post using openai
23 | const messages: any = [
24 | {
25 | role: "user",
26 | content: `write a blog post around 200 words about the following topics: [${prompt}]
27 | Before generating the blog content follow my instructions:
28 | 1. write a only title of the blog in bold under 10 words.
29 | 2. Ensure 100% originality to avoid plagiarism.`,
30 | },
31 | ];
32 |
33 | const completion = await openai.chat.completions.create({
34 | messages,
35 | model: "gpt-3.5-turbo",
36 | });
37 |
38 | const content = completion?.choices?.[0]?.message?.content;
39 | if (!content) {
40 | return { error: "Unable to generate the blog content" };
41 | }
42 |
43 | // generate an image using openai
44 | const image = await openai.images.generate({
45 | model: "dall-e-2",
46 | prompt: `Generate an image for a blog post about [${prompt}]
47 | Before creating an image for a blog post, adhere to these instructions:
48 | 1. A 3D unreal most realistic image of this ${prompt}.`,
49 | n: 1,
50 | size: "1024x1024",
51 | response_format: "b64_json",
52 | });
53 | const imageName = `blog-${Date.now()}`;
54 | const imageData = image?.data?.[0]?.b64_json as string;
55 | if (!imageData) {
56 | return { error: "Unable to generate the blog image" };
57 | }
58 |
59 | //upload the image to supabase storage
60 | const { data, error } = await supabase.storage
61 | .from("blogs")
62 | .upload(imageName, decode(imageData), {
63 | contentType: "image/png",
64 | });
65 | if (error) {
66 | return { error: "Unable to Upload the blog image to Storage" };
67 | }
68 |
69 | const path = data?.path;
70 | const url = process.env.SUPABASE_URL;
71 | const imageUrl = `${url}/storage/v1/object/public/blogs/${path}`;
72 |
73 | // create a new blog post in supabase
74 | const { data: blog, error: blogError } = await supabase
75 | .from("blogs")
76 | .insert([{ title: prompt, content, imageUrl, userId }])
77 | .select();
78 |
79 | if (blogError) {
80 | return { error: "Unable to insert the blog into the database." };
81 | }
82 | const blogId = blog?.[0]?.id;
83 | revalidatePath("/");
84 | redirect(`/blog/${blogId}`);
85 | }
86 |
--------------------------------------------------------------------------------
/app/blog/[id]/page.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import Image from "next/image";
3 | import { getBlogId } from "@/lib/supabase";
4 | import { ChevronLeft } from "lucide-react";
5 | import Markdown from "react-markdown";
6 |
7 | const Blog = async ({ params }: { params: { id: string } }) => {
8 | const { content, imageUrl } = await getBlogId(Number(params.id));
9 | return (
10 |
11 |
15 |
16 | Go back
17 |
18 |
19 |
23 |
24 | );
25 | };
26 |
27 | export default Blog;
28 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anayatkhan1/GenieBlog/674279c7720026515a68c21e400e4ccf1524753d/app/favicon.ico
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 240 10% 3.9%;
9 | --card: 0 0% 100%;
10 | --card-foreground: 240 10% 3.9%;
11 | --popover: 0 0% 100%;
12 | --popover-foreground: 240 10% 3.9%;
13 | --primary: 240 5.9% 10%;
14 | --primary-foreground: 0 0% 98%;
15 | --secondary: 240 4.8% 95.9%;
16 | --secondary-foreground: 240 5.9% 10%;
17 | --muted: 240 4.8% 95.9%;
18 | --muted-foreground: 240 3.8% 46.1%;
19 | --accent: 240 4.8% 95.9%;
20 | --accent-foreground: 240 5.9% 10%;
21 | --destructive: 0 84.2% 60.2%;
22 | --destructive-foreground: 0 0% 98%;
23 | --border: 240 5.9% 90%;
24 | --input: 240 5.9% 90%;
25 | --ring: 240 5.9% 10%;
26 | --radius: 0.5rem;
27 | }
28 |
29 | .dark {
30 | --background: 240 10% 3.9%;
31 | --foreground: 0 0% 98%;
32 | --card: 240 10% 3.9%;
33 | --card-foreground: 0 0% 98%;
34 | --popover: 240 10% 3.9%;
35 | --popover-foreground: 0 0% 98%;
36 | --primary: 0 0% 98%;
37 | --primary-foreground: 240 5.9% 10%;
38 | --secondary: 240 3.7% 15.9%;
39 | --secondary-foreground: 0 0% 98%;
40 | --muted: 240 3.7% 15.9%;
41 | --muted-foreground: 240 5% 64.9%;
42 | --accent: 240 3.7% 15.9%;
43 | --accent-foreground: 0 0% 98%;
44 | --destructive: 0 62.8% 30.6%;
45 | --destructive-foreground: 0 0% 98%;
46 | --border: 240 3.7% 15.9%;
47 | --input: 240 3.7% 15.9%;
48 | --ring: 240 4.9% 83.9%;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { ClerkProvider } from "@clerk/nextjs";
3 | import { Toaster } from "@/components/ui/sonner";
4 | import { Inter } from "next/font/google";
5 | import "./globals.css";
6 |
7 | const inter = Inter({ subsets: ["latin"] });
8 |
9 | export const metadata: Metadata = {
10 | title: "Genie Blog",
11 | description:
12 | "Step into GenieBlog, where your writing dreams come true with just a prompt! Our AI-powered platform transforms your ideas into polished blog posts in a blink. No more staring at a blank page let GenieBlog unleash your creativity effortlessly!",
13 | };
14 |
15 | export default function RootLayout({
16 | children,
17 | }: {
18 | children: React.ReactNode;
19 | }) {
20 | return (
21 |
22 |
23 |
24 |
25 |
26 |
27 | {children}
28 |
29 |
30 |
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "app/globals.css",
9 | "baseColor": "zinc",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/components/all-blogs.tsx:
--------------------------------------------------------------------------------
1 | import { getAllBlogs } from "@/lib/supabase";
2 | import Link from "next/link";
3 | import Image from "next/image";
4 | import { formData } from "@/lib/utils";
5 | import { Card, CardContent } from "@/components/ui/card";
6 |
7 | const AllBlogs = async () => {
8 | const blogs = await getAllBlogs();
9 | return (
10 |
11 |
12 | Recent Blogs
13 |
14 |
15 | {blogs?.map(blog => (
16 |
17 |
18 |
19 |
26 |
27 |
28 | {blog.title}
29 |
30 |
31 | {formData(blog.created_at)}
32 |
33 |
34 |
35 |
36 |
37 | ))}
38 |
39 |
40 | );
41 | };
42 |
43 | export default AllBlogs;
44 |
--------------------------------------------------------------------------------
/components/generate.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Input } from "./ui/input";
3 |
4 | import {
5 | Card,
6 | CardContent,
7 | CardDescription,
8 | CardHeader,
9 | CardTitle,
10 | } from "./ui/card";
11 | import { SignedIn, SignedOut, SignInButton } from "@clerk/nextjs";
12 | import { Button } from "./ui/button";
13 | import { toast } from "sonner";
14 | import { createCompletion } from "@/app/action";
15 | import { useFormStatus } from "react-dom";
16 | import { cn } from "@/lib/utils";
17 |
18 | const Generate = () => {
19 | async function action(formData: FormData) {
20 | const prompt = formData.get("prompt");
21 |
22 | // call server action
23 |
24 | const result = await createCompletion(prompt as string);
25 | if (result?.error) {
26 | toast.error(result.error);
27 | }
28 | }
29 |
30 | return (
31 |
32 |
33 |
34 |
35 | Genie AI Blogger
36 |
37 |
38 | Generate a blog post about anything
39 |
40 |
41 |
42 |
50 |
51 |
52 |
53 | );
54 | };
55 |
56 | const SubmitButton = () => {
57 | const { pending } = useFormStatus();
58 |
59 | return (
60 | <>
61 |
62 |
72 |
73 |
74 |
75 |
83 |
84 |
85 | >
86 | );
87 | };
88 |
89 | export default Generate;
90 |
--------------------------------------------------------------------------------
/components/nav/footer.tsx:
--------------------------------------------------------------------------------
1 | const Footer = () => {
2 | return ;
3 | };
4 |
5 | export default Footer;
6 |
--------------------------------------------------------------------------------
/components/nav/index.ts:
--------------------------------------------------------------------------------
1 | import Footer from "./footer";
2 | import Navbar from "./navbar";
3 |
4 | export { Navbar, Footer };
5 |
--------------------------------------------------------------------------------
/components/nav/navbar.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import Image from "next/image";
3 | import { Github } from "lucide-react";
4 | import { Button } from "../ui/button";
5 | import { SignInButton, SignedIn, SignedOut, UserButton } from "@clerk/nextjs";
6 |
7 | const Navbar = () => {
8 | return (
9 |
10 |
37 |
38 | );
39 | };
40 |
41 | export default Navbar;
42 |
--------------------------------------------------------------------------------
/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Slot } from "@radix-ui/react-slot";
3 | import { cva, type VariantProps } from "class-variance-authority";
4 |
5 | import { cn } from "@/lib/utils";
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90",
14 | destructive:
15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16 | outline:
17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
18 | secondary:
19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20 | ghost: "hover:bg-accent hover:text-accent-foreground",
21 | link: "text-primary underline-offset-4 hover:underline",
22 | },
23 | size: {
24 | default: "h-9 px-4 py-2",
25 | sm: "h-8 rounded-md px-3 text-xs",
26 | lg: "h-10 rounded-md px-8",
27 | icon: "h-9 w-9",
28 | },
29 | },
30 | defaultVariants: {
31 | variant: "default",
32 | size: "default",
33 | },
34 | }
35 | );
36 |
37 | export interface ButtonProps
38 | extends React.ButtonHTMLAttributes,
39 | VariantProps {
40 | asChild?: boolean;
41 | }
42 |
43 | const Button = React.forwardRef(
44 | ({ className, variant, size, asChild = false, ...props }, ref) => {
45 | const Comp = asChild ? Slot : "button";
46 | return (
47 |
52 | );
53 | }
54 | );
55 | Button.displayName = "Button";
56 |
57 | export { Button, buttonVariants };
58 |
--------------------------------------------------------------------------------
/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "@/lib/utils";
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ));
18 | Card.displayName = "Card";
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ));
30 | CardHeader.displayName = "CardHeader";
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
41 | ));
42 | CardTitle.displayName = "CardTitle";
43 |
44 | const CardDescription = React.forwardRef<
45 | HTMLParagraphElement,
46 | React.HTMLAttributes
47 | >(({ className, ...props }, ref) => (
48 |
53 | ));
54 | CardDescription.displayName = "CardDescription";
55 |
56 | const CardContent = React.forwardRef<
57 | HTMLDivElement,
58 | React.HTMLAttributes
59 | >(({ className, ...props }, ref) => (
60 |
61 | ));
62 | CardContent.displayName = "CardContent";
63 |
64 | const CardFooter = React.forwardRef<
65 | HTMLDivElement,
66 | React.HTMLAttributes
67 | >(({ className, ...props }, ref) => (
68 |
73 | ));
74 | CardFooter.displayName = "CardFooter";
75 |
76 | export {
77 | Card,
78 | CardHeader,
79 | CardFooter,
80 | CardTitle,
81 | CardDescription,
82 | CardContent,
83 | };
84 |
--------------------------------------------------------------------------------
/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "@/lib/utils";
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | );
21 | }
22 | );
23 | Input.displayName = "Input";
24 |
25 | export { Input };
26 |
--------------------------------------------------------------------------------
/components/ui/sonner.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useTheme } from "next-themes";
4 | import { Toaster as Sonner } from "sonner";
5 |
6 | type ToasterProps = React.ComponentProps;
7 |
8 | const Toaster = ({ ...props }: ToasterProps) => {
9 | const { theme = "system" } = useTheme();
10 |
11 | return (
12 |
27 | );
28 | };
29 |
30 | export { Toaster };
31 |
--------------------------------------------------------------------------------
/lib/supabase.ts:
--------------------------------------------------------------------------------
1 | import { createClient } from "@supabase/supabase-js";
2 |
3 | const supabaseUrl = process.env.SUPABASE_URL as string;
4 | const supabaseKey = process.env.SUPABASE_SERVICE_KEY as string;
5 |
6 | export const supabase = createClient(supabaseUrl, supabaseKey);
7 |
8 | export async function getBlogId(id: number) {
9 | const { data, error } = await supabase
10 | .from("blogs")
11 | .select()
12 | .eq("id", id)
13 | .single();
14 |
15 | return data;
16 | }
17 |
18 | export async function getAllBlogs() {
19 | const { data, error } = await supabase
20 | .from("blogs")
21 | .select()
22 | .order("created_at", { ascending: false });
23 | return data;
24 | }
25 |
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
8 | export function formData(date: string) {
9 | return new Date(date).toLocaleDateString("en-US", {
10 | month: "long",
11 | day: "numeric",
12 | year: "numeric",
13 | });
14 | }
15 |
--------------------------------------------------------------------------------
/middleware.ts:
--------------------------------------------------------------------------------
1 | import { authMiddleware } from "@clerk/nextjs";
2 |
3 | export default authMiddleware({
4 | publicRoutes: () => true,
5 | });
6 | export const config = {
7 | matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"],
8 | };
9 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | images: {
4 | remotePatterns: [
5 | {
6 | protocol: "https",
7 | hostname: "pdjofjygvmxklpeamnld.supabase.co",
8 | },
9 | ],
10 | },
11 | env: {
12 | SUPABASE_URL: process.env.SUPABASE_URL,
13 | SUPABASE_SERVICE_KEY: process.env.SUPABASE_SERVICE_KEY,
14 | OPENAI_API_KEY: process.env.OPENAI_API_KEY,
15 | CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY,
16 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
17 | },
18 | };
19 |
20 | module.exports = nextConfig;
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "genie-blog",
3 | "description": "Ai generated Blog",
4 | "author": "Anayat khan",
5 | "version": "0.1.0",
6 | "private": true,
7 | "scripts": {
8 | "dev": "next dev",
9 | "build": "next build",
10 | "start": "next start",
11 | "lint": "eslint . --ext ts --ext tsx --ext js",
12 | "format": "prettier --write ."
13 | },
14 | "lint-staged": {
15 | "*.@(ts|tsx)": [
16 | "npm run lint",
17 | "npm run format"
18 | ]
19 | },
20 | "dependencies": {
21 | "@clerk/nextjs": "^4.29.7",
22 | "@radix-ui/react-icons": "^1.3.0",
23 | "@radix-ui/react-slot": "^1.0.2",
24 | "@supabase/auth-helpers-nextjs": "^0.9.0",
25 | "@supabase/supabase-js": "^2.39.7",
26 | "base64-arraybuffer": "^1.0.2",
27 | "class-variance-authority": "^0.7.0",
28 | "clsx": "^2.1.0",
29 | "lucide-react": "^0.338.0",
30 | "next": "14.1.0",
31 | "next-themes": "^0.2.1",
32 | "openai": "^4.28.0",
33 | "react": "^18.2.0",
34 | "react-dom": "^18.2.0",
35 | "react-markdown": "^9.0.1",
36 | "sonner": "^1.4.1",
37 | "tailwind-merge": "^2.2.1",
38 | "tailwindcss-animate": "^1.0.7"
39 | },
40 | "devDependencies": {
41 | "@tailwindcss/typography": "^0.5.10",
42 | "@types/node": "^20.11.10",
43 | "@types/react": "^18.2.48",
44 | "@types/react-dom": "^18.2.18",
45 | "autoprefixer": "^10.4.17",
46 | "eslint": "^8.56.0",
47 | "eslint-config-next": "14.1.0",
48 | "husky": "^9.0.10",
49 | "lint-staged": "^15.2.2",
50 | "postcss": "^8.4.33",
51 | "prettier": "^3.2.4",
52 | "prettier-plugin-tailwindcss": "^0.5.11",
53 | "tailwindcss": "^3.4.1",
54 | "typescript": "^5.3.3"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/public/brand-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/brand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/anayatkhan1/GenieBlog/674279c7720026515a68c21e400e4ccf1524753d/public/brand.png
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | const config = {
4 | darkMode: ["class"],
5 | content: [
6 | "./pages/**/*.{ts,tsx}",
7 | "./components/**/*.{ts,tsx}",
8 | "./app/**/*.{ts,tsx}",
9 | "./src/**/*.{ts,tsx}",
10 | ],
11 | prefix: "",
12 | theme: {
13 | container: {
14 | center: true,
15 | padding: "2rem",
16 | screens: {
17 | "2xl": "1400px",
18 | },
19 | },
20 | extend: {
21 | colors: {
22 | border: "hsl(var(--border))",
23 | input: "hsl(var(--input))",
24 | ring: "hsl(var(--ring))",
25 | background: "hsl(var(--background))",
26 | foreground: "hsl(var(--foreground))",
27 | primary: {
28 | DEFAULT: "hsl(var(--primary))",
29 | foreground: "hsl(var(--primary-foreground))",
30 | },
31 | secondary: {
32 | DEFAULT: "hsl(var(--secondary))",
33 | foreground: "hsl(var(--secondary-foreground))",
34 | },
35 | destructive: {
36 | DEFAULT: "hsl(var(--destructive))",
37 | foreground: "hsl(var(--destructive-foreground))",
38 | },
39 | muted: {
40 | DEFAULT: "hsl(var(--muted))",
41 | foreground: "hsl(var(--muted-foreground))",
42 | },
43 | accent: {
44 | DEFAULT: "hsl(var(--accent))",
45 | foreground: "hsl(var(--accent-foreground))",
46 | },
47 | popover: {
48 | DEFAULT: "hsl(var(--popover))",
49 | foreground: "hsl(var(--popover-foreground))",
50 | },
51 | card: {
52 | DEFAULT: "hsl(var(--card))",
53 | foreground: "hsl(var(--card-foreground))",
54 | },
55 | },
56 | borderRadius: {
57 | lg: "var(--radius)",
58 | md: "calc(var(--radius) - 2px)",
59 | sm: "calc(var(--radius) - 4px)",
60 | },
61 | keyframes: {
62 | "accordion-down": {
63 | from: { height: "0" },
64 | to: { height: "var(--radix-accordion-content-height)" },
65 | },
66 | "accordion-up": {
67 | from: { height: "var(--radix-accordion-content-height)" },
68 | to: { height: "0" },
69 | },
70 | },
71 | animation: {
72 | "accordion-down": "accordion-down 0.2s ease-out",
73 | "accordion-up": "accordion-up 0.2s ease-out",
74 | },
75 | },
76 | },
77 | plugins: [
78 | require("@tailwindcss/typography"),
79 | require("tailwindcss-animate"),
80 | ],
81 | };
82 |
83 | export default config;
84 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------