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 961 additions and 9 deletions
export const getUser = async (username: string | undefined) => {
try {
const user = await fetch(`/api/users/${username}`)
.then((result) => result.json())
return user
} catch (error: any) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log(error.response.data)
console.log(error.response.status)
console.log(error.response.headers)
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.log(error.request)
} else {
// Something happened in setting up the request that triggered an Error
console.log("Error", error.message)
}
console.log(error.config)
}
}
\ No newline at end of file
export const getUsers = async (id?: string) => {
try {
const data = await fetch(`/api/users${id ? `?id=${id}` : ""}`)
.then((result) => result.json())
return data
} catch (error: any) {
return error.response.data
}
}
\ No newline at end of file
export const unfollowUser = async (userId: string) => {
try {
const data = await fetch(`/api/users/follow`, {
method: "DELETE",
body: JSON.stringify({ userId }),
}).then((result) => result.json())
return data
} catch (error: any) {
return error.message
}
}
\ No newline at end of file
import { uploadFiles } from "@/lib/uploadthing"
import { IProfile } from "./../types/index"
export const updateProfile = async ({
profile,
userId,
}: {
profile: IProfile
userId: string
}) => {
if (!profile) return
try {
let bannerfileprops: { fileUrl: string; fileKey: string } = { fileUrl: '', fileKey: '' }
let imagefileprops: { fileUrl: string; fileKey: string } = { fileUrl: '', fileKey: '' }
if (profile.banner.file) {
[bannerfileprops] = await uploadFiles({ files: [profile.banner.file], endpoint: 'mediaUploader' })
}
if (profile.image.file) {
[imagefileprops] = await uploadFiles({ files: [profile.image.file], endpoint: 'imageUploader' })
}
console.log('bannerfileprops', profile.banner.removed)
const data = await fetch(`/api/users/${userId}`, {
method: 'PUT',
body: JSON.stringify({
userId,
name: profile.name,
bio: profile.bio,
location: profile.location,
website: profile.website,
banner: profile.banner.removed ? "" : bannerfileprops.fileUrl !== "" ? bannerfileprops.fileUrl : undefined,
image: imagefileprops.fileUrl !== "" ? imagefileprops.fileUrl : undefined,
})
}).then((result) => result.json())
return data
} catch (error: any) {
return error.Message
}
}
\ No newline at end of file
"use client"
import { Icons } from "@/components/icons"
import { AspectRatio } from "@/components/ui/aspect-ratio"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Form, FormControl, FormField, FormItem, FormMessage } from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
import { toast } from "@/components/ui/use-toast"
import { UserAvatar } from "@/components/user-avatar"
import { zodResolver } from "@hookform/resolvers/zod"
import Image from "next/image"
import { useRouter } from "next/navigation"
import { use, useEffect, useRef, useState } from "react"
import { useForm } from "react-hook-form"
import { z } from "zod"
import { useUpdateProfile } from "../hooks/use-update-profile"
import { IProfile, IUser } from "../types"
const FormSchema = z.object({
name: z.string().min(1, "Name can't be blank").max(50, "Name can't be more than 50 characters"),
bio: z.string().max(160, "Bio can't be more than 160 characters").optional(),
location: z.string().max(30, "Location can't be more than 30 characters").optional(),
website: z.string().max(100, "Website can't be more than 100 characters").optional(),
})
const ImagesSchema = z.custom<File>().refine((file) => file instanceof File, { message: "Expected a file" })
.refine((file) => file?.size < 4000000, { message: "Images must be less than 4MB" })
.optional()
export const EditProfileModal = ({ user }: { user: IUser }) => {
const { isLoading, isSuccess, mutate } = useUpdateProfile()
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
name: user.name,
bio: user.bio || "",
location: user.location || "",
website: user.website || "",
},
})
const [open, setOpen] = useState(false)
const [imageErrorMessage, setImageErrorMessage] = useState("")
const [chosenBanner, setChosenBanner] = useState<Partial<IProfile>>({
banner: { removed: false, url: user?.banner || "", file: undefined },
})
const [chosenImages, setChosenImages] = useState<Partial<IProfile>>({
image: { url: user?.image || "", file: undefined },
})
const bannerInputRef = useRef<HTMLInputElement>(null)
const imageInputRef = useRef<HTMLInputElement>(null)
const router = useRouter()
async function onSave(formData: z.infer<typeof FormSchema>) {
if (!user) return null
const dirty = form.formState.dirtyFields
const profile: IProfile = {
name: formData.name,
bio: dirty.bio ? formData.bio : undefined,
location: dirty.location ? formData.location : undefined,
website: dirty.website ? formData.website : undefined,
banner: { removed: chosenBanner.banner?.removed, url: user?.banner || "", file: chosenBanner.banner?.file },
image: { url: user?.image || "", file: chosenImages.image?.file },
}
mutate({
profile,
userId: user.id,
})
}
let disable = true
if (!isLoading) {
if (chosenBanner.banner?.file !== undefined || chosenImages.image?.file !== undefined || form.formState.isDirty) {
disable = false
}
}
useEffect(() => {
if (!open) {
form.reset()
form.setValue("name", user?.name)
form.setValue("bio", user?.bio || "")
form.setValue("location", user?.location || "")
form.setValue("website", user?.website || "")
// wait 150ms for dialog to close before resetting image
setTimeout(() => {
setImageErrorMessage("")
setChosenBanner({ banner: { removed: false, url: user?.banner || "", file: undefined } })
setChosenImages({ image: { url: user?.image || "", file: undefined } })
}, 150)
}
}, [form, open, user])
useEffect(() => {
if (isSuccess) {
toast({
description: "Successfully updated profile.",
})
setOpen(false)
router.refresh()
}
}, [isSuccess, router])
const chooseImage = async (event: any, type: string) => {
const file = event.target.files[0]
if (!file) return
try {
ImagesSchema.parse(file)
setImageErrorMessage("")
} catch (error: any) {
const err = error as z.ZodError
setImageErrorMessage(err.issues[0].message)
return
}
const reader = new FileReader()
if (type === "banner" && bannerInputRef.current) {
bannerInputRef.current.value = ""
reader.onloadend = () => {
setChosenBanner({
["banner"]: { removed: false, url: reader.result as string, file },
})
}
}
if (type === "image" && imageInputRef.current) {
imageInputRef.current.value = ""
reader.onloadend = () => {
setChosenImages({
["image"]: { url: reader.result as string, file },
})
}
}
reader.readAsDataURL(file)
}
if (!user) return null
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button variant="outline">Edit Profile</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[600px]">
<DialogHeader>
<DialogTitle>Edit profile</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you are done.
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSave)}>
<div className="grid gap-3 py-3">
<div className="space-y-2">
<div className="grid grid-cols-4 items-center gap-4">
<div className="col-span-1 relative w-full overflow-hidden items-center">
<div className="object-center object-cover w-full h-full">
{!chosenImages.image?.url ?
<UserAvatar
user={{ username: user.username, image: null }}
className="object-center object-cover w-full h-full aspect-square"
/>
:
<AspectRatio ratio={1 / 1} className="overflow-hidden rounded-full">
<Image
src={chosenImages.image?.url}
alt={"user image"}
fill
priority
className="object-center object-cover w-full h-full"
/>
</AspectRatio>
}
</div>
<input
className="hidden resize-none"
type="file"
accept="image/*"
ref={imageInputRef}
onChange={(e) => chooseImage(e, "image")}
disabled={isLoading}
/>
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 flex space-x-3">
<Button
type="button"
variant="outline"
size="icon"
className="bg-opacity-50 dark:bg-opacity-50"
onClick={() => imageInputRef?.current?.click()}
disabled={isLoading}
>
<Icons.camera />
</Button>
</div>
</div>
<Card className="col-span-3 relative w-full overflow-hidden border-none">
<AspectRatio ratio={3 / 1} className="bg-muted overflow-hidden">
{chosenBanner.banner?.url &&
<Image
src={chosenBanner.banner?.url}
alt={"user banner image"}
fill
priority
className="object-center object-cover w-full h-full"
/>
}
</AspectRatio>
<input
className="hidden w-full resize-none"
type="file"
accept="image/*"
ref={bannerInputRef}
onChange={(e) => chooseImage(e, "banner")}
disabled={isLoading}
/>
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 flex space-x-3">
<Button
type="button"
variant="outline"
size="icon"
className="bg-opacity-50 dark:bg-opacity-50"
onClick={() => bannerInputRef.current?.click()}
disabled={isLoading}
>
<Icons.camera />
</Button>
{chosenBanner.banner?.url && (
<Button
type="button"
variant="outline"
size="icon"
className="bg-opacity-50 dark:bg-opacity-50"
onClick={() => setChosenBanner({ banner: { removed: true, url: "", file: undefined } })}
disabled={isLoading}
>
<Icons.close />
</Button>
)}
</div>
</Card>
</div>
{imageErrorMessage &&
<div className="grid grid-cols-4 items-center gap-4">
<div className="col-span-1"></div>
<div className="col-span-3">
<p className="text-sm font-medium text-destructive">{imageErrorMessage}</p>
</div>
</div>
}
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="name" className="text-right">
Name
</Label>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem className="col-span-3">
<FormControl>
<Input id="name" disabled={isLoading} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="bio" className="text-right">
Bio
</Label>
<FormField
control={form.control}
name="bio"
render={({ field }) => (
<FormItem className="col-span-3">
<FormControl>
<Textarea id="bio" className="resize-none" disabled={isLoading} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="location" className="text-right">
Location
</Label>
<FormField
control={form.control}
name="location"
render={({ field }) => (
<FormItem className="col-span-3">
<FormControl>
<Input id="location" disabled={isLoading} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="website" className="text-right">
Website
</Label>
<FormField
control={form.control}
name="website"
render={({ field }) => (
<FormItem className="col-span-3">
<FormControl>
<Input id="website" disabled={isLoading} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
<DialogFooter>
<Button type="submit" disabled={disable}>
{isLoading && (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
)}
Save changes
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
)
}
\ No newline at end of file
"use client"
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
import Link from "next/link"
import { usePathname } from "next/navigation"
export const ProfileNavbar = ({ children, param }: { children: React.ReactNode, param: string }) => {
const pathValue = usePathname().split("/")[2] || "gweets"
return (
<Tabs defaultValue={"gweets"} value={pathValue} className="w-full h-full p-6 md:p-12 ">
<TabsList className="grid w-full grid-cols-4">
<Link href={`/${param}`} scroll={false} className="w-full inline-flex items-center justify-center">
<TabsTrigger value="gweets" className="w-full">
Gweets
</TabsTrigger>
</Link>
<Link href={`/${param}/games`} scroll={false} className="w-full inline-flex items-center justify-center">
<TabsTrigger value="games" className="w-full">
Games
</TabsTrigger>
</Link>
<Link href={`/${param}/media`} scroll={false} className="w-full inline-flex items-center justify-center">
<TabsTrigger value="media" className="w-full">
Media
</TabsTrigger>
</Link>
<Link href={`/${param}/likes`} scroll={false} className="w-full inline-flex items-center justify-center">
<TabsTrigger value="likes" className="w-full">
Likes
</TabsTrigger>
</Link>
</TabsList>
{children}
</Tabs>
)
}
\ No newline at end of file
import { Card } from "@/components/ui/card"
import { Skeleton } from "@/components/ui/skeleton"
export const ProfileSideContent = () => {
return (
<Card className="p-6 grid items-start gap-2 bg-secondary">
<h1>Media</h1>
<div className="grid grid-cols-1 gap-4">
{Array.from({ length: 2 }, (_, i) => i + 1).map((i) => {
return (
<Skeleton key={i} className="aspect-[264/374] bg-gray-300" />
)
})}
</div>
</Card>
)
}
\ No newline at end of file
import { FollowButton } from "@/components/follow-button"
import { Icons } from "@/components/icons"
import { AspectRatio } from "@/components/ui/aspect-ratio"
import { UserAvatar } from "@/components/user-avatar"
import { getCurrentUser } from "@/lib/session"
import Image from "next/image"
import Link from "next/link"
import { IUser } from "../types"
import { following } from "../utils/following"
import { EditProfileModal } from "./edit-profile-modal"
import { UserJoinDate } from "./user-join-date"
export const ProfileUserInfo = async ({ user }: { user: IUser }) => {
const session = await getCurrentUser()
const isFollowing = following({
user,
sessionUserId: session ? session.id : "",
})
return (
<>
<AspectRatio ratio={3 / 1} className="bg-muted w-full overflow-hidden">
{user.banner &&
<Image
src={user.banner}
alt={"user banner image"}
fill
priority
className="object-center object-cover w-full h-full"
/>
}
</AspectRatio>
<div className="relative">
<div className="flex items-end justify-between p-6 md:px-12">
<div>
<div className="absolute bottom-6 md:bottom-3">
<UserAvatar
user={{ username: user.username, image: user.image || null }}
className="h-20 md:h-40 w-20 md:w-40"
/>
</div>
</div>
<div>
{session?.id === user.id ? (
<EditProfileModal user={user} />
) : (
<FollowButton
userId={user.id}
username={user.username ? user.username : ""}
isFollowing={isFollowing}
/>
)}
</div>
</div>
</div>
<div className="px-6 md:px-12 flex flex-col space-y-3 w-full">
<div className="pb-3 whitespace-nowrap items-center">
<h1 className="text-2xl font-bold">{user.name}</h1>
<h1 className="text-md text-sky-500">@{user.username}</h1>
</div>
{user.bio && <h1 className="break-words">{user.bio}</h1>}
<div className="flex whitespace-nowrap items-center space-x-6 text-muted-foreground">
{user.location &&
<div className="flex items-center">
<Icons.location className="mr-1" />
<div>{user.location}</div>
</div>
}
{user.website &&
<div className="flex items-center">
<Icons.website className="mr-1" />
<div>{user.website}</div>
</div>
}
{user.createdAt && <UserJoinDate date={user.createdAt} />}
</div>
<div className="flex whitespace-nowrap items-center space-x-6">
<Link href={`/${user.username}/following`}>
<span className="font-bold">{user._count?.following}</span>
<span className="text-muted-foreground"> Following</span>
</Link>
<Link href={`/${user.username}/followers`}>
<span className="font-bold">{user._count?.followers}</span>
<span className="text-muted-foreground"> Followers</span>
</Link>
</div>
</div>
</ >
)
}
\ No newline at end of file
import GameItem from "@/components/game-item"
import { db } from "@/lib/db"
import { getFavoriteGames } from "@/lib/igdb"
import { getCurrentUser } from "@/lib/session"
import { IGame } from "@/types/igdb-types"
import { redirect } from "next/navigation"
export const UserGames = async ({ username }: { username: string }) => {
const user = await getCurrentUser()
const sessionUser = await db.user.findFirst({
where: {
id: user?.id
},
include: {
following: true,
followers: true
}
})
const fullUser = await db.user.findFirst({
where: {
username: username
},
include: {
following: true,
followers: true
}
})
if (!fullUser) {
redirect('/home')
}
let favoritegames = undefined
let playingGames = undefined
let finishedGames = undefined
let planningGames = undefined
if (fullUser?.favGameList?.length !== 0 && fullUser?.favGameList?.length != undefined) {
favoritegames = await getFavoriteGames(fullUser?.favGameList!)
}
if (fullUser?.playingGameList?.length !== 0) {
playingGames = await getFavoriteGames(fullUser?.playingGameList!)
}
if (fullUser?.finishedGameList?.length !== 0) {
finishedGames = await getFavoriteGames(fullUser?.finishedGameList!)
}
if (fullUser?.planningGameList?.length !== 0) {
planningGames = await getFavoriteGames(fullUser?.planningGameList!)
}
return (
<div className="p-3 space-y-12">
<div>
<h1 className="text-2xl font-bold pb-3">Favorite Games</h1>
<div className="grid grid-cols-1 ss:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4 lg:gap-8 items-center">
{favoritegames ? favoritegames.map((game: IGame) => (
<GameItem id={game.id} name={game.name} cover={game.cover} key={game.id} />
))
:
<p>No favorites currently...</p>}
</div>
</div>
<div>
<h1 className="text-2xl font-bold pb-3">Currently playing</h1>
<div className="grid grid-cols-1 ss:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4 lg:gap-8 items-center">
{playingGames ? playingGames.map((game: IGame) => (
<GameItem id={game.id} name={game.name} cover={game.cover} key={game.id} />
))
:
<p>Currently not playing any games...</p>}
</div>
</div>
<div>
<h1 className="text-2xl font-bold pb-3">Planning to play</h1>
<div className="grid grid-cols-1 ss:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4 lg:gap-8 items-center">
{planningGames ? planningGames.map((game: IGame) => (
<GameItem id={game.id} name={game.name} cover={game.cover} key={game.id} />
))
:
<p>Currently not planning to play any games...</p>}
</div>
</div>
<div>
<h1 className="text-2xl font-bold pb-3">Finished Games</h1>
<div className="grid grid-cols-1 ss:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4 lg:gap-8 items-center">
{finishedGames ? finishedGames.map((game: IGame) => (
<GameItem id={game.id} name={game.name} cover={game.cover} key={game.id} />
))
:
<p>No finished games...</p>}
</div>
</div>
</div >
)
}
\ No newline at end of file
"use client"
import { InfiniteGweets } from "@/components/gweets/components/infinite-gweets"
import { useGweets } from "@/components/gweets/hooks/use-gweets"
import LoadingItem from "@/components/loading-item"
import { TryAgain } from "@/components/try-again"
import { usePathname } from "next/navigation"
export const UserGweets = ({ username, sessionname }: { username: string, sessionname: string }) => {
const pathValue = usePathname().split("/")[2] || "gweets"
const {
data: gweets,
isLoading,
isError,
isSuccess,
isFetchingNextPage,
fetchNextPage,
hasNextPage,
} = useGweets({
queryKey: ["gweets", username, pathValue],
type: "user_" + pathValue,
id: username,
})
if (isLoading) {
return (
<LoadingItem />
)
}
if (isError) {
return (
<TryAgain />
)
}
if (gweets?.pages[0].gweets.length === 0) {
if (sessionname === username) {
return (
<div className="m-6 flex justify-center">
<div className="font-bold">
<h1>You haven&apos;t gweeted anything yet.</h1>
<p>When you do, it&apos;ll show up here.</p>
</div>
</div>
)
} else {
return (
<div className="m-6 flex justify-center">
<div className="font-bold">
<h1><span className="text-sky-500">@{username}</span> hasn&apos;t gweeted anything yet.</h1>
<p>When they do, it&apos;ll show up here.</p>
</div>
</div>
)
}
}
return (
<InfiniteGweets
gweets={gweets}
hasNextPage={hasNextPage}
fetchNextPage={fetchNextPage}
isFetchingNextPage={isFetchingNextPage}
isSuccess={isSuccess}
/>
)
}
\ No newline at end of file
import { Icons } from "@/components/icons"
import dayjs from "dayjs"
export const UserJoinDate = ({
date,
showIcon = true,
}: {
date: Date | undefined
showIcon?: boolean
}) => {
return (
<div className="flex items-center">
{showIcon && (<Icons.calendar className="mr-1" />)}
<div>
Joined {dayjs(date).format("MMMM YYYY")}
</div>
</div>
)
}
\ No newline at end of file
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { followUser } from "../api/follow-user"
import { unfollowUser } from "../api/unfollow-user"
export const useFollow = (type: "follow" | "unfollow") => {
const queryClient = useQueryClient()
return useMutation(
({
userId,
}: {
userId: string
}) => {
return type === "follow"
? followUser(userId)
: unfollowUser(userId)
},
{
onSuccess: () => {
console.log("success")
},
onError: () => {
console.log("error")
},
onSettled: ({ userId }) => {
queryClient.invalidateQueries(["users", userId])
queryClient.invalidateQueries(["tweets"])
},
},
)
}
\ No newline at end of file
import { useQuery } from "@tanstack/react-query"
import { getFollows } from "../api/get-follows"
import { IUser } from "../types"
export const useGetFollows = ({
id,
type,
}: {
id: string | undefined
type: string | undefined
}) => {
return useQuery<IUser[]>(
["users", id, type],
async () => {
return getFollows(id, type)
},
{
refetchOnWindowFocus: false,
},
)
}
\ No newline at end of file
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { updateProfile } from "../api/update-profile"
import { IProfile } from "../types"
export const useUpdateProfile = () => {
const queryClient = useQueryClient()
return useMutation(
({
profile,
userId,
}: {
profile: IProfile
userId: string
}) => {
return updateProfile({ profile, userId })
},
{
onSuccess: () => {
console.log("success")
},
onError: (error) => {
console.log(error)
},
onSettled: ({ userId }) => {
queryClient.invalidateQueries(["users", userId])
},
},
)
}
\ No newline at end of file
import { useQuery } from "@tanstack/react-query"
import { ILike } from "@/components/gweets/types"
import { getUserLikes } from "../api/get-user-likes"
export const useUserLikes = (id: string | undefined) => {
return useQuery<ILike[]>(
["likes", { userId: id }],
async () => {
return getUserLikes(id)
},
{
refetchOnWindowFocus: false,
},
)
}
\ No newline at end of file
import { useQuery, useQueryClient } from "@tanstack/react-query"
import { getUser } from "../api/get-user"
import { IUser } from "../types"
export const useUser = (username: string | undefined) => {
const queryClient = useQueryClient()
return useQuery<IUser>(
["users", username],
async () => {
return getUser(username)
},
{
refetchOnWindowFocus: false,
onSuccess: (data) => {
queryClient.setQueryData(["users", username], data)
},
},
)
}
\ No newline at end of file
import { useQuery, useQueryClient } from "@tanstack/react-query"
import { useSession } from "next-auth/react"
import { getUsers } from "../api/get-users"
import { IUser } from "../types"
export const useUsers = () => {
const { data: session } = useSession()
const queryClient = useQueryClient()
return useQuery<IUser[]>(
["users"],
async () => {
return getUsers(session?.user?.id)
},
{
refetchOnWindowFocus: false,
onSuccess: (data) => {
queryClient.setQueryData(["users"], data)
},
},
)
}
\ No newline at end of file
import { Follows, Like, User } from "@prisma/client"
import { Like, User } from "@prisma/client"
import { IGweet } from "@/components/gweets/types"
export interface IUser extends User {
gweets: IGweet[]
followers: IFollow[]
following: IFollow[]
followers: User[]
following: User[]
likes: ILike[]
_count?: {
followers?: number
......@@ -16,19 +16,19 @@ export interface IUser extends User {
export interface IProfile {
name: string
bio: string | undefined
location: string | undefined
website: string | undefined
banner: {
removed: boolean | undefined
url: string | undefined
file: File | undefined
}
avatar: {
image: {
url: string | undefined
file: File | undefined
}
}
export interface IFollow extends Follows {
follower: IUser
following: IUser
}
export interface ILike extends Like {
user: IUser
gweet: IGweet
......
import { IUser } from "../types"
export const following = ({
user,
sessionUserId,
}: {
user: IUser
sessionUserId: string
}): boolean => {
return user?.followers?.some((follower) => follower.id === sessionUserId)
}
\ No newline at end of file
......@@ -55,6 +55,8 @@ export function UserAuthForm({ type, className, ...props }: UserAuthFormProps) {
if (!res.ok) {
if (res.status === 422) {
setError('email', { type: 'manual', message: 'This email is already in use. Please choose another one.' })
setIsLoading(false)
return
}
setIsLoading(false)
......
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