Skip to content
Snippets Groups Projects
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>
        </>
    )
}