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 470 additions and 391 deletions
import { Prisma } from "@prisma/client";
import { NextRequest, NextResponse } from "next/server";
import { putLike, putLikeComment } from "./likeService";
import { getServerSession } from "next-auth/next";
import { authOptions } from "@/lib/auth";
import { revalidatePath } from "next/cache";
type like = Prisma.LikeUncheckedCreateInput
export async function PUT(req: NextRequest) {
const session = await getServerSession(authOptions);
if (!session) {
return NextResponse.json({ status: 401 });
}
const userId = session.user.id
const data: like = await req.json()
data.userId = userId;
console.log("router data: " + data, "status:")
try {
if (data.commentId == undefined) {
const msg = await putLike(data)
} else {
putLikeComment(data)
}
const path = req.nextUrl.searchParams.get('path') || '/';
revalidatePath(path);
return NextResponse.json({ status: 200, message: 'Like handled' })
} catch (error) {
console.log("fail" + error);
return NextResponse.json(error, { status: 500 });
}
}
\ No newline at end of file
import { db } from "@/lib/db";
import { getCurrentUser } from "@/lib/session";
import { revalidatePath } from "next/cache";
import { NextRequest, NextResponse } from "next/server";
export async function POST(req: NextRequest) {
const user = await getCurrentUser();
if (!user) {
return NextResponse.json({ status: 401, message: 'Unauthorized' });
}
const userId = user.id;
const content = await req.json()
try {
await db.post.create({
data: {
content: content.gweet,
userId: userId,
}
})
const path = req.nextUrl.searchParams.get('path') || '/';
revalidatePath(path);
return NextResponse.json({ status: 201, message: 'Message Created' })
} catch (error: any) {
return NextResponse.json({ status: 500, message: error.message })
}
}
export async function GET() {
try {
const messages = await db.post.findMany({
orderBy: {
createdAt: "desc"
},
include: {
user: true,
Comment: true,
Like: true
},
})
return NextResponse.json(messages);
} catch (error) {
return NextResponse.json(error, { status: 500 });
}
}
\ No newline at end of file
import { getCurrentUser } from "@/lib/session";
import { createUploadthing, type FileRouter } from "uploadthing/next";
const f = createUploadthing();
export const ourFileRouter = {
imageUploader: 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 }) => { }),
} satisfies FileRouter;
export type OurFileRouter = typeof ourFileRouter;
\ No newline at end of file
import { createNextRouteHandler } from "uploadthing/next";
import { ourFileRouter } from "./core";
export const { GET, POST } = createNextRouteHandler({
router: ourFileRouter,
});
\ No newline at end of file
File moved
import { Inter } from 'next/font/google' import { Inter } from 'next/font/google'
import './globals.css' import './globals.css'
import Providers from '@/components/react-query/provider'
import { ThemeProvider } from '@/components/ui/theme-provider' import { ThemeProvider } from '@/components/ui/theme-provider'
import { Toaster } from '@/components/ui/toaster' import { Toaster } from '@/components/ui/toaster'
import Providers from '@/lib/react-query/provider'
const inter = Inter({ subsets: ['latin'] }) const inter = Inter({ subsets: ['latin'] })
......
{
"style": "default",
"rsc": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
\ No newline at end of file
...@@ -15,7 +15,7 @@ export default function AddGameToFavList(props: { userGameList: Number[], gameId ...@@ -15,7 +15,7 @@ export default function AddGameToFavList(props: { userGameList: Number[], gameId
formData.gameId = gameId; formData.gameId = gameId;
formData.add = false; formData.add = false;
const response = await fetch('/api/favgameslist', { const response = await fetch('/api/users/favgameslist', {
method: 'PUT', method: 'PUT',
body: JSON.stringify(formData) body: JSON.stringify(formData)
}) })
...@@ -33,7 +33,7 @@ export default function AddGameToFavList(props: { userGameList: Number[], gameId ...@@ -33,7 +33,7 @@ export default function AddGameToFavList(props: { userGameList: Number[], gameId
formData.gameId = gameId; formData.gameId = gameId;
formData.add = true; formData.add = true;
const response = await fetch('/api/favgameslist', { const response = await fetch('/api/users/favgameslist', {
method: 'PUT', method: 'PUT',
body: JSON.stringify(formData) body: JSON.stringify(formData)
}) })
......
"use client";
import { useRouter } from "next/navigation";
import { Icons } from "./icons";
import { Button } from "./ui/button";
export const BackButton = () => {
const router = useRouter();
return (
<Button
variant="ghost"
size="icon"
onClick={() => router.back()}
title="Go Back"
>
<Icons.chevronLeft />
</Button>
);
};
\ No newline at end of file
import { BackButton } from "@/components/back-button";
export const BackHeader = ({ children }: { children: React.ReactNode }) => {
return (
<div className="flex items-center space-x-3">
<BackButton />
{children}
</div>
);
}
\ 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 { FormEvent, Fragment, useEffect, useState } from "react";
// import CommentItem from "./comment-item";
// import { Icons } from "./icons";
// 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 PostCommentForm(props: { postid: string }) {
// const [isGweetLoading, setIsGweetLoading] = useState<boolean>(false);
// const [isLoading, setIsLoading] = useState<boolean>(false);
// const [messages, setMessages] = useState<any[]>([]);
// const form = useForm<z.infer<typeof FormSchema>>({
// resolver: zodResolver(FormSchema),
// })
// async function onCommentGweet(data: z.infer<typeof FormSchema>) {
// setIsGweetLoading(true);
// await fetch('/api/comments', {
// method: 'POST',
// body: JSON.stringify(props.postid)
// })
// toast({
// title: "Your comment 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/comments?postid=${props.postid}`);
// if (!res.ok) {
// throw new Error("Failed to fetch comments");
// }
// 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);
// }
// useEffect(() => {
// fetchMessages();
// }, []);
// async function onSubmit(event: FormEvent<HTMLFormElement>) {
// event.preventDefault();
// await fetchMessages();
// }
// // console.log((messages[0] as IPost).user.image);
// return (
// <div>
// {/* <PostItem msg={(messages[0])} /> */}
// <Form {...form}>
// <form onSubmit={form.handleSubmit(onCommentGweet)} className="space-y-6">
// <FormField
// control={form.control}
// name="gweet"
// render={({ field }) => (
// <FormItem>
// <FormLabel>Comment</FormLabel>
// <FormControl>
// <Textarea
// placeholder="What's on your mind?"
// className="resize-none"
// disabled={isGweetLoading}
// {...field}
// />
// </FormControl>
// <FormDescription>
// Your comment will be public, and everyone can see them.
// </FormDescription>
// <FormMessage />
// </FormItem>
// )}
// />
// <Button type="submit" disabled={isGweetLoading}>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}>
// <CommentItem 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>
// </div>
// <Separator className="mt-3 mb-6" />
// </>
// ))}
// </>
// )}
// </form>
// </Card>
// </div>
// )
// }
\ No newline at end of file
// import { formatTimeElapsed } from "@/lib/utils";
// import { UserAvatar } from "./user-avatar";
// export default function CommentItem({ msg }: { msg: any }) {
// 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.message}</h1>
// </div>
// </div>
// </div>
// )
// }
\ No newline at end of file
import { postHashtags, retrieveHashtagsFromGweet } from "@/components/trends";
import { uploadFiles } from "@/lib/uploadthing";
export const postGweet = async ({
content,
files,
authorId,
replyToGweetId,
quoteGweetId,
}: {
content: string;
files: File[];
authorId: string;
replyToGweetId?: string | null;
quoteGweetId?: string | null;
}) => {
const gweet = {
content,
authorId,
...(replyToGweetId && { replyToGweetId }),
...(quoteGweetId && { quoteGweetId }),
};
try {
let fileprops: { fileUrl: string; fileKey: string; }[] = [];
if (files.length > 0) {
fileprops = await uploadFiles({ files, endpoint: 'imageUploader' })
}
const data = await fetch('/api/gweets', {
method: 'POST',
body: JSON.stringify({ gweet, fileprops })
}).then((result) => result.json())
const hashtags = retrieveHashtagsFromGweet(content);
if (hashtags) await postHashtags(hashtags);
return data;
} catch (error: any) {
return error.response.data;
}
};
\ No newline at end of file
"use client";
import { useState } from "react";
import { CreateGweet } from "./create-gweet";
export const CreateGweetWrapper = ({
replyToGweetId,
}: {
replyToGweetId: string | null;
}) => {
const [isComment, setIsComment] = useState(true);
return (
<div className="px-6">
<CreateGweet
replyToGweetId={replyToGweetId}
placeholder="Gweet your reply..."
isComment={isComment}
/>
{isComment && (
<button
onClick={() => {
setIsComment(false);
}}
/>
)}
</div>
);
};
\ No newline at end of file
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { useSession } from "next-auth/react";
import Image from "next/image";
import { useRef, useState } from "react";
import { useForm } from "react-hook-form";
import * as z from "zod";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormMessage,
} from "@/components/ui/form";
import { Textarea } from "@/components/ui/textarea";
import { toast } from "@/components/ui/use-toast";
import { IGweet } from "@/components/gweets/types";
import { UserAvatar } from "@/components/user-avatar";
import { Icons } from "@/components/icons";
import { Card } from "@/components/ui/card";
import { useCreateGweet } from "../hooks/use-create-gweet";
import { IChosenImages } from "../types";
const FormSchema = z.object({
gweet: z
.string()
.min(1, { message: "Come on post something..." })
.max(240, { message: "Gweets cannot be more that 240 characters." }),
})
export const CreateGweet = ({
parent_gweet,
quoted_gweet,
replyToGweetId,
placeholder,
isComment = false,
}: {
parent_gweet?: IGweet | null;
quoted_gweet?: IGweet | null;
replyToGweetId?: string | null;
placeholder?: string | null;
isComment?: boolean;
}) => {
const [chosenImages, setChosenImages] = useState<IChosenImages[]>([]);
const imageUploadRef = useRef<HTMLInputElement>(null);
const { data: session } = useSession();
const { isLoading, mutate, data } = useCreateGweet();
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
})
async function onGweet(formData: z.infer<typeof FormSchema>) {
if (!session) return null;
mutate({
content: formData.gweet,
authorId: session?.user?.id,
replyToGweetId,
files: chosenImages.map((image) => image.file),
quoteGweetId: quoted_gweet?.id || null,
})
toast({
description: "Your gweet was send.",
})
form.setValue('gweet', '');
setChosenImages([]);
}
const chooseImages = async (
event: React.ChangeEvent<HTMLInputElement>,
setChosenImages: (images: IChosenImages[]) => void,
) => {
const files = event.target.files;
if (files && files.length > 0) {
const newImages: IChosenImages[] = [];
const totalSelectedImages = chosenImages.length + files.length;
if (totalSelectedImages > 4) {
return toast({
variant: "destructive",
description: "You can only upload 4 images per gweet.",
})
}
for (let i = 0; i < files.length; i++) {
const filePath = files[i];
const reader = new FileReader();
reader.readAsDataURL(filePath);
reader.onload = () => {
newImages.push({
url: reader.result,
file: filePath,
});
if (newImages.length === files.length) {
setChosenImages([...chosenImages, ...newImages]);
}
};
}
}
};
if (!session) return null;
return (
<>
{/* TODO showing if is replying */}
{parent_gweet && (
<div className="grid grid-cols-2 gap-11 p-4">
<div className="grid place-items-center grid-rows-2 gap-1">
<UserAvatar
user={{ username: parent_gweet?.author?.username, image: parent_gweet?.author?.image || null }}
/>
<div className="bg-gray-300 h-full w-px"></div>
</div>
<div className="flex flex-col">
<>
<div className="flex gap-1">
<span className="text-secondary text-sm font-medium truncate hover:underline">
{parent_gweet?.author?.name}
</span>
<span className="text-tertiary text-sm truncate">
@{parent_gweet?.author?.email?.split("@")[0]}
</span>
<span className="text-tertiary">·</span>
</div>
<div>
{parent_gweet?.content && (
<div className="text">{parent_gweet?.content}</div>
)}
</div>
</>
{!isComment && (
<div className={`${!parent_gweet ? 'ml-16' : ''} flex items-center gap-1 cursor-pointer`}>
<span className="text-tertiary truncate">Replying to</span>
<span className="text-primary truncate">
@{parent_gweet?.authorId}
</span>
</div>
)}
</div>
</div>
)}
<div className="relative">
<Form {...form}>
<form onSubmit={form.handleSubmit(onGweet)} className="space-y-6">
<Card className="p-3 flex items-start relative">
<UserAvatar
className="mr-3"
user={{ username: session?.user.username || null, image: session?.user?.image || null }}
/>
<Button
type="button"
variant="ghost"
size="icon"
className="absolute bottom-0 left-0 mb-3 ml-3"
onClick={() => imageUploadRef.current?.click()}
disabled={isLoading || !session.user}
>
<Icons.media className="text-muted-foreground" />
</Button>
<div className="flex-grow">
<FormField
control={form.control}
name="gweet"
render={({ field }) => (
<FormItem>
<FormControl>
<Textarea
placeholder={placeholder || "What's on your mind?"}
className="resize-none min-h-[100px]"
disabled={isLoading || !session.user}
{...field}
/>
</FormControl>
{!isComment ?
<FormDescription>
Your gweets will be public, and everyone can see them.
</FormDescription>
: null
}
<FormMessage />
</FormItem>
)}
/>
<input
className="hidden w-full resize-none"
type="file"
multiple
onChange={(e) => chooseImages(e, setChosenImages)}
ref={imageUploadRef}
disabled={isLoading || !session.user}
/>
{chosenImages.length > 0 && (
<div className={`grid object-cover h-[600px] pt-2
${chosenImages.length === 1 ? "grid-cols-1"
: chosenImages.length === 2 ? "grid-cols-2 gap-3"
: chosenImages.length === 3 || 4 ? "grid-cols-2 grid-rows-2 gap-3"
: ""
}`}
>
{chosenImages.map((image, i) => {
const isFirstImage = chosenImages.length === 3 && i === 0;
return (
<Card key={i} className={`relative max-h-[600px] overflow-hidden ${isFirstImage ? "row-span-2" : ""}`}>
<Button
type="button"
size="icon"
variant="secondary"
className="rounded-full absolute top-1 right-1 z-40"
onClick={() => {
setChosenImages(
chosenImages.filter((img, j) => j !== i),
);
}}
>
<Icons.close className="w-6 h-6" />
</Button>
<Image
src={image.url as string}
alt="gweet image"
fill
className="object-cover rounded-lg"
/>
</Card>
);
})}
</div>
)}
{/* {quoted_gweet && <QuotedGweet gweet={quoted_gweet} />} */}
</div>
</Card>
<div className="flex justify-end">
<Button type="submit" size="lg" className="w-20" disabled={isLoading || !session.user}>
{isLoading ? (
<Icons.spinner className="h-4 w-4 animate-spin" />
) : (
isComment ? 'Reply' : 'Gweet'
)}
</Button>
</div>
</form>
</Form>
</div>
</>
);
};
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { postGweet } from "../api/post-gweet";
export const useCreateGweet = () => {
const queryClient = useQueryClient();
return useMutation(
({
content,
files,
authorId,
replyToGweetId,
quoteGweetId,
}: {
content: string;
files: File[];
authorId: string;
replyToGweetId?: string | null;
quoteGweetId?: string | null;
}) => {
return postGweet({
content,
files,
authorId,
replyToGweetId,
quoteGweetId,
});
},
{
onSuccess: () => {
queryClient.invalidateQueries(["gweets"]);
queryClient.invalidateQueries(["hashtags"]);
},
onError: (error) => {
console.log("error", error);
},
},
);
};
\ No newline at end of file
export interface Post {
id: string;
}
export interface IChosenImages {
url: string | ArrayBuffer | null;
file: File;
}
\ No newline at end of file
"use client"
// import { PrismaClient } from '@prisma/client';
import { useState } from 'react';
import { Button } from './ui/button';
// Muss in die API route
// const prisma = new PrismaClient();
// async function getFollower(userId: number, followerId: number) {
// const follower = await prisma.follows.findFirst({
// where: {
// followerId: followerId,
// followingId: userId,
// },
// });
// return follower;
// }
export default function FollowButton({ userId, followerId }: { userId: number; followerId: number }) {
const [isFollowing, setIsFollowing] = useState(false);
const handleFollow = async () => {
// const follower = await getFollower(userId, followerId);
// if (follower) {
// // User is already following, so unfollow
// await prisma.follows.delete({
// where: {
// followerId_followingId: {
// followerId: followerId,
// followingId: userId,
// },
// },
// });
// setIsFollowing(false);
// } else {
// // User is not following, so follow
// await prisma.follows.create({
// data: {
// followerId: followerId,
// followingId: userId,
// },
// });
// setIsFollowing(true);
// }
};
return (
<Button onClick={handleFollow}>
{isFollowing ? 'Unfollow' : 'Follow'}
</Button>
);
}
\ No newline at end of file
"use client"
// import { PrismaClient } from '@prisma/client';
import { useEffect, useState } from 'react';
// Muss in die API route
// const prisma = new PrismaClient();
interface Follower {
id: number;
name: string;
email: string | null;
}
export default function FollowersList({ userId }: { userId: number }) {
const [followers, setFollowers] = useState<Follower[]>([]);
useEffect(() => {
async function fetchFollowers() {
// const followersList = await prisma.follows.findMany({
// where: {
// followingId: userId,
// },
// include: {
// follower: true,
// },
// });
// const filteredFollowers = followersList.map((follow: any) => {
// const { id, name, email } = follow.follower;
// return { id, name: name ?? "", email };
// });
// setFollowers(filteredFollowers);
}
fetchFollowers();
}, [userId]);
return (
<ul>
{followers.map((follower) => (
<li key={follower.id}>{follower.name} ({follower.email})</li>
))}
</ul>
);
}
\ No newline at end of file
export const deleteGweet = async (gweetId: string) => {
try {
const data = await fetch(`/api/gweets?id=${gweetId}`, {
method: 'DELETE'
}).then((result) => result.json());
return data;
} catch (error: any) {
console.log(error);
}
};
\ 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