Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Add "Add bookmark button" #121

Merged
merged 9 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 11 additions & 12 deletions src/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
'use client';

import React, { forwardRef } from 'react';
import { AiOutlineLoading3Quarters as LoadingIcon } from '@react-icons/all-files/ai/AiOutlineLoading3Quarters';
import clsx from 'clsx';
import { VariantProps, cva } from 'class-variance-authority';
import clsx from 'clsx';
import React, { forwardRef } from 'react';

const buttonVariants = cva(
'rounded-md font-semibold focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 flex items-center justify-center gap-2 cursor-pointer',
{
variants: {
variant: {
default:
'ring-1 shadow-sm bg-violet-600 hover:bg-violet-700 text-white ring-violet-600',
default: 'shadow-sm bg-violet-600 hover:bg-violet-700 text-white',
outline:
'ring-1 shadow-sm bg-transparent hover:bg-violet-700 text-violet-700 ring-1 ring-violet-600 hover:bg-violet-100 hover:text-white',
'border border-1 shadow-sm bg-transparent hover:bg-violet-700 text-violet-700 border-violet-600 hover:text-white box-border',
destructive:
'ring-1 shadow-sm bg-red-700 hover:bg-red-600 text-white ring-red-700',
'border-1 shadow-sm bg-red-700 hover:bg-red-600 text-white border-red-700',
destructiveOutline:
'ring-1 shadow-sm bg-transparent hover:bg-red-700 text-red-700 ring-1 ring-red-700 hover:bg-red-100 hover:text-white',
'border-1 shadow-sm bg-transparent text-red-700 border-1 border-red-700 hover:bg-red-100 hover:text-white',
success:
'ring-1 shadow-sm bg-[#4ade80] hover:bg-[#3dba6b] text-white ring-[#4ade80] hover:ring-[#3dba6b]',
'border-1 shadow-sm bg-[#4ade80] hover:bg-[#3dba6b] text-white border-[#4ade80] hover:border-[#3dba6b]',
ghost:
'bg-transparent text-violet-700 ring-1 ring-transparent hover:bg-violet-100 hover:ring-violet-100',
'bg-transparent text-violet-700 border-1 border-transparent hover:bg-violet-100 hover:border-violet-100',
destructiveGhost:
'bg-transparent text-red-700 ring-1 ring-transparent hover:bg-red-100 hover:ring-red-100',
link: 'ring-0 text-gray-700 text-sm underline hover:text-gray-400 font-light !px-0 !font-normal',
'bg-transparent text-red-700 border-1 border-transparent hover:bg-red-100 hover:border-red-100',
link: 'border-0 text-gray-700 text-sm underline hover:text-gray-400 !px-0 !font-normal',
},
size: {
sm: 'py-1 px-4 text-sm',
Expand Down Expand Up @@ -82,7 +81,7 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
[disabledClass]: disabled,
},
{
'w-full': fullWidth,
'w-full box-border': fullWidth,
},
className
)}
Expand Down
6 changes: 3 additions & 3 deletions src/components/auth/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
'use client';
import { routes } from '@/core/constants';
import message from '@/messages/en';
import { EmailProvider, getEmailProvider } from '@/utils/getEmailProvider';
import { CheckCircleIcon } from '@heroicons/react/24/solid';
import Link from 'next/link';
import { signIn } from 'next-auth/react';
import Link from 'next/link';
import { useSearchParams } from 'next/navigation';
import { useState } from 'react';
import { useMutation } from 'react-query';
import Button from '../Button';
import { Input } from '../Input';
import message from '@/messages/en';

const LoginForm = () => {
const searchParams = useSearchParams();
Expand Down Expand Up @@ -37,7 +37,7 @@ const LoginForm = () => {
);

return (
<div className="flex flex-col space-y-6 items-stretch">
<div className="flex flex-col space-y-6 items-stretch box-border">
{isSuccess ? (
<>
<div className="text-center flex justify-center">
Expand Down
27 changes: 18 additions & 9 deletions src/components/bookmark/BookmarksTeamList.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
'use client';

import { TeamLinks } from '@/services/database/link';
import { Team } from '@prisma/client';
import { BsFillBookmarkFill } from '@react-icons/all-files/bs/BsFillBookmarkFill';
import NoContent from '../layout/NoContent';
import { BookmarkItem } from './BookmarkItem';
import Link from 'next/link';
import SearchInput from '../digests/SearchInput';
import { TeamLinks } from '@/services/database/link';
import NoContent from '../layout/NoContent';
import { BookmarkItem } from './BookmarkItem';
import CreateBookmarkButton from './CreateBookmarkButton';

type Props = {
teamLinks: TeamLinks;
teamId: string;
teamSlug: string;
team: Team;
};

export const BookmarksTeamList = ({ teamLinks, teamId, teamSlug }: Props) => {
export const BookmarksTeamList = ({ teamLinks, team }: Props) => {
return (
<div className="w-full">
<SearchInput className="mb-4" />
<div className="flex w-full justify-between items-center gap-4 mb-4">
<div className="flex-1 ">
<SearchInput />
</div>
<div className="flex">
<CreateBookmarkButton team={team} />
</div>
</div>

{teamLinks.length < 1 ? (
<NoContent
icon={<BsFillBookmarkFill />}
Expand All @@ -34,8 +43,8 @@ export const BookmarksTeamList = ({ teamLinks, teamId, teamSlug }: Props) => {
>
<BookmarkItem
teamLink={teamLink}
teamSlug={teamSlug}
teamId={teamId}
teamSlug={team.slug}
teamId={team.id}
digestId={
teamLink.bookmark?.find(
(teamLink) => teamLink?.digestBlocks?.length
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,40 @@
'use client';

import { PlusIcon } from '@heroicons/react/24/solid';
import { Team } from '@prisma/client';
import { FaTelegramPlane } from '@react-icons/all-files/fa/FaTelegramPlane';
import { BookmarkModal } from './BookmarkModal';
import { Dialog, DialogContent, DialogTrigger } from '../Dialog';
import { useState } from 'react';
import Button from '../Button';
import { Dialog, DialogContent, DialogTrigger } from '../Dialog';
import { BookmarkModal } from './BookmarkModal';

type Props = { team: Team };
interface Props {
team: Team;
}

const BookmarkButton = ({ team }: Props) => {
export default function CreateBookmarkButton({ team }: Props) {
const [isDialogOpen, setIsDialogOpen] = useState(false);
return (
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogTrigger asChild>
<button
className="btn-add-link"
aria-label="Add bookmark"
title="Add bookmark"
<Button
onClick={() => setIsDialogOpen(true)}
variant="default"
size="md"
icon={<PlusIcon className="h-4 w-4" aria-hidden="true" />}
title="Create a new bookmark"
className="px-0"
>
<span className="hidden md:block">Add Bookmark</span>
<FaTelegramPlane />
</button>
New bookmark
</Button>
</DialogTrigger>
<DialogContent
containerClassName="w-full sm:max-w-md"
title="New Bookmark"
title="New bookmark"
description="Add a new link to your team feed"
closeIcon
>
<BookmarkModal onSuccess={() => setIsDialogOpen(false)} team={team} />
</DialogContent>
</Dialog>
);
};

export default BookmarkButton;
}
51 changes: 51 additions & 0 deletions src/components/bookmark/HeaderCreateBookmarkButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { PlusIcon } from '@heroicons/react/24/solid';
import { Team } from '@prisma/client';
import { useEffect, useState } from 'react';
import { Dialog, DialogContent, DialogTrigger } from '../Dialog';
import { BookmarkModal } from './BookmarkModal';

type Props = { team: Team };

const HeaderCreateBookmarkButton = ({ team }: Props) => {
const [isDialogOpen, setIsDialogOpen] = useState(false);

function onKeyDown(event: KeyboardEvent) {
if (event.key === 'b' && event.ctrlKey) {
setIsDialogOpen(true);
}
}

useEffect(() => {
window.addEventListener('keydown', onKeyDown);
return () => {
window.removeEventListener('keydown', onKeyDown);
};
}, []);

return (
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogTrigger asChild>
<button
className="btn-add-link"
aria-label="New bookmark"
title="New bookmark"
>
<span className="hidden md:block whitespace-nowrap">
New bookmark
</span>
<PlusIcon className="h-3.5 w-3.5" aria-hidden="true" />
</button>
</DialogTrigger>
<DialogContent
containerClassName="w-full sm:max-w-md"
title="New Bookmark"
description="Add a new link to your team feed"
closeIcon
>
<BookmarkModal onSuccess={() => setIsDialogOpen(false)} team={team} />
</DialogContent>
</Dialog>
);
};

export default HeaderCreateBookmarkButton;
8 changes: 4 additions & 4 deletions src/components/digests/SearchInput.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
'use client';

import { InputHTMLAttributes, useCallback, useTransition } from 'react';
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
import { AiOutlineLoading3Quarters as LoadingIcon } from '@react-icons/all-files/ai/AiOutlineLoading3Quarters';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import debounce from 'lodash/debounce';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import { InputHTMLAttributes, useCallback, useTransition } from 'react';

const SearchInput = ({
className,
Expand All @@ -30,7 +30,7 @@ const SearchInput = ({

return (
<div className={className}>
<div className="mt-2 flex rounded-md shadow-sm">
<div className="flex rounded-md shadow-sm">
<div className="relative flex flex-grow items-stretch focus-within:z-10">
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
{isPending ? (
Expand All @@ -49,7 +49,7 @@ const SearchInput = ({
type="search"
name="search"
id="search"
className="block w-full rounded-md border-0 py-1.5 pl-10 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
className="block w-full rounded-md border-0 py-1.5 pl-10 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-violet-600 sm:text-sm sm:leading-6"
placeholder="Search bookmarks"
onInput={onSearch}
defaultValue={searchParams?.get('search') || ''}
Expand Down
22 changes: 11 additions & 11 deletions src/components/layout/NavMenu/NavMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
'use client';

import { routes } from '@/core/constants';
import { Team } from '@prisma/client';
import { HiChevronDown } from '@react-icons/all-files/hi/HiChevronDown';
import * as NavigationMenu from '@radix-ui/react-navigation-menu';
import { usePathname } from 'next/navigation';
import BookmarkButton from '../../bookmark/BookmarkButton';
import { signOut } from 'next-auth/react';
import Divider from './Divider';
import Item from './Item';
import {
AdjustmentsVerticalIcon,
CheckIcon,
UserIcon,
ArrowLeftOnRectangleIcon,
CheckIcon,
PlusIcon,
UserIcon,
} from '@heroicons/react/24/solid';
import { Team } from '@prisma/client';
import * as NavigationMenu from '@radix-ui/react-navigation-menu';
import { HiChevronDown } from '@react-icons/all-files/hi/HiChevronDown';
import { signOut } from 'next-auth/react';
import { usePathname } from 'next/navigation';
import HeaderCreateBookmarkButton from '../../bookmark/HeaderCreateBookmarkButton';
import Divider from './Divider';
import Item from './Item';

type Props = {
teams?: Team[];
Expand All @@ -36,7 +36,7 @@ export const NavMenu = ({ teams }: Props) => {
<div>
{currentTeam && pathName !== '/' && (
<>
<BookmarkButton team={currentTeam} />
<HeaderCreateBookmarkButton team={currentTeam} />
</>
)}
</div>
Expand Down
30 changes: 19 additions & 11 deletions src/components/pages/DigestEditPage.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
'use client';

import { useRouter } from 'next/navigation';
import { routes } from '@/core/constants';
import useCustomToast from '@/hooks/useCustomToast';
import useTransitionRefresh from '@/hooks/useTransitionRefresh';
import api from '@/lib/api';
import { useRouter } from 'next/navigation';

import useAddAndRemoveBlockOnDigest from '@/hooks/useAddAndRemoveBlockOnDigest';
import { ApiDigestResponseSuccess } from '@/pages/api/teams/[teamId]/digests';
import { getDigest } from '@/services/database/digest';
import { TeamLinksData } from '@/services/database/link';
import { getTeamBySlug } from '@/services/database/team';
import { reorderList } from '@/utils/actionOnList';
import { getRelativeDate } from '@/utils/date';
import { digestBlockToTemplateBlocks } from '@/utils/template';
import { EyeSlashIcon, RssIcon, TrashIcon } from '@heroicons/react/24/outline';
import { EyeIcon } from '@heroicons/react/24/solid';
import { DigestBlock, DigestBlockType } from '@prisma/client';
import { BsFillBookmarkFill } from '@react-icons/all-files/bs/BsFillBookmarkFill';
import { AxiosError, AxiosResponse } from 'axios';
Expand All @@ -26,22 +31,18 @@ import { useMutation } from 'react-query';
import Button from '../Button';
import { Input, TextArea } from '../Input';
import { DeletePopover } from '../Popover';
import { BlockListDnd } from '../digests/BlockListDnd';
import BookmarkListDnd from '../bookmark/BookmarkListDnd';
import CreateBookmarkButton from '../bookmark/CreateBookmarkButton';
import { BlockListDnd } from '../digests/BlockListDnd';
import SearchInput from '../digests/SearchInput';
import CreateTemplateModal from '../digests/templates/CreateTemplateModal';
import NoContent from '../layout/NoContent';
import SectionContainer from '../layout/SectionContainer';
import Pagination from '../list/Pagination';
import { Breadcrumb } from '../teams/Breadcrumb';
import DigestEditVisit from './DigestEditVisit';
import DigestEditTypefully from './DigestEditTypefully';
import DigestEditSendNewsletter from './DigestEditSendNewsletter';
import { EyeIcon } from '@heroicons/react/24/solid';
import CreateTemplateModal from '../digests/templates/CreateTemplateModal';
import { digestBlockToTemplateBlocks } from '@/utils/template';
import { TeamLinksData } from '@/services/database/link';
import { getDigest } from '@/services/database/digest';
import { getTeamBySlug } from '@/services/database/team';
import DigestEditTypefully from './DigestEditTypefully';
import DigestEditVisit from './DigestEditVisit';

type Props = {
teamLinksData: TeamLinksData;
Expand Down Expand Up @@ -285,7 +286,14 @@ export const DigestEditPage = ({
</div>

<SectionContainer title="Bookmarks" className="relative">
<SearchInput className="mb-4" />
<div className="flex w-full justify-between items-center gap-4 mb-4">
<div className="flex-1">
<SearchInput />
</div>
<div className="flex">
<CreateBookmarkButton team={team} />
</div>
</div>
<div className="flex flex-col gap-2">
{teamLinks && teamLinks.length > 0 ? (
<BookmarkListDnd
Expand Down
Loading
Loading