initial commit

This commit is contained in:
2026-04-03 18:25:54 +01:00
commit 17e26fc87f
35 changed files with 4716 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
import { clerkClient, auth } from "@clerk/nextjs/server";
import { createUploadthing, type FileRouter } from "uploadthing/next";
import { UploadThingError } from "uploadthing/server";
const f = createUploadthing();
// FileRouter for your app, can contain multiple FileRoutes
export const ourFileRouter = {
// Define as many FileRoutes as you like, each with a unique routeSlug
imageUploader: f({
image: {
/**
* For full list of options and defaults, see the File Route API reference
* @see https://docs.uploadthing.com/file-routes#route-config
*/
maxFileSize: "4MB",
maxFileCount: 1,
},
})
// Set permissions and file types for this FileRoute
.middleware(async () => {
// This code runs on your server before upload
const user = await auth();
// If you throw, the user will not be able to upload
if (!user) throw new UploadThingError("Unauthorized");
// Whatever is returned here is accessible in onUploadComplete as `metadata`
return { userId: user.userId };
})
.onUploadComplete(async ({ metadata, file }) => {
// This code RUNS ON YOUR SERVER after upload
console.log("Upload complete for userId:", metadata.userId);
console.log("file url", file.ufsUrl);
// !!! Whatever is returned here is sent to the clientside `onClientUploadComplete` callback
return { uploadedBy: metadata.userId };
}),
} satisfies FileRouter;
export type OurFileRouter = typeof ourFileRouter;

View File

@@ -0,0 +1,11 @@
import { createRouteHandler } from "uploadthing/next";
import { ourFileRouter } from "./core";
// Export routes for Next App Router
export const { GET, POST } = createRouteHandler({
router: ourFileRouter,
// Apply an (optional) custom config:
// config: { ... },
});

View File

@@ -0,0 +1,5 @@
export default function layout({ children }: { children: React.ReactNode}){
return <>
{children}
</>
}

57
app/create_post/page.tsx Normal file
View File

@@ -0,0 +1,57 @@
import Form from 'next/form'
import { auth } from '@clerk/nextjs/server';
import { prisma } from "@/lib/prisma";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
export default async function Page() {
async function createPost(formData: FormData) {
"use server";
const { userId } = await auth(); //
if (!userId) {
throw new Error("You must be logged in to post!");
}
const first_name = formData.get("fname") as string;
const last_name = formData.get("lname") as string;
const post_text = formData.get("post-text") as string;
const pet_species = formData.get("pet_species") as string;
console.log(first_name, last_name, post_text, pet_species);
await prisma.post.create({
data: {
content: post_text,
petId: 3,
},
});
revalidatePath("/");
redirect("/");
}
return <>
<h1>This is create post content</h1>
<Form action={createPost}>
<label htmlFor="fname">First name:</label><br/>
<input type="text" id="fname" name="fname" className="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:placeholder:text-gray-500 dark:focus:outline-indigo-500"/><br/>
<label htmlFor="lname">Last name:</label><br/>
<input type="text" id="lname" name="lname" className="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:placeholder:text-gray-500 dark:focus:outline-indigo-500"/><br/>
<label htmlFor="post-text">Content: </label><br/>
<textarea id="post-text" name="post-text"className="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline-2 focus:-outline-offset-2 focus:outline-indigo-600 sm:text-sm/6 dark:bg-white/5 dark:text-white dark:outline-white/10 dark:placeholder:text-gray-500 dark:focus:outline-indigo-500"/><br/> <br/>
<label>What animal is posting this?</label><br />
<input type="radio" name="pet_species" value="Dog" />
<label htmlFor="html">Dog</label><br />
<input type="radio" name="pet_species" value="Cat" />
<label htmlFor="css">Cat</label><br />
<input type="radio" name="pet_species" value="Goat" />
<label htmlFor="javascript">Goat</label><br/>
<button type="submit" className="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-indigo-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:bg-indigo-500 dark:shadow-none dark:focus-visible:outline-indigo-500">Post</button>
</Form>
</>
}

BIN
app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

56
app/globals.css Normal file
View File

@@ -0,0 +1,56 @@
@import "tailwindcss";
@import "uploadthing/tw/v4";
@source "../node_modules/@uploadthing/react/dist";
:root {
--background: #ffffff;
--foreground: #171717;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
body {
margin: 10px;
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}
.nav-element {
color: black;
font-weight: bold;
background-color: #ffffff;
border-radius: 20px;
margin-right: 5px;
margin-left: 5px;
padding: 9px;
}
.nav-element:hover {
color: white;
background-color: #171717;
cursor: pointer;
}
.active-nav-element {
color: white;
font-weight: bold;
background-color: #171717;
border-radius: 20px;
margin-right: 5px;
margin-left: 5px;
padding: 9px;
}

69
app/layout.tsx Normal file
View File

@@ -0,0 +1,69 @@
import type { Metadata } from 'next'
import { dark, neobrutalism } from '@clerk/themes'
import {
ClerkProvider,
SignInButton,
SignUpButton,
SignedIn,
SignedOut,
UserButton,
} from '@clerk/nextjs'
import { Geist, Geist_Mono } from 'next/font/google'
import './globals.css'
import Menu from './menu'
const geistSans = Geist({
variable: '--font-geist-sans',
subsets: ['latin'],
})
const geistMono = Geist_Mono({
variable: '--font-geist-mono',
subsets: ['latin'],
})
export const metadata: Metadata = {
title: 'Pawship',
description: 'Generated by create next app',
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<ClerkProvider appearance={{
theme: dark,
signIn: { theme: neobrutalism },
}}>
<html lang="en">
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
<header className="flex justify-end items-center p-4 gap-4 h-16">
{/* Show the sign-in and sign-up buttons when the user is signed out */}
<Menu />
<SignedOut>
<SignInButton>
<button className="nav-element">
Sign In
</button>
</SignInButton>
<SignUpButton>
<button className="nav-element">
Sign Up
</button>
</SignUpButton>
</SignedOut>
{/* Show the user button when the user is signed in */}
<SignedIn>
<UserButton />
</SignedIn>
</header>
<h1>This is the index layout warpper \/</h1>
{children}
<h1>This is the index layout warpper /\</h1>
</body>
</html>
</ClerkProvider>
)
}

3
app/login/page.tsx Normal file
View File

@@ -0,0 +1,3 @@
export default async function Page() {
return <h1>This is login content</h1>
}

View File

@@ -0,0 +1,6 @@
export default async function Page() {
return <>
<h1>This is manage pet account content</h1>
<p>Given the user account ID, need to load all active pet accounts and display data like friends and whatnot. have button to create/delete pet account</p>
</>
}

22
app/menu.tsx Normal file
View File

@@ -0,0 +1,22 @@
'use client'
import Link from 'next/link';
import { SignedIn } from '@clerk/nextjs';
import { usePathname } from 'next/navigation';
//import { Client } from '@clerk/nextjs/server';
export default function Menu() {
const is_active = (path: string) => path === usePathname();
return <>
<nav>
<Link href="/" className={is_active("/") ? 'active-nav-element' : 'nav-element'}>Home</Link>
<SignedIn>
<Link href="/settings" className={is_active("/settings") ? 'active-nav-element' : 'nav-element'}>Settings</Link>
<Link href="/create_post" className={is_active("/create_post") ? 'active-nav-element' : 'nav-element'}>Create A Post</Link>
<Link href="/manage_pet_account" className={is_active("/manage_pet_account") ? 'active-nav-element' : 'nav-element'}>Manage Pet Accounts</Link>
</SignedIn>
</nav>
</>
}

19
app/page.tsx Normal file
View File

@@ -0,0 +1,19 @@
import { prisma } from "../lib/prisma"
import Link from 'next/link'
import Post from './post'
const allPosts = await prisma.post.findMany({
include: {
pet: true
}
});
export default async function Page() {
const posts = allPosts.map(item => <Post key={item.id} postData={item}></Post>);
return <>
<h1>Posts</h1>
{posts}
</>
}

19
app/post.tsx Normal file
View File

@@ -0,0 +1,19 @@
import type { Prisma } from "../generated/prisma/client"
type PostWithPet = Prisma.PostGetPayload<{
include: { pet: true }
}>;
interface PostProps {
postData: PostWithPet;
}
export default async function Post({ postData }: PostProps) {
return <>
<h1>{postData.petId}</h1>
<p>{postData.content}</p>
<br/>
<p>Posted by a: <b>{postData.pet.species}</b> called <b>{postData.pet.name}</b></p>
<hr/>
</>
}

9
app/settings/layout.tsx Normal file
View File

@@ -0,0 +1,9 @@
export default function layout({ children }: { children: React.ReactNode}){
return <>
<h1>This is a Settings layout \/</h1>
{children}
<h1>This is a Settings layout /\</h1>
</>
}

22
app/settings/page.tsx Normal file
View File

@@ -0,0 +1,22 @@
"use client";
import { UploadButton } from "../../lib/uploadthing";
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<UploadButton
endpoint="imageUploader"
onClientUploadComplete={(res) => {
// Do something with the response
console.log("Files: ", res);
alert("Upload Completed");
}}
onUploadError={(error: Error) => {
// Do something with the error.
alert(`ERROR! ${error.message}`);
}}
/>
</main>
);
}