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

Merge branch 'main' of gitlab.bht-berlin.de:s86116/project_ss23 into testing + some small changes

parents 5793ac18 82a1dae3
No related branches found
No related tags found
1 merge request!33Testing
Pipeline #38827 passed
Showing
with 425 additions and 308 deletions
import { buttonVariants } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { UserAvatar } from "@/components/user-avatar";
import { cn, formatTimeElapsed } from "@/lib/utils";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { IGweet } from "../types";
import { GweetActions } from "./gweet-actions";
import { GweetOptions } from "./gweet-options";
export const Gweet = ({ gweet }: { gweet: IGweet }) => {
const router = useRouter();
return (
<div
tabIndex={0}
onClick={() => router.push(`/${gweet.author.username}/status/${gweet.id}`)}
className={cn(buttonVariants({ variant: "ghost" }), "flex flex-col flex-grow h-auto w-full text-left cursor-pointer items-start p-3 space-y-3")}
>
{/* TODO replyto */}
{/* <div className="">
{gweet?.replyToGweetId && (
<div className="">
<span className="">Replying to</span>
<button
onClick={(e) => {
e.stopPropagation();
router.push(`/${gweet?.author.username}`);
}}
className=""
>
@{gweet?.author.username}
</button>
</div>
)}
</div> */}
<div className="flex flex-row h-auto w-full">
<UserAvatar
user={{ username: gweet.author.username, image: gweet.author.image }}
className="h-10 w-10"
/>
<div className="flex flex-col flex-grow space-y-3 ml-3 w-1">
<div className="flex items-start">
<div className="flex space-x-2 flex-grow">
<h1 className="font-bold">{gweet.author.name}</h1>
<h1 className="text-sky-500 text-sm">
@{gweet.author.username}
</h1>
<span>·</span>
<h1 className="text-gray-500 text-sm">
{formatTimeElapsed(gweet.createdAt)}
</h1>
</div>
<div className="ml-auto">
<GweetOptions gweet={gweet} />
</div>
</div>
<div className="flex flex-col flex-grow space-y-3 w-full">
{gweet.content && <h1 className="break-words">{gweet.content}</h1>}
{gweet.media.length > 0 && (
<div className={`grid object-cover h-[600px] ${gweet.media.length === 1 ? "grid-cols-1"
: gweet.media.length === 2 ? "grid-cols-2 gap-3"
: gweet.media.length === 3 || 4 ? "grid-cols-2 grid-rows-2 gap-3"
: ""
}`}
>
{gweet.media.map((image, i) => {
const isFirstImage = gweet.media.length === 3 && i === 0;
return (
<Card key={i} className={`relative max-h-[600px] overflow-hidden ${isFirstImage ? "row-span-2" : ""}`}>
<Image
src={image.url as string}
alt="gweet image"
fill
className="object-cover rounded-lg"
/>
</Card>
);
})}
</div>
)}
{/* TODO */}
{/* {quoted_gweet && <QuotedGweet gweet={quoted_gweet} />} */}
</div>
</div>
</div>
<div className="flex justify-end flex-grow w-full" >
<GweetActions gweet={gweet} />
</div>
</div>
);
};
\ No newline at end of file
"use client";
import LoadingItem from "@/components/loading-item";
import { TryAgain } from "@/components/try-again";
import { Card } from "@/components/ui/card";
import { useGweets } from "../hooks/use-gweets";
import { InfiniteGweets } from "./infinite-gweets";
export const Gweets = () => {
const {
data: gweets,
isLoading,
isError,
isSuccess,
isFetchingNextPage,
fetchNextPage,
hasNextPage,
} = useGweets({});
if (isLoading) {
return <LoadingItem />;
}
if (isError) {
return <TryAgain />;
}
return (
<Card className="w-full h-full mt-6 p-2 xl:p-4 ">
<InfiniteGweets
gweets={gweets}
isSuccess={isSuccess}
isFetchingNextPage={isFetchingNextPage}
fetchNextPage={fetchNextPage}
hasNextPage={hasNextPage}
/>
</Card>
);
};
"use client"
import LoadingItem from "@/components/loading-item";
import { Separator } from "@/components/ui/separator";
import { useEffect } from "react";
import { useInView } from "react-intersection-observer";
import { IInfiniteGweets } from "../types";
import { Gweet } from "./gweet";
export const InfiniteGweets = ({
gweets,
isSuccess,
isFetchingNextPage,
fetchNextPage,
hasNextPage,
}: {
gweets: IInfiniteGweets;
isSuccess: boolean | undefined;
isFetchingNextPage: boolean | undefined;
fetchNextPage: () => Promise<any> | void;
hasNextPage: boolean | undefined;
}) => {
const { ref, inView } = useInView();
useEffect(() => {
if (inView && hasNextPage) {
fetchNextPage();
}
}, [inView, hasNextPage, fetchNextPage]);
return (
<div>
{isSuccess &&
gweets?.pages?.map((page) => {
return page?.gweets?.map((gweet, index) => (
<div
ref={index === page.gweets.length - 4 ? ref : undefined}
key={gweet.id}
>
<Gweet gweet={gweet} />
<div className="px-6">
<Separator className="my-3" />
</div>
</div>
));
})}
{isFetchingNextPage && <LoadingItem />}
</div>
);
};
\ No newline at end of file
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { deleteGweet } from "../api/delete-gweet";
export const useDeleteGweet = () => {
const queryClient = useQueryClient();
return useMutation(({ gweetId }: { gweetId: string }) => {
return deleteGweet(gweetId);
}, {
onSuccess: () => {
queryClient.invalidateQueries(["gweets"]);
},
onError: (error) => {
console.log(error);
},
});
};
\ No newline at end of file
import { useQuery, useQueryClient } from "@tanstack/react-query";
import getGweet from "../api/get-gweet";
import { IGweet } from "../types";
export const useGweet = (id: string | undefined) => {
const queryClient = useQueryClient();
return useQuery<IGweet>(
["gweets", id],
async () => {
return getGweet(id);
},
{
refetchOnWindowFocus: false,
onSuccess: (data) => {
queryClient.setQueryData(["gweets", id], data);
},
},
);
};
import { useInfiniteQuery } from "@tanstack/react-query";
import { getGweets } from "../api/get-gweets";
export const useGweets = ({
queryKey,
type,
id,
}: {
queryKey?: string[];
type?: string;
id?: string;
}) => {
const data = useInfiniteQuery(
queryKey ?? ["gweets"],
({ pageParam = "" }) =>
getGweets({
pageParam,
limit: 20,
type,
id,
}),
{
getNextPageParam: (lastPage) => {
return lastPage?.nextId ?? false;
},
refetchOnWindowFocus: false,
},
);
return data;
};
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { toggleLike } from "../api/toggle-like";
export const useLike = ({
gweetAuthorId,
sessionOwnerId,
}: {
gweetAuthorId: string | undefined;
sessionOwnerId: string | undefined;
}) => {
const queryClient = useQueryClient();
return useMutation(
({ gweetId, userId }: { gweetId: string | undefined; userId: string }) => {
return toggleLike({ gweetId, userId });
},
{
onSuccess: () => {
queryClient.invalidateQueries(["gweets"]);
queryClient.invalidateQueries(["users", sessionOwnerId]);
if (sessionOwnerId !== gweetAuthorId)
queryClient.invalidateQueries(["users", gweetAuthorId]);
},
onError: () => {
console.log("error");
},
},
);
};
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { handleRegweet } from "../api/handle-regweet";
export const useRegweet = () => {
const QueryClient = useQueryClient();
return useMutation(
({ gweetId, userId }: { gweetId: string; userId: string }) => {
return handleRegweet(gweetId, userId);
},
{
onSuccess: () => {
QueryClient.invalidateQueries(["gweets"]);
QueryClient.invalidateQueries(["users"]);
},
onError: (error: any) => {
console.log(error);
},
onSettled: () => { },
},
);
};
\ No newline at end of file
import { Gweet, Like, Media, Regweet } from "@prisma/client";
import { IUser } from "@/components/profile/types";
export interface IFeed {
id: number;
}
export interface IGweet extends Gweet {
author: IUser;
quotedGweet: IGweet;
likes: ILike[];
media: IMedia[];
regweets: IRegweet[];
allQuotes: IGweet[];
allComments: IGweet[];
}
export interface ILike extends Like {
user: IUser;
gweet: IGweet;
}
export interface IMedia extends Media {
gweet: IGweet;
}
export interface IRegweet extends Regweet {
user: IUser;
}
export interface IInfiniteGweets {
pages: { gweets: IGweet[]; nextId?: string | undefined }[];
pageParams: any;
}
...@@ -7,7 +7,7 @@ import { GameUnityLogo } from "./logo" ...@@ -7,7 +7,7 @@ import { GameUnityLogo } from "./logo"
export function MainNav() { export function MainNav() {
return ( return (
<div className="flex gap-6 md:gap-10"> <div className="flex gap-6 md:gap-10">
<Link href="/" className="items-center space-x-2 flex"> <Link href="/home" className="items-center space-x-2 flex">
<GameUnityLogo className="h-8 w-8" /> <GameUnityLogo className="h-8 w-8" />
</Link> </Link>
</div> </div>
......
...@@ -17,13 +17,16 @@ import { ...@@ -17,13 +17,16 @@ import {
Home, Home,
Image, Image,
Laptop, Laptop,
Link,
Loader2, Loader2,
LucideProps, LucideProps,
MessageCircle, MessageCircle,
Moon, Moon,
MoreHorizontal,
MoreVertical, MoreVertical,
Pizza, Pizza,
Plus, Plus,
Repeat2,
Settings, Settings,
SunMedium, SunMedium,
Trash, Trash,
...@@ -78,9 +81,12 @@ export const Icons: IconsType = { ...@@ -78,9 +81,12 @@ export const Icons: IconsType = {
chevronLeft: ChevronLeft, // Back Login Arrow chevronLeft: ChevronLeft, // Back Login Arrow
spinner: Loader2, // Loading Spinner spinner: Loader2, // Loading Spinner
github: Github, // Github Icon github: Github, // Github Icon
close: X, close: X, // Close Button
chevronRight: ChevronRight, moreOptions: MoreHorizontal, // More Options Button
trash: Trash, chevronRight: ChevronRight, // dropdown chevron
trash: Trash, // Delete Button
link: Link, // Link Button
regweet: Repeat2, // Regweet Button
post: FileText, post: FileText,
page: File, page: File,
media: Image, media: Image,
......
import { Icons } from "./icons"
export default function LoadingItem() {
return (
<div className="w-full m-6 flex items-center justify-center">
<Icons.spinner className="h-6 w-6 animate-spin" />
</div>
)
}
\ No newline at end of file
File moved
// import Link from "next/link";
// import { Icons } from "./icons";
// import { Button } from "./ui/button";
// export default function CommentButton(props: { data: any }) {
// const postid = props.data.id
// const replyCount = props.data.Comment.length
// return (
// <Link href={`/home/${postid}`}>
// <Button type="submit" variant="ghost" size="lg" className="px-6 py-3" >
// <span className="pr-1">{replyCount}</span>
// <Icons.messagecircle className="h-5 w-5" />
// </Button>
// </Link>
// )
// }
\ No newline at end of file
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import * as z from "zod"
import { Button } from "@/components/ui/button"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Textarea } from "@/components/ui/textarea"
import { toast } from "@/components/ui/use-toast"
import { useSession } from "next-auth/react"
import { FormEvent, Fragment, useEffect, useState } from "react"
import { Icons } from "./icons"
import PostItem from "./post-item"
import { Card } from "./ui/card"
import { Separator } from "./ui/separator"
import { Skeleton } from "./ui/skeleton"
const FormSchema = z.object({
gweet: z
.string()
.min(1, { message: "Come on post something...", })
.max(1000, { message: "Gweets cannot be more that 1000 characters.", }),
})
export function PostGweets() {
const [isGweetLoading, setIsGweetLoading] = useState<boolean>(false);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [messages, setMessages] = useState<any[]>([]);
const user = useSession();
useEffect(() => {
fetchMessages();
}, []);
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
})
async function onGweet(data: z.infer<typeof FormSchema>) {
setIsGweetLoading(true);
await fetch('/api/messages', {
method: 'POST',
body: JSON.stringify(data),
next: { tags: ['collection'] }
})
toast({
title: "Your gweet is being processed...",
description: (
<pre className="mt-2 w-[340px] rounded-md bg-slate-600 p-4">
<code className="text-white">{JSON.stringify(data, null, 2)}</code>
</pre>
),
})
setIsGweetLoading(false);
form.setValue('gweet', '');
await fetchMessages();
}
async function fetchMessages() {
setIsLoading(true);
try {
const res = await fetch(`/api/messages`);
if (!res.ok) {
throw new Error("Failed to fetch messages");
}
const data = await res.json();
setMessages(data);
} catch (error) {
return toast({
variant: "destructive",
title: "Uh oh! Something went wrong.",
description: "Failed to fetch messages. Please try again.",
});
}
setIsLoading(false);
}
async function onSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
await fetchMessages();
}
return (
<div>
<Form {...form}>
<form onSubmit={form.handleSubmit(onGweet)} className="space-y-6">
<FormField
control={form.control}
name="gweet"
render={({ field }) => (
<FormItem>
<FormLabel>Gweet</FormLabel>
<FormControl>
<Textarea
placeholder="What's on your mind?"
className="resize-none"
disabled={isGweetLoading || !user.data?.user}
{...field}
/>
</FormControl>
<FormDescription>
Your gweets will be public, and everyone can see them.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" disabled={isGweetLoading || !user.data?.user}>Submit</Button>
</form>
</Form>
<Card className="w-full h-full overflow-hidden p-6 md:p-12 mt-12">
<form onSubmit={onSubmit}>
<Button disabled={isLoading} type="submit" className="w-full mb-6">
{isLoading && (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
)}
Load More
</Button>
{messages.length > 0 ? (
messages.map((message: any) => (
<Fragment key={message.id}>
<PostItem msg={message} />
<Separator className="mt-3 mb-6" />
</Fragment>
))
) : (
<>
{Array.from({ length: 4 }, (_, i) => i + 1).map((i) => (
<>
<div className="flex">
<Skeleton className="h-10 w-10 rounded-full" />
<div className="ml-4 flex flex-col flex-grow">
<div>
<div className="flex items-center">
<div className="mx-auto w-full space-y-6">
<Skeleton className="h-[30px] w-1/4" />
<Skeleton className="h-[20px] w-2/3" />
<Skeleton className="h-[20px] w-full" />
</div>
</div>
</div>
<div className="flex justify-end space-x-3 mt-3" >
<Skeleton key={i} className="h-10 w-20 rounded-full" />
<Skeleton key={i} className="h-10 w-20 rounded-full" />
</div>
</div>
</div>
<Separator className="mt-3 mb-6" />
</>
))}
</>
)}
</form>
</Card>
</div>
)
}
\ No newline at end of file
import { formatTimeElapsed } from "@/lib/utils";
import { IPost } from "@/types/prisma-item";
// import CommentButton from "./post-comment-button";
// import LikeButton from "./post-like-button";
import { UserAvatar } from "./user-avatar";
export default function PostItem({ msg }: { msg: IPost }) {
return (
<div className="flex">
<UserAvatar
user={{ name: msg.user.username || null, image: msg.user.image || null }}
className="h-10 w-10"
/>
<div className="ml-4 flex flex-col flex-grow">
<div>
<div className="flex items-center">
<h1 className="font-bold mr-2">{msg.user.name}</h1>
<h1 className="text-sky-500 text-sm">
@{msg.user.username}
</h1>
<h1 className="text-gray-500 text-sm ml-auto">
{formatTimeElapsed(msg.createdAt)}
</h1>
</div>
<h1>{msg.content}</h1>
</div>
{/* <div className="flex justify-end" >
<LikeButton data={msg} />
<CommentButton data={msg} />
</div> */}
</div>
</div>
)
}
\ No newline at end of file
// "use client"
// import { Prisma } from "@prisma/client";
// import { useRouter } from "next/navigation";
// import { startTransition } from "react";
// import { Icons } from "./icons";
// import { Button } from "./ui/button";
// type likeType = Prisma.LikeUncheckedCreateInput
// type postType = Prisma.PostUncheckedCreateInput
// type commentType = Prisma.CommentUncheckedCreateInput
// // type commentWithLikes = Prisma.CommentGetPayload<typeof commentWithPosts>
// export default function LikeButton(props: { data: any }) {
// const router = useRouter();
// const likeCount = props.data.Like.length
// // const likeCount = countLikes(likeArray, props.data);
// async function postLike(e: any) {
// e.preventDefault()
// const postLikeData = props.data;
// const likeData = {} as likeType
// if (postLikeData.postId == undefined) {
// likeData.postId = postLikeData.id!
// } else {
// likeData.postId = postLikeData.postId
// likeData.commentId = postLikeData.id
// }
// likeData.userId = postLikeData.userId
// const response = await fetch('/api/likes', {
// method: 'PUT',
// body: JSON.stringify(likeData)
// })
// startTransition(() => {
// // Refresh the current route and fetch new data from the server without
// // losing client-side browser or React state.
// router.refresh();
// });
// return await response.json()
// }
// return (
// <form onSubmit={postLike}>
// <Button type="submit" variant="ghost" size="lg" className="px-6 py-3" >
// <span className="pr-1">{likeCount}</span>
// <Icons.heart className="h-5 w-5" />
// </Button>
// </form>
// )
// }
// function countLikes(likeArray: any, msg: any): number {
// let likeCount = 0;
// if (msg.postId == undefined) {
// likeArray.forEach(function (like: any) {
// if (like.postId == undefined) {
// likeCount++;
// }
// })
// } else {
// likeArray.forEach(function (like: any) {
// if (like.postId != undefined) {
// likeCount++;
// }
// })
// }
// return likeCount;
// }
import { Follows, Like, User } from "@prisma/client";
import { IGweet } from "@/components/gweets/types";
export interface IUser extends User {
gweets: IGweet[];
followers: IFollow[];
following: IFollow[];
likes: ILike[];
_count?: {
followers?: number;
following?: number;
};
}
export interface IProfile {
name: string;
bio: string | undefined;
banner: {
url: string | undefined;
};
avatar: {
url: string | undefined;
};
}
export interface IFollow extends Follows {
follower: IUser;
following: IUser;
}
export interface ILike extends Like {
user: IUser;
gweet: IGweet;
}
export const getHashtags = async () => {
try {
const data = await fetch(`/api/hashtags`).then((result) => result.json());
return data;
} catch (error: any) {
return error.response.data;
}
};
export const postHashtags = async (hashtags: string[]) => {
try {
const data = await fetch(`/api/hashtags`, {
method: 'POST',
body: JSON.stringify(hashtags)
}).then((result) => result.json())
return data;
} catch (error: any) {
return error.response.data;
}
};
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