From 2f0e1dd375ea0ea330225a13ee3217a5b8c2b7f1 Mon Sep 17 00:00:00 2001 From: Sonny Date: Mon, 13 May 2024 00:04:01 +0200 Subject: [PATCH] feat: add dropdown for links and collection header --- app/controllers/collections_controller.ts | 23 ++++- config/inertia.ts | 6 +- .../components/common/dropdown/dropdown.tsx | 9 +- .../common/dropdown/dropdown_container.tsx | 1 + .../common/dropdown/dropdown_item.tsx | 22 ++++- .../collection/collection_controls.tsx | 56 +++++------ .../dashboard/link_list/link_controls.tsx | 98 +++++++++++++++++++ .../dashboard/link_list/link_item.tsx | 88 ++--------------- .../dashboard/link_list/link_list.tsx | 3 +- inertia/components/footer/footer.tsx | 3 + inertia/components/layouts/content_layout.tsx | 10 +- inertia/components/layouts/form_layout.tsx | 10 +- inertia/lib/navigation.ts | 6 +- 13 files changed, 204 insertions(+), 131 deletions(-) create mode 100644 inertia/components/dashboard/link_list/link_controls.tsx diff --git a/app/controllers/collections_controller.ts b/app/controllers/collections_controller.ts index 5c67f20..7b1bddc 100644 --- a/app/controllers/collections_controller.ts +++ b/app/controllers/collections_controller.ts @@ -6,18 +6,33 @@ import type { HttpContext } from '@adonisjs/core/http'; export default class CollectionsController { // Dashboard - async index({ auth, inertia, response }: HttpContext) { + async index({ auth, inertia, request, response }: HttpContext) { const collections = await this.getCollectionByAuthorId(auth.user!.id); if (collections.length === 0) { return response.redirect('/collections/create'); } - return inertia.render('dashboard', { collections }); + const activeCollectionId = request.qs()?.collectionId ?? ''; + const activeCollection = collections.find( + (c) => c.id === activeCollectionId + ); + + if (!activeCollection && !!activeCollectionId) { + return response.redirect('/dashboard'); + } + + return inertia.render('dashboard', { + collections, + activeCollection: activeCollection || collections[0], + }); } // Create collection form - async showCreatePage({ inertia }: HttpContext) { - return inertia.render('collections/create'); + async showCreatePage({ inertia, auth }: HttpContext) { + const collections = await this.getCollectionByAuthorId(auth.user!.id); + return inertia.render('collections/create', { + disableHomeLink: collections.length === 0, + }); } // Method called when creating a collection diff --git a/config/inertia.ts b/config/inertia.ts index 3c44d89..d79782c 100644 --- a/config/inertia.ts +++ b/config/inertia.ts @@ -12,10 +12,10 @@ export default defineConfig({ sharedData: { errors: (ctx) => ctx.session?.flashMessages.get('errors'), auth: async (ctx) => { - await ctx.auth.check(); + await ctx.auth?.check(); return { - user: ctx.auth.user, - isAuthenticated: ctx.auth.isAuthenticated, + user: ctx.auth?.user || null, + isAuthenticated: ctx.auth?.isAuthenticated || false, }; }, }, diff --git a/inertia/components/common/dropdown/dropdown.tsx b/inertia/components/common/dropdown/dropdown.tsx index 4c72e55..613203b 100644 --- a/inertia/components/common/dropdown/dropdown.tsx +++ b/inertia/components/common/dropdown/dropdown.tsx @@ -32,9 +32,11 @@ const DropdownStyle = styled.div<{ opened: boolean }>(({ opened, theme }) => ({ export default function Dropdown({ children, label, + className, }: { children: ReactNode; label: ReactNode | string; + className?: string; }) { const dropdownRef = useRef(null); const { isShowing, toggle, close } = useToggle(); @@ -48,7 +50,12 @@ export default function Dropdown({ }); return ( - + {label} {children} diff --git a/inertia/components/common/dropdown/dropdown_container.tsx b/inertia/components/common/dropdown/dropdown_container.tsx index 54ab57c..ec198b9 100644 --- a/inertia/components/common/dropdown/dropdown_container.tsx +++ b/inertia/components/common/dropdown/dropdown_container.tsx @@ -1,6 +1,7 @@ import styled from '@emotion/styled'; const DropdownContainer = styled.div<{ show: boolean }>(({ show, theme }) => ({ + zIndex: 99, position: 'absolute', top: 'calc(100% + 0.5em)', right: 0, diff --git a/inertia/components/common/dropdown/dropdown_item.tsx b/inertia/components/common/dropdown/dropdown_item.tsx index aaeaca1..be72f13 100644 --- a/inertia/components/common/dropdown/dropdown_item.tsx +++ b/inertia/components/common/dropdown/dropdown_item.tsx @@ -1,11 +1,10 @@ import styled from '@emotion/styled'; +import { Link } from '@inertiajs/react'; -const DropdownItem = styled.div(({ theme }) => ({ +const DropdownItemBase = styled.div(({ theme }) => ({ fontSize: '14px', + whiteSpace: 'nowrap', padding: '8px 12px', - display: 'flex', - gap: '0.35em', - alignItems: 'center', borderRadius: theme.border.radius, '&:hover': { @@ -13,4 +12,17 @@ const DropdownItem = styled.div(({ theme }) => ({ }, })); -export default DropdownItem; +const DropdownItemButton = styled(DropdownItemBase)({ + display: 'flex', + gap: '0.75em', + alignItems: 'center', +}); + +const DropdownItemLink = styled(DropdownItemBase.withComponent(Link))({ + width: '100%', + display: 'flex', + gap: '0.75em', + alignItems: 'center', +}); + +export { DropdownItemButton, DropdownItemLink }; diff --git a/inertia/components/dashboard/collection/collection_controls.tsx b/inertia/components/dashboard/collection/collection_controls.tsx index 80db94a..05f6209 100644 --- a/inertia/components/dashboard/collection/collection_controls.tsx +++ b/inertia/components/dashboard/collection/collection_controls.tsx @@ -1,34 +1,28 @@ +import PATHS from '#constants/paths'; import styled from '@emotion/styled'; -import QuickResourceAction from '~/components/dashboard/quick_action/quick_action'; -import useActiveCollection from '~/hooks/use_active_collection'; +import { BsThreeDotsVertical } from 'react-icons/bs'; +import { HiOutlinePencil } from 'react-icons/hi2'; +import { IoIosAddCircleOutline } from 'react-icons/io'; +import { IoTrashOutline } from 'react-icons/io5'; +import Dropdown from '~/components/common/dropdown/dropdown'; +import { DropdownItemLink } from '~/components/common/dropdown/dropdown_item'; -const CollectionControlsStyle = styled.span({ - display: 'flex', - gap: '0.5em', - alignItems: 'center', -}); +const DeleteItem = styled(DropdownItemLink)(({ theme }) => ({ + color: theme.colors.lightRed, +})); -export default function CollectionControls() { - const { activeCollection } = useActiveCollection(); - return ( - activeCollection && ( - - - - - - ) - ); -} +const CollectionControls = () => ( + }> + + Add + + + Edit + + + Delete + + +); + +export default CollectionControls; diff --git a/inertia/components/dashboard/link_list/link_controls.tsx b/inertia/components/dashboard/link_list/link_controls.tsx new file mode 100644 index 0000000..5d552b6 --- /dev/null +++ b/inertia/components/dashboard/link_list/link_controls.tsx @@ -0,0 +1,98 @@ +import PATHS from '#constants/paths'; +import type Link from '#models/link'; +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { useCallback } from 'react'; +import { AiFillStar, AiOutlineStar } from 'react-icons/ai'; +import { BsThreeDotsVertical } from 'react-icons/bs'; +import { HiOutlinePencil } from 'react-icons/hi2'; +import { IoTrashOutline } from 'react-icons/io5'; +import Dropdown from '~/components/common/dropdown/dropdown'; +import { + DropdownItemButton, + DropdownItemLink, +} from '~/components/common/dropdown/dropdown_item'; +import useCollections from '~/hooks/use_collections'; +import { appendCollectionId } from '~/lib/navigation'; +import { makeRequest } from '~/lib/request'; + +const StartItem = styled(DropdownItemButton)(({ theme }) => ({ + color: theme.colors.yellow, +})); + +const DeleteItem = styled(DropdownItemLink)(({ theme }) => ({ + color: theme.colors.lightRed, +})); + +export default function LinkControls({ link }: { link: Link }) { + const theme = useTheme(); + const { collections, setCollections } = useCollections(); + + const toggleFavorite = useCallback( + (linkId: Link['id']) => { + let linkIndex = 0; + const collectionIndex = collections.findIndex(({ links }) => { + const lIndex = links.findIndex((l) => l.id === linkId); + if (lIndex !== -1) { + linkIndex = lIndex; + } + return lIndex !== -1; + }); + + const collectionLink = collections[collectionIndex].links[linkIndex]; + const collectionsCopy = [...collections]; + collectionsCopy[collectionIndex].links[linkIndex] = { + ...collectionLink, + favorite: !collectionLink.favorite, + }; + + setCollections(collectionsCopy); + }, + [collections, setCollections] + ); + + const onFavorite = () => { + makeRequest({ + url: `${PATHS.API.LINK}/${link.id}`, + method: 'PUT', + body: { + name: link.name, + url: link.url, + favorite: !link.favorite, + collectionId: link.collectionId, + }, + }) + .then(() => toggleFavorite(link.id)) + .catch(console.error); + }; + + console.log(link.favorite, link.favorite ? 'oui' : 'non'); + return ( + } + css={{ backgroundColor: theme.colors.secondary }} + > + + {!link.favorite ? ( + <> + Add to favorites + + ) : ( + <> + Remove from favorites + + )} + + + Edit + + + Delete + + + ); +} diff --git a/inertia/components/dashboard/link_list/link_item.tsx b/inertia/components/dashboard/link_list/link_item.tsx index 13a76f1..1d36c7c 100644 --- a/inertia/components/dashboard/link_list/link_item.tsx +++ b/inertia/components/dashboard/link_list/link_item.tsx @@ -1,14 +1,9 @@ -import PATHS from '#constants/paths'; import type Link from '#models/link'; import styled from '@emotion/styled'; -import { useCallback } from 'react'; import { AiFillStar } from 'react-icons/ai'; import ExternalLink from '~/components/common/external_link'; import LinkFavicon from '~/components/dashboard/link/link_favicon'; -import QuickResourceAction from '~/components/dashboard/quick_action/quick_action'; -import QuickLinkFavorite from '~/components/dashboard/quick_action/quick_favorite_link'; -import useCollections from '~/hooks/use_collections'; -import { makeRequest } from '~/lib/request'; +import LinkControls from '~/components/dashboard/link_list/link_controls'; const LinkWrapper = styled.li(({ theme }) => ({ userSelect: 'none', @@ -20,16 +15,21 @@ const LinkWrapper = styled.li(({ theme }) => ({ padding: '0.75em 1em', border: `1px solid ${theme.colors.lightGrey}`, borderRadius: theme.border.radius, - outline: '3px solid transparent', + + '&:hover': { + outlineWidth: '1px', + outlineStyle: 'solid', + }, })); const LinkHeader = styled.div(({ theme }) => ({ display: 'flex', + gap: '1em', alignItems: 'center', '& > a': { height: '100%', - maxWidth: 'calc(100% - 125px)', // TODO: fix this, it is ugly af :( + maxWidth: 'calc(100% - 75px)', // TODO: fix this, it is ugly af :( textDecoration: 'none', display: 'flex', flex: 1, @@ -49,22 +49,6 @@ const LinkName = styled.div({ overflow: 'hidden', }); -const LinkControls = styled.div({ - display: 'none', - alignItems: 'center', - justifyContent: 'center', - gap: '10px', - - '& svg': { - height: '20px', - width: '20px', - }, - - '&:hover *': { - transform: 'scale(1.3)', - }, -}); - const LinkDescription = styled.div(({ theme }) => ({ marginTop: '0.5em', color: theme.colors.font, @@ -97,46 +81,6 @@ export default function LinkItem({ showUserControls: boolean; }) { const { id, name, url, description, favorite } = link; - const { collections, setCollections } = useCollections(); - - const toggleFavorite = useCallback( - (linkId: Link['id']) => { - let linkIndex = 0; - const collectionIndex = collections.findIndex(({ links }) => { - const lIndex = links.findIndex((l) => l.id === linkId); - if (lIndex !== -1) { - linkIndex = lIndex; - } - return lIndex !== -1; - }); - - const collectionLink = collections[collectionIndex].links[linkIndex]; - const collectionsCopy = [...collections]; - collectionsCopy[collectionIndex].links[linkIndex] = { - ...collectionLink, - favorite: !collectionLink.favorite, - }; - - setCollections(collectionsCopy); - }, - [collections, setCollections] - ); - - const onFavorite = () => { - makeRequest({ - url: `${PATHS.API.LINK}/${link.id}`, - method: 'PUT', - body: { - name, - url, - favorite: !favorite, - collectionId: link.collectionId, - }, - }) - .then(() => toggleFavorite(link.id)) - .catch(console.error); - }; - return ( @@ -147,21 +91,7 @@ export default function LinkItem({ - {showUserControls && ( - - - - - - )} + {showUserControls && } {description && {description}} diff --git a/inertia/components/dashboard/link_list/link_list.tsx b/inertia/components/dashboard/link_list/link_list.tsx index f9eeac8..d8791a3 100644 --- a/inertia/components/dashboard/link_list/link_list.tsx +++ b/inertia/components/dashboard/link_list/link_list.tsx @@ -19,8 +19,9 @@ const LinksWrapper = styled.div({ }); const CollectionHeaderWrapper = styled.h2(({ theme }) => ({ + fontWeight: 400, color: theme.colors.primary, - fontWeight: 500, + paddingInline: '1em', display: 'flex', gap: '0.4em', alignItems: 'center', diff --git a/inertia/components/footer/footer.tsx b/inertia/components/footer/footer.tsx index 29c00a9..9366087 100644 --- a/inertia/components/footer/footer.tsx +++ b/inertia/components/footer/footer.tsx @@ -10,6 +10,9 @@ const FooterStyle = styled.footer(({ theme }) => ({ color: theme.colors.grey, textAlign: 'center', paddingTop: '0.75em', + '& a:hover': { + textDecoration: 'underline', + }, })); export default function Footer() { diff --git a/inertia/components/layouts/content_layout.tsx b/inertia/components/layouts/content_layout.tsx index 7dbdb1e..932235e 100644 --- a/inertia/components/layouts/content_layout.tsx +++ b/inertia/components/layouts/content_layout.tsx @@ -1,13 +1,20 @@ import styled from '@emotion/styled'; import { ReactNode } from 'react'; +import Footer from '~/components/footer/footer'; import Navbar from '../navbar/navbar'; import BaseLayout from './_base_layout'; const ContentLayoutStyle = styled.div(({ theme }) => ({ - height: 'auto', + height: '100%', width: theme.media.small_desktop, maxWidth: '100%', padding: '1em', + display: 'flex', + flexDirection: 'column', + + '& main': { + flex: 1, + }, })); const ContentLayout = ({ children }: { children: ReactNode }) => ( @@ -15,6 +22,7 @@ const ContentLayout = ({ children }: { children: ReactNode }) => (
{children}
+