Skip to content

Commit

Permalink
✨ Finish create folder
Browse files Browse the repository at this point in the history
  • Loading branch information
weichen-lin committed Jun 7, 2024
1 parent 6b64119 commit 375cd48
Show file tree
Hide file tree
Showing 6 changed files with 273 additions and 59 deletions.
13 changes: 13 additions & 0 deletions app/(auth)/dashboard/[folder_id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use server'

import { Path, Disk, Actions } from '@/app/(auth)/dashboard/components'

export default async function Dashboard() {
return (
<div className='w-full h-full pt-24 px-[3%] overflow-y-scroll flex flex-col gap-y-4'>
<Actions />
<Path />
<Disk />
</div>
)
}
144 changes: 114 additions & 30 deletions app/(auth)/dashboard/components/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,132 @@ import { Button } from '@/components/ui/button'
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
import { FolderPlus, FileArrowUp } from '@phosphor-icons/react'
import { useState } from 'react'

export default function Actions() {
const [open, setOpen] = useState(false)
const [isLoaded, setIsLoaded] = useState(false)
const [name, setName] = useState('')
const [error, setError] = useState('')

const createFolder = async () => {
setIsLoaded(true)
setError('')

const response = await fetch('/api/disk/folder', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
locate_at: null,
name,
}),
})

if (response.ok) {
setOpen(false)
setName('')
} else {
const json = await response.json()
setError(json?.message ?? 'An error occurred')
}

setIsLoaded(false)
}

const toggle = () => {
if (isLoaded) return
setOpen(!open)
setError('')
setName('')
}

return (
<div className='w-full flex gap-x-4 items-center'>
<Dialog>
<DialogTrigger>
<div className='flex gap-x-2 border-[1px] border-input px-4 py-[6px] rounded-md'>
<FolderPlus className='w-5 h-5' />
<div className='hidden md:block'>New Folder</div>
</div>
</DialogTrigger>
<AddFolder />
<Button variant='outline' className='flex gap-x-2'>
<FileArrowUp className='w-5 h-5' />
<div className='hidden md:block'>Upload</div>
</Button>
</div>
)
}

const AddFolder = () => {
const [open, setOpen] = useState(false)
const [isLoaded, setIsLoaded] = useState(false)
const [name, setName] = useState('')
const [error, setError] = useState('')

const createFolder = async () => {
setIsLoaded(true)
setError('')

const response = await fetch('/api/disk/folder', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
locate_at: null,
name,
}),
})

if (response.ok) {
setOpen(false)
setName('')
} else {
const json = await response.json()
setError(json?.message ?? 'An error occurred')
}

setIsLoaded(false)
}

const toggle = () => {
if (isLoaded) return
setOpen(!open)
setError('')
setName('')
}

return (
<Dialog
open={open}
onOpenChange={e => {
if (isLoaded) return
setOpen(e)
}}
>
<DialogTrigger>
<div className='flex gap-x-2 border-[1px] border-input px-4 py-[6px] rounded-md'>
<FolderPlus className='w-5 h-5' />
<div className='hidden md:block'>New Folder</div>
</div>
</DialogTrigger>
{open && (
<DialogContent className='w-[380px] flex flex-col gap-y-4'>
<DialogHeader>
<DialogTitle>Create Folder</DialogTitle>
</DialogHeader>
<Input placeholder='Folder Name' />
<Input placeholder='Folder Name' value={name} onChange={e => setName(e.target.value)} />
{error && (
<div className='my-1 px-2'>
<div className='text-red-500'>{error}</div>
</div>
)}
<DialogFooter className='gap-x-4'>
<Button variant='outline'>Cancel</Button>
<Button
className='bg-slate-700'
onClick={() => {
fetch('/api/disk/folder', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
locate_at: '869a34ca-26da-4b78-8ac6-2c51e831ab3f',
name: 'new folderssadasdsassssss',
}),
})
}}
>
<Button variant='outline' disabled={isLoaded} onClick={toggle}>
Cancel
</Button>
<Button className='bg-slate-700' disabled={!name || isLoaded} onClick={createFolder} loading={isLoaded}>
Create
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<Button variant='outline' className='flex gap-x-2'>
<FileArrowUp className='w-5 h-5' />
<div className='hidden md:block'>Upload</div>
</Button>
</div>
)}
</Dialog>
)
}
4 changes: 2 additions & 2 deletions app/(auth)/dashboard/components/disk.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ export default function Disk() {
<TabsList>
<TabsTrigger value='folder' className='px-10 flex gap-x-2 items-center'>
{activeTab === 'folder' ? (
<img src='double-check.gif' className='w-6 h-6' />
<img src='/double-check.gif' className='w-6 h-6' />
) : (
<FolderIcon className='w-6 h-6 text-slate-700' />
)}
Folder
</TabsTrigger>
<TabsTrigger value='file' className='px-10 flex gap-x-2 items-center'>
{activeTab === 'file' ? (
<img src='double-check.gif' className='w-6 h-6' />
<img src='/double-check.gif' className='w-6 h-6' />
) : (
<FileImage className='w-6 h-6 text-slate-700' />
)}
Expand Down
71 changes: 57 additions & 14 deletions app/(auth)/dashboard/components/path.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use client'

import {
Breadcrumb,
BreadcrumbItem,
Expand All @@ -7,26 +9,67 @@ import {
BreadcrumbSeparator,
BreadcrumbEllipsis,
} from '@/components/ui/breadcrumb'
import { useParams } from 'next/navigation'
import { useState, useEffect } from 'react'
import type { IFullPath } from '@/supabase/crud/folder'

export default function Path() {
const params = useParams()
const [isLoaded, setIsLoaded] = useState(true)
const [path, setPath] = useState<IFullPath>([])

const folder_id = params.folder_id

useEffect(() => {
const fetchPath = async () => {
const res = await fetch(`/api/disk/folder?folder_id=${params.folder_id}`)
const data = await res.json()
setPath(data.full_path)
}

if (folder_id) fetchPath()
setIsLoaded(false)
}, [])

return isLoaded ? <BreadcrumbLoading /> : <BreadCrumbs path={path} />
}

const BreadcrumbLoading = () => <div className='w-[360px] p-3 rounded-sm bg-slate-200 animate-pulse'></div>

const BreadCrumbs = ({ path }: { path: IFullPath }) => {
return (
<Breadcrumb className=''>
<Breadcrumb className='mt-4'>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href='/'>Home</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href='/components'>Components</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbEllipsis />
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
<BreadcrumbLink href='/dashboard'>Root directory</BreadcrumbLink>
</BreadcrumbItem>
{path.length > 2 ? (
<>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbEllipsis />
</BreadcrumbItem>
{path.slice(-2).map((p, i) => (
<span key={`bread-${p.id}`}>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href={`/dashboard/${p.id}`}>{p.name}</BreadcrumbLink>
</BreadcrumbItem>
</span>
))}
</>
) : (
<>
{path.map((p, i) => (
<span key={`bread-${p.id}`}>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href={`/dashboard/${p.id}`}>{p.name}</BreadcrumbLink>
</BreadcrumbItem>
</span>
))}
</>
)}
</BreadcrumbList>
</Breadcrumb>
)
Expand Down
24 changes: 23 additions & 1 deletion app/api/disk/folder/route.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
import { NextRequest, NextResponse } from 'next/server'
import useServerUser from '@/supabase/sever'
import { z } from 'zod'
import { createFolder } from '@/supabase/crud/folder'
import { createFolder, getFolderFullPath } from '@/supabase/crud/folder'

const createFolderSchema = z.object({
locate_at: z.string().nullable(),
name: z.string(),
})

export const GET = async (req: NextRequest, res: NextResponse) => {
const user = await useServerUser()

if (!user) {
return NextResponse.json({ message: 'unauthorized' }, { status: 401 })
}

const d = req.nextUrl.searchParams.get('folder_id')

if (!d) {
return NextResponse.json({ message: 'invalid data', full_path: [] }, { status: 400 })
}

const { error, data } = await getFolderFullPath(user.id, d)

if (error) {
return NextResponse.json({ message: error, full_path: [] }, { status: 400 })
}

return NextResponse.json({ full_path: data })
}

export const POST = async (req: NextRequest, res: NextResponse) => {
const user = await useServerUser()

Expand Down
Loading

0 comments on commit 375cd48

Please sign in to comment.