New feature: add category

master
TZGyn 2 years ago
parent b7446257d0
commit 19f4a2ebde
Signed by: TZGyn
GPG Key ID: 122EAF77AE81FD4A

@ -9,6 +9,7 @@ import clsx from 'clsx'
import { userSchema } from '@/types' import { userSchema } from '@/types'
import { cookies } from 'next/headers' import { cookies } from 'next/headers'
import { getUser } from '@/lib/auth' import { getUser } from '@/lib/auth'
import { getBookmarkCategories } from '@/lib/utils'
export const metadata: Metadata = { export const metadata: Metadata = {
title: { title: {
@ -40,6 +41,10 @@ export default async function RootLayout({
user = userSchema.parse(userData) user = userSchema.parse(userData)
} }
const bookmarkCategories = await getBookmarkCategories(
userData ? userData.id : null
)
return ( return (
<html <html
lang='en' lang='en'
@ -53,7 +58,10 @@ export default async function RootLayout({
<Providers <Providers
themeProps={{ attribute: 'class', defaultTheme: 'dark' }}> themeProps={{ attribute: 'class', defaultTheme: 'dark' }}>
<div className='relative flex h-screen flex-col'> <div className='relative flex h-screen flex-col'>
<Navbar user={user} /> <Navbar
user={user}
categories={bookmarkCategories}
/>
<main className='container mx-auto max-w-7xl flex-grow px-6 pt-16'> <main className='container mx-auto max-w-7xl flex-grow px-6 pt-16'>
{children} {children}
</main> </main>

@ -30,9 +30,15 @@ import { GithubIcon } from '@/components/icons'
import { Icon } from '@iconify/react' import { Icon } from '@iconify/react'
import NewBookmarkForm from '@/components/newBookmarkForm' import NewBookmarkForm from '@/components/newBookmarkForm'
import { usePathname, useRouter } from 'next/navigation' import { usePathname, useRouter } from 'next/navigation'
import { User } from '@/types' import { BookmarkCategory, User } from '@/types'
export const Navbar = ({ user }: { user: User | null }) => { export const Navbar = ({
user,
categories,
}: {
user: User | null
categories: BookmarkCategory[]
}) => {
const pathname = usePathname() const pathname = usePathname()
const router = useRouter() const router = useRouter()
@ -95,7 +101,7 @@ export const Navbar = ({ user }: { user: User | null }) => {
<NavbarItem className='hidden gap-2 sm:flex'> <NavbarItem className='hidden gap-2 sm:flex'>
{pathname.startsWith('/dashboard') && ( {pathname.startsWith('/dashboard') && (
<div className='mr-4'> <div className='mr-4'>
<NewBookmarkForm /> <NewBookmarkForm categories={categories} />
</div> </div>
)} )}
<Link <Link

@ -8,30 +8,37 @@ import {
ModalFooter, ModalFooter,
useDisclosure, useDisclosure,
} from '@nextui-org/modal' } from '@nextui-org/modal'
import { import { Select, SelectSection, SelectItem } from '@nextui-org/select'
Dropdown, import { Selection } from '@nextui-org/react'
DropdownTrigger,
DropdownMenu,
DropdownItem,
} from '@nextui-org/dropdown'
import { Button } from '@nextui-org/button' import { Button } from '@nextui-org/button'
import { Input } from '@nextui-org/input' import { Input } from '@nextui-org/input'
import { Icon } from '@iconify/react' import { Icon } from '@iconify/react'
import { ChevronDownIcon } from './ChevronDownIcon'
import { useRouter } from 'next/navigation' import { useRouter } from 'next/navigation'
import { useState } from 'react' import { useState } from 'react'
import { Tab, Tabs } from '@nextui-org/tabs'
import { BookmarkCategory } from '@/types'
export default function NewBookmarkForm() { export default function NewBookmarkForm({
categories,
}: {
categories: BookmarkCategory[]
}) {
const { isOpen, onOpen, onOpenChange } = useDisclosure() const { isOpen, onOpen, onOpenChange } = useDisclosure()
const router = useRouter() const router = useRouter()
const onSubmit = async () => { const onSubmit = () => {
if (selected === 'bookmark') submitBookmark()
else if (selected === 'bookmark_category') submitCategory()
else return
}
const submitBookmark = async () => {
const body = { const body = {
name, name,
link, link,
description, description,
url, url,
category_id: selectedKey === 'Other' ? 2 : 1, category_id: parseInt(value.currentKey),
} }
await fetch('/api/bookmark', { await fetch('/api/bookmark', {
method: 'POST', method: 'POST',
@ -41,12 +48,28 @@ export default function NewBookmarkForm() {
router.refresh() router.refresh()
} }
const submitCategory = async () => {
const body = {
name: categoryName,
}
await fetch('/api/bookmark_category', {
method: 'POST',
body: JSON.stringify(body),
})
onOpenChange()
router.refresh()
}
const [selected, setSelected] = useState('bookmark')
const [name, setName] = useState('') const [name, setName] = useState('')
const [link, setLink] = useState('') const [link, setLink] = useState('')
const [description, setDescription] = useState('') const [description, setDescription] = useState('')
const [url, setUrl] = useState('') const [url, setUrl] = useState('')
const [selectedKey, setSelectedKey] = useState('Other') const [categoryName, setCategoryName] = useState('')
const [value, setValue] = useState<Selection>(new Set([]))
return ( return (
<> <>
@ -67,84 +90,95 @@ export default function NewBookmarkForm() {
onOpenChange={onOpenChange} onOpenChange={onOpenChange}
placement='center' placement='center'
hideCloseButton> hideCloseButton>
<ModalContent <ModalContent>
onKeyUp={(e) => {
if (e.key === 'Enter') onSubmit()
}}>
{(onClose) => ( {(onClose) => (
<> <>
<ModalHeader className='flex gap-2'> <ModalHeader className='flex gap-2'>
New Bookmark New Bookmark/Category
<Icon <Icon
icon='mdi:bookmark-box' icon='mdi:bookmark-box'
fontSize={30} fontSize={30}
/> />
</ModalHeader> </ModalHeader>
<ModalBody className='gap-4'> <ModalBody className='gap-4'>
<Dropdown> <Tabs
<DropdownTrigger className='flex justify-start'> selectedKey={selected}
<Button onSelectionChange={(key) =>
startContent={ setSelected(key.toString())
<span className='text-default-400'> }
Category: fullWidth
</span> color='primary'>
} <Tab
endContent={ key='bookmark'
<ChevronDownIcon className='text-small' /> title={'Bookmark'}>
} <form className='flex flex-col gap-4'>
variant='flat'> <Select
<div className='w-full text-start'> label='Select category'
{selectedKey} placeholder='Select a category'
</div> selectedKeys={value}
</Button> onSelectionChange={setValue}>
</DropdownTrigger> {categories.length > 0 ? (
<DropdownMenu categories.map(
disallowEmptySelection (category) => (
selectionMode='single' <SelectItem
selectedKeys={selectedKey} key={
disabledKeys={['disabled']}> category.id
<DropdownItem }
key='disabled' value={
className='capitalize'> category.id
Select Category }>
</DropdownItem> {category.name}
<DropdownItem </SelectItem>
key='Coding' )
onClick={() => { )
setSelectedKey('Coding') ) : (
}} <SelectItem
className='capitalize'> key={'test'}
Coding value={'test'}>
</DropdownItem> {'test'}
<DropdownItem </SelectItem>
key='Other' )}
onClick={() => { </Select>
setSelectedKey('Other') <Input
}} autoFocus
className='capitalize'> label='Name'
Other placeholder='Enter Name'
</DropdownItem> variant='bordered'
</DropdownMenu> value={name}
</Dropdown> onChange={(event) => {
<Input setName(event.target.value)
autoFocus }}
label='Name' />
placeholder='Enter Name' <Input
variant='bordered' label='Url'
value={name} placeholder='Enter Url'
onChange={(event) => { variant='bordered'
setName(event.target.value) value={url}
}} onChange={(event) => {
/> setUrl(event.target.value)
<Input }}
label='Url' />
placeholder='Enter Url' </form>
variant='bordered' </Tab>
value={url} <Tab
onChange={(event) => { key='bookmark_category'
setUrl(event.target.value) title={'Bookmark Category'}>
}} <form className='flex flex-col gap-4'>
/> <Input
autoFocus
label='Category Name'
placeholder='Enter Category Name'
variant='bordered'
value={categoryName}
onChange={(event) => {
setCategoryName(
event.target.value
)
}}
/>
</form>
</Tab>
</Tabs>
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button <Button

@ -0,0 +1,10 @@
import { db } from './db'
export const getBookmarkCategories = async (user: number | null) => {
const bookmarkCategories = await db.query.bookmarkCategory.findMany({
where: (bookmarkCategory, { eq }) =>
eq(bookmarkCategory.userId, user ?? 0),
})
return bookmarkCategories
}

@ -22,6 +22,8 @@
"@nextui-org/link": "2.0.0", "@nextui-org/link": "2.0.0",
"@nextui-org/modal": "^2.0.13", "@nextui-org/modal": "^2.0.13",
"@nextui-org/navbar": "2.0.0", "@nextui-org/navbar": "2.0.0",
"@nextui-org/react": "^2.1.5",
"@nextui-org/select": "^2.1.4",
"@nextui-org/snippet": "2.0.0", "@nextui-org/snippet": "2.0.0",
"@nextui-org/switch": "2.0.0", "@nextui-org/switch": "2.0.0",
"@nextui-org/system": "2.0.0", "@nextui-org/system": "2.0.0",

File diff suppressed because it is too large Load Diff

@ -34,9 +34,12 @@ export const newBookmarkCategorySchema = z.object({
export const bookmarkCategorySchema = z.object({ export const bookmarkCategorySchema = z.object({
id: z.number(), id: z.number(),
userId: z.number(),
...bookmarkCategory, ...bookmarkCategory,
}) })
export type BookmarkCategory = z.infer<typeof bookmarkCategorySchema>
export const bookmarkCategoryWithBookmarksSchema = z.object({ export const bookmarkCategoryWithBookmarksSchema = z.object({
id: z.number(), id: z.number(),
...bookmarkCategory, ...bookmarkCategory,

Loading…
Cancel
Save