-
Yusuf Akgül authoredYusuf Akgül authored
create-gweet.tsx 13.10 KiB
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useSession } from "next-auth/react"
import Image from "next/image"
import { useEffect, 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." }),
})
const ImagesArraySchema = z.array(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 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, isSuccess, mutate } = useCreateGweet()
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
})
let disable = true
if (!isLoading && session?.user.username !== undefined) {
// console.log("form is valid", form.formState.isValid)
if (form.formState.isDirty && form.formState.isValid) {
if (chosenImages.length !== 0) {
disable = false
} else {
disable = true
}
disable = false
}
}
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,
})
disable = true
}
useEffect(() => {
form.formState.isValid // needs to be checked to trigger validation on first load (weird behavior)
if (isSuccess) {
toast({
description: "Your gweet was send.",
})
form.reset()
form.setValue('gweet', '')
setChosenImages([])
}
}, [form, isSuccess])
let chooseImages = async (
event: React.ChangeEvent<HTMLInputElement>,
setChosenImages: (images: IChosenImages[]) => void,
) => {
const files = event.target.files
try {
const fileArray = Array.from(files?.length ? files : [])
ImagesArraySchema.parse(fileArray)
} catch (error: any) {
const err = error as z.ZodError
return toast({
variant: "destructive",
description: err.issues[0]?.message,
})
}
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()
if (filePath) {
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 p-1 md:p-0">
<Form {...form}>
<form onSubmit={form.handleSubmit(onGweet)} className="space-y-3">
<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 || isComment ? "Gweet your reply" : "What's on your mind?"}
className="resize-none min-h-[100px]"
disabled={isLoading || !session.user}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<input
className="hidden w-full resize-none"
type="file"
accept="image/*"
multiple
max={4}
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" : ""} ${isLoading ? "opacity-50" : ""}`}>
<Button
type="button"
size="icon"
variant="secondary"
className="rounded-full absolute top-1 right-1 z-40"
disabled={isLoading}
onClick={() => {
setChosenImages(
chosenImages.filter((img, j) => j !== i),
)
}}
>
<Icons.close />
</Button>
<Image
src={image.url as string}
alt="gweet image"
fill
className="object-cover rounded-lg"
/>
</Card>
)
})}
</div>
<p className="pt-2 text-sm text-muted-foreground">Give your media a story before you can post.</p>
</>
)}
{/* {quoted_gweet && <QuotedGweet gweet={quoted_gweet} />} */}
</div>
</Card>
<div className="flex justify-end">
<Button type="submit" size="lg" className="w-20" disabled={disable}
variant={form.watch("gweet") && form.watch("gweet").length > 240 ? "destructive" : "default"}>
{isLoading ? (
<Icons.spinner className="animate-spin" />
) : (
form.watch("gweet") && form.watch("gweet").length > 240 ? form.watch("gweet").length :
isComment ? 'Reply' : 'Gweet'
)}
</Button>
</div>
</form>
</Form>
</div>
</>
)
}