Skip to content

Commit

Permalink
📩 Store comment draft in local storage per submission (#453)
Browse files Browse the repository at this point in the history
Co-authored-by: nezouse <stankiewicz1998@gmail.com>
  • Loading branch information
ClumsyVlad and nezouse committed May 13, 2024
1 parent 76a2397 commit 2324c6c
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
"use client";

import { useState } from "react";
import { useParams } from "next/navigation";
import { DURATIONS } from "@/constants/durations";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";

import { type ApplicationWithComments } from "@/types/Application";
import { parseApplicationParams } from "@/lib/paramsValidation";
import {
getLocalStorageValue,
LOCAL_STORAGE_KEYS,
LocalStorageKey,
removeLocalStorageItem,
saveToLocalStorage,
} from "@/lib/localStorage";
import { useDebounceCallback } from "@/hooks/useDebounceCallback";
import { Button } from "@/components/ui/button";
import { Editor } from "@/components/ui/editor/editor";
import {
Expand Down Expand Up @@ -39,16 +47,26 @@ export function AddCommentForm({
reviewValidationError,
commentValidationError,
}: AddCommentFormProps) {
const { waveId, applicationId } = parseApplicationParams(useParams());

const [editorKey, setEditorKey] = useState(0);
const commentLocalStorageKey = LOCAL_STORAGE_KEYS.commentData(application.id);

const storedCommentValue = getLocalStorageValue(
z.string(),
commentLocalStorageKey,
"",
);

const form = useForm<AddCommentSchema>({
resolver: zodResolver(addCommentSchema),
defaultValues: {
content: "",
content: storedCommentValue,
},
});

const onDebounce = useDebounceCallback((value: string) => {
saveToLocalStorage(commentLocalStorageKey as LocalStorageKey, value);
}, DURATIONS.commentDebounce);

const handleSubmit = (
action: (payload: AddCommentActionPayload) => Promise<void>,
) =>
Expand All @@ -58,6 +76,7 @@ export function AddCommentForm({
applicationId: application.id,
waveId: application.waveId,
});
removeLocalStorageItem(commentLocalStorageKey as LocalStorageKey);
setEditorKey((prev) => prev + 1);
form.reset();
});
Expand All @@ -71,7 +90,14 @@ export function AddCommentForm({
render={({ field }) => (
<FormItem>
<FormControl>
<Editor onChange={field.onChange} key={editorKey} />
<Editor
initialMarkdown={storedCommentValue}
onChange={(value) => {
field.onChange(value);
onDebounce(value);
}}
key={editorKey}
/>
</FormControl>
<FormMessage />
</FormItem>
Expand Down
6 changes: 6 additions & 0 deletions src/components/ui/editor/config/editorConfig.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { CodeNode } from "@lexical/code";
import { AutoLinkNode, LinkNode } from "@lexical/link";
import { ListItemNode, ListNode } from "@lexical/list";
import { $convertFromMarkdownString, TRANSFORMERS } from "@lexical/markdown";
import type { LexicalComposer } from "@lexical/react/LexicalComposer";
import { HorizontalRuleNode } from "@lexical/react/LexicalHorizontalRuleNode";
import { HeadingNode, QuoteNode } from "@lexical/rich-text";

type InitialConfig = Parameters<typeof LexicalComposer>[0]["initialConfig"];

interface EditorConfigProps {
initialMarkdown: string | undefined;
namespace: string;
}

export const getInitialConfig = ({
initialMarkdown,
namespace,
}: EditorConfigProps): InitialConfig => ({
namespace,
Expand All @@ -29,6 +32,9 @@ export const getInitialConfig = ({
onError(error) {
console.error(error);
},
editorState: initialMarkdown
? () => $convertFromMarkdownString(initialMarkdown, TRANSFORMERS)
: undefined,
nodes: [
HeadingNode,
ListNode,
Expand Down
6 changes: 5 additions & 1 deletion src/components/ui/editor/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,19 @@ import { ToolbarPlugin } from "./plugins/toolbarPlugin";
interface EditorProps {
onChange: (value: string) => void;
placeholder?: string;
initialMarkdown?: string;
}

function Editor({
onChange,
placeholder,
initialMarkdown,
size,
}: EditorProps & VariantProps<typeof editorVariants>) {
return (
<LexicalComposer initialConfig={getInitialConfig({ namespace: "Editor" })}>
<LexicalComposer
initialConfig={getInitialConfig({ initialMarkdown, namespace: "Editor" })}
>
<div className="group rounded-3xl border shadow-sm transition-colors focus-within:border-primary">
<ToolbarPlugin />
<div className="relative">
Expand Down
3 changes: 3 additions & 0 deletions src/constants/durations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const DURATIONS = {
commentDebounce: 3000,
};
26 changes: 24 additions & 2 deletions src/lib/localStorage.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
import { z } from "zod";

import { ApplicationId } from "@/types/Application";

export const LOCAL_STORAGE_KEYS = {
waveStepsData: "waveStepsData",
applicationStepsData: "applicationStepsData",
commentData: (applicationId: ApplicationId) =>
`commentData-${applicationId}` as const,
} as const;

type LocalStorageKey =
(typeof LOCAL_STORAGE_KEYS)[keyof typeof LOCAL_STORAGE_KEYS];
type LocalStorageValueMapping = {
[Key in keyof typeof LOCAL_STORAGE_KEYS]: (typeof LOCAL_STORAGE_KEYS)[Key] extends (
...args: any[]
) => any
? ReturnType<(typeof LOCAL_STORAGE_KEYS)[Key]>
: (typeof LOCAL_STORAGE_KEYS)[Key];
};

export type LocalStorageKey =
LocalStorageValueMapping[keyof LocalStorageValueMapping];

export function getLocalStorageValue<T extends z.ZodTypeAny>(
schema: T,
Expand Down Expand Up @@ -36,3 +48,13 @@ export function saveToLocalStorage(key: LocalStorageKey, value: unknown) {
}
}
}

export function removeLocalStorageItem(key: LocalStorageKey) {
if (typeof localStorage !== "undefined") {
try {
localStorage.removeItem(key);
} catch (error) {
console.error("Error removing from local storage", error);
}
}
}

0 comments on commit 2324c6c

Please sign in to comment.