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

Merge branch 'rewrite.Gweets' into 'main'

Rewrite.gweets

See merge request !30
parents 0edda241 aea3b5e1
No related branches found
No related tags found
1 merge request!30Rewrite.gweets
Pipeline #38797 passed
Showing
with 279 additions and 308 deletions
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;
}
};
export const retrieveHashtagsFromGweet = (text: string): string[] | null => {
const hashtags = text.match(/#\w+/gi);
return hashtags ? hashtags.map((hashtag) => hashtag.slice(1)) : null;
};
"use client";
import { useRouter } from "next/navigation";
import { iTrendProps } from "../types";
export const Trend = ({ ranking = 1, title, gweets = 1 }: iTrendProps) => {
const router = useRouter();
return (
<div
onClick={() => { router.push(`/search?query=${title.toLowerCase()}`); }}
className="flex justify-between items-center p-1 cursor-pointer hover:bg-trends-hover active:bg-trends-active">
<div className="p-2">
<div className="flex items-center gap-2 text-tertiary">
<span>{ranking}</span>
<span className="w-2 h-2 bg-tertiary rounded-full"></span>
<span>Trending</span>
</div>
<div className="text-secondary font-medium">{title}</div>
<div className="text-tertiary">
{gweets} {gweets === 1 ? "gweet" : "gweets"}
</div>
</div>
</div>
);
};
\ No newline at end of file
import Link from "next/link";
import { Icons } from "@/components/icons";
import { TryAgain } from "@/components/try-again";
import { useHashtags } from "../hooks/use-hashtags";
import { Trend } from "./trend";
export const Trends = ({ title = "Trends" }: { title?: string }) => {
const { data: hashtags, isLoading, isError, isSuccess } = useHashtags();
if (hashtags && hashtags?.length <= 0) return null;
return (
<div className="">
{isLoading ? (
<div className="">
<Icons.spinner className="h-4 w-4 animate-spin" />
</div>
) : isError ? (
<div className="">
<TryAgain />
</div>
) : (
<>
<div className="">
<h1 className="">{title}</h1>
{isSuccess &&
hashtags?.map((hashtag, index) => {
return (
<Trend
key={hashtag.id}
ranking={index + 1}
title={hashtag.text}
gweets={hashtag.score}
/>
);
})}
</div>
<button className="">
<Link href={`trends`}>Show more</Link>
</button>
</>
)}
</div>
);
};
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { getHashtags } from "../api/get-hashtags";
import { IHashtag } from "../types";
export const useHashtags = () => {
const queryClient = useQueryClient();
return useQuery<IHashtag[]>(
["hashtags"],
async () => {
return getHashtags();
},
{
refetchOnWindowFocus: false,
onSuccess: (data) => {
queryClient.setQueryData(["hashtags"], data);
},
},
);
};
export * from "./api/get-hashtags";
export * from "./api/post-hashtags";
export * from "./api/retrieve-hashtags-from-gweet";
export * from "./components/trend";
export * from "./components/trends";
export * from "./hooks/use-hashtags";
export * from "./types";
export interface IHashtag {
id: string;
text: string;
score: number;
}
export interface iTrendProps {
ranking: number;
title: string;
gweets: number;
}
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