├── .eslintrc.json
├── .gitignore
├── README.md
├── app
├── api
│ └── paste
│ │ └── route.ts
├── delete
│ └── [slug]
│ │ └── page.tsx
├── favicon.ico
├── globals.css
├── layout.tsx
├── loading.tsx
├── page.tsx
├── paste
│ └── [slug]
│ │ └── page.tsx
├── pastes
│ └── page.tsx
└── raw
│ └── [slug]
│ └── route.ts
├── components.json
├── components
├── ads
│ └── ad.tsx
├── footer
│ └── footer.tsx
├── highlight
│ └── code.tsx
├── main
│ ├── combo-box.tsx
│ └── upload-area.tsx
├── navigation
│ └── nav.tsx
├── theme
│ ├── theme-provider.tsx
│ └── theme-toggle.tsx
└── ui
│ ├── button.tsx
│ ├── command.tsx
│ ├── dialog.tsx
│ ├── dropdown-menu.tsx
│ ├── popover.tsx
│ ├── separator.tsx
│ ├── textarea.tsx
│ ├── toast.tsx
│ ├── toaster.tsx
│ └── use-toast.ts
├── lib
├── prisma.ts
└── utils.ts
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── prisma
├── migrations
│ ├── 20230902234402_wow
│ │ └── migration.sql
│ └── migration_lock.toml
└── schema.prisma
├── public
├── cat.png
└── catdns.gif
├── tailwind.config.js
├── tailwind.config.ts
└── tsconfig.json
/.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 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env*.local
29 | .env
30 | /.env/
31 |
32 | # vercel
33 | .vercel
34 |
35 | # typescript
36 | *.tsbuildinfo
37 | next-env.d.ts
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | 1. **Clone the Repository**
4 |
5 | ```sh
6 | git clone https://github.com/TeaByte/catpaste.git
7 | cd catpaste
8 | ```
9 |
10 | 2. **Install Dependencies**
11 |
12 | ```sh
13 | npm install
14 | ```
15 |
16 | 3. **Make `.env` File**
17 |
18 | ```env
19 | DATABASE_URL=postgres://TeaByte:HuypCZG....
20 | SHADOW_DATABASE_URL=postgres://TeaByte:H....
21 | ```
22 |
23 | Neon PostgreSQL: https://console.neon.tech
24 |
25 | 4. **Start the Development Server**
26 |
27 | ```sh
28 | npm run dev
29 | ```
30 |
31 | ##
32 |
--------------------------------------------------------------------------------
/app/api/paste/route.ts:
--------------------------------------------------------------------------------
1 | import { prisma } from "@/lib/prisma";
2 | import { NextResponse } from "next/server";
3 | import { NextRequest } from "next/server";
4 | import { v4 as uuidv4 } from "uuid";
5 |
6 | export async function POST(request: NextRequest) {
7 | const data = await request.json();
8 | let { text, syntax } = data;
9 | if (!syntax && !text) {
10 | return NextResponse.json({
11 | message: "Not all fields are filled.",
12 | });
13 | }
14 | syntax = syntax.toLowerCase();
15 |
16 | if ((text as string).length > 2000) {
17 | return NextResponse.json({
18 | message: "Text too long.",
19 | });
20 | }
21 |
22 | try {
23 | const deleteToken = uuidv4();
24 | const paste = await prisma.paste.create({
25 | data: {
26 | text: text as string,
27 | syntax: syntax as string,
28 | delete: deleteToken,
29 | },
30 | });
31 | return NextResponse.json({
32 | message: `${paste.id} Pasted successfully.`,
33 | token: deleteToken,
34 | id: paste.id,
35 | });
36 | } catch (e) {
37 | return NextResponse.json({
38 | message: "Error while creating paste.",
39 | });
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/delete/[slug]/page.tsx:
--------------------------------------------------------------------------------
1 | import { prisma } from "@/lib/prisma";
2 | import {
3 | ExclamationTriangleIcon,
4 | CheckCircledIcon,
5 | } from "@radix-ui/react-icons";
6 |
7 | interface Props {
8 | params: { slug: string };
9 | }
10 |
11 | export default async function DeletePage({ params }: Props) {
12 | try {
13 | const paste = await prisma.paste.delete({
14 | where: {
15 | delete: params.slug,
16 | },
17 | });
18 | return (
19 |
20 |
21 | {`${paste.id} deleted successfully.`}
22 |
23 | );
24 | } catch {
25 | return (
26 |
27 |
28 | Already deleted or not found.
29 |
30 | );
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeaByte/cat-paste/7d112ab891574a83b7cb1d50eec2859b563bf9f4/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: 142.1 76.2% 36.3%;
14 | --primary-foreground: 355.7 100% 97.3%;
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: 142.1 76.2% 36.3%;
26 | --radius: 0.5rem;
27 | }
28 |
29 | .dark {
30 | --background: 20 14.3% 4.1%;
31 | --foreground: 0 0% 95%;
32 | --card: 24 9.8% 10%;
33 | --card-foreground: 0 0% 95%;
34 | --popover: 0 0% 9%;
35 | --popover-foreground: 0 0% 95%;
36 | --primary: 142.1 70.6% 45.3%;
37 | --primary-foreground: 144.9 80.4% 10%;
38 | --secondary: 240 3.7% 15.9%;
39 | --secondary-foreground: 0 0% 98%;
40 | --muted: 0 0% 15%;
41 | --muted-foreground: 240 5% 64.9%;
42 | --accent: 12 6.5% 15.1%;
43 | --accent-foreground: 0 0% 98%;
44 | --destructive: 0 62.8% 30.6%;
45 | --destructive-foreground: 0 85.7% 97.3%;
46 | --border: 240 3.7% 15.9%;
47 | --input: 240 3.7% 15.9%;
48 | --ring: 142.4 71.8% 29.2%;
49 | }
50 | }
51 |
52 | @layer base {
53 | * {
54 | @apply border-border;
55 | }
56 | body {
57 | @apply bg-background text-foreground;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import "./globals.css";
2 |
3 | import Nav from "@/components/navigation/nav";
4 | import Ad from "@/components/ads/ad";
5 | import Footer from "@/components/footer/footer";
6 | import { ThemeProvider } from "@/components/theme/theme-provider";
7 | import { Toaster } from "@/components/ui/toaster";
8 |
9 | import type { Metadata } from "next";
10 | import { Rubik } from "next/font/google";
11 |
12 | const font = Rubik({ subsets: ["latin"] });
13 |
14 | export const metadata: Metadata = {
15 | title: "Cat Paste",
16 | description:
17 | "CatPaste is a user-friendly online platform that allows individuals to easily and securely share and store text-based information, code snippets, and notes. Similar in concept to the popular Pastebin service, CatPaste offers a simple and efficient way to create, manage, and share text-based content with others. Whether you're a developer looking to share code snippets, a student collaborating on a project, or anyone in need of a convenient text-sharing solution, CatPaste is designed to simplify the process. With its clean and intuitive interface, users can quickly paste, share, and access text-based content, making it an indispensable tool for the digital age.",
18 | keywords: [
19 | "Text sharing",
20 | "Code sharing",
21 | "Paste tool",
22 | "Collaborative notes",
23 | "Secure sharing",
24 | "Text storage",
25 | "Content management",
26 | "Data sharing",
27 | "Online clipboard",
28 | "Information sharing",
29 | "Note collaboration",
30 | "Code repository",
31 | "Text collaboration",
32 | "Document sharing",
33 | "Text hosting",
34 | "Code storage",
35 | "Collaborative text",
36 | "Note hosting",
37 | "Text repository",
38 | ],
39 | };
40 |
41 | export default function RootLayout({
42 | children,
43 | }: {
44 | children: React.ReactNode;
45 | }) {
46 | return (
47 |
48 |
49 |
50 |
51 | {children}
52 |
53 |
54 |
55 |
56 |
57 |
58 | );
59 | }
60 |
--------------------------------------------------------------------------------
/app/loading.tsx:
--------------------------------------------------------------------------------
1 | import { ReloadIcon } from "@radix-ui/react-icons";
2 |
3 | export default function Loading() {
4 | return (
5 |
6 |
7 | Loading...
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/app/page.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import Code from "@/components/main/upload-area";
3 |
4 | export default function Home() {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/app/paste/[slug]/page.tsx:
--------------------------------------------------------------------------------
1 | import { prisma } from "@/lib/prisma";
2 | import Code from "@/components/highlight/code";
3 | import { ExclamationTriangleIcon } from "@radix-ui/react-icons";
4 |
5 | interface Props {
6 | params: { slug: string };
7 | }
8 |
9 | export default async function PastePage({ params }: Props) {
10 | const paste = await prisma.paste.findUnique({
11 | where: {
12 | id: params.slug,
13 | },
14 | });
15 |
16 | if (paste)
17 | return (
18 |
19 |
20 |
21 | );
22 |
23 | return (
24 |
25 |
26 | No Paste Found At This URL
27 |
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/app/pastes/page.tsx:
--------------------------------------------------------------------------------
1 | export const revalidate = 1;
2 |
3 | import { Share2Icon } from "@radix-ui/react-icons";
4 |
5 | import { Button } from "@/components/ui/button";
6 | import Link from "next/link";
7 | import { prisma } from "@/lib/prisma";
8 |
9 | export default async function Pastes() {
10 | const pastes = await prisma.paste.findMany({
11 | orderBy: {
12 | createdAt: "desc",
13 | },
14 | take: 6,
15 | });
16 |
17 | return (
18 |
19 |
20 |
21 |
28 |
29 |
30 | Last 6 Pastes
31 |
32 | {pastes.map((paste) => (
33 |
34 |
35 |
36 | {paste.text}
37 |
38 |
39 | ( {paste.syntax} )
40 |
41 |
42 |
43 | ))}
44 |
45 |
46 |
47 |
48 | );
49 | }
50 |
--------------------------------------------------------------------------------
/app/raw/[slug]/route.ts:
--------------------------------------------------------------------------------
1 | import { prisma } from "@/lib/prisma";
2 | import { NextResponse } from "next/server";
3 | import { NextRequest } from "next/server";
4 |
5 | interface Props {
6 | params: { slug: string };
7 | }
8 |
9 | export async function GET(request: NextRequest, { params }: Props) {
10 | const paste = await prisma.paste.findUnique({
11 | where: {
12 | id: params.slug,
13 | },
14 | });
15 |
16 | if (paste) {
17 | return new NextResponse(paste.text, {
18 | status: 200,
19 | headers: {
20 | "content-type": "text/plain",
21 | },
22 | });
23 | }
24 | return new NextResponse("Not Found!.", {
25 | status: 404,
26 | headers: {
27 | "content-type": "text/plain",
28 | },
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "app/globals.css",
9 | "baseColor": "slate",
10 | "cssVariables": true
11 | },
12 | "aliases": {
13 | "components": "@/components",
14 | "utils": "@/lib/utils"
15 | }
16 | }
--------------------------------------------------------------------------------
/components/ads/ad.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 |
3 | import { Separator } from "@/components/ui/separator";
4 | import ThemeToggle from "@/components/theme/theme-toggle";
5 | import { Button } from "../ui/button";
6 |
7 | export default function Ad() {
8 | return (
9 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/components/footer/footer.tsx:
--------------------------------------------------------------------------------
1 | import { Separator } from "@/components/ui/separator";
2 |
3 | export default function Footer() {
4 | return (
5 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/components/highlight/code.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import Prism from "prismjs";
4 | import "prismjs/themes/prism-twilight.css";
5 |
6 | import "prismjs/plugins/line-numbers/prism-line-numbers";
7 | import "prismjs/plugins/line-numbers/prism-line-numbers.css";
8 |
9 | import "prismjs/components/prism-python";
10 | import "prismjs/components/prism-rust";
11 | import "prismjs/components/prism-css";
12 | import "prismjs/components/prism-lua";
13 |
14 | import { useEffect, useState } from "react";
15 | import { FileTextIcon, ClipboardCopyIcon } from "@radix-ui/react-icons";
16 | import { Button } from "@/components/ui/button";
17 |
18 | interface Props {
19 | text: string;
20 | syntax: string;
21 | slug: string;
22 | }
23 |
24 | export default function Code({ text, syntax, slug }: Props) {
25 | const [copyButtonMessage, setCopyButtonMessage] = useState("Copy");
26 |
27 | useEffect(() => {
28 | const highlight = async () => {
29 | await Prism.highlightAll();
30 | };
31 | highlight();
32 | console.log(Object(Prism.languages));
33 | }, []);
34 |
35 | function handleCopy() {
36 | navigator.clipboard.writeText(text);
37 | setCopyButtonMessage("Copied!");
38 | }
39 |
40 | return (
41 | <>
42 |
61 |
62 | {text}
63 |
64 | >
65 | );
66 | }
67 |
--------------------------------------------------------------------------------
/components/main/combo-box.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useState } from "react";
4 | import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
5 | import { cn } from "@/lib/utils";
6 |
7 | import { Button } from "@/components/ui/button";
8 | import {
9 | Command,
10 | CommandEmpty,
11 | CommandGroup,
12 | CommandInput,
13 | CommandItem,
14 | } from "@/components/ui/command";
15 | import {
16 | Popover,
17 | PopoverContent,
18 | PopoverTrigger,
19 | } from "@/components/ui/popover";
20 | import { langs } from "@/lib/utils";
21 |
22 | const syntaxs = langs.map((lang) => ({
23 | value: lang.toLowerCase(),
24 | label: lang,
25 | }));
26 |
27 | interface ComboBoxProps {
28 | value: string;
29 | selectSyntax: (selectedValue: string) => void;
30 | }
31 |
32 | export function ComboBox({ value, selectSyntax }: ComboBoxProps) {
33 | const [open, setOpen] = useState(false);
34 |
35 | return (
36 | setOpen(!open)}>
37 |
38 |
49 |
50 |
51 |
52 |
53 | No Syntax found.
54 |
55 | {syntaxs.map((syntax) => (
56 | {
59 | selectSyntax(currentValue);
60 | setOpen(!open);
61 | }}
62 | >
63 | {syntax.label}
64 |
70 |
71 | ))}
72 |
73 |
74 |
75 |
76 | );
77 | }
78 |
--------------------------------------------------------------------------------
/components/main/upload-area.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { ReloadIcon } from "@radix-ui/react-icons";
4 | import { useRouter } from "next/navigation";
5 |
6 | import { Share2Icon } from "@radix-ui/react-icons";
7 | import { useState } from "react";
8 |
9 | import { useToast } from "@/components/ui/use-toast";
10 | import { ComboBox } from "@/components/main/combo-box";
11 | import { Textarea } from "@/components/ui/textarea";
12 | import { Button } from "@/components/ui/button";
13 |
14 | export default function ParentComponent() {
15 | const { toast } = useToast();
16 | const [syntax, setSyntax] = useState("");
17 | const [text, setText] = useState("");
18 |
19 | const [deleteURL, setDeleteURL] = useState("");
20 | const [refURL, setRefURL] = useState("");
21 |
22 | const [copyButtonMessage, setCopyButtonMessage] = useState("⚠️ Delete URL");
23 | const [copyPasteButtonMessage, setCopyPasteButtonMessage] =
24 | useState("📝 Paste URL");
25 |
26 | const [isError, setIsError] = useState(false);
27 | const [isSuccess, setIsSuccess] = useState(false);
28 | const [isLoading, setIsLoading] = useState(false);
29 |
30 | const router = useRouter();
31 |
32 | const selectSyntax = (selectedValue: string) => {
33 | setSyntax(selectedValue);
34 | };
35 |
36 | function copyURL() {
37 | navigator.clipboard.writeText(deleteURL);
38 | setCopyButtonMessage("📋 Delete Copied!");
39 | }
40 |
41 | function copyPasteURL() {
42 | navigator.clipboard.writeText(
43 | `https://cat-paste.vercel.app/paste/${refURL}`
44 | );
45 | setCopyPasteButtonMessage("📝 Paste Copied!");
46 | }
47 |
48 | function onRef() {
49 | router.push(`/paste/${refURL}`);
50 | }
51 |
52 | const onError = () => {
53 | setIsSuccess(false);
54 | setIsError(true);
55 | };
56 |
57 | const onSuccess = () => {
58 | setIsSuccess(true);
59 | setIsError(false);
60 | };
61 |
62 | async function handleSubmit() {
63 | if (syntax === "") {
64 | onError();
65 | toast({
66 | variant: "destructive",
67 | title: "Select a syntax.",
68 | description: "You must select a syntax before submitting.",
69 | });
70 | } else if (text.length > 2000) {
71 | onError();
72 | toast({
73 | variant: "destructive",
74 | title: "Text too long.",
75 | description: "Try a shorter text.",
76 | });
77 | } else if (text.length < 10) {
78 | onError();
79 | toast({
80 | variant: "destructive",
81 | title: "Text too short.",
82 | description: "Try a longer text.",
83 | });
84 | } else {
85 | setIsLoading(true);
86 | try {
87 | const response = await fetch("/api/paste", {
88 | method: "POST",
89 | body: JSON.stringify({ text, syntax }),
90 | });
91 | const data = await response.json();
92 | const token = data.token;
93 | const id = data.id;
94 | if (token && id) {
95 | setDeleteURL(`https://cat-paste.vercel.app/delete/${token}`);
96 | setRefURL(`${id}`);
97 | toast({
98 | title: "Your paste pasted successfully.",
99 | description: "Don't forget to copy the delete URL.",
100 | });
101 | onSuccess();
102 | } else {
103 | onError();
104 | toast({
105 | title: "Error while getting token.",
106 | description: "Contact support.",
107 | });
108 | }
109 | } catch {
110 | onError();
111 | toast({
112 | title: "Error while submitting.",
113 | description: "Contact support.",
114 | });
115 | } finally {
116 | setIsLoading(false);
117 | }
118 | }
119 | }
120 |
121 | return (
122 |
123 |
124 |
125 | {isLoading ? (
126 |
130 | ) : (
131 |
142 | )}
143 |
144 |
150 |
151 |
Your paste pasted successfully. 🎉
152 |
Save your delete URL ⚠️
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
169 | );
170 | }
171 |
--------------------------------------------------------------------------------
/components/navigation/nav.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import Link from "next/link";
3 | import { OpenInNewWindowIcon } from "@radix-ui/react-icons";
4 |
5 | import { Separator } from "@/components/ui/separator";
6 | import ThemeToggle from "@/components/theme/theme-toggle";
7 | import { Button } from "../ui/button";
8 |
9 | export default function Nav() {
10 | return (
11 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/components/theme/theme-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { ThemeProvider as NextThemesProvider } from "next-themes";
5 | import { type ThemeProviderProps } from "next-themes/dist/types";
6 |
7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
8 | return {children};
9 | }
10 |
--------------------------------------------------------------------------------
/components/theme/theme-toggle.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { MoonIcon, SunIcon } from "@radix-ui/react-icons";
5 | import { useTheme } from "next-themes";
6 |
7 | import { Button } from "@/components/ui/button";
8 | import {
9 | DropdownMenu,
10 | DropdownMenuContent,
11 | DropdownMenuItem,
12 | DropdownMenuTrigger,
13 | } from "@/components/ui/dropdown-menu";
14 |
15 | export default function ThemeToggle() {
16 | const { setTheme } = useTheme();
17 |
18 | return (
19 |
20 |
21 |
26 |
27 |
28 | setTheme("light")}>
29 | Light
30 |
31 | setTheme("dark")}>
32 | Dark
33 |
34 | setTheme("system")}>
35 | System
36 |
37 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | )
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : "button"
45 | return (
46 |
51 | )
52 | }
53 | )
54 | Button.displayName = "Button"
55 |
56 | export { Button, buttonVariants }
57 |
--------------------------------------------------------------------------------
/components/ui/command.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { DialogProps } from "@radix-ui/react-dialog"
5 | import { Command as CommandPrimitive } from "cmdk"
6 | import { Search } from "lucide-react"
7 |
8 | import { cn } from "@/lib/utils"
9 | import { Dialog, DialogContent } from "@/components/ui/dialog"
10 |
11 | const Command = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, ...props }, ref) => (
15 |
23 | ))
24 | Command.displayName = CommandPrimitive.displayName
25 |
26 | interface CommandDialogProps extends DialogProps {}
27 |
28 | const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
29 | return (
30 |
37 | )
38 | }
39 |
40 | const CommandInput = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
45 |
46 |
54 |
55 | ))
56 |
57 | CommandInput.displayName = CommandPrimitive.Input.displayName
58 |
59 | const CommandList = React.forwardRef<
60 | React.ElementRef,
61 | React.ComponentPropsWithoutRef
62 | >(({ className, ...props }, ref) => (
63 |
68 | ))
69 |
70 | CommandList.displayName = CommandPrimitive.List.displayName
71 |
72 | const CommandEmpty = React.forwardRef<
73 | React.ElementRef,
74 | React.ComponentPropsWithoutRef
75 | >((props, ref) => (
76 |
81 | ))
82 |
83 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName
84 |
85 | const CommandGroup = React.forwardRef<
86 | React.ElementRef,
87 | React.ComponentPropsWithoutRef
88 | >(({ className, ...props }, ref) => (
89 |
97 | ))
98 |
99 | CommandGroup.displayName = CommandPrimitive.Group.displayName
100 |
101 | const CommandSeparator = React.forwardRef<
102 | React.ElementRef,
103 | React.ComponentPropsWithoutRef
104 | >(({ className, ...props }, ref) => (
105 |
110 | ))
111 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName
112 |
113 | const CommandItem = React.forwardRef<
114 | React.ElementRef,
115 | React.ComponentPropsWithoutRef
116 | >(({ className, ...props }, ref) => (
117 |
125 | ))
126 |
127 | CommandItem.displayName = CommandPrimitive.Item.displayName
128 |
129 | const CommandShortcut = ({
130 | className,
131 | ...props
132 | }: React.HTMLAttributes) => {
133 | return (
134 |
141 | )
142 | }
143 | CommandShortcut.displayName = "CommandShortcut"
144 |
145 | export {
146 | Command,
147 | CommandDialog,
148 | CommandInput,
149 | CommandList,
150 | CommandEmpty,
151 | CommandGroup,
152 | CommandItem,
153 | CommandShortcut,
154 | CommandSeparator,
155 | }
156 |
--------------------------------------------------------------------------------
/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DialogPrimitive from "@radix-ui/react-dialog"
5 | import { X } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Dialog = DialogPrimitive.Root
10 |
11 | const DialogTrigger = DialogPrimitive.Trigger
12 |
13 | const DialogPortal = ({
14 | className,
15 | ...props
16 | }: DialogPrimitive.DialogPortalProps) => (
17 |
18 | )
19 | DialogPortal.displayName = DialogPrimitive.Portal.displayName
20 |
21 | const DialogOverlay = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef
24 | >(({ className, ...props }, ref) => (
25 |
33 | ))
34 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
35 |
36 | const DialogContent = React.forwardRef<
37 | React.ElementRef,
38 | React.ComponentPropsWithoutRef
39 | >(({ className, children, ...props }, ref) => (
40 |
41 |
42 |
50 | {children}
51 |
52 |
53 | Close
54 |
55 |
56 |
57 | ))
58 | DialogContent.displayName = DialogPrimitive.Content.displayName
59 |
60 | const DialogHeader = ({
61 | className,
62 | ...props
63 | }: React.HTMLAttributes) => (
64 |
71 | )
72 | DialogHeader.displayName = "DialogHeader"
73 |
74 | const DialogFooter = ({
75 | className,
76 | ...props
77 | }: React.HTMLAttributes) => (
78 |
85 | )
86 | DialogFooter.displayName = "DialogFooter"
87 |
88 | const DialogTitle = React.forwardRef<
89 | React.ElementRef,
90 | React.ComponentPropsWithoutRef
91 | >(({ className, ...props }, ref) => (
92 |
100 | ))
101 | DialogTitle.displayName = DialogPrimitive.Title.displayName
102 |
103 | const DialogDescription = React.forwardRef<
104 | React.ElementRef,
105 | React.ComponentPropsWithoutRef
106 | >(({ className, ...props }, ref) => (
107 |
112 | ))
113 | DialogDescription.displayName = DialogPrimitive.Description.displayName
114 |
115 | export {
116 | Dialog,
117 | DialogTrigger,
118 | DialogContent,
119 | DialogHeader,
120 | DialogFooter,
121 | DialogTitle,
122 | DialogDescription,
123 | }
124 |
--------------------------------------------------------------------------------
/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5 | import { Check, ChevronRight, Circle } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const DropdownMenu = DropdownMenuPrimitive.Root
10 |
11 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
12 |
13 | const DropdownMenuGroup = DropdownMenuPrimitive.Group
14 |
15 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal
16 |
17 | const DropdownMenuSub = DropdownMenuPrimitive.Sub
18 |
19 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
20 |
21 | const DropdownMenuSubTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef & {
24 | inset?: boolean
25 | }
26 | >(({ className, inset, children, ...props }, ref) => (
27 |
36 | {children}
37 |
38 |
39 | ))
40 | DropdownMenuSubTrigger.displayName =
41 | DropdownMenuPrimitive.SubTrigger.displayName
42 |
43 | const DropdownMenuSubContent = React.forwardRef<
44 | React.ElementRef,
45 | React.ComponentPropsWithoutRef
46 | >(({ className, ...props }, ref) => (
47 |
55 | ))
56 | DropdownMenuSubContent.displayName =
57 | DropdownMenuPrimitive.SubContent.displayName
58 |
59 | const DropdownMenuContent = React.forwardRef<
60 | React.ElementRef,
61 | React.ComponentPropsWithoutRef
62 | >(({ className, sideOffset = 4, ...props }, ref) => (
63 |
64 |
73 |
74 | ))
75 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
76 |
77 | const DropdownMenuItem = React.forwardRef<
78 | React.ElementRef,
79 | React.ComponentPropsWithoutRef & {
80 | inset?: boolean
81 | }
82 | >(({ className, inset, ...props }, ref) => (
83 |
92 | ))
93 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
94 |
95 | const DropdownMenuCheckboxItem = React.forwardRef<
96 | React.ElementRef,
97 | React.ComponentPropsWithoutRef
98 | >(({ className, children, checked, ...props }, ref) => (
99 |
108 |
109 |
110 |
111 |
112 |
113 | {children}
114 |
115 | ))
116 | DropdownMenuCheckboxItem.displayName =
117 | DropdownMenuPrimitive.CheckboxItem.displayName
118 |
119 | const DropdownMenuRadioItem = React.forwardRef<
120 | React.ElementRef,
121 | React.ComponentPropsWithoutRef
122 | >(({ className, children, ...props }, ref) => (
123 |
131 |
132 |
133 |
134 |
135 |
136 | {children}
137 |
138 | ))
139 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
140 |
141 | const DropdownMenuLabel = React.forwardRef<
142 | React.ElementRef,
143 | React.ComponentPropsWithoutRef & {
144 | inset?: boolean
145 | }
146 | >(({ className, inset, ...props }, ref) => (
147 |
156 | ))
157 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
158 |
159 | const DropdownMenuSeparator = React.forwardRef<
160 | React.ElementRef,
161 | React.ComponentPropsWithoutRef
162 | >(({ className, ...props }, ref) => (
163 |
168 | ))
169 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
170 |
171 | const DropdownMenuShortcut = ({
172 | className,
173 | ...props
174 | }: React.HTMLAttributes) => {
175 | return (
176 |
180 | )
181 | }
182 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
183 |
184 | export {
185 | DropdownMenu,
186 | DropdownMenuTrigger,
187 | DropdownMenuContent,
188 | DropdownMenuItem,
189 | DropdownMenuCheckboxItem,
190 | DropdownMenuRadioItem,
191 | DropdownMenuLabel,
192 | DropdownMenuSeparator,
193 | DropdownMenuShortcut,
194 | DropdownMenuGroup,
195 | DropdownMenuPortal,
196 | DropdownMenuSub,
197 | DropdownMenuSubContent,
198 | DropdownMenuSubTrigger,
199 | DropdownMenuRadioGroup,
200 | }
201 |
--------------------------------------------------------------------------------
/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as PopoverPrimitive from "@radix-ui/react-popover"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Popover = PopoverPrimitive.Root
9 |
10 | const PopoverTrigger = PopoverPrimitive.Trigger
11 |
12 | const PopoverContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
17 |
27 |
28 | ))
29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName
30 |
31 | export { Popover, PopoverTrigger, PopoverContent }
32 |
--------------------------------------------------------------------------------
/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref
15 | ) => (
16 |
27 | )
28 | )
29 | Separator.displayName = SeparatorPrimitive.Root.displayName
30 |
31 | export { Separator }
32 |
--------------------------------------------------------------------------------
/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/components/ui/toast.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as ToastPrimitives from "@radix-ui/react-toast"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 | import { X } from "lucide-react"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const ToastProvider = ToastPrimitives.Provider
9 |
10 | const ToastViewport = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
22 | ))
23 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName
24 |
25 | const toastVariants = cva(
26 | "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
27 | {
28 | variants: {
29 | variant: {
30 | default: "border bg-background text-foreground",
31 | destructive:
32 | "destructive group border-destructive bg-destructive text-destructive-foreground",
33 | },
34 | },
35 | defaultVariants: {
36 | variant: "default",
37 | },
38 | }
39 | )
40 |
41 | const Toast = React.forwardRef<
42 | React.ElementRef,
43 | React.ComponentPropsWithoutRef &
44 | VariantProps
45 | >(({ className, variant, ...props }, ref) => {
46 | return (
47 |
52 | )
53 | })
54 | Toast.displayName = ToastPrimitives.Root.displayName
55 |
56 | const ToastAction = React.forwardRef<
57 | React.ElementRef,
58 | React.ComponentPropsWithoutRef
59 | >(({ className, ...props }, ref) => (
60 |
68 | ))
69 | ToastAction.displayName = ToastPrimitives.Action.displayName
70 |
71 | const ToastClose = React.forwardRef<
72 | React.ElementRef,
73 | React.ComponentPropsWithoutRef
74 | >(({ className, ...props }, ref) => (
75 |
84 |
85 |
86 | ))
87 | ToastClose.displayName = ToastPrimitives.Close.displayName
88 |
89 | const ToastTitle = React.forwardRef<
90 | React.ElementRef,
91 | React.ComponentPropsWithoutRef
92 | >(({ className, ...props }, ref) => (
93 |
98 | ))
99 | ToastTitle.displayName = ToastPrimitives.Title.displayName
100 |
101 | const ToastDescription = React.forwardRef<
102 | React.ElementRef,
103 | React.ComponentPropsWithoutRef
104 | >(({ className, ...props }, ref) => (
105 |
110 | ))
111 | ToastDescription.displayName = ToastPrimitives.Description.displayName
112 |
113 | type ToastProps = React.ComponentPropsWithoutRef
114 |
115 | type ToastActionElement = React.ReactElement
116 |
117 | export {
118 | type ToastProps,
119 | type ToastActionElement,
120 | ToastProvider,
121 | ToastViewport,
122 | Toast,
123 | ToastTitle,
124 | ToastDescription,
125 | ToastClose,
126 | ToastAction,
127 | }
128 |
--------------------------------------------------------------------------------
/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import {
4 | Toast,
5 | ToastClose,
6 | ToastDescription,
7 | ToastProvider,
8 | ToastTitle,
9 | ToastViewport,
10 | } from "@/components/ui/toast"
11 | import { useToast } from "@/components/ui/use-toast"
12 |
13 | export function Toaster() {
14 | const { toasts } = useToast()
15 |
16 | return (
17 |
18 | {toasts.map(function ({ id, title, description, action, ...props }) {
19 | return (
20 |
21 |
22 | {title && {title}}
23 | {description && (
24 | {description}
25 | )}
26 |
27 | {action}
28 |
29 |
30 | )
31 | })}
32 |
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/components/ui/use-toast.ts:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import type { ToastActionElement, ToastProps } from "@/components/ui/toast";
4 |
5 | const TOAST_LIMIT = 1;
6 | const TOAST_REMOVE_DELAY = 6000;
7 |
8 | type ToasterToast = ToastProps & {
9 | id: string;
10 | title?: React.ReactNode;
11 | description?: React.ReactNode;
12 | action?: ToastActionElement;
13 | };
14 |
15 | const actionTypes = {
16 | ADD_TOAST: "ADD_TOAST",
17 | UPDATE_TOAST: "UPDATE_TOAST",
18 | DISMISS_TOAST: "DISMISS_TOAST",
19 | REMOVE_TOAST: "REMOVE_TOAST",
20 | } as const;
21 |
22 | let count = 0;
23 |
24 | function genId() {
25 | count = (count + 1) % Number.MAX_VALUE;
26 | return count.toString();
27 | }
28 |
29 | type ActionType = typeof actionTypes;
30 |
31 | type Action =
32 | | {
33 | type: ActionType["ADD_TOAST"];
34 | toast: ToasterToast;
35 | }
36 | | {
37 | type: ActionType["UPDATE_TOAST"];
38 | toast: Partial;
39 | }
40 | | {
41 | type: ActionType["DISMISS_TOAST"];
42 | toastId?: ToasterToast["id"];
43 | }
44 | | {
45 | type: ActionType["REMOVE_TOAST"];
46 | toastId?: ToasterToast["id"];
47 | };
48 |
49 | interface State {
50 | toasts: ToasterToast[];
51 | }
52 |
53 | const toastTimeouts = new Map>();
54 |
55 | const addToRemoveQueue = (toastId: string) => {
56 | if (toastTimeouts.has(toastId)) {
57 | return;
58 | }
59 |
60 | const timeout = setTimeout(() => {
61 | toastTimeouts.delete(toastId);
62 | dispatch({
63 | type: "REMOVE_TOAST",
64 | toastId: toastId,
65 | });
66 | }, TOAST_REMOVE_DELAY);
67 |
68 | toastTimeouts.set(toastId, timeout);
69 | };
70 |
71 | export const reducer = (state: State, action: Action): State => {
72 | switch (action.type) {
73 | case "ADD_TOAST":
74 | return {
75 | ...state,
76 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
77 | };
78 |
79 | case "UPDATE_TOAST":
80 | return {
81 | ...state,
82 | toasts: state.toasts.map((t) =>
83 | t.id === action.toast.id ? { ...t, ...action.toast } : t
84 | ),
85 | };
86 |
87 | case "DISMISS_TOAST": {
88 | const { toastId } = action;
89 |
90 | // ! Side effects ! - This could be extracted into a dismissToast() action,
91 | // but I'll keep it here for simplicity
92 | if (toastId) {
93 | addToRemoveQueue(toastId);
94 | } else {
95 | state.toasts.forEach((toast) => {
96 | addToRemoveQueue(toast.id);
97 | });
98 | }
99 |
100 | return {
101 | ...state,
102 | toasts: state.toasts.map((t) =>
103 | t.id === toastId || toastId === undefined
104 | ? {
105 | ...t,
106 | open: false,
107 | }
108 | : t
109 | ),
110 | };
111 | }
112 | case "REMOVE_TOAST":
113 | if (action.toastId === undefined) {
114 | return {
115 | ...state,
116 | toasts: [],
117 | };
118 | }
119 | return {
120 | ...state,
121 | toasts: state.toasts.filter((t) => t.id !== action.toastId),
122 | };
123 | }
124 | };
125 |
126 | const listeners: Array<(state: State) => void> = [];
127 |
128 | let memoryState: State = { toasts: [] };
129 |
130 | function dispatch(action: Action) {
131 | memoryState = reducer(memoryState, action);
132 | listeners.forEach((listener) => {
133 | listener(memoryState);
134 | });
135 | }
136 |
137 | type Toast = Omit;
138 |
139 | function toast({ ...props }: Toast) {
140 | const id = genId();
141 |
142 | const update = (props: ToasterToast) =>
143 | dispatch({
144 | type: "UPDATE_TOAST",
145 | toast: { ...props, id },
146 | });
147 | const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
148 |
149 | dispatch({
150 | type: "ADD_TOAST",
151 | toast: {
152 | ...props,
153 | id,
154 | open: true,
155 | onOpenChange: (open) => {
156 | if (!open) dismiss();
157 | },
158 | },
159 | });
160 |
161 | return {
162 | id: id,
163 | dismiss,
164 | update,
165 | };
166 | }
167 |
168 | function useToast() {
169 | const [state, setState] = React.useState(memoryState);
170 |
171 | React.useEffect(() => {
172 | listeners.push(setState);
173 | return () => {
174 | const index = listeners.indexOf(setState);
175 | if (index > -1) {
176 | listeners.splice(index, 1);
177 | }
178 | };
179 | }, [state]);
180 |
181 | return {
182 | ...state,
183 | toast,
184 | dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
185 | };
186 | }
187 |
188 | export { useToast, toast };
189 |
--------------------------------------------------------------------------------
/lib/prisma.ts:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from "@prisma/client";
2 | export const prisma = new PrismaClient();
3 |
--------------------------------------------------------------------------------
/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 const langs = [
9 | "plain",
10 | "javascript",
11 | "python",
12 | "rust",
13 | "lua",
14 | "clike",
15 | "html",
16 | "css",
17 | "markup",
18 | "svg",
19 | "xml",
20 | ];
21 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {}
3 |
4 | module.exports = nextConfig
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "catpaste",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "generate": "npx prisma generate",
7 | "postinstall": "npm run generate",
8 | "dev": "next dev",
9 | "build": "next build",
10 | "start": "next start",
11 | "lint": "next lint"
12 | },
13 | "dependencies": {
14 | "@prisma/client": "^5.2.0",
15 | "@radix-ui/react-dialog": "^1.0.4",
16 | "@radix-ui/react-dropdown-menu": "^2.0.5",
17 | "@radix-ui/react-icons": "^1.3.0",
18 | "@radix-ui/react-popover": "^1.0.6",
19 | "@radix-ui/react-select": "^1.2.2",
20 | "@radix-ui/react-separator": "^1.0.3",
21 | "@radix-ui/react-slot": "^1.0.2",
22 | "@radix-ui/react-toast": "^1.1.4",
23 | "@types/node": "20.5.8",
24 | "@types/react": "18.2.21",
25 | "@types/react-dom": "18.2.7",
26 | "autoprefixer": "10.4.15",
27 | "class-variance-authority": "^0.7.0",
28 | "clsx": "^2.0.0",
29 | "cmdk": "^0.2.0",
30 | "eslint": "8.48.0",
31 | "eslint-config-next": "13.4.19",
32 | "lucide-react": "^0.274.0",
33 | "next": "13.4.19",
34 | "next-themes": "^0.2.1",
35 | "postcss": "8.4.29",
36 | "prismjs": "^1.29.0",
37 | "react": "18.2.0",
38 | "react-dom": "18.2.0",
39 | "tailwind-merge": "^1.14.0",
40 | "tailwindcss": "3.3.3",
41 | "tailwindcss-animate": "^1.0.7",
42 | "typescript": "5.2.2",
43 | "uuid": "^9.0.0"
44 | },
45 | "devDependencies": {
46 | "@types/prismjs": "^1.26.0",
47 | "@types/uuid": "^9.0.3",
48 | "prisma": "^5.2.0"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/prisma/migrations/20230902234402_wow/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "Paste" (
3 | "id" TEXT NOT NULL,
4 | "text" TEXT NOT NULL,
5 | "syntax" TEXT NOT NULL,
6 | "delete" TEXT NOT NULL,
7 | "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
8 |
9 | CONSTRAINT "Paste_pkey" PRIMARY KEY ("id")
10 | );
11 |
12 | -- CreateIndex
13 | CREATE UNIQUE INDEX "Paste_delete_key" ON "Paste"("delete");
14 |
--------------------------------------------------------------------------------
/prisma/migrations/migration_lock.toml:
--------------------------------------------------------------------------------
1 | # Please do not edit this file manually
2 | # It should be added in your version-control system (i.e. Git)
3 | provider = "postgresql"
--------------------------------------------------------------------------------
/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | generator client {
2 | provider = "prisma-client-js"
3 | }
4 |
5 | datasource db {
6 | provider = "postgresql"
7 | url = env("DATABASE_URL")
8 | shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
9 | }
10 |
11 | model Paste {
12 | id String @id @default(cuid())
13 | text String
14 | syntax String
15 | delete String @unique
16 | createdAt DateTime @default(now())
17 | }
18 |
--------------------------------------------------------------------------------
/public/cat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeaByte/cat-paste/7d112ab891574a83b7cb1d50eec2859b563bf9f4/public/cat.png
--------------------------------------------------------------------------------
/public/catdns.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeaByte/cat-paste/7d112ab891574a83b7cb1d50eec2859b563bf9f4/public/catdns.gif
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | darkMode: ["class"],
4 | content: [
5 | './pages/**/*.{ts,tsx}',
6 | './components/**/*.{ts,tsx}',
7 | './app/**/*.{ts,tsx}',
8 | './src/**/*.{ts,tsx}',
9 | ],
10 | theme: {
11 | container: {
12 | center: true,
13 | padding: "2rem",
14 | screens: {
15 | "2xl": "1400px",
16 | },
17 | },
18 | extend: {
19 | colors: {
20 | border: "hsl(var(--border))",
21 | input: "hsl(var(--input))",
22 | ring: "hsl(var(--ring))",
23 | background: "hsl(var(--background))",
24 | foreground: "hsl(var(--foreground))",
25 | primary: {
26 | DEFAULT: "hsl(var(--primary))",
27 | foreground: "hsl(var(--primary-foreground))",
28 | },
29 | secondary: {
30 | DEFAULT: "hsl(var(--secondary))",
31 | foreground: "hsl(var(--secondary-foreground))",
32 | },
33 | destructive: {
34 | DEFAULT: "hsl(var(--destructive))",
35 | foreground: "hsl(var(--destructive-foreground))",
36 | },
37 | muted: {
38 | DEFAULT: "hsl(var(--muted))",
39 | foreground: "hsl(var(--muted-foreground))",
40 | },
41 | accent: {
42 | DEFAULT: "hsl(var(--accent))",
43 | foreground: "hsl(var(--accent-foreground))",
44 | },
45 | popover: {
46 | DEFAULT: "hsl(var(--popover))",
47 | foreground: "hsl(var(--popover-foreground))",
48 | },
49 | card: {
50 | DEFAULT: "hsl(var(--card))",
51 | foreground: "hsl(var(--card-foreground))",
52 | },
53 | },
54 | borderRadius: {
55 | lg: "var(--radius)",
56 | md: "calc(var(--radius) - 2px)",
57 | sm: "calc(var(--radius) - 4px)",
58 | },
59 | keyframes: {
60 | "accordion-down": {
61 | from: { height: 0 },
62 | to: { height: "var(--radix-accordion-content-height)" },
63 | },
64 | "accordion-up": {
65 | from: { height: "var(--radix-accordion-content-height)" },
66 | to: { height: 0 },
67 | },
68 | },
69 | animation: {
70 | "accordion-down": "accordion-down 0.2s ease-out",
71 | "accordion-up": "accordion-up 0.2s ease-out",
72 | },
73 | },
74 | },
75 | plugins: [require("tailwindcss-animate")],
76 | }
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'tailwindcss'
2 |
3 | const config: Config = {
4 | content: [
5 | './pages/**/*.{js,ts,jsx,tsx,mdx}',
6 | './components/**/*.{js,ts,jsx,tsx,mdx}',
7 | './app/**/*.{js,ts,jsx,tsx,mdx}',
8 | ],
9 | theme: {
10 | extend: {
11 | backgroundImage: {
12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
13 | 'gradient-conic':
14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
15 | },
16 | },
17 | },
18 | plugins: [],
19 | }
20 | export default config
21 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "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 |
--------------------------------------------------------------------------------