├── .eslintrc.json ├── app ├── favicon.ico ├── page.tsx ├── dashboard │ ├── (.)p │ │ └── [id] │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ ├── p │ │ └── [id] │ │ │ ├── edit │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ │ └── page.tsx │ ├── (home) │ │ ├── layout.tsx │ │ └── page.tsx │ ├── [username] │ │ ├── page.tsx │ │ ├── saved │ │ │ └── page.tsx │ │ ├── following │ │ │ └── page.tsx │ │ ├── followers │ │ │ └── page.tsx │ │ └── layout.tsx │ ├── layout.tsx │ ├── not-found.tsx │ ├── (settings) │ │ ├── edit-profile │ │ │ └── page.tsx │ │ └── layout.tsx │ └── create │ │ └── page.tsx ├── api │ ├── auth │ │ └── [...nextauth] │ │ │ └── route.ts │ └── uploadthing │ │ ├── route.ts │ │ └── core.ts ├── (auth) │ ├── layout.tsx │ └── login │ │ └── page.tsx ├── fonts.ts ├── layout.tsx └── globals.css ├── postcss.config.js ├── public ├── fonts │ └── CalSans-SemiBold.woff2 ├── vercel.svg └── next.svg ├── components ├── ui │ ├── aspect-ratio.tsx │ ├── skeleton.tsx │ ├── label.tsx │ ├── textarea.tsx │ ├── separator.tsx │ ├── input.tsx │ ├── switch.tsx │ ├── hover-card.tsx │ ├── avatar.tsx │ ├── scroll-area.tsx │ ├── button.tsx │ ├── tabs.tsx │ ├── card.tsx │ ├── dialog.tsx │ ├── form.tsx │ ├── select.tsx │ └── dropdown-menu.tsx ├── AuthProvider.tsx ├── Posts.tsx ├── ThemeProvider.tsx ├── ViewPost.tsx ├── ActionIcon.tsx ├── SubmitButton.tsx ├── ShareButton.tsx ├── Logo.tsx ├── ProfileHeader.tsx ├── Error.tsx ├── FollowButton.tsx ├── SideNav.tsx ├── MorePosts.tsx ├── LoginForm.tsx ├── PostActions.tsx ├── UserAvatar.tsx ├── ProfileLink.tsx ├── Header.tsx ├── Following.tsx ├── Follower.tsx ├── Timestamp.tsx ├── MiniPost.tsx ├── CommentOptions.tsx ├── Comment.tsx ├── FollowingModal.tsx ├── FollowersModal.tsx ├── BookmarkButton.tsx ├── Like.tsx ├── PostsGrid.tsx ├── PostOptions.tsx ├── NavLinks.tsx ├── Post.tsx ├── CommentForm.tsx ├── ProfileTabs.tsx ├── EditPost.tsx ├── Comments.tsx ├── SinglePost.tsx ├── PostView.tsx ├── Skeletons.tsx ├── MoreDropdown.tsx ├── ProfileAvatar.tsx └── ProfileForm.tsx ├── lib ├── uploadthing.ts ├── prisma.ts ├── utils.ts ├── definitions.ts ├── schemas.ts ├── data.ts └── actions.ts ├── hooks └── useMount.ts ├── components.json ├── next-auth.d.ts ├── next.config.js ├── .gitignore ├── middleware.ts ├── tsconfig.json ├── README.md ├── package.json ├── auth.ts ├── tailwind.config.ts └── prisma └── schema.prisma /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukef7fywmrp/pixelgram-yt/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/fonts/CalSans-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lukef7fywmrp/pixelgram-yt/HEAD/public/fonts/CalSans-SemiBold.woff2 -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation"; 2 | 3 | async function Page() { 4 | redirect("/dashboard"); 5 | } 6 | 7 | export default Page; 8 | -------------------------------------------------------------------------------- /app/dashboard/(.)p/[id]/loading.tsx: -------------------------------------------------------------------------------- 1 | import { ViewPostSkeleton } from "@/components/Skeletons"; 2 | 3 | function Loading() { 4 | return ; 5 | } 6 | 7 | export default Loading; 8 | -------------------------------------------------------------------------------- /app/dashboard/p/[id]/edit/loading.tsx: -------------------------------------------------------------------------------- 1 | import { EditPostSkeleton } from "@/components/Skeletons"; 2 | 3 | function Loading() { 4 | return ; 5 | } 6 | 7 | export default Loading; 8 | -------------------------------------------------------------------------------- /app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import { config } from "@/auth"; 2 | import NextAuth from "next-auth/next"; 3 | 4 | const handler = NextAuth(config); 5 | 6 | export { handler as GET, handler as POST }; 7 | -------------------------------------------------------------------------------- /components/ui/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" 4 | 5 | const AspectRatio = AspectRatioPrimitive.Root 6 | 7 | export { AspectRatio } 8 | -------------------------------------------------------------------------------- /app/(auth)/layout.tsx: -------------------------------------------------------------------------------- 1 | function AuthLayout({ children }: { children: React.ReactNode }) { 2 | return
{children}
; 3 | } 4 | 5 | export default AuthLayout; 6 | -------------------------------------------------------------------------------- /app/(auth)/login/page.tsx: -------------------------------------------------------------------------------- 1 | import LoginForm from "@/components/LoginForm"; 2 | 3 | function LoginPage() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | 11 | export default LoginPage; 12 | -------------------------------------------------------------------------------- /lib/uploadthing.ts: -------------------------------------------------------------------------------- 1 | import { OurFileRouter } from "@/app/api/uploadthing/core"; 2 | import { generateComponents } from "@uploadthing/react"; 3 | 4 | export const { UploadButton, UploadDropzone, Uploader } = 5 | generateComponents(); 6 | -------------------------------------------------------------------------------- /app/fonts.ts: -------------------------------------------------------------------------------- 1 | import { Inter } from "next/font/google"; 2 | import localFont from "next/font/local"; 3 | 4 | export const calSans = localFont({ 5 | src: "../public/fonts/CalSans-SemiBold.woff2", 6 | }); 7 | 8 | export const inter = Inter({ subsets: ["latin"] }); 9 | -------------------------------------------------------------------------------- /app/api/uploadthing/route.ts: -------------------------------------------------------------------------------- 1 | import { createNextRouteHandler } from "uploadthing/next"; 2 | 3 | import { ourFileRouter } from "./core"; 4 | 5 | // Export routes for Next App Router 6 | export const { GET, POST } = createNextRouteHandler({ 7 | router: ourFileRouter, 8 | }); 9 | -------------------------------------------------------------------------------- /components/AuthProvider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { SessionProvider } from "next-auth/react"; 4 | 5 | function AuthProvider({ children }: { children: React.ReactNode }) { 6 | return {children}; 7 | } 8 | 9 | export default AuthProvider; 10 | -------------------------------------------------------------------------------- /app/dashboard/(home)/layout.tsx: -------------------------------------------------------------------------------- 1 | import Header from "@/components/Header"; 2 | 3 | function HomePageLayout({ children }: { children: React.ReactNode }) { 4 | return ( 5 |
6 |
7 | {children} 8 |
9 | ); 10 | } 11 | 12 | export default HomePageLayout; 13 | -------------------------------------------------------------------------------- /hooks/useMount.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect, useState } from "react"; 4 | 5 | function useMount() { 6 | const [mount, setMount] = useState(false); 7 | 8 | useEffect(() => { 9 | setMount(true); 10 | }, [mount]); 11 | 12 | return mount; 13 | } 14 | 15 | export default useMount; 16 | -------------------------------------------------------------------------------- /lib/prisma.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | declare global { 4 | var prisma: PrismaClient | undefined; 5 | } 6 | 7 | const prisma = global.prisma || new PrismaClient(); 8 | 9 | if (process.env.NODE_ENV !== "production") global.prisma = prisma; 10 | 11 | export default prisma; 12 | -------------------------------------------------------------------------------- /components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /components/Posts.tsx: -------------------------------------------------------------------------------- 1 | import { fetchPosts } from "@/lib/data"; 2 | import Post from "./Post"; 3 | 4 | async function Posts() { 5 | const posts = await fetchPosts(); 6 | 7 | return ( 8 | <> 9 | {posts.map((post) => ( 10 | 11 | ))} 12 | 13 | ); 14 | } 15 | 16 | export default Posts; 17 | -------------------------------------------------------------------------------- /components/ThemeProvider.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.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.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /app/dashboard/[username]/page.tsx: -------------------------------------------------------------------------------- 1 | import PostsGrid from "@/components/PostsGrid"; 2 | import { fetchPostsByUsername } from "@/lib/data"; 3 | 4 | async function ProfilePage({ 5 | params: { username }, 6 | }: { 7 | params: { username: string }; 8 | }) { 9 | const posts = await fetchPostsByUsername(username); 10 | 11 | return ; 12 | } 13 | 14 | export default ProfilePage; 15 | -------------------------------------------------------------------------------- /components/ViewPost.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | 3 | function ViewPost({ className }: { className?: string }) { 4 | return ( 5 |
6 | 12 |
13 | ); 14 | } 15 | 16 | export default ViewPost; 17 | -------------------------------------------------------------------------------- /next-auth.d.ts: -------------------------------------------------------------------------------- 1 | import type { Session, User } from "next-auth"; 2 | import type { JWT } from "@auth/core/jwt"; 3 | 4 | declare module "next-auth/jwt" { 5 | interface JWT { 6 | id: string; 7 | username?: string | null; 8 | } 9 | } 10 | 11 | declare module "next-auth" { 12 | interface Session { 13 | user: User & { 14 | username?: string | null; 15 | }; 16 | } 17 | 18 | interface User { 19 | username?: string | null; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/dashboard/[username]/saved/page.tsx: -------------------------------------------------------------------------------- 1 | import PostsGrid from "@/components/PostsGrid"; 2 | import { fetchSavedPostsByUsername } from "@/lib/data"; 3 | 4 | async function SavedPosts({ 5 | params: { username }, 6 | }: { 7 | params: { username: string }; 8 | }) { 9 | const savedPosts = await fetchSavedPostsByUsername(username); 10 | const posts = savedPosts?.map((savedPost) => savedPost.post); 11 | 12 | return ; 13 | } 14 | 15 | export default SavedPosts; 16 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { auth } from "@/auth"; 2 | import { type ClassValue, clsx } from "clsx"; 3 | import { twMerge } from "tailwind-merge"; 4 | 5 | export function cn(...inputs: ClassValue[]) { 6 | return twMerge(clsx(inputs)); 7 | } 8 | 9 | export const getUserId = async () => { 10 | const session = await auth(); 11 | const userId = session?.user?.id; 12 | 13 | if (!userId) { 14 | throw new Error("You must be signed in to use this feature"); 15 | } 16 | 17 | return userId; 18 | }; 19 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | remotePatterns: [ 5 | { 6 | protocol: "https", 7 | hostname: "utfs.io", 8 | }, 9 | { 10 | protocol: "https", 11 | hostname: "lh3.googleusercontent.com", 12 | }, 13 | { 14 | protocol: "https", 15 | hostname: "instagram.fpnq13-1.fna.fbcdn.net", 16 | }, 17 | ], 18 | }, 19 | }; 20 | 21 | module.exports = nextConfig; 22 | -------------------------------------------------------------------------------- /app/dashboard/(.)p/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import PostView from "@/components/PostView"; 2 | import { fetchPostById } from "@/lib/data"; 3 | import { notFound } from "next/navigation"; 4 | 5 | type Props = { 6 | params: { 7 | id: string; 8 | }; 9 | }; 10 | 11 | async function PostModal({ params: { id } }: Props) { 12 | const post = await fetchPostById(id); 13 | 14 | if (!post) { 15 | notFound(); 16 | } 17 | 18 | return ; 19 | } 20 | 21 | export default PostModal; 22 | -------------------------------------------------------------------------------- /app/dashboard/[username]/following/page.tsx: -------------------------------------------------------------------------------- 1 | import FollowingModal from "@/components/FollowingModal"; 2 | import { fetchProfile } from "@/lib/data"; 3 | 4 | async function FollowingPage({ 5 | params: { username }, 6 | }: { 7 | params: { 8 | username: string; 9 | }; 10 | }) { 11 | const profile = await fetchProfile(username); 12 | const following = profile?.following; 13 | 14 | return ; 15 | } 16 | 17 | export default FollowingPage; 18 | -------------------------------------------------------------------------------- /app/dashboard/[username]/followers/page.tsx: -------------------------------------------------------------------------------- 1 | import FollowersModal from "@/components/FollowersModal"; 2 | import { fetchProfile } from "@/lib/data"; 3 | 4 | async function FollowersPage({ 5 | params: { username }, 6 | }: { 7 | params: { 8 | username: string; 9 | }; 10 | }) { 11 | const profile = await fetchProfile(username); 12 | const followers = profile?.followedBy; 13 | 14 | return ; 15 | } 16 | 17 | export default FollowersPage; 18 | -------------------------------------------------------------------------------- /components/ActionIcon.tsx: -------------------------------------------------------------------------------- 1 | import { Button, ButtonProps } from "@/components/ui/button"; 2 | 3 | type Props = Partial & { 4 | children: React.ReactNode; 5 | }; 6 | 7 | function ActionIcon({ children, ...buttonProps }: Props) { 8 | return ( 9 | 18 | ); 19 | } 20 | 21 | export default ActionIcon; 22 | -------------------------------------------------------------------------------- /app/dashboard/p/[id]/edit/page.tsx: -------------------------------------------------------------------------------- 1 | import { fetchPostById } from "@/lib/data"; 2 | import EditPost from "@/components/EditPost"; 3 | import { notFound } from "next/navigation"; 4 | 5 | type Props = { 6 | params: { 7 | id: string; 8 | }; 9 | }; 10 | 11 | async function EditPostPage({ params: { id } }: Props) { 12 | const post = await fetchPostById(id); 13 | 14 | if (!post) { 15 | notFound(); 16 | } 17 | 18 | return ; 19 | } 20 | 21 | export default EditPostPage; 22 | -------------------------------------------------------------------------------- /components/SubmitButton.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ButtonProps } from "@/components/ui/button"; 4 | import { useFormStatus } from "react-dom"; 5 | 6 | type Props = ButtonProps & { 7 | children: React.ReactNode; 8 | }; 9 | 10 | function SubmitButton({ children, ...props }: Props) { 11 | const { pending } = useFormStatus(); 12 | 13 | return ( 14 | 17 | ); 18 | } 19 | 20 | export default SubmitButton; 21 | -------------------------------------------------------------------------------- /app/dashboard/(home)/page.tsx: -------------------------------------------------------------------------------- 1 | import Posts from "@/components/Posts"; 2 | import { PostsSkeleton } from "@/components/Skeletons"; 3 | import { Suspense } from "react"; 4 | 5 | function DashboardPage() { 6 | return ( 7 |
8 |
9 | }> 10 | 11 | 12 |
13 |
14 | ); 15 | } 16 | 17 | export default DashboardPage; 18 | -------------------------------------------------------------------------------- /.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*.local 30 | .env 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /app/dashboard/layout.tsx: -------------------------------------------------------------------------------- 1 | import SideNav from "@/components/SideNav"; 2 | 3 | export default function DashboardLayout({ 4 | children, 5 | }: { 6 | children: React.ReactNode; 7 | }) { 8 | return ( 9 |
10 |
11 | 12 |
13 |
14 | {children} 15 |
16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /middleware.ts: -------------------------------------------------------------------------------- 1 | import { withAuth } from "next-auth/middleware"; 2 | 3 | export default withAuth({ 4 | callbacks: { 5 | authorized({ req, token }) { 6 | const isLoggedIn = !!token; 7 | const isOnDashboard = req.nextUrl.pathname.startsWith("/dashboard"); 8 | if (isOnDashboard) { 9 | if (isLoggedIn) return true; 10 | return false; // Redirect unauthenticated users to login page 11 | } 12 | return true; 13 | }, 14 | }, 15 | }); 16 | 17 | export const config = { 18 | // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher 19 | matcher: ["/((?!api|_next/static|_next/image|.png).*)"], 20 | }; 21 | -------------------------------------------------------------------------------- /components/ShareButton.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import ActionIcon from "@/components/ActionIcon"; 4 | import { Link, Send } from "lucide-react"; 5 | import { toast } from "sonner"; 6 | 7 | function ShareButton({ postId }: { postId: string }) { 8 | return ( 9 | { 11 | navigator.clipboard.writeText( 12 | `${window.location.origin}/dashboard/p/${postId}` 13 | ); 14 | toast("Link copied to clipboard", { 15 | icon: , 16 | }); 17 | }} 18 | > 19 | 20 | 21 | ); 22 | } 23 | 24 | export default ShareButton; 25 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/dashboard/not-found.tsx: -------------------------------------------------------------------------------- 1 | import { Frown } from "lucide-react"; 2 | import Link from "next/link"; 3 | 4 | export default function NotFound() { 5 | return ( 6 |
7 | 8 |

404 Not Found

9 |

10 | The page you are looking for does not exist. Please check the URL or go 11 | back. 12 |

13 | 17 | Go Back 18 | 19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /app/dashboard/p/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SinglePostSkeleton } from "@/components/Skeletons"; 2 | import { Suspense } from "react"; 3 | import { Separator } from "@/components/ui/separator"; 4 | import SinglePost from "@/components/SinglePost"; 5 | import MorePosts from "@/components/MorePosts"; 6 | 7 | function PostPage({ params: { id } }: { params: { id: string } }) { 8 | return ( 9 |
10 | }> 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | ); 21 | } 22 | 23 | export default PostPage; 24 | -------------------------------------------------------------------------------- /components/Logo.tsx: -------------------------------------------------------------------------------- 1 | import { SwitchCamera } from "lucide-react"; 2 | import Link from "next/link"; 3 | import { buttonVariants } from "./ui/button"; 4 | import { calSans } from "@/app/fonts"; 5 | 6 | function Logo() { 7 | return ( 8 | 17 | 18 | 23 | 24 | ); 25 | } 26 | 27 | export default Logo; 28 | -------------------------------------------------------------------------------- /app/dashboard/(settings)/edit-profile/page.tsx: -------------------------------------------------------------------------------- 1 | import { auth } from "@/auth"; 2 | import ProfileForm from "@/components/ProfileForm"; 3 | import { fetchProfile } from "@/lib/data"; 4 | import { Metadata } from "next"; 5 | import { notFound } from "next/navigation"; 6 | 7 | export const metadata: Metadata = { 8 | title: "Edit profile", 9 | description: "Edit profile", 10 | }; 11 | 12 | async function EditProfile() { 13 | const session = await auth(); 14 | const profile = await fetchProfile(session?.user.username!); 15 | 16 | if (!profile) { 17 | notFound(); 18 | } 19 | 20 | return ( 21 |
22 |

Edit profile

23 | 24 | 25 |
26 | ); 27 | } 28 | 29 | export default EditProfile; 30 | -------------------------------------------------------------------------------- /app/api/uploadthing/core.ts: -------------------------------------------------------------------------------- 1 | import { createUploadthing, type FileRouter } from "uploadthing/next"; 2 | import { auth } from "@/auth"; 3 | 4 | const f = createUploadthing(); 5 | 6 | export const ourFileRouter = { 7 | imageUploader: f({ image: { maxFileSize: "4MB" } }) 8 | .middleware(async ({ req }) => { 9 | const session = await auth(); 10 | const user = session?.user; 11 | 12 | if (!user) throw new Error("Unauthorized"); 13 | 14 | return { userId: user.id }; 15 | }) 16 | .onUploadComplete(async ({ metadata, file }) => { 17 | console.log("Upload complete for userId:", metadata.userId); 18 | 19 | console.log("file url", file.url); 20 | 21 | return { uploadedBy: metadata.userId }; 22 | }), 23 | } satisfies FileRouter; 24 | 25 | export type OurFileRouter = typeof ourFileRouter; 26 | -------------------------------------------------------------------------------- /components/ProfileHeader.tsx: -------------------------------------------------------------------------------- 1 | import { ChevronDown, Settings, UserPlus } from "lucide-react"; 2 | import { Button } from "./ui/button"; 3 | 4 | function ProfileHeader({ username }: { username: string | null }) { 5 | return ( 6 |
7 | 10 | 11 |
12 |

{username}

13 | 14 |
15 | 16 | 19 |
20 | ); 21 | } 22 | 23 | export default ProfileHeader; 24 | -------------------------------------------------------------------------------- /components/Error.tsx: -------------------------------------------------------------------------------- 1 | type Props = { 2 | res: 3 | | { 4 | errors: { [key: string]: string[] | undefined } | undefined; 5 | message: string; 6 | } 7 | | { 8 | message: string; 9 | errors?: undefined; 10 | }; 11 | }; 12 | 13 | function Error({ res: { errors, message } }: Props) { 14 | return ( 15 |
16 | {errors && 17 | Object.entries(errors).map(([key, value]) => ( 18 |
19 | {key}: 20 | {value} 21 |
22 | ))} 23 | 24 |
25 | {message} 26 |
27 |
28 | ); 29 | } 30 | 31 | export default Error; 32 | -------------------------------------------------------------------------------- /components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /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 |