diff --git a/api/useInbox.tsx b/api/useInbox.tsx index 014768684..c7c552149 100644 --- a/api/useInbox.tsx +++ b/api/useInbox.tsx @@ -71,17 +71,6 @@ const useInbox = () => { mutate, } = useSWR("/api/inbox", fetchInbox); - const createInbox = async (note: EditorJsonContent) => { - const { data, errors } = await client.models.Inbox.create({ - noteJson: JSON.stringify(note), - note: null, - formatVersion: 2, - status: "new", - }); - if (errors) handleApiErrors(errors, "Error creating inbox item"); - return data?.id; - }; - const updateNote = async (id: string, note: EditorJsonContent) => { const updated = inbox?.map((item) => item.id !== id ? item : { ...item, note } @@ -98,11 +87,14 @@ const useInbox = () => { return data?.id; }; + const mutateInbox = (newItem?: Inbox) => + inbox && mutate([...inbox, ...(newItem ? [newItem] : [])]); + return { inbox, errorInbox, - createInbox, updateNote, + mutateInbox, }; }; diff --git a/components/activities/activity.tsx b/components/activities/activity.tsx index 5a1e5dcc4..4f0289f35 100644 --- a/components/activities/activity.tsx +++ b/components/activities/activity.tsx @@ -34,7 +34,6 @@ const ActivityComponent: FC = ({ showDates, showMeeting, showProjects, - autoFocus, allowAddingProjects, }) => { const { activity, updateNotes, updateDate, addProjectToActivity } = @@ -139,7 +138,6 @@ const ActivityComponent: FC = ({ diff --git a/components/header/CreateInboxItem.tsx b/components/header/CreateInboxItem.tsx new file mode 100644 index 000000000..e8dd36f86 --- /dev/null +++ b/components/header/CreateInboxItem.tsx @@ -0,0 +1,30 @@ +import { Plus } from "lucide-react"; +import { useCreateInboxItemContext } from "../inbox/CreateInboxItemDialog"; +import { Button } from "../ui/button"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "../ui/tooltip"; + +const CreateInboxItem = () => { + const { open } = useCreateInboxItemContext(); + + return ( + + + + + + +

Create a new inbox item

+
+
+
+ ); +}; + +export default CreateInboxItem; diff --git a/components/header/Header.tsx b/components/header/Header.tsx index b793d25d2..58d740782 100644 --- a/components/header/Header.tsx +++ b/components/header/Header.tsx @@ -1,8 +1,8 @@ import { Context } from "@/contexts/ContextContext"; import { FC } from "react"; +import CreateInboxItem from "./CreateInboxItem"; import Logo from "./Logo"; import ProfilePicture from "./ProfilePicture"; -import SearchBar from "./SearchBar"; type HeaderProps = { context?: Context; @@ -12,7 +12,7 @@ const Header: FC = ({ context }) => (
- +
diff --git a/components/inbox/CreateInboxItemDialog.tsx b/components/inbox/CreateInboxItemDialog.tsx new file mode 100644 index 000000000..7fa36406a --- /dev/null +++ b/components/inbox/CreateInboxItemDialog.tsx @@ -0,0 +1,145 @@ +import { type Schema } from "@/amplify/data/resource"; +import { handleApiErrors } from "@/api/globals"; +import useInbox from "@/api/useInbox"; +import { generateClient } from "aws-amplify/data"; +import { FC, ReactNode, createContext, useContext, useState } from "react"; +import NotesWriter, { + EditorJsonContent, + getTextFromEditorJsonContent, +} from "../ui-elements/notes-writer/NotesWriter"; +import { Button } from "../ui/button"; +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "../ui/dialog"; +import { useToast } from "../ui/use-toast"; + +const client = generateClient(); + +interface CreateInboxItemContextType { + state: boolean; + open: () => void; + close: () => void; + inboxItemText: EditorJsonContent | undefined; + setInboxItemText: (val: { json: EditorJsonContent } | undefined) => void; + createInboxItem: () => Promise; +} + +interface CreateInobxItemProviderProps { + children: ReactNode; +} + +export const CreateInboxItemProvider: FC = ({ + children, +}) => { + const [isOpen, setIsOpen] = useState(false); + const { mutateInbox } = useInbox(); + const [inboxItemText, setInboxItemText] = useState< + EditorJsonContent | undefined + >(); + const { toast } = useToast(); + + const handleUpdate = (val: { json: EditorJsonContent } | undefined) => + val && setInboxItemText(val.json); + + const createInboxItem = async () => { + if (!inboxItemText) return; + const { data, errors } = await client.models.Inbox.create({ + noteJson: JSON.stringify(inboxItemText), + note: null, + formatVersion: 2, + status: "new", + }); + if (errors) handleApiErrors(errors, "Error creating inbox item"); + if (!data) return; + toast({ + title: "New Inbox Item Created", + description: getTextFromEditorJsonContent(inboxItemText), + }); + mutateInbox({ + id: crypto.randomUUID(), + createdAt: new Date(), + status: "new", + note: inboxItemText, + }); + setInboxItemText(undefined); + setIsOpen(false); + return data.id; + }; + + return ( + setIsOpen(true), + close: () => setIsOpen(false), + inboxItemText, + setInboxItemText: handleUpdate, + createInboxItem, + }} + > + {children} + + ); +}; + +const CreateInboxItemContext = createContext< + CreateInboxItemContextType | undefined +>(undefined); + +export const useCreateInboxItemContext = () => { + const context = useContext(CreateInboxItemContext); + if (!context) + throw new Error( + "useCreateInboxItemContext must be used within CreateInboxItemProvider" + ); + return context; +}; + +const CreateInboxItemDialog = () => { + const { + state, + open, + close, + inboxItemText, + setInboxItemText, + createInboxItem, + } = useCreateInboxItemContext(); + + return ( + (state ? close() : open())}> + + + Create a New Inbox Item + + Get it out of your head so you can process and organize it later and + continue to focus on what matters now. + + + setInboxItemText(serializer())} + showSaveStatus={false} + autoFocus + /> + {JSON.stringify(inboxItemText)} + + + + + + + + + ); +}; + +export default CreateInboxItemDialog; diff --git a/components/layouts/MainLayout.tsx b/components/layouts/MainLayout.tsx index 25f134a3e..d5b6fbc25 100644 --- a/components/layouts/MainLayout.tsx +++ b/components/layouts/MainLayout.tsx @@ -10,6 +10,10 @@ import { addKeyDownListener } from "@/helpers/keyboard-events/main-layout"; import Head from "next/head"; import { useRouter } from "next/router"; import { FC, ReactNode, useEffect } from "react"; +import CreateInboxItemDialog, { + CreateInboxItemProvider, + useCreateInboxItemContext, +} from "../inbox/CreateInboxItemDialog"; import { Toaster } from "../ui/toaster"; type MainLayoutProps = CategoryTitleProps & { @@ -27,13 +31,20 @@ const MainLayoutInner: FC = ({ ...categoryTitleProps }) => { const { toggleMenu } = useNavMenuContext(); + const { open: openCreateInboxItemDialog } = useCreateInboxItemContext(); const { context: storedContext, setContext } = useContextContext(); const context = propsContext || storedContext || "family"; const router = useRouter(); useEffect( - () => addKeyDownListener(router, setContext, toggleMenu), - [router, setContext, toggleMenu] + () => + addKeyDownListener( + router, + setContext, + toggleMenu, + openCreateInboxItemDialog + ), + [openCreateInboxItemDialog, router, setContext, toggleMenu] ); return ( @@ -56,6 +67,7 @@ const MainLayoutInner: FC = ({
+
@@ -64,7 +76,9 @@ const MainLayoutInner: FC = ({ const MainLayout: FC = (props) => ( - + + + ); diff --git a/components/navigation-menu/NavigationMenu.tsx b/components/navigation-menu/NavigationMenu.tsx index 56a4510ce..84ed77d95 100644 --- a/components/navigation-menu/NavigationMenu.tsx +++ b/components/navigation-menu/NavigationMenu.tsx @@ -1,10 +1,12 @@ import { useContextContext } from "@/contexts/ContextContext"; import { useNavMenuContext } from "@/contexts/NavMenuContext"; +import { Plus } from "lucide-react"; import { useRouter } from "next/router"; import { BiConversation } from "react-icons/bi"; import { GoTasklist } from "react-icons/go"; import { IconType } from "react-icons/lib"; import { PiHandFist } from "react-icons/pi"; +import { useCreateInboxItemContext } from "../inbox/CreateInboxItemDialog"; import { CommandDialog, CommandEmpty, @@ -28,6 +30,7 @@ type NavigationItem = { const NavigationMenu = () => { const { isWorkContext } = useContextContext(); const { menuIsOpen, toggleMenu } = useNavMenuContext(); + const { open: openCreateInboxItemDialog } = useCreateInboxItemContext(); const router = useRouter(); const mainNavigation: NavigationItem[] = [ @@ -103,6 +106,10 @@ const NavigationMenu = () => { ))} + + + Create Inbox Item + {}}>
diff --git a/components/ui-elements/notes-writer/NotesWriter.tsx b/components/ui-elements/notes-writer/NotesWriter.tsx index 0cdf6943f..4a035df5f 100644 --- a/components/ui-elements/notes-writer/NotesWriter.tsx +++ b/components/ui-elements/notes-writer/NotesWriter.tsx @@ -72,6 +72,7 @@ type NotesWriterProps = { placeholder?: string; onSubmit?: (item: EditorJsonContent) => void; readonly?: boolean; + showSaveStatus?: boolean; }; const NotesWriter: FC = ({ @@ -81,6 +82,7 @@ const NotesWriter: FC = ({ placeholder = "Start taking notes...", onSubmit, readonly, + showSaveStatus = true, }) => { const editor = useEditor({ extensions: [ @@ -122,7 +124,9 @@ const NotesWriter: FC = ({ attributes: { class: cn( "prose w-full max-w-full text-notesEditor rounded-md p-2 bg-inherit transition duration-1000 ease", - !isUpToDate(notes, editor.getJSON()) && "bg-destructive/10" + showSaveStatus && + !isUpToDate(notes, editor.getJSON()) && + "bg-destructive/10" ), }, }, @@ -130,6 +134,11 @@ const NotesWriter: FC = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [editor?.getJSON(), notes]); + useEffect(() => { + if (editor?.isActive && autoFocus && !editor?.isFocused) + editor.commands.focus(); + }, [editor?.commands, editor?.isActive, editor?.isFocused, autoFocus]); + return ; }; diff --git a/docs/releases/next.md b/docs/releases/next.md index da532fc42..81e1baec6 100644 --- a/docs/releases/next.md +++ b/docs/releases/next.md @@ -2,3 +2,4 @@ - Bei Suchen nach Personen, Accounts und Projekten musste auf Groß- und Kleinschreibung geachtet werden. Das ist nun nicht mehr so. - Actions mit Bullet Points sahen in der Vorschau bei einem geschlossenen Accordion etwas komisch aus und waren mit doppelten Kommas durchsät. Das ist jetzt nicht mehr so. +- Das Erstellen eines neuen Eintrags in der Inbox ist in das Navigationsmenü und in den Header verlegt worden. diff --git a/helpers/keyboard-events/main-layout.ts b/helpers/keyboard-events/main-layout.ts index 379fe718b..c889472ef 100644 --- a/helpers/keyboard-events/main-layout.ts +++ b/helpers/keyboard-events/main-layout.ts @@ -4,7 +4,8 @@ import { NextRouter } from "next/router"; export const addKeyDownListener = ( router: NextRouter, setContext: (context: Context) => void, - toggleNavMenu: () => void + toggleNavMenu: () => void, + openCreateInboxItemDialog: () => void ) => { const handleKeyDown = (event: KeyboardEvent) => { if (event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) { @@ -18,7 +19,7 @@ export const addKeyDownListener = ( w: () => setContext("work"), h: () => setContext("hobby"), f: () => setContext("family"), - k: () => console.log("Focus search bar"), + "+": openCreateInboxItemDialog, }[event.key.toLowerCase()]; if (func) { func(); diff --git a/pages/inbox/index.tsx b/pages/inbox/index.tsx index 0b63e1033..5eb9634e3 100644 --- a/pages/inbox/index.tsx +++ b/pages/inbox/index.tsx @@ -1,15 +1,16 @@ import useInbox from "@/api/useInbox"; +import { useCreateInboxItemContext } from "@/components/inbox/CreateInboxItemDialog"; import WorkFlowItem from "@/components/inbox/WorkflowItem"; import MainLayout from "@/components/layouts/MainLayout"; import ContextSwitcher from "@/components/navigation-menu/ContextSwitcher"; import ToProcessItem from "@/components/ui-elements/list-items/to-process-item"; -import NotesWriter, { +import { EditorJsonContent, SerializerOutput, } from "@/components/ui-elements/notes-writer/NotesWriter"; import { Button } from "@/components/ui/button"; import { debounce } from "lodash"; -import { useState } from "react"; +import { Plus } from "lucide-react"; import { GrCycle } from "react-icons/gr"; type ApiResponse = Promise; @@ -28,42 +29,24 @@ export const debouncedOnChangeInboxNote = debounce( 1500 ); -const InboxPage = () => { - const [newItem, setNewItem] = useState(""); - const [newItemKey, setNewItemKey] = useState(crypto.randomUUID()); - - const { inbox, createInbox } = useInbox(); +const CreateItemButton = () => { + const { open } = useCreateInboxItemContext(); + return ( +
+ +
+ ); +}; - const onSubmit = async (item: EditorJsonContent) => { - const data = await createInbox(item); - if (!data) return; - setNewItem(""); - setNewItemKey(crypto.randomUUID()); - }; +const InboxPage = () => { + const { inbox } = useInbox(); return ( -
- setNewItem(s().json)} - placeholder="What's on your mind?" - onSubmit={onSubmit} - /> - } - /> - -
+