Skip to content
Snippets Groups Projects
Commit fd324f31 authored by Yusuf Akgül's avatar Yusuf Akgül :hatching_chick:
Browse files

Merge branch 'profile' into 'main'

Profile

See merge request !38
parents 730e3738 95b938d3
No related branches found
No related tags found
1 merge request!38Profile
Pipeline #39781 passed
Showing
with 450 additions and 117 deletions
......@@ -9,5 +9,5 @@ export async function GET() {
return new NextResponse(JSON.stringify({ error: 'unauthorized' }), { status: 401 })
}
return NextResponse.json({ authenticated: !!session })
return NextResponse.json({ authenticated: !!session }, { status: 200 })
}
\ No newline at end of file
......@@ -19,7 +19,7 @@ export async function POST(req: Request) {
})
if (existingUser) {
throw new Error('email already exists')
return NextResponse.json({ error: 'Email already exists' }, { status: 422 })
}
let isUnique = false
......@@ -46,21 +46,11 @@ export async function POST(req: Request) {
}
})
return NextResponse.json({ usernameOrEmail: user.email })
} catch (error) {
return NextResponse.json({
usernameOrEmail: user.email
})
} catch (err: any) {
if (err.message === 'email already exists') {
return new NextResponse(JSON.stringify({
error: err.message
}), { status: 422 }
)
}
return new NextResponse(
JSON.stringify({
error: err.message
}), { status: 500 }
)
message: "Something went wrong",
error,
}, { status: 500 })
}
}
\ No newline at end of file
......@@ -4,7 +4,27 @@ import { createUploadthing, type FileRouter } from "uploadthing/next"
const f = createUploadthing()
export const ourFileRouter = {
imageUploader: f({ image: { maxFileSize: "4MB", maxFileCount: 4 } })
mediaUploader: f({ image: { maxFileSize: "4MB", maxFileCount: 4 } })
.middleware(async ({ req }) => {
const user = await getCurrentUser()
if (!user) throw new Error("Unauthorized")
return { userId: user.id }
})
.onUploadComplete(async ({ metadata, file }) => { }),
imageUploader: f({ image: { maxFileSize: "4MB", maxFileCount: 1 } })
.middleware(async ({ req }) => {
const user = await getCurrentUser()
if (!user) throw new Error("Unauthorized")
return { userId: user.id }
})
.onUploadComplete(async ({ metadata, file }) => { }),
bannerUploader: f({ image: { maxFileSize: "4MB", maxFileCount: 1 } })
.middleware(async ({ req }) => {
const user = await getCurrentUser()
......
import { db } from "@/lib/db"
import { NextResponse } from "next/server"
import { z } from "zod"
export async function GET(request: Request, context: { params: { username: string } }) {
const { username } = context.params
const idSchema = z.string()
const zod = idSchema.safeParse(username)
if (!zod.success) {
return NextResponse.json(zod.error, { status: 400 })
}
try {
const user = await db.user.findUnique({
where: {
username,
},
select: {
id: true,
name: true,
username: true,
email: true,
image: true,
banner: true,
createdAt: true,
bio: true,
location: true,
website: true,
followers: true,
following: true,
_count: {
select: {
followers: true,
following: true,
},
},
},
})
return NextResponse.json(user, { status: 200 })
} catch (error: any) {
return NextResponse.json(error.message, { status: 500 })
}
}
export async function PUT(request: Request) {
const {
userId,
name,
bio,
location,
website,
banner,
image,
} = await request.json()
const userSchema = z
.object({
userId: z.string().cuid(),
name: z.string().min(1).max(50),
bio: z.string().max(160).optional(),
location: z.string().max(30).optional(),
website: z.string().max(100).optional(),
banner: z.string().optional(),
image: z.string().optional(),
})
.strict()
const zod = userSchema.safeParse({
userId,
name,
bio,
location,
website,
banner,
image,
})
if (!zod.success) {
return NextResponse.json(zod.error, { status: 400 })
}
try {
const user = await db.user.update({
where: {
id: userId,
},
data: {
name,
bio,
location,
website,
banner,
image,
},
})
return NextResponse.json(user, { status: 200 })
} catch (error: any) {
return NextResponse.json(error.message, { status: 500 })
}
}
\ No newline at end of file
......@@ -4,13 +4,13 @@ import { revalidatePath } from "next/cache"
import { NextRequest, NextResponse } from "next/server"
export async function PUT(req: NextRequest) {
const user = await getCurrentUser()
const session = await getCurrentUser()
if (!user) {
if (!session) {
return NextResponse.json({ status: 401, message: 'Unauthorized' })
}
const userId = user.id
const userId = session.id
const data = await req.json()
data.gameId = parseInt(data.gameId)
......@@ -51,9 +51,9 @@ export async function PUT(req: NextRequest) {
const path = req.nextUrl.searchParams.get('path') || '/'
revalidatePath(path)
return NextResponse.json({ status: 201, message: 'Game Hinzugefügt' })
return NextResponse.json({ message: 'Game added' }, { status: 201 })
} catch (error: any) {
return NextResponse.json({ status: 500, message: error.message })
return NextResponse.json({ message: error.message }, { status: 500 })
}
}
\ No newline at end of file
import { db } from "@/lib/db"
import { getCurrentUser } from "@/lib/session"
import { NextResponse } from "next/server"
import { z } from "zod"
// get following or followers information
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const userId = searchParams.get("userId") || undefined
const type = searchParams.get("type") || undefined
const userIdSchema = z.string().cuid().optional()
const zod = userIdSchema.safeParse(userId)
if (!zod.success) {
return NextResponse.json(zod.error, { status: 400 })
}
try {
if (type === "followers") {
const followers = await db.user
.findUnique({
where: {
id: userId,
},
})
.followers()
return NextResponse.json(followers, { status: 200 })
} else if (type === "following") {
const following = await db.user
.findUnique({
where: {
id: userId,
},
})
.following()
return NextResponse.json(following, { status: 200 })
}
} catch (error: any) {
return NextResponse.json(error.message, { status: 500 })
}
}
// follow a user
export async function PUT(request: Request) {
const session = await getCurrentUser()
const { userId } = await request.json()
const followerIdSchema = z
.object({
userId: z.string().cuid(),
})
.strict()
const zod = followerIdSchema.safeParse({ userId })
if (!zod.success) {
return NextResponse.json(zod.error, { status: 400 })
}
try {
await db.user.update({
where: {
id: userId,
},
data: {
followers: {
connect: {
id: session?.id,
},
},
},
})
return NextResponse.json(
{
message: "followed",
}, { status: 200 }
)
} catch (error: any) {
return NextResponse.json(error.message, { status: 500 })
}
}
// unfollow a user
export async function DELETE(request: Request) {
const session = await getCurrentUser()
const { userId } = await request.json()
const followerIdSchema = z
.object({
userId: z.string().cuid(),
})
.strict()
const zod = followerIdSchema.safeParse({ userId })
if (!zod.success) {
return NextResponse.json(zod.error, { status: 400 })
}
try {
await db.user.update({
where: {
id: userId,
},
data: {
followers: {
disconnect: {
id: session?.id,
},
},
},
})
return NextResponse.json(
{
message: "unfollowed",
}, { status: 200 },
)
} catch (error: any) {
return NextResponse.json(error.message, { status: 500 })
}
}
\ No newline at end of file
......@@ -8,11 +8,11 @@ export async function PUT(req: NextRequest) {
const sessionUser = await getCurrentUser()
const data: User = await req.json()
console.log("userid", sessionUser!.id, "formdataid", data.id)
if (!sessionUser || sessionUser.id != data.id) {
return NextResponse.json({ status: 401, message: 'Unauthorized' })
return NextResponse.json({ message: 'Unauthorized' }, { status: 401 })
}
console.log("put list")
try {
const dbUser = await db.user.findFirst({
where: {
......@@ -41,11 +41,12 @@ export async function PUT(req: NextRequest) {
}
})
}
} catch (error) {
}
const path = req.nextUrl.searchParams.get('path') || '/'
revalidatePath(path)
const path = req.nextUrl.searchParams.get('path') || '/'
revalidatePath(path)
return NextResponse.json({ status: 201, message: 'Game Hinzugefügt' })
return NextResponse.json({ message: 'Game added' }, { status: 201 })
} catch (error: any) {
return NextResponse.json({ message: error.message }, { status: 500 })
}
}
\ No newline at end of file
import { db } from "@/lib/db"
import { NextResponse } from "next/server"
import { z } from "zod"
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const id = searchParams.get("id") || undefined
const idSchema = z.string().cuid().optional()
const zod = idSchema.safeParse(id)
if (!zod.success) {
return NextResponse.json(zod.error, { status: 400 })
}
try {
const users = await db.user.findMany({
where: {
NOT: {
id,
},
},
orderBy: {
createdAt: "desc",
},
select: {
id: true,
name: true,
username: true,
email: true,
image: true,
following: true,
followers: true,
},
})
return NextResponse.json(users, { status: 200 })
} catch (error: any) {
return NextResponse.json(error.message, { status: 500 })
}
}
\ No newline at end of file
......@@ -33,7 +33,7 @@ export default function AddGameToFinishedList(props: { gameId: string, user: Use
formData.id = user.id
formData.finishedGameList = props.user.finishedGameList.filter((id) => id !== gameId)
console.log(formData.finishedGameList)
const response = await fetch('/api/gamelists', {
const response = await fetch('/api/users/gamelists', {
method: 'PUT',
body: JSON.stringify(formData)
})
......@@ -52,7 +52,7 @@ export default function AddGameToFinishedList(props: { gameId: string, user: Use
formData.id = user.id
props.user.finishedGameList.push(gameId)
formData.finishedGameList = props.user.finishedGameList
const response = await fetch('/api/gamelists', {
const response = await fetch('/api/users/gamelists', {
method: 'PUT',
body: JSON.stringify(formData)
})
......
......@@ -33,7 +33,7 @@ export default function AddGameToPlanList(props: { gameId: string, user: User })
formData.id = user.id
formData.planningGameList = props.user.planningGameList.filter((id) => id !== gameId)
console.log(formData.planningGameList)
const response = await fetch('/api/gamelists', {
const response = await fetch('/api/users/gamelists', {
method: 'PUT',
body: JSON.stringify(formData)
})
......@@ -52,7 +52,7 @@ export default function AddGameToPlanList(props: { gameId: string, user: User })
formData.id = user.id
props.user.planningGameList.push(gameId)
formData.planningGameList = props.user.planningGameList
const response = await fetch('/api/gamelists', {
const response = await fetch('/api/users/gamelists', {
method: 'PUT',
body: JSON.stringify(formData)
})
......
......@@ -33,7 +33,7 @@ export default function AddGameToPlayingList(props: { gameId: string, user: User
formData.id = user.id
formData.playingGameList = props.user.playingGameList.filter((id) => id !== gameId)
console.log(formData.playingGameList)
const response = await fetch('/api/gamelists', {
const response = await fetch('/api/users/gamelists', {
method: 'PUT',
body: JSON.stringify(formData)
})
......@@ -52,7 +52,7 @@ export default function AddGameToPlayingList(props: { gameId: string, user: User
formData.id = user.id
props.user.playingGameList.push(gameId)
formData.playingGameList = props.user.playingGameList
const response = await fetch('/api/gamelists', {
const response = await fetch('/api/users/gamelists', {
method: 'PUT',
body: JSON.stringify(formData)
})
......
......@@ -25,7 +25,7 @@ export const postGweet = async ({
try {
let fileprops: { fileUrl: string; fileKey: string }[] = []
if (files.length > 0) {
fileprops = await uploadFiles({ files, endpoint: 'imageUploader' })
fileprops = await uploadFiles({ files, endpoint: 'mediaUploader' })
}
const data = await fetch('/api/gweets', {
......
"use client"
import { useRouter } from "next/navigation"
import { useEffect, useState } from "react"
import { Icons } from "./icons"
import { useFollow } from "./profile/hooks/use-follow"
import { Button } from "./ui/button"
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog"
export const FollowButton = ({
userId,
username,
isFollowing = false,
}: {
userId: string
username: string
isFollowing?: boolean
}) => {
const followMutation = useFollow("follow")
const unfollowMutation = useFollow("unfollow")
const [text, setText] = useState<"Following" | "Unfollow">("Following")
const [isHovered, setIsHovered] = useState(false)
const [open, setOpen] = useState(false)
const router = useRouter()
useEffect(() => {
if (followMutation.isSuccess) {
router.refresh()
}
if (unfollowMutation.isSuccess) {
setOpen(false)
router.refresh()
}
}, [router, followMutation.isSuccess, unfollowMutation.isSuccess])
return (
<Dialog open={open} onOpenChange={setOpen}>
{isFollowing ? (
<DialogTrigger asChild>
<Button variant={`${isHovered ? "destructive" : "outline"}`} size="lg"
className="w-24"
onMouseEnter={() => {
setText("Unfollow")
setIsHovered(true)
}}
onMouseOut={() => {
setText("Following")
setIsHovered(false)
}}>
{text}
</Button>
</DialogTrigger>
) : (
<Button size="lg" onClick={() => followMutation.mutate({ userId })}>
Follow
</Button>
)}
<DialogContent>
<DialogHeader>
<DialogTitle>Unfollow @{username}?</DialogTitle>
<DialogDescription>
You can still view their profile, unless their Gweets are protected.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button type="submit" onClick={() => unfollowMutation.mutate({ userId })} disabled={unfollowMutation.isLoading}>
{unfollowMutation.isLoading && (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
)}
Unfollow
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
\ No newline at end of file
"use client"
import { Follows, Prisma } from '@prisma/client'
import { useSession } from 'next-auth/react'
import { usePathname } from "next/navigation"
import { useEffect, useState } from 'react'
import { Button } from './ui/button'
// 1: Define a type that includes the relation to `Post`
const userWithFollows = Prisma.validator<Prisma.UserArgs>()({
include: { followers: true, following: true },
})
// 2: Define a type that only contains a subset of the scalar fields
const userPersonalData = Prisma.validator<Prisma.UserArgs>()({
select: { email: true, name: true },
})
// 3: This type will include a user and all their posts
type UserWithFollows = Prisma.UserGetPayload<typeof userWithFollows>
export default function FollowButton({ user, followingId }: { user: UserWithFollows; followingId: string }) {
const [isFollowing, setIsFollowing] = useState(false)
const [follows, setFollows] = useState([])
const [buttonPressed, setButtonPress] = useState(false)
const pathname = usePathname()
const { data: session } = useSession()
async function fetchFollows() {
try {
const response = await fetch('/api/followers')
const data = await response.json()
setFollows(data.follows)
setIsFollowing(false)
data.follows?.forEach((f: Follows) => {
if (f.followerId == user.id && f.followingId == followingId) {
setIsFollowing(true)
return
}
})
} catch (error) {
}
}
useEffect(() => {
fetchFollows()
// TODO: fix this warning
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [buttonPressed])
async function handleFollow(e: any) {
e.preventDefault()
const response = await fetch('/api/followers', {
method: 'PUT',
body: JSON.stringify({ user, followingId })
})
setButtonPress(!buttonPressed)
}
return (
<>
{pathname !== `/${session?.user.username}` &&
<Button onClick={handleFollow}>
{isFollowing ? 'Unfollow' : 'Follow'}
</Button>}
</>
)
}
\ No newline at end of file
......@@ -49,9 +49,11 @@ export const InfiniteGweets = ({
key={gweet.id}
>
<Gweet gweet={gweet} />
<div className="px-6">
<Separator className="my-3" />
</div>
{index !== page.gweets.length - 1 && (
<div className="px-6">
<Separator className="my-3" />
</div>
)}
</div>
))
})}
......
......@@ -4,6 +4,7 @@ import {
ArrowRight,
ArrowUpToLine,
BellRing,
CalendarRange,
Check,
ChevronLeft,
ChevronRight,
......@@ -18,8 +19,10 @@ import {
Image,
Laptop,
Link,
Link2,
Loader2,
LucideProps,
MapPin,
MessageCircle,
Moon,
MoreHorizontal,
......@@ -29,6 +32,7 @@ import {
Repeat2,
Settings,
SunMedium,
SwitchCamera,
Trash,
User,
Users,
......@@ -87,6 +91,10 @@ export const Icons: IconsType = {
trash: Trash, // Delete Button
link: Link, // Link Button
regweet: Repeat2, // Regweet Button
location: MapPin, // Location Button
website: Link2, // Website Button
calendar: CalendarRange, // Calendar Button
camera: SwitchCamera, // Change Image Button
post: FileText,
page: File,
media: Image,
......
......@@ -31,7 +31,7 @@ export const Sidebar = ({ items, user }: { items: SidebarNavItem[], user: User |
<span
className={cn(
"group flex items-center rounded-md px-3 py-2 font-medium hover:bg-accent hover:text-accent-foreground",
path === item.href ? "bg-accent" : "transparent",
path.startsWith(item.href) ? "bg-accent" : "transparent",
item.disabled && "cursor-not-allowed opacity-80"
)}
>
......
export const followUser = async (userId: string) => {
try {
const data = await fetch("/api/users/follow", {
method: "PUT",
body: JSON.stringify({ userId }),
}).then((result) => result.json())
return data
} catch (error: any) {
return error.message
}
}
\ No newline at end of file
export const getFollows = async (id: string | undefined, type: string | undefined) => {
try {
const data = await fetch(`/api/users/follow?type=${type}&user_id=${id}`)
.then((result) => result.json())
return data
} catch (error: any) {
return error.message
}
}
\ No newline at end of file
export const getUserLikes = async (id: string | undefined) => {
try {
const data = await fetch(`/api/tweets/likes?user_id=${id}`)
.then((result) => result.json())
return data
} catch (error: any) {
return error.message
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment