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 899 additions and 89 deletions
"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 { 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';
// Muss in die API route
// const prisma = new PrismaClient();
// 1: Define a type that includes the relation to `Post`
const userWithFollows = Prisma.validator<Prisma.UserArgs>()({
include: { followers: true, following: true },
})
// async function getFollower(userId: number, followerId: number) {
// const follower = await prisma.follows.findFirst({
// where: {
// followerId: followerId,
// followingId: userId,
// },
// });
// 2: Define a type that only contains a subset of the scalar fields
const userPersonalData = Prisma.validator<Prisma.UserArgs>()({
select: { email: true, name: true },
})
// return follower;
// }
// 3: This type will include a user and all their posts
type UserWithFollows = Prisma.UserGetPayload<typeof userWithFollows>
export default function FollowButton({ userId, followerId }: { userId: number; followerId: number }) {
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 })
})
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);
// }
};
setButtonPress(!buttonPressed)
}
return (
<Button onClick={handleFollow}>
{isFollowing ? 'Unfollow' : 'Follow'}
</Button>
<>
{pathname !== `/${session?.user.username}` &&
<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
export default async function getGweet(id: string | undefined) {
try {
const data = await fetch(`/api/gweets/${id}`).then((result) => result.json());
return data;
} catch (error: any) {
return error.response.data;
}
}
\ No newline at end of file
export const getGweets = async ({
pageParam = "",
limit = 20,
type,
id,
}: {
pageParam?: string;
limit?: number;
type?: string;
id?: string;
}) => {
try {
const url = `/api/gweets?cursor=${pageParam}&limit=${limit}${type ? `&type=${type}` : ""}${id ? `&id=${id}` : ""}`;
const data = await fetch(url).then((result) => result.json());
return data;
} catch (error: any) {
return error.response.data;
}
};
\ No newline at end of file
export const handleRegweet = async (gweetId: string, userId: string) => {
try {
const data = await fetch("/api/gweets/regweets", {
method: "POST",
body: JSON.stringify({
gweet_id: gweetId,
user_id: userId,
}),
}).then((result) => result.json());
return data;
} catch (error: any) {
return error.response.data;
}
};
\ No newline at end of file
export const toggleLike = async ({
gweetId,
userId,
}: {
gweetId: string | undefined;
userId: string | undefined;
}) => {
try {
const data = await fetch("/api/gweets/likes", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
gweet_id: gweetId,
user_id: userId,
}),
}).then((result) => result.json());
return data;
} catch (error: any) {
return error.message;
}
};
\ No newline at end of file
import { Button } from "@/components/ui/button";
import { IGweet } from "../../types";
import { Icons } from "@/components/icons";
export const CommentButton = ({ gweet }: { gweet: IGweet }) => {
return (
<div className="relative inline-flex items-center">
<Button variant="ghost" size="icon" className="hover:bg-sky-800 z-40">
<Icons.messagecircle className="w-6 h-6" />
</Button>
{gweet.allComments.length > 0 && (
<span className="absolute pl-12">{gweet.allComments.length}</span>
)}
</div>
);
};
\ No newline at end of file
import { useSession } from "next-auth/react";
import { Icons } from "@/components/icons";
import { Button } from "@/components/ui/button";
import { useLike } from "../../hooks/use-like";
import { IGweet } from "../../types";
export const LikeButton = ({ gweet }: { gweet: IGweet }) => {
const { data: session } = useSession();
const hasLiked = gweet.likes.some(
(like) => like.userId === session?.user.id,
);
const mutation = useLike({
gweetAuthorId: gweet.author.id,
sessionOwnerId: session?.user.id,
});
return (
<div className="inline-flex items-center">
<Button
onClick={(e) => {
e.stopPropagation();
if (session) {
mutation.mutate({ gweetId: gweet.id, userId: session.user.id });
}
}}
variant="ghost" size="icon" className="hover:bg-red-800"
>
{hasLiked ?
<Icons.heart className="w-6 h-6 fill-red-600 text-red-600" />
: <Icons.heart className="w-6 h-6" />
}
</Button>
{gweet.likes.length > 0 && (
<span className="px-2">{gweet?.likes?.length}</span>
)}
</div>
);
};
\ No newline at end of file
import { Icons } from "@/components/icons";
import { Button } from "@/components/ui/button";
import { useSession } from "next-auth/react";
import { useRegweet } from "../../hooks/use-regweet";
import { IGweet } from "../../types";
export const RegweetButton = ({ gweet }: { gweet: IGweet }) => {
const { data: session } = useSession();
const hasRegweeted = gweet.regweets.some(
(regweet) => regweet.userId === session?.user?.id,
);
const mutation = useRegweet();
return (
<div className="relative inline-flex items-center">
<Button
onClick={(e) => {
e.stopPropagation();
if (session) {
mutation.mutate({
gweetId: gweet.id,
userId: session.user.id,
});
}
}}
variant="ghost" size="icon" className="hover:bg-green-800 z-40"
>
{hasRegweeted ?
<Icons.regweet className="w-6 h-6 text-green-600" />
: <Icons.regweet className="w-6 h-6" />
}
</Button>
{gweet.regweets.length > 0 && (
<span className="absolute pl-12">{gweet.regweets.length}</span>
)}
</div>
);
};
\ No newline at end of file
import { TryAgain } from "@/components/try-again";
import LoadingItem from "@/components/loading-item";
import { useGweets } from "../hooks/use-gweets";
import { InfiniteGweets } from "./infinite-gweets";
export const Comments = ({ gweetId }: { gweetId: string }) => {
const {
data: comments,
isLoading,
isError,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
isSuccess,
} = useGweets({
queryKey: ["gweets", gweetId, "comments"],
type: "comments",
id: gweetId,
});
if (isLoading) {
return <LoadingItem />;
}
if (isError) {
return <TryAgain />;
}
return (
<InfiniteGweets
gweets={comments}
fetchNextPage={fetchNextPage}
hasNextPage={hasNextPage}
isFetchingNextPage={isFetchingNextPage}
isSuccess={isSuccess}
/>
);
};
\ No newline at end of file
"use client";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
import { useDeleteGweet } from "../hooks/use-delete-gweet";
import { IGweet } from "../types";
import { Icons } from "@/components/icons";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { useRouter } from "next/navigation";
export const DeleteGweetModal = ({ gweet, props, forwardedRef }: { gweet: IGweet, props: any, forwardedRef: any }) => {
const { triggerChildren, onSelect, onOpenChange, ...itemProps } = props;
const { isLoading, mutate, isSuccess } = useDeleteGweet();
const router = useRouter();
if (isSuccess) router.push("/home");
return (
<Dialog onOpenChange={onOpenChange}>
<DialogTrigger asChild>
<DropdownMenuItem
{...itemProps}
ref={forwardedRef}
onSelect={(event) => {
event.preventDefault();
onSelect && onSelect();
}}>
{triggerChildren}
</DropdownMenuItem>
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Delete gweet?</DialogTitle>
<DialogDescription>
You are about to delete this gweet. This can&apos;t be undone and will be removed from your profile,
the timeline of any accounts that follow you, and from the search results.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
variant="destructive"
type="submit"
disabled={isLoading}
onClick={() => {
mutate({
gweetId: gweet?.id,
});
}}>
{isLoading && (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
)}
Delete
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
\ No newline at end of file
import { IGweet } from "../types";
import { CommentButton } from "./actions/comment-button";
import { LikeButton } from "./actions/like-button";
import { RegweetButton } from "./actions/regweet-button";
export const GweetActions = ({
gweet,
}: {
gweet: IGweet;
}) => {
return (
<div className="space-x-12 w-60">
<CommentButton gweet={gweet} />
<RegweetButton gweet={gweet} />
<LikeButton gweet={gweet} />
</div>
);
};
\ No newline at end of file
import { UserAvatar } from "@/components/user-avatar";
import Link from "next/link";
import { IGweet } from "../types";
import { GweetOptions } from "./gweet-options";
export const GweetAuthor = ({ gweet }: { gweet: IGweet }) => {
return (
<div className="flex items-center">
<Link href={`/${gweet.author.username}`}>
<UserAvatar
user={{ username: gweet.author.username, image: gweet.author.image }}
className="h-10 w-10"
/>
</Link>
<div className="flex flex-col ml-3">
<span className="font-bold">{gweet.author.name}</span>
<span className="text-sky-500 text-sm">@{gweet.author.username}</span>
</div>
<div className="ml-auto">
<GweetOptions gweet={gweet} />
</div>
</div>
);
};
\ No newline at end of file
import dayjs from "dayjs";
export const GweetCreationDate = ({ date }: { date: Date }) => {
return (
<div className="space-x-2">
<span>{dayjs(date).format(`h:mm A`)}</span>
<span>·</span>
<span>{dayjs(date).format(`MMM D, YYYY`)}</span>
</div>
);
};
\ No newline at end of file
"use client"
import { CreateGweetWrapper } from "@/components/create-gweet/components/create-gweet-wrapper";
import LoadingItem from "@/components/loading-item";
import { TryAgain } from "@/components/try-again";
import { Card } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import Image from "next/image";
import { usePathname } from "next/navigation";
import { useGweet } from "../hooks/use-gweet";
import { Comments } from "./comments";
import { GweetActions } from "./gweet-actions";
import { GweetAuthor } from "./gweet-author";
import { GweetCreationDate } from "./gweet-creation-date";
export const GweetDetails = () => {
// TODO use params
const pathname = usePathname();
const id = pathname?.split(`/`)[3] || ``;
const { data: gweet, isLoading, isError } = useGweet(id);
if (isLoading) {
return <LoadingItem />;
}
if (isError) {
return <TryAgain />;
}
if (!isLoading && !isError && !gweet) {
return <>Not Found!</>
}
return (
<>
<div className="flex flex-col space-y-3 px-3 pt-3">
<GweetAuthor gweet={gweet} />
{/* TODO needs handling of all gweets above and under the gweet */}
<div className="flex flex-col space-y-3">
{gweet.content && <h1 className="break-words">{gweet.content}</h1>}
{/* TODO make own component */}
{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 */}
{/* {gweet?.quotedGweet && <QuotedGweet gweet={gweet?.quotedGweet} />} */}
</div>
<div className="flex flex-col items-center flex-wrap flex-grow sm:flex-row space-y-3 sm:space-y-0">
<div className="flex-grow text-muted-foreground">
<GweetCreationDate date={gweet.createdAt} />
</div>
<div className="sm:items-end">
<GweetActions gweet={gweet} />
</div>
</div>
<CreateGweetWrapper replyToGweetId={gweet?.id} />
</div>
<Separator className="h-1 mb-3" />
<Comments gweetId={gweet?.id} />
</>
);
};
\ No newline at end of file
"use client";
import { Icons } from "@/components/icons";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { toast } from "@/components/ui/use-toast";
import { env } from "@/env.mjs";
import getURL from "@/lib/utils";
import { useSession } from "next-auth/react";
import { useRef, useState } from "react";
import { IGweet } from "../types";
import { DeleteGweetModal } from "./delete-gweet-modal";
export const GweetOptions = ({ gweet }: { gweet: IGweet }) => {
const { data: session } = useSession();
const [dropdownOpen, setDropdownOpen] = useState<boolean>(false);
const [hasOpenDialog, setHasOpenDialog] = useState<boolean>(false);
const dropdownTriggerRef = useRef<HTMLButtonElement | null>(null);
const focusRef = useRef<HTMLButtonElement | null>(null);
function handleDialogItemSelect() {
focusRef.current = dropdownTriggerRef.current;
}
function handleDialogItemOpenChange(open: boolean) {
setHasOpenDialog(open);
if (open === false) {
setDropdownOpen(false);
}
}
const url = getURL(`/status/${gweet?.id}`);
return (
<DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
<DropdownMenuTrigger asChild>
<div className="h-5 flex items-center">
<Button variant="ghost" size="option" ref={dropdownTriggerRef}>
<Icons.moreOptions />
</Button>
</div>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="font-bold cursor-pointer"
hidden={hasOpenDialog}
onClick={(event) => {
event.stopPropagation();
}}
onCloseAutoFocus={(event) => {
if (focusRef.current) {
focusRef.current.focus();
focusRef.current = null;
event.preventDefault();
}
}}>
<DropdownMenuGroup>
{gweet.author.id === session?.user?.id && (
<DeleteGweetModal
gweet={gweet}
props={{
triggerChildren: (
<div className="text-red-600 flex">
<Icons.trash className="mr-2 h-4 w-4 " />
<span>Delete</span>
</div>
),
onSelect: handleDialogItemSelect,
onOpenChange: handleDialogItemOpenChange,
}}
forwardedRef={focusRef}
/>
)}
<DropdownMenuItem
onClick={() => {
navigator.clipboard.writeText(url);
return toast({
description: "Copied link to gweet.",
})
}}>
<Icons.link className="mr-2 h-4 w-4 " />
<span>Copy link to gweet</span>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
);
};
\ 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