initial commit
This commit is contained in:
41
app/api/uploadthing/core.ts
Normal file
41
app/api/uploadthing/core.ts
Normal 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;
|
||||
11
app/api/uploadthing/route.ts
Normal file
11
app/api/uploadthing/route.ts
Normal 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: { ... },
|
||||
});
|
||||
5
app/create_post/layout.tsx
Normal file
5
app/create_post/layout.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
export default function layout({ children }: { children: React.ReactNode}){
|
||||
return <>
|
||||
{children}
|
||||
</>
|
||||
}
|
||||
57
app/create_post/page.tsx
Normal file
57
app/create_post/page.tsx
Normal 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
BIN
app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
56
app/globals.css
Normal file
56
app/globals.css
Normal 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
69
app/layout.tsx
Normal 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
3
app/login/page.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export default async function Page() {
|
||||
return <h1>This is login content</h1>
|
||||
}
|
||||
6
app/manage_pet_account/page.tsx
Normal file
6
app/manage_pet_account/page.tsx
Normal 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
22
app/menu.tsx
Normal 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
19
app/page.tsx
Normal 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
19
app/post.tsx
Normal 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
9
app/settings/layout.tsx
Normal 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
22
app/settings/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user