Skip to content

Commit

Permalink
add upload gallery page
Browse files Browse the repository at this point in the history
  • Loading branch information
docimin committed Jul 12, 2024
1 parent b55b191 commit 62afa03
Show file tree
Hide file tree
Showing 8 changed files with 312 additions and 3 deletions.
2 changes: 1 addition & 1 deletion src/app/[locale]/(main)/gallery/[galleryId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default async function GalleryPage({ params: { galleryId } }) {

const isNsfwImage = galleryDocuments?.nsfw && !enableNsfw

const sanitizedBio = sanitizeHtml(galleryDocuments.longText)
const sanitizedBio = sanitizeHtml(galleryDocuments?.longText)
const bioWithLineBreaks = sanitizedBio.replace(/\n/g, '<br />')

return (
Expand Down
1 change: 1 addition & 0 deletions src/app/[locale]/(main)/gallery/page.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export default function FetchGallery({ enableNsfw }) {
<video
className="h-full w-full object-cover"
controls
controlsList="nodownload"
autoPlay={false}
loop={true}
muted={true}
Expand Down
12 changes: 11 additions & 1 deletion src/app/[locale]/(main)/gallery/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Client from './page.client'
import { getUser } from '@/utils/server-api/account/user'
import PageLayout from '@/components/pageLayout'
import { Link } from '@/navigation'
import { Button } from '@/components/ui/button'

export const metadata = {
title: 'Gallery',
Expand All @@ -10,12 +12,20 @@ export const metadata = {

export const runtime = 'edge'

function UploadButton() {
return (
<Link href={'/gallery/upload'}>
<Button>Upload</Button>
</Link>
)
}

export default async function Gallery() {
const accountData = await getUser()
const enableNsfw = accountData?.prefs?.nsfw

return (
<PageLayout title={'Gallery'}>
<PageLayout title={'Gallery'} middleComponent={<UploadButton />}>
<Client enableNsfw={enableNsfw || false} />
</PageLayout>
)
Expand Down
12 changes: 12 additions & 0 deletions src/app/[locale]/(main)/gallery/upload/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import PageLayout from '@/components/pageLayout'
import UploadImage from '@/components/gallery/uploadImage'

export const runtime = 'edge'

export default function Page() {
return (
<PageLayout title={'Upload'}>
<UploadImage />
</PageLayout>
)
}
268 changes: 268 additions & 0 deletions src/components/gallery/uploadImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
'use client'
import React, { useRef, useState } from 'react'
import { useToast } from '@/components/ui/use-toast'
import { databases, ID, storage } from '@/app/appwrite-client'
import { useGetUser } from '@/utils/getUserData'
import * as Sentry from '@sentry/nextjs'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Checkbox } from '@/components/ui/checkbox'
import { Textarea } from '@/components/ui/textarea'
import { useRouter } from '@/navigation'

export default function UploadPage() {
const [isUploading, setIsUploading] = useState(false)
const { userMe } = useGetUser()
const { toast } = useToast()
const router = useRouter()
const [selectedFile, setSelectedFile] = useState(null)
const fileInputRef = useRef(null)

const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files[0]
if (file) {
const reader = new FileReader()
reader.onload = function (e) {
const img = document.getElementById(
'selected-image'
) as HTMLImageElement
if (img) {
console.log('File read successfully, updating image src.') // Debugging log
img.src = e.target.result as string
setSelectedFile(e.target.result as string)
} else {
console.error('Failed to find the image element.') // Debugging log
}
}
reader.onerror = function (error) {
console.error('Error reading file:', error) // Debugging log
}
reader.readAsDataURL(file)
} else {
console.log('No file selected.') // Debugging log
}
}

const handleDrop = (event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault()
const file = event.dataTransfer.files[0]
if (file) {
const reader = new FileReader()
reader.onload = function (e) {
const img = document.getElementById(
'selected-image'
) as HTMLImageElement
if (img) {
console.log('File dropped successfully, updating image src.') // Debugging log
img.src = e.target.result as string
setSelectedFile(e.target.result as string)
} else {
console.error('Failed to find the image element.') // Debugging log
}
}
reader.onerror = function (error) {
console.error('Error reading file:', error) // Debugging log
}
reader.readAsDataURL(file)
} else {
console.log('No file dropped.') // Debugging log
}
}

const handleClick = (
event: React.MouseEvent<HTMLImageElement, MouseEvent>
) => {
event.nativeEvent.stopImmediatePropagation() // Use the native event to stop propagation
fileInputRef.current.click()
}

const handleSubmit = async (event: React.ChangeEvent<HTMLFormElement>) => {
event.preventDefault()

try {
setIsUploading(true) // Set isUploading to true before making the API call

const fileData = storage.createFile('gallery', ID.unique(), selectedFile)

fileData.then(
function (fileDataResponse) {
const postDocument = databases.createDocument(
'hp_db',
'gallery-images',
fileDataResponse.$id,
{
name: (document.getElementById('imagename') as HTMLInputElement)
.value, // Extract the value
longText: (
document.getElementById('longtext') as HTMLInputElement
).value, // Extract the value
nsfw: (document.getElementById('nsfw') as HTMLInputElement)
.checked, // Extract the checked state
galleryId: fileDataResponse.$id,
userId: userMe.$id,
}
)

postDocument.then(
function () {
toast({
title: 'Success!',
description:
"Thanks for sharing your image with us. It's now live!",
})
router.push({
pathname: `/account/gallery/[galleryId]`,
params: { galleryId: fileDataResponse.$id },
})
},
function (error) {
console.log(error) // Failure
storage.deleteFile('gallery', fileDataResponse.$id)
Sentry.captureException(error)
toast({
title: 'Error',
description:
"You encountered an error. But don't worry, we're on it.",
variant: 'destructive',
})
setIsUploading(false)
}
)
},
function (error) {
console.log(error) // Failure
Sentry.captureException(error)
toast({
title: 'Error',
description:
"You encountered an error. But don't worry, we're on it.",
variant: 'destructive',
})
setIsUploading(false)
}
)
} catch (error) {
console.error(error)
Sentry.captureException(error)
toast({
title: 'Error',
description: "You encountered an error. But don't worry, we're on it.",
variant: 'destructive',
})
setIsUploading(false)
}
}

return (
<>
<form onSubmit={handleSubmit}>
<div className="space-y-6">
<div>
<h2 className="text-base font-semibold leading-7">Image Upload</h2>
<p className="mt-1 text-sm leading-6 text-gray-900 dark:text-gray-400">
This information will be displayed publicly so be careful what you
share.
</p>

<div className="mt-10 grid">
<div className="col-span-full">
<label
htmlFor="cover-photo"
className="block text-sm font-medium leading-6"
>
Cover photo
</label>
<div
className="mt-2 flex justify-center rounded-lg border border-dashed border-black/25 px-6 py-10 dark:border-white/25 relative cursor-pointer"
onDragOver={(event) => event.preventDefault()}
onDragEnter={(event) => event.preventDefault()}
onDrop={handleDrop}
onClick={handleClick}
>
<div className="text-center">
<Label className="rounded-md bg-gray-900 font-semibold text-white focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-600 focus-within:ring-offset-2 focus-within:ring-offset-gray-900 hover:text-indigo-500">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
id="selected-image"
className="mx-auto h-96 min-w-full rounded-md object-contain cursor-pointer"
alt="Placeholder Image"
src={
selectedFile || '/images/placeholder-image-color.webp'
} // Fallback to a placeholder if selectedFile is null
/>
</Label>
</div>
<input
ref={fileInputRef}
id="file-upload"
name="file-upload"
type="file"
className="sr-only bg-transparent"
onChange={handleFileChange}
/>
</div>
</div>
</div>
</div>

<div className={'border-b border-white/10 pb-8'}>
<div className="flex text-sm leading-6 text-gray-400 items-center">
<Label>Supported:</Label>
<p className="pl-1">
PNG, JPEG, GIF, SVG, TIFF, ICO, DVU up to 16MB
</p>
</div>
</div>

<div className="border-b border-white/10 pb-12">
<h2 className="text-base font-semibold leading-7">Informationen</h2>
<p className="mt-1 text-sm leading-6 text-gray-900 dark:text-gray-400">
Alles mit ein asterisk (<span className="text-red-500">*</span>)
ist nötig.
</p>

<div className="mt-10 grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
<div className="sm:col-span-3">
<Label>
Name <span className="text-red-500">*</span>
</Label>
<div className="mt-2">
<Input type="text" name="imagename" id="imagename" required />
</div>
</div>

<div className="sm:col-span-3">
<Label>NSFW</Label>
<div className="mt-2">
<Checkbox name="nsfw" id="nsfw" />
</div>
</div>

<div className="col-span-full">
<Label htmlFor="biostatus">Description</Label>
<div className="relative mt-2">
<Textarea id="longtext" name="longtext" />
</div>
</div>
</div>
</div>
</div>

<div className="mt-6 flex items-center justify-end gap-x-6">
<button type="button" className="text-sm font-semibold leading-6">
Cancel
</button>
<button
type="submit"
value="Submit"
className="rounded-md bg-indigo-500 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-500"
disabled={isUploading} // Disable the button if isUploading is true
>
{isUploading ? 'Uploading...' : 'Save'}{' '}
{/* Show different text based on the upload state */}
</button>
</div>
</form>
</>
)
}
7 changes: 6 additions & 1 deletion src/components/pageLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ import { ThemeToggle } from '@/components/ThemeToggle'
import { Separator } from '@/components/ui/separator'
import ChangeLanguage from '@/components/system/changeLanguage'

export default function PageLayout({ children, title }) {
export default function PageLayout({
children,
title,
middleComponent = null,
}) {
return (
<>
<div className={'relative'}>
<div className="flex flex-col lg:flex-row items-center px-4 py-1.5 justify-between align-middle">
<h1 className="text-xl font-bold">{title || 'Undefined'}</h1>
{middleComponent}
<div className={'align-middle flex gap-2 lg:mt-0 mt-4'}>
<ChangeLanguage />
<ThemeToggle />
Expand Down
1 change: 1 addition & 0 deletions src/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const pathnames = {
'#': '#',
'/': '/',
'/gallery': '/gallery',
'/gallery/upload': '/gallery/upload',
'/gallery/[galleryId]': '/gallery/[galleryId]',
'/community': '/community',
'/community/[communityId]': '/community/[communityId]',
Expand Down
12 changes: 12 additions & 0 deletions src/utils/actions/documents/createDocument.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use server'
import { createSessionServerClient } from '@/app/appwrite-session'

export async function createDocument(
database: string,
collection: string,
documentId: string,
body: any
) {
const { databases } = await createSessionServerClient()
return await databases.createDocument(database, collection, documentId, body)
}

0 comments on commit 62afa03

Please sign in to comment.