diff --git a/src/components/MarkTypeStrategy/FullName/Input.tsx b/src/components/MarkTypeStrategy/FullName/Input.tsx new file mode 100644 index 0000000..7b63ae6 --- /dev/null +++ b/src/components/MarkTypeStrategy/FullName/Input.tsx @@ -0,0 +1,20 @@ +import { useDidMount } from '../../../hooks' +import { useLocalStorage } from '../../../hooks/useLocalStorage' +import { MarkInputProps } from '../MarkStrategy' +import { MarkInputText } from '../Text/Input' + +export const MarkInputFullName = (props: MarkInputProps) => { + const [fullName, setFullName] = useLocalStorage('mark-fullname', '') + useDidMount(() => { + props.handler(fullName) + }) + return MarkInputText({ + ...props, + placeholder: 'Full Name', + value: fullName, + handler: (value) => { + setFullName(value) + props.handler(value) + } + }) +} diff --git a/src/components/MarkTypeStrategy/FullName/index.tsx b/src/components/MarkTypeStrategy/FullName/index.tsx new file mode 100644 index 0000000..1574c42 --- /dev/null +++ b/src/components/MarkTypeStrategy/FullName/index.tsx @@ -0,0 +1,7 @@ +import { MarkStrategy } from '../MarkStrategy' +import { MarkInputFullName } from './Input' + +export const FullNameStrategy: MarkStrategy = { + input: MarkInputFullName, + render: ({ value }) => <>{value} +} diff --git a/src/components/MarkTypeStrategy/MarkStrategy.tsx b/src/components/MarkTypeStrategy/MarkStrategy.tsx index 562302e..f842220 100644 --- a/src/components/MarkTypeStrategy/MarkStrategy.tsx +++ b/src/components/MarkTypeStrategy/MarkStrategy.tsx @@ -2,6 +2,7 @@ import { MarkType } from '../../types/drawing' import { CurrentUserMark, Mark } from '../../types/mark' import { TextStrategy } from './Text' import { SignatureStrategy } from './Signature' +import { FullNameStrategy } from './FullName' export interface MarkInputProps { value: string @@ -28,5 +29,6 @@ export type MarkStrategies = { export const MARK_TYPE_CONFIG: MarkStrategies = { [MarkType.TEXT]: TextStrategy, - [MarkType.SIGNATURE]: SignatureStrategy + [MarkType.SIGNATURE]: SignatureStrategy, + [MarkType.FULLNAME]: FullNameStrategy } diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts new file mode 100644 index 0000000..ec6c9cb --- /dev/null +++ b/src/hooks/useLocalStorage.ts @@ -0,0 +1,64 @@ +import React, { useMemo } from 'react' +import { + getLocalStorageItem, + mergeWithInitialValue, + removeLocalStorageItem, + setLocalStorageItem +} from '../utils' + +const useLocalStorageSubscribe = (callback: () => void) => { + window.addEventListener('storage', callback) + return () => window.removeEventListener('storage', callback) +} + +export function useLocalStorage( + key: string, + initialValue: T +): [T, React.Dispatch>] { + const getSnapshot = () => { + // Get the stored value + const storedValue = getLocalStorageItem(key, initialValue) + + // Parse the value + const parsedStoredValue = JSON.parse(storedValue) + + // Merge the default and the stored in case some of the required fields are missing + return JSON.stringify( + mergeWithInitialValue(parsedStoredValue, initialValue) + ) + } + + const data = React.useSyncExternalStore(useLocalStorageSubscribe, getSnapshot) + + const setState: React.Dispatch> = React.useCallback( + (v: React.SetStateAction) => { + try { + const nextState = + typeof v === 'function' + ? (v as (prevState: T) => T)(JSON.parse(data)) + : v + + if (nextState === undefined || nextState === null) { + removeLocalStorageItem(key) + } else { + setLocalStorageItem(key, JSON.stringify(nextState)) + } + } catch (e) { + console.warn(e) + } + }, + [data, key] + ) + + React.useEffect(() => { + // Set local storage only when it's empty + const data = window.localStorage.getItem(key) + if (data === null) { + setLocalStorageItem(key, JSON.stringify(initialValue)) + } + }, [key, initialValue]) + + const memoized = useMemo(() => JSON.parse(data) as T, [data]) + + return [memoized, setState] +} diff --git a/src/utils/localStorage.ts b/src/utils/localStorage.ts index 8196e35..b0eb381 100644 --- a/src/utils/localStorage.ts +++ b/src/utils/localStorage.ts @@ -42,3 +42,47 @@ export const clear = () => { clearAuthToken() clearState() } + +export function mergeWithInitialValue(storedValue: T, initialValue: T): T { + if ( + !Array.isArray(storedValue) && + typeof storedValue === 'object' && + storedValue !== null + ) { + return { ...initialValue, ...storedValue } + } + return storedValue +} + +export function getLocalStorageItem(key: string, defaultValue: T): string { + try { + const data = window.localStorage.getItem(key) + if (data === null) return JSON.stringify(defaultValue) + return data + } catch (err) { + console.error(`Error while fetching local storage value: `, err) + return JSON.stringify(defaultValue) + } +} + +export function setLocalStorageItem(key: string, value: string) { + try { + window.localStorage.setItem(key, value) + dispatchLocalStorageEvent(key, value) + } catch (err) { + console.error(`Error while saving local storage value: `, err) + } +} + +export function removeLocalStorageItem(key: string) { + try { + window.localStorage.removeItem(key) + dispatchLocalStorageEvent(key, null) + } catch (err) { + console.error(`Error while deleting local storage value: `, err) + } +} + +function dispatchLocalStorageEvent(key: string, newValue: string | null) { + window.dispatchEvent(new StorageEvent('storage', { key, newValue })) +} diff --git a/src/utils/mark.ts b/src/utils/mark.ts index 1868403..319371c 100644 --- a/src/utils/mark.ts +++ b/src/utils/mark.ts @@ -171,8 +171,7 @@ export const DEFAULT_TOOLBOX: DrawTool[] = [ { identifier: MarkType.FULLNAME, icon: faIdCard, - label: 'Full Name', - isComingSoon: true + label: 'Full Name' }, { identifier: MarkType.JOBTITLE,