├── .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
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 |21 | Pixelgram 22 |
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 |{username}
13 |14 | More posts from{" "} 15 | 19 | {postUsername} 20 | {" "} 21 |
22 | 23 |35 | Profile 36 |
37 | 38 | ); 39 | } 40 | 41 | export default ProfileLink; 42 | -------------------------------------------------------------------------------- /components/Header.tsx: -------------------------------------------------------------------------------- 1 | import { Heart, Search } from "lucide-react"; 2 | import Link from "next/link"; 3 | import { Button } from "./ui/button"; 4 | import { calSans } from "@/app/fonts"; 5 | 6 | function Header() { 7 | return ( 8 |11 | Pixelgram 12 |
13 | 14 | 15 |{following.following.username}
24 | 25 | {!isCurrentUser && ( 26 |{follower.follower.username}
26 | 27 | {!isCurrentUser && ( 28 |{post.caption}
29 |{comment.body}
31 |52 | {optimisticLikes.length}{" "} 53 | {optimisticLikes.length === 1 ? "like" : "likes"} 54 |
55 | )} 56 |No more posts.
11 |{post.likes.length}
34 |{post.comments.length}
41 |74 | {link.name} 75 |
76 | 77 | ); 78 | })} 79 | > 80 | ); 81 | } 82 | 83 | export default NavLinks; 84 | -------------------------------------------------------------------------------- /components/ui/tabs.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as TabsPrimitive from "@radix-ui/react-tabs" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Tabs = TabsPrimitive.Root 9 | 10 | const TabsList = React.forwardRef< 11 | React.ElementRef
26 | {username}
27 |
32 | •
33 |
34 |
37 | Dubai, United Arab Emirates 38 |
39 |{post.caption}
62 |79 | {tab.title} 80 |
81 |{comment.body}
75 |{postUsername}
59 |60 | {post.user.name} 61 |
62 |73 | No comments yet. 74 |
75 |Start the conversation.
76 |Settings
78 |Your activity
82 |Saved
86 |Switch appearance
94 |Log out
99 |Switch appearance
108 | {theme === "dark" ? ( 109 |161 | {body} 162 |
163 | ) 164 | }) 165 | FormMessage.displayName = "FormMessage" 166 | 167 | export { 168 | useFormField, 169 | Form, 170 | FormItem, 171 | FormLabel, 172 | FormControl, 173 | FormDescription, 174 | FormMessage, 175 | FormField, 176 | } 177 | -------------------------------------------------------------------------------- /lib/data.ts: -------------------------------------------------------------------------------- 1 | import { unstable_noStore as noStore } from "next/cache"; 2 | import prisma from "./prisma"; 3 | 4 | export async function fetchPosts() { 5 | // equivalent to doing fetch, cache: no-store 6 | noStore(); 7 | 8 | try { 9 | const data = await prisma.post.findMany({ 10 | include: { 11 | comments: { 12 | include: { 13 | user: true, 14 | }, 15 | orderBy: { 16 | createdAt: "desc", 17 | }, 18 | }, 19 | likes: { 20 | include: { 21 | user: true, 22 | }, 23 | }, 24 | savedBy: true, 25 | user: true, 26 | }, 27 | orderBy: { 28 | createdAt: "desc", 29 | }, 30 | }); 31 | 32 | return data; 33 | } catch (error) { 34 | console.error("Database Error:", error); 35 | throw new Error("Failed to fetch posts"); 36 | } 37 | } 38 | 39 | export async function fetchPostById(id: string) { 40 | noStore(); 41 | 42 | try { 43 | const data = await prisma.post.findUnique({ 44 | where: { 45 | id, 46 | }, 47 | include: { 48 | comments: { 49 | include: { 50 | user: true, 51 | }, 52 | orderBy: { 53 | createdAt: "desc", 54 | }, 55 | }, 56 | likes: { 57 | include: { 58 | user: true, 59 | }, 60 | }, 61 | savedBy: true, 62 | user: true, 63 | }, 64 | }); 65 | 66 | return data; 67 | } catch (error) { 68 | console.error("Database Error:", error); 69 | throw new Error("Failed to fetch post"); 70 | } 71 | } 72 | 73 | export async function fetchPostsByUsername(username: string, postId?: string) { 74 | noStore(); 75 | 76 | try { 77 | const data = await prisma.post.findMany({ 78 | where: { 79 | user: { 80 | username, 81 | }, 82 | NOT: { 83 | id: postId, 84 | }, 85 | }, 86 | include: { 87 | comments: { 88 | include: { 89 | user: true, 90 | }, 91 | orderBy: { 92 | createdAt: "desc", 93 | }, 94 | }, 95 | likes: { 96 | include: { 97 | user: true, 98 | }, 99 | }, 100 | savedBy: true, 101 | user: true, 102 | }, 103 | orderBy: { 104 | createdAt: "desc", 105 | }, 106 | }); 107 | 108 | return data; 109 | } catch (error) { 110 | console.error("Database Error:", error); 111 | throw new Error("Failed to fetch posts"); 112 | } 113 | } 114 | 115 | export async function fetchProfile(username: string) { 116 | noStore(); 117 | 118 | try { 119 | const data = await prisma.user.findUnique({ 120 | where: { 121 | username, 122 | }, 123 | include: { 124 | posts: { 125 | orderBy: { 126 | createdAt: "desc", 127 | }, 128 | }, 129 | saved: { 130 | orderBy: { 131 | createdAt: "desc", 132 | }, 133 | }, 134 | followedBy: { 135 | include: { 136 | follower: { 137 | include: { 138 | following: true, 139 | followedBy: true, 140 | }, 141 | }, 142 | }, 143 | }, 144 | following: { 145 | include: { 146 | following: { 147 | include: { 148 | following: true, 149 | followedBy: true, 150 | }, 151 | }, 152 | }, 153 | }, 154 | }, 155 | }); 156 | 157 | return data; 158 | } catch (error) { 159 | console.error("Database Error:", error); 160 | throw new Error("Failed to fetch profile"); 161 | } 162 | } 163 | 164 | export async function fetchSavedPostsByUsername(username: string) { 165 | noStore(); 166 | 167 | try { 168 | const data = await prisma.savedPost.findMany({ 169 | where: { 170 | user: { 171 | username, 172 | }, 173 | }, 174 | include: { 175 | post: { 176 | include: { 177 | comments: { 178 | include: { 179 | user: true, 180 | }, 181 | orderBy: { 182 | createdAt: "desc", 183 | }, 184 | }, 185 | likes: { 186 | include: { 187 | user: true, 188 | }, 189 | }, 190 | savedBy: true, 191 | user: true, 192 | }, 193 | }, 194 | }, 195 | orderBy: { 196 | createdAt: "desc", 197 | }, 198 | }); 199 | 200 | return data; 201 | } catch (error) { 202 | console.error("Database Error:", error); 203 | throw new Error("Failed to fetch saved posts"); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /app/dashboard/create/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Error from "@/components/Error"; 4 | import { AspectRatio } from "@/components/ui/aspect-ratio"; 5 | import { Button } from "@/components/ui/button"; 6 | import { 7 | Dialog, 8 | DialogContent, 9 | DialogHeader, 10 | DialogTitle, 11 | } from "@/components/ui/dialog"; 12 | import { 13 | Form, 14 | FormControl, 15 | FormDescription, 16 | FormField, 17 | FormItem, 18 | FormLabel, 19 | FormMessage, 20 | } from "@/components/ui/form"; 21 | import { Input } from "@/components/ui/input"; 22 | import useMount from "@/hooks/useMount"; 23 | import { createPost } from "@/lib/actions"; 24 | import { CreatePost } from "@/lib/schemas"; 25 | import { UploadButton } from "@/lib/uploadthing"; 26 | import { zodResolver } from "@hookform/resolvers/zod"; 27 | import Image from "next/image"; 28 | import { usePathname, useRouter } from "next/navigation"; 29 | import { useState } from "react"; 30 | import { useForm } from "react-hook-form"; 31 | import { toast } from "sonner"; 32 | import { z } from "zod"; 33 | 34 | function CreatePage() { 35 | const pathname = usePathname(); 36 | const isCreatePage = pathname === "/dashboard/create"; 37 | const router = useRouter(); 38 | const mount = useMount(); 39 | const form = useForm{profile.username}
61 | {isCurrentUser ? ( 62 | <> 63 | 70 | 78 | Edit profile 79 | 80 | 87 | > 88 | ) : ( 89 | <> 90 | 97 |114 | {profile.posts.length} posts 115 |
116 | 117 | 121 | {profile.followedBy.length} followers 122 | 123 | 124 | 128 | {profile.following.length} following 129 | 130 |{profile.bio}
135 |{profile.username}
58 |60 | Change profile photo 61 |
62 |