Skip to content

Commit

Permalink
feat: implement post functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
acatzk committed Jan 28, 2024
1 parent 28dcd34 commit 7df0309
Show file tree
Hide file tree
Showing 11 changed files with 310 additions and 23 deletions.
14 changes: 14 additions & 0 deletions app/api/uploadthing/core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createUploadthing } from 'uploadthing/next'

const f = createUploadthing()

// FileRouter for your app, can contain multiple FileRoutes
export const ourFileRouter = {
// * Takes a 4 2mb images and/or 1 256mb video
mediaPost: f({
image: { maxFileSize: '32MB', maxFileCount: 10 },
video: { maxFileSize: '256MB', maxFileCount: 5 }
}).onUploadComplete(() => {})
}

export type OurFileRouter = typeof ourFileRouter
8 changes: 8 additions & 0 deletions app/api/uploadthing/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createRouteHandler } from 'uploadthing/next'

import { ourFileRouter } from './core'

// Export routes for Next App Router
export const { GET, POST } = createRouteHandler({
router: ourFileRouter
})
4 changes: 4 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import type { Metadata } from 'next'
import React, { ReactNode } from 'react'
import { Inter } from 'next/font/google'
import { ClerkProvider } from '@clerk/nextjs'
import { extractRouterConfig } from 'uploadthing/server'
import { NextSSRPlugin } from '@uploadthing/react/next-ssr-plugin'

import '~/styles/globals.css'
import { ourFileRouter } from '~/app/api/uploadthing/core'
import { TRPCProvider } from '~/components/providers/trpc-provider'
import { ModalProvider } from '~/components/providers/modal-providers'

Expand Down Expand Up @@ -32,6 +35,7 @@ export default function RootLayout({ children }: RootLayoutProps): JSX.Element {
<ClerkProvider>
<html lang="en" suppressHydrationWarning>
<body className={inter.className}>
<NextSSRPlugin routerConfig={extractRouterConfig(ourFileRouter)} />
<TRPCProvider>
<Toaster position="bottom-center" />
<ModalProvider />
Expand Down
98 changes: 91 additions & 7 deletions components/modals/upload-post-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
'use client'

import Image from 'next/image'
import { toast } from 'sonner'
import { isEmpty } from 'lodash'
import { zodResolver } from '@hookform/resolvers/zod'
import { SubmitHandler, useForm } from 'react-hook-form'
import { FileWithPath, useDropzone } from 'react-dropzone'
import { ChevronDown, ChevronLeft, MapPin } from 'lucide-react'
import React, { ChangeEvent, ReactNode, useState } from 'react'
import { generateClientDropzoneAccept } from 'uploadthing/client'
import React, { ChangeEvent, ReactNode, useCallback, useState } from 'react'

import { cn } from '~/lib/utils'
import { trpc } from '~/trpc/client'
Expand All @@ -14,6 +17,7 @@ import { useUpload } from '~/hooks/use-upload'
import { Emoji } from '~/helpers/emoji-helpers'
import { Textarea } from '~/components/ui/textarea'
import { PostSchema, PostSchemaType } from '~/zod/schema'
import { MediaFiles, useUploadThing } from '~/helpers/uploadthing'
import { Dialog, DialogContent, DialogHeader } from '~/components/ui/dialog'
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '~/components/ui/collapsible'
import {
Expand All @@ -37,6 +41,7 @@ export const UploadPostModal = (): JSX.Element => {
const upload = useUpload()
const currentUser = trpc.user.currentUser.useQuery()
const hashtags = trpc.hashtag.getHashtags.useQuery()
const createPost = trpc.post.create.useMutation()

const [tags, setTags] = useState<Tag[]>([])
const [files, setFiles] = useState<File[]>([])
Expand Down Expand Up @@ -77,6 +82,38 @@ export const UploadPostModal = (): JSX.Element => {
)
}

// * UPLOAD THING HOOKS
const { startUpload } = useUploadThing('mediaPost', {
onClientUploadComplete: (res) => {
const mediaFiles = res?.map((file) => file)
setValue(
'mediaFiles',
mediaFiles?.map((m) => ({
key: m.key,
url: m.url
})) as MediaFiles[]
)
},
onUploadError: (error) => {
toast.error(error.message)
}
})

// * UPLOAD THING ONDROP
const onDrop = useCallback((acceptedFiles: FileWithPath[]) => {
setFiles(acceptedFiles) // * USE TO PREPARE TO UPLOAD IN `UPLOADTHING`

// * USE TO PARTIALLY DISPLAY THE IMAGE/VIDEOS
const urls = acceptedFiles.map((file) => URL.createObjectURL(file))
setFileUrls(urls)
}, [])

const fileTypes = ['image', 'video']
const { getRootProps, getInputProps } = useDropzone({
onDrop,
accept: !isEmpty(fileTypes) ? generateClientDropzoneAccept(fileTypes) : undefined
})

const handleCollapsibleChange = (): void => setAdvanceSetting((prev) => !prev)

const handleReset = (): void => {
Expand All @@ -100,10 +137,54 @@ export const UploadPostModal = (): JSX.Element => {
}

const onPost: SubmitHandler<PostSchemaType> = async (data): Promise<void> => {
// console.log({
// ...data,
// tags
// })
const uploads = await startUpload(files)
.then(
(p) =>
p?.map((d) => ({
key: d.key,
url: d.url
}))
)
.catch(
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
(error: any) => toast.error(error?.message)
)

if (!isEmpty(uploads)) {
await createPost.mutateAsync(
{
title: data.captions ?? '',
mediaFiles: {
createMany: {
data: uploads as MediaFiles[]
}
},
isHideLikeAndCount: data.isHideLikeAndCount,
isTurnOffComment: data.isTurnOffComment,
postHashtags: {
create: tags?.map((t) => ({
hashtag: {
connectOrCreate: {
where: { tag: t.text },
create: { tag: t.text }
}
}
}))
}
},
{
onSuccess() {
toast.success('Created new post successfully!')
},
onSettled() {
handleReset()
upload.onClose()
}
}
)
} else {
toast.error('Something went wrong!')
}
}

return (
Expand Down Expand Up @@ -153,8 +234,11 @@ export const UploadPostModal = (): JSX.Element => {
>
{isEmpty(isFileExist) && (
<section className="flex flex-col items-center justify-center">
<div className="flex flex-col items-center rounded-lg border border-dashed border-core-secondary-100 p-4">
<input className="hidden" />
<div
{...getRootProps()}
className="flex flex-col items-center rounded-lg border border-dashed border-core-secondary-100 p-4"
>
<input {...getInputProps()} className="hidden" />
<UploadPhotoVideoIcon className="h-36 w-40 text-core-secondary" />
<h1 className="text-xl font-medium text-core-secondary">
Drag photos and videos here
Expand Down
12 changes: 12 additions & 0 deletions helpers/uploadthing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { generateComponents } from '@uploadthing/react'
import { generateReactHelpers } from '@uploadthing/react/hooks'

import { OurFileRouter } from '~/app/api/uploadthing/core'

export const { UploadButton, UploadDropzone, Uploader } = generateComponents<OurFileRouter>()
export const { useUploadThing, uploadFiles } = generateReactHelpers<OurFileRouter>()

export type MediaFiles = {
key: string
url: string
}
126 changes: 126 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 7df0309

Please sign in to comment.