From cf4f7f542e6e6236328cfd9fda5946acd25796f9 Mon Sep 17 00:00:00 2001 From: adipol1359 Date: Mon, 16 Jan 2023 19:53:30 +0100 Subject: [PATCH 01/11] feat: add page to allow user to change his password/email/name --- src/components/common/Input.tsx | 2 +- .../dashboard/generalTab/GeneralTab.tsx | 35 +++++++++++ .../dashboard/generalTab/UserSettingsRow.tsx | 59 +++++++++++++++++++ .../dashboard/settingsRow/Action.tsx | 16 +++++ .../dashboard/settingsRow/SettingsRow.tsx | 27 +++++++++ src/components/dashboard/tabs/Panel.tsx | 10 ++++ src/components/dashboard/tabs/Tabs.tsx | 56 ++++++++++++++++++ src/views/dashboard/settings/Settings.tsx | 28 ++++++++- 8 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 src/components/dashboard/generalTab/GeneralTab.tsx create mode 100644 src/components/dashboard/generalTab/UserSettingsRow.tsx create mode 100644 src/components/dashboard/settingsRow/Action.tsx create mode 100644 src/components/dashboard/settingsRow/SettingsRow.tsx create mode 100644 src/components/dashboard/tabs/Panel.tsx create mode 100644 src/components/dashboard/tabs/Tabs.tsx diff --git a/src/components/common/Input.tsx b/src/components/common/Input.tsx index 5d420b90..951225c0 100644 --- a/src/components/common/Input.tsx +++ b/src/components/common/Input.tsx @@ -13,7 +13,7 @@ export const Input = forwardRef< -
+
{ + const { data } = useSession(); + + return ( +
+
+

Profile

+

+ This information will be displayed publicly so be careful what you + share. +

+
+ + {data?.user && ( +
    + + + +
+ )} +
+ ); +}; diff --git a/src/components/dashboard/generalTab/UserSettingsRow.tsx b/src/components/dashboard/generalTab/UserSettingsRow.tsx new file mode 100644 index 00000000..97133d5c --- /dev/null +++ b/src/components/dashboard/generalTab/UserSettingsRow.tsx @@ -0,0 +1,59 @@ +import { useState } from "react"; + +import { Button } from "../../common/Button"; +import { Input } from "../../common/Input"; +import { SettingsRow } from "../settingsRow/SettingsRow"; + +import type { HTMLInputTypeAttribute } from "react"; + +interface UserSettingsRowProps { + readonly name: string; + readonly type: HTMLInputTypeAttribute; + readonly initValue?: string; +} + +export const UserSettingsRow = ({ + name, + type, + initValue, +}: UserSettingsRowProps) => { + const [value, setValue] = useState(initValue); + const [editMode, setEditMode] = useState(false); + + const handleSaveButtonClick = () => { + console.log("saving..."); + setEditMode(false); + }; + + return ( + + {editMode ? ( + setValue(event.target.value)} + /> + ) : ( + value + )} + + } + rightSection={ + <> + {editMode ? ( + + ) : ( + setEditMode(true)}> + Edit + + )} + + } + /> + ); +}; diff --git a/src/components/dashboard/settingsRow/Action.tsx b/src/components/dashboard/settingsRow/Action.tsx new file mode 100644 index 00000000..89f27cf0 --- /dev/null +++ b/src/components/dashboard/settingsRow/Action.tsx @@ -0,0 +1,16 @@ +import type { ReactNode } from "react"; + +interface ActionProps { + readonly onClick?: () => void; + readonly children: ReactNode; +} + +export const Action = ({ onClick, children }: ActionProps) => ( + +); diff --git a/src/components/dashboard/settingsRow/SettingsRow.tsx b/src/components/dashboard/settingsRow/SettingsRow.tsx new file mode 100644 index 00000000..3aeccf81 --- /dev/null +++ b/src/components/dashboard/settingsRow/SettingsRow.tsx @@ -0,0 +1,27 @@ +import { Action } from "./Action"; + +import type { ReactNode } from "react"; + +interface SettingsRowProps { + readonly leftSection?: ReactNode; + readonly middleSection?: ReactNode; + readonly rightSection?: ReactNode; +} + +export const SettingsRow = ({ + leftSection, + middleSection, + rightSection, +}: SettingsRowProps) => ( +
  • +
    + {leftSection} +
    +
    +
    {middleSection}
    +
    {rightSection}
    +
    +
  • +); + +SettingsRow.Action = Action; diff --git a/src/components/dashboard/tabs/Panel.tsx b/src/components/dashboard/tabs/Panel.tsx new file mode 100644 index 00000000..49b62198 --- /dev/null +++ b/src/components/dashboard/tabs/Panel.tsx @@ -0,0 +1,10 @@ +import type { ReactNode } from "react"; + +interface PanelProps { + readonly index: number; + readonly value: number; + readonly children: ReactNode; +} + +export const Panel = ({ index, value, children }: PanelProps) => + index === value ? <>{children} : null; diff --git a/src/components/dashboard/tabs/Tabs.tsx b/src/components/dashboard/tabs/Tabs.tsx new file mode 100644 index 00000000..f611c20a --- /dev/null +++ b/src/components/dashboard/tabs/Tabs.tsx @@ -0,0 +1,56 @@ +import { twMerge } from "tailwind-merge"; + +import { Panel } from "./Panel"; + +import type { ChangeEvent } from "react"; + +interface TabsProps { + readonly labels: string[]; + readonly index: number; + readonly onChange: (index: number) => void; +} + +export const Tabs = ({ labels, index, onChange }: TabsProps) => { + const handleSelectChange = (event: ChangeEvent) => { + const value = Number(event.target.value); + + if (!Number.isNaN(Number(value))) { + onChange(value); + } + }; + + return ( + <> + +
      + {labels.map((label, i) => ( +
    • onChange(i)} + > + {label} +
    • + ))} +
    + + ); +}; + +Tabs.Panel = Panel; diff --git a/src/views/dashboard/settings/Settings.tsx b/src/views/dashboard/settings/Settings.tsx index c15c4c9d..983d0a2f 100644 --- a/src/views/dashboard/settings/Settings.tsx +++ b/src/views/dashboard/settings/Settings.tsx @@ -1,7 +1,29 @@ +import { useState } from "react"; + +import { GeneralTab } from "../../../components/dashboard/generalTab/GeneralTab"; +import { Tabs } from "../../../components/dashboard/tabs/Tabs"; + +const tabs = [{ label: "General", panel: }]; + +const labels = tabs.map(({ label }) => label); +const panels = tabs.map(({ panel }) => panel); + export const SettingsView = () => { + const [activeTab, setActiveTab] = useState(0); + return ( -
    -

    Settings view coming soon...

    -
    +
    +

    + Settings +

    + + + + {panels.map((panel, index) => ( + + {panel} + + ))} +
    ); }; From 9244bbf3614ee2993b51b0a76a6a2d17a8c8ecd1 Mon Sep 17 00:00:00 2001 From: adipol1359 Date: Mon, 16 Jan 2023 20:00:22 +0100 Subject: [PATCH 02/11] refactor: remove double value cast to number --- src/components/dashboard/tabs/Tabs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/dashboard/tabs/Tabs.tsx b/src/components/dashboard/tabs/Tabs.tsx index f611c20a..0a477103 100644 --- a/src/components/dashboard/tabs/Tabs.tsx +++ b/src/components/dashboard/tabs/Tabs.tsx @@ -14,7 +14,7 @@ export const Tabs = ({ labels, index, onChange }: TabsProps) => { const handleSelectChange = (event: ChangeEvent) => { const value = Number(event.target.value); - if (!Number.isNaN(Number(value))) { + if (!Number.isNaN(value)) { onChange(value); } }; From af87779aa0d4317f3ca5f7b02004d06b6be82075 Mon Sep 17 00:00:00 2001 From: adipol1359 Date: Tue, 17 Jan 2023 19:12:23 +0100 Subject: [PATCH 03/11] feat: add heading component --- .../dashboard/generalTab/GeneralTab.tsx | 4 ++- src/components/dashboard/heading/Heading.tsx | 32 +++++++++++++++++++ src/views/dashboard/Home.tsx | 9 +++--- src/views/dashboard/device/Device.tsx | 5 ++- src/views/dashboard/feeds/Feeds.tsx | 5 ++- src/views/dashboard/settings/Settings.tsx | 5 +-- 6 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 src/components/dashboard/heading/Heading.tsx diff --git a/src/components/dashboard/generalTab/GeneralTab.tsx b/src/components/dashboard/generalTab/GeneralTab.tsx index 152c41b6..335bd006 100644 --- a/src/components/dashboard/generalTab/GeneralTab.tsx +++ b/src/components/dashboard/generalTab/GeneralTab.tsx @@ -1,5 +1,7 @@ import { useSession } from "next-auth/react"; +import { Heading } from "../heading/Heading"; + import { UserSettingsRow } from "./UserSettingsRow"; export const GeneralTab = () => { @@ -8,7 +10,7 @@ export const GeneralTab = () => { return (
    -

    Profile

    + Profile

    This information will be displayed publicly so be careful what you share. diff --git a/src/components/dashboard/heading/Heading.tsx b/src/components/dashboard/heading/Heading.tsx new file mode 100644 index 00000000..1fde97b0 --- /dev/null +++ b/src/components/dashboard/heading/Heading.tsx @@ -0,0 +1,32 @@ +import { twMerge } from "tailwind-merge"; + +import type { HTMLAttributes } from "react"; + +const variants = { + 1: "text-2xl", + 2: "text-xl", + 3: "text-lg", + 4: "text-base", + 5: "text-sm", + 6: "text-xs", +}; + +type HeadingProps = Readonly<{ + level?: keyof typeof variants; +}> & + HTMLAttributes; + +export const Heading = ({ level = 1, className, ...props }: HeadingProps) => { + const Tag = `h${level}` as const; + + return ( + + ); +}; diff --git a/src/views/dashboard/Home.tsx b/src/views/dashboard/Home.tsx index 03889938..12ef2657 100644 --- a/src/views/dashboard/Home.tsx +++ b/src/views/dashboard/Home.tsx @@ -6,6 +6,7 @@ import EmptySyncsIcon from "public/svg/empty-syncs.svg"; import { Button } from "../../components/common/Button"; import { Empty } from "../../components/common/Empty"; +import { Heading } from "../../components/dashboard/heading/Heading"; import { Profile } from "../../components/dashboard/profile/Profile"; import { SyncsList } from "../../components/dashboard/sync/SyncsList"; import { Tile } from "../../components/dashboard/tile/Tile"; @@ -127,9 +128,7 @@ export const HomeView = () => {

    -

    - Overview -

    + Overview
    {DASHBOARD_CARDS.map((card, index) => ( {
    -

    + Recent syncs -

    + {syncs.length ? (
    { onAdd={onAdd} />
    -

    - Your device -

    + Your device {device ? (
    diff --git a/src/views/dashboard/feeds/Feeds.tsx b/src/views/dashboard/feeds/Feeds.tsx index 4cc630d4..ca351d5f 100644 --- a/src/views/dashboard/feeds/Feeds.tsx +++ b/src/views/dashboard/feeds/Feeds.tsx @@ -6,6 +6,7 @@ import EmptyFeedsIcon from "public/svg/empty-feeds.svg"; import { Button } from "../../../components/common/Button"; import { Empty } from "../../../components/common/Empty"; +import { Heading } from "../../../components/dashboard/heading/Heading"; import { AddFeedModal } from "../../../components/modal/feed/AddFeedModal"; import { FeedTile } from "../../../components/tile/feedTile/FeedTile"; import { useGenericLoader } from "../../../hooks/useGenericLoader"; @@ -69,9 +70,7 @@ export const FeedsView = () => { />
    -

    - All feeds -

    + All feeds diff --git a/src/views/dashboard/settings/Settings.tsx b/src/views/dashboard/settings/Settings.tsx index 983d0a2f..f58b75c0 100644 --- a/src/views/dashboard/settings/Settings.tsx +++ b/src/views/dashboard/settings/Settings.tsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { GeneralTab } from "../../../components/dashboard/generalTab/GeneralTab"; +import { Heading } from "../../../components/dashboard/heading/Heading"; import { Tabs } from "../../../components/dashboard/tabs/Tabs"; const tabs = [{ label: "General", panel: }]; @@ -13,9 +14,9 @@ export const SettingsView = () => { return (
    -

    + Settings -

    + From 020018b47ba9215dd969c2276fca45ba07fed809 Mon Sep 17 00:00:00 2001 From: adipol1359 Date: Fri, 20 Jan 2023 19:00:00 +0100 Subject: [PATCH 04/11] refactor: settings row refactor --- src/components/common/Button.tsx | 35 +++------ .../dashboard/generalTab/GeneralTab.tsx | 30 +++++--- .../dashboard/generalTab/UserSettingsRow.tsx | 59 --------------- .../dashboard/settingsRow/Action.tsx | 16 ---- .../dashboard/settingsRow/SettingsRow.tsx | 75 ++++++++++++++----- src/components/dashboard/sync/SyncsList.tsx | 6 +- .../modal/device/AddDeviceModal.tsx | 4 +- src/components/modal/feed/AddFeedModal.tsx | 4 +- src/components/tile/deviceTile/DeviceTile.tsx | 4 +- src/views/dashboard/Home.tsx | 4 +- 10 files changed, 96 insertions(+), 141 deletions(-) delete mode 100644 src/components/dashboard/generalTab/UserSettingsRow.tsx delete mode 100644 src/components/dashboard/settingsRow/Action.tsx diff --git a/src/components/common/Button.tsx b/src/components/common/Button.tsx index f4b2751d..c369d5b4 100644 --- a/src/components/common/Button.tsx +++ b/src/components/common/Button.tsx @@ -1,42 +1,29 @@ import { forwardRef } from "react"; import { twMerge } from "tailwind-merge"; -const variants = [ - { - name: "primary", - className: - "border-transparent bg-indigo-600 text-white enabled:hover:bg-indigo-700", - }, - { - name: "secondary", - className: - "border-gray-300 bg-white text-gray-700 enabled:hover:bg-gray-50", - }, - { - name: "danger", - className: - "border-transparent bg-red-600 text-white enabled:hover:bg-red-700 focus:ring-red-500", - }, -] as const; +export enum BUTTON_VARIANT { + PRIMARY = "border-transparent bg-indigo-600 text-white enabled:hover:bg-indigo-700", + SECONDARY = "border-gray-300 bg-white text-gray-700 enabled:hover:bg-gray-50", + DANGER = "border-transparent bg-red-600 text-white enabled:hover:bg-red-700 focus:ring-red-500", + TEXT = "rounded-md bg-gray-100 font-medium text-indigo-600 hover:text-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:ring-offset-gray-100", +} export const Button = forwardRef< HTMLButtonElement, JSX.IntrinsicElements["button"] & { - variant?: typeof variants[number]["name"]; + variant?: BUTTON_VARIANT; } ->(({ variant = "primary", children, ...props }, ref) => { +>(({ variant = BUTTON_VARIANT.PRIMARY, ...props }, ref) => { return ( + {...props} + /> ); }); diff --git a/src/components/dashboard/generalTab/GeneralTab.tsx b/src/components/dashboard/generalTab/GeneralTab.tsx index 335bd006..2b164ee8 100644 --- a/src/components/dashboard/generalTab/GeneralTab.tsx +++ b/src/components/dashboard/generalTab/GeneralTab.tsx @@ -1,12 +1,15 @@ import { useSession } from "next-auth/react"; import { Heading } from "../heading/Heading"; - -import { UserSettingsRow } from "./UserSettingsRow"; +import { SettingsRow } from "../settingsRow/SettingsRow"; export const GeneralTab = () => { const { data } = useSession(); + const handleRowChange = (content: string) => { + console.log("saving...", content); + }; + return (
    @@ -19,17 +22,22 @@ export const GeneralTab = () => { {data?.user && (
      - + - -
    )}
    diff --git a/src/components/dashboard/generalTab/UserSettingsRow.tsx b/src/components/dashboard/generalTab/UserSettingsRow.tsx deleted file mode 100644 index 97133d5c..00000000 --- a/src/components/dashboard/generalTab/UserSettingsRow.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { useState } from "react"; - -import { Button } from "../../common/Button"; -import { Input } from "../../common/Input"; -import { SettingsRow } from "../settingsRow/SettingsRow"; - -import type { HTMLInputTypeAttribute } from "react"; - -interface UserSettingsRowProps { - readonly name: string; - readonly type: HTMLInputTypeAttribute; - readonly initValue?: string; -} - -export const UserSettingsRow = ({ - name, - type, - initValue, -}: UserSettingsRowProps) => { - const [value, setValue] = useState(initValue); - const [editMode, setEditMode] = useState(false); - - const handleSaveButtonClick = () => { - console.log("saving..."); - setEditMode(false); - }; - - return ( - - {editMode ? ( - setValue(event.target.value)} - /> - ) : ( - value - )} - - } - rightSection={ - <> - {editMode ? ( - - ) : ( - setEditMode(true)}> - Edit - - )} - - } - /> - ); -}; diff --git a/src/components/dashboard/settingsRow/Action.tsx b/src/components/dashboard/settingsRow/Action.tsx deleted file mode 100644 index 89f27cf0..00000000 --- a/src/components/dashboard/settingsRow/Action.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { ReactNode } from "react"; - -interface ActionProps { - readonly onClick?: () => void; - readonly children: ReactNode; -} - -export const Action = ({ onClick, children }: ActionProps) => ( - -); diff --git a/src/components/dashboard/settingsRow/SettingsRow.tsx b/src/components/dashboard/settingsRow/SettingsRow.tsx index 3aeccf81..a8946fa0 100644 --- a/src/components/dashboard/settingsRow/SettingsRow.tsx +++ b/src/components/dashboard/settingsRow/SettingsRow.tsx @@ -1,27 +1,62 @@ -import { Action } from "./Action"; +import { useState } from "react"; -import type { ReactNode } from "react"; +import { Button, BUTTON_VARIANT } from "../../common/Button"; +import { Input } from "../../common/Input"; + +import type { HTMLInputTypeAttribute } from "react"; interface SettingsRowProps { - readonly leftSection?: ReactNode; - readonly middleSection?: ReactNode; - readonly rightSection?: ReactNode; + readonly label: string; + readonly content?: string; + readonly contentType?: HTMLInputTypeAttribute; + readonly onChange: (content: string) => void; } export const SettingsRow = ({ - leftSection, - middleSection, - rightSection, -}: SettingsRowProps) => ( -
  • -
    - {leftSection} -
    -
    -
    {middleSection}
    -
    {rightSection}
    -
    -
  • -); + label, + content, + contentType, + onChange, +}: SettingsRowProps) => { + const [value, setValue] = useState(content ?? ""); + const [isEditMode, setIsEditMode] = useState(false); + + const handleButtonClick = () => { + onChange(value); + setIsEditMode(false); + }; -SettingsRow.Action = Action; + return ( +
  • +
    {label}
    +
    +
    + {isEditMode ? ( + setValue(event.target.value)} + /> + ) : ( + content + )} +
    +
    + {isEditMode ? ( + + ) : ( + + )} +
    +
    +
  • + ); +}; diff --git a/src/components/dashboard/sync/SyncsList.tsx b/src/components/dashboard/sync/SyncsList.tsx index c40736a1..37963cc9 100644 --- a/src/components/dashboard/sync/SyncsList.tsx +++ b/src/components/dashboard/sync/SyncsList.tsx @@ -1,6 +1,6 @@ import { memo } from "react"; -import { Button } from "../../common/Button"; +import { Button, BUTTON_VARIANT } from "../../common/Button"; import { SyncItem } from "./SyncItem"; @@ -111,7 +111,7 @@ export const SyncsList = memo(
    diff --git a/src/components/modal/feed/AddFeedModal.tsx b/src/components/modal/feed/AddFeedModal.tsx index e8b202d2..3c640970 100644 --- a/src/components/modal/feed/AddFeedModal.tsx +++ b/src/components/modal/feed/AddFeedModal.tsx @@ -6,7 +6,7 @@ import { useForm } from "react-hook-form"; import { onPromise } from "../../../utils/functions"; import { createFeedSchema } from "../../../utils/validation"; -import { Button } from "../../common/Button"; +import { Button, BUTTON_VARIANT } from "../../common/Button"; import { Input } from "../../common/Input"; import type { CreateFeedInput } from "../../../utils/validation"; @@ -100,7 +100,7 @@ export const AddFeedModal = memo( diff --git a/src/components/tile/deviceTile/DeviceTile.tsx b/src/components/tile/deviceTile/DeviceTile.tsx index 9fb6c69b..d69f6a19 100644 --- a/src/components/tile/deviceTile/DeviceTile.tsx +++ b/src/components/tile/deviceTile/DeviceTile.tsx @@ -4,7 +4,7 @@ import { memo } from "react"; import RemarkableIcon from "public/svg/remarkable.svg"; import { onPromise } from "../../../utils/functions"; -import { Button } from "../../common/Button"; +import { Button, BUTTON_VARIANT } from "../../common/Button"; import type { Device } from "@prisma/client"; @@ -33,7 +33,7 @@ export const DeviceTile = memo(({ device, onDelete }) => { diff --git a/src/views/dashboard/Home.tsx b/src/views/dashboard/Home.tsx index 12ef2657..f1a2eac9 100644 --- a/src/views/dashboard/Home.tsx +++ b/src/views/dashboard/Home.tsx @@ -4,7 +4,7 @@ import { toast } from "react-hot-toast"; import EmptySyncsIcon from "public/svg/empty-syncs.svg"; -import { Button } from "../../components/common/Button"; +import { Button, BUTTON_VARIANT } from "../../components/common/Button"; import { Empty } from "../../components/common/Empty"; import { Heading } from "../../components/dashboard/heading/Heading"; import { Profile } from "../../components/dashboard/profile/Profile"; @@ -115,7 +115,7 @@ export const HomeView = () => {
    From 44e6c5a485d6adf6f660685816dbc4e85285fb9b Mon Sep 17 00:00:00 2001 From: adipol1359 Date: Sun, 22 Jan 2023 13:03:19 +0100 Subject: [PATCH 06/11] feat: add form to settings row --- src/components/common/Button.tsx | 8 +-- .../dashboard/generalTab/GeneralTab.tsx | 9 +++ .../dashboard/settingsRow/RowForm.tsx | 70 +++++++++++++++++++ .../dashboard/settingsRow/SettingsRow.tsx | 57 ++++++++------- src/hooks/useYupForm.ts | 29 ++++++++ src/utils/validation.ts | 17 +++++ 6 files changed, 159 insertions(+), 31 deletions(-) create mode 100644 src/components/dashboard/settingsRow/RowForm.tsx create mode 100644 src/hooks/useYupForm.ts diff --git a/src/components/common/Button.tsx b/src/components/common/Button.tsx index c369d5b4..d619afd4 100644 --- a/src/components/common/Button.tsx +++ b/src/components/common/Button.tsx @@ -5,7 +5,7 @@ export enum BUTTON_VARIANT { PRIMARY = "border-transparent bg-indigo-600 text-white enabled:hover:bg-indigo-700", SECONDARY = "border-gray-300 bg-white text-gray-700 enabled:hover:bg-gray-50", DANGER = "border-transparent bg-red-600 text-white enabled:hover:bg-red-700 focus:ring-red-500", - TEXT = "rounded-md bg-gray-100 font-medium text-indigo-600 hover:text-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:ring-offset-gray-100", + TEXT = "rounded-md border-none bg-gray-100 font-medium text-indigo-600 shadow-none hover:text-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:ring-offset-gray-100", } export const Button = forwardRef< @@ -13,15 +13,15 @@ export const Button = forwardRef< JSX.IntrinsicElements["button"] & { variant?: BUTTON_VARIANT; } ->(({ variant = BUTTON_VARIANT.PRIMARY, ...props }, ref) => { +>(({ variant = BUTTON_VARIANT.PRIMARY, className, ...props }, ref) => { return ( + +
    + + ); +}; diff --git a/src/components/dashboard/settingsRow/SettingsRow.tsx b/src/components/dashboard/settingsRow/SettingsRow.tsx index a8946fa0..0a8d5ad0 100644 --- a/src/components/dashboard/settingsRow/SettingsRow.tsx +++ b/src/components/dashboard/settingsRow/SettingsRow.tsx @@ -1,52 +1,55 @@ import { useState } from "react"; import { Button, BUTTON_VARIANT } from "../../common/Button"; -import { Input } from "../../common/Input"; + +import { RowForm } from "./RowForm"; import type { HTMLInputTypeAttribute } from "react"; +import type { ZodObject, ZodString } from "zod"; + +export const SECTIONS_WRAPPER_STYLES = + "mt-1 flex items-start sm:col-span-2 sm:mt-0 sm:items-center"; +export const LEFT_SECTION_STYLES = "grow text-gray-800"; +export const RIGHT_SECTION_STYLES = + "ml-2.5 flex h-full flex-shrink-0 flex-col items-stretch gap-1 font-medium sm:flex-row sm:items-center"; interface SettingsRowProps { readonly label: string; + readonly schema: ZodObject<{ content: ZodString }>; + readonly contentType: HTMLInputTypeAttribute; readonly content?: string; - readonly contentType?: HTMLInputTypeAttribute; readonly onChange: (content: string) => void; } export const SettingsRow = ({ label, - content, + schema, contentType, + content, onChange, }: SettingsRowProps) => { - const [value, setValue] = useState(content ?? ""); const [isEditMode, setIsEditMode] = useState(false); - const handleButtonClick = () => { - onChange(value); + const handleFormSubmit = (content: string) => { + onChange(content); setIsEditMode(false); }; return ( -
  • +
  • {label}
    -
    -
    - {isEditMode ? ( - setValue(event.target.value)} - /> - ) : ( - content - )} -
    -
    - {isEditMode ? ( - - ) : ( + {isEditMode ? ( + setIsEditMode(false)} + /> + ) : ( +
    +
    {content}
    +
    - )} +
    -
    + )}
  • ); }; diff --git a/src/hooks/useYupForm.ts b/src/hooks/useYupForm.ts new file mode 100644 index 00000000..0163537e --- /dev/null +++ b/src/hooks/useYupForm.ts @@ -0,0 +1,29 @@ +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; + +import type { + SubmitHandler, + SubmitErrorHandler, + UseFormProps, +} from "react-hook-form"; +import type { ZodType, TypeOf } from "zod"; + +interface Options { + onSubmit: SubmitHandler>; + onInvalid?: SubmitErrorHandler>; +} + +export const useYupForm = ( + schema: T, + { onSubmit, onInvalid }: Options, + props?: Omit>, "resolver">, +) => { + const { handleSubmit, ...rest } = useForm>({ + resolver: zodResolver(schema), + ...props, + }); + + const handleFormSubmit = handleSubmit(onSubmit, onInvalid); + + return { handleFormSubmit, ...rest }; +}; diff --git a/src/utils/validation.ts b/src/utils/validation.ts index eb279e78..da491bbd 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -67,6 +67,23 @@ export const unregisterAndDisconnectDeviceSchema = z.object({ email: z.string().email(), }); +export const updateUserNameSchema = z.object({ + content: z.string().min(1, "Name is required."), +}); + +export const updateUserEmailSchema = z.object({ + content: z.string().email("Email must be a valid email."), +}); + +export const updateUserPasswordSchema = z.object({ + content: z + .string() + .regex( + PASSWORD_REGEX, + "Password must contain an uppercase letter, a special character, a number and must be at least 8 characters long.", + ), +}); + export type RegisterUserInput = TypeOf; export type LoginUserInput = TypeOf; export type CreateFeedInput = TypeOf; From a85ae885781624137874a6710a43e200d678415d Mon Sep 17 00:00:00 2001 From: adipol1359 Date: Sun, 22 Jan 2023 13:37:11 +0100 Subject: [PATCH 07/11] refactor: add settings layout --- .../settingsLayout/SettingsLayout.tsx | 26 ++++++++ src/components/dashboard/tabs/Tabs.tsx | 46 +++++++------ src/pages/dashboard/settings.tsx | 14 ---- src/pages/dashboard/settings/index.tsx | 17 +++++ src/views/dashboard/settings/Settings.tsx | 64 +++++++++++++------ 5 files changed, 114 insertions(+), 53 deletions(-) create mode 100644 src/components/dashboard/settingsLayout/SettingsLayout.tsx delete mode 100644 src/pages/dashboard/settings.tsx create mode 100644 src/pages/dashboard/settings/index.tsx diff --git a/src/components/dashboard/settingsLayout/SettingsLayout.tsx b/src/components/dashboard/settingsLayout/SettingsLayout.tsx new file mode 100644 index 00000000..d7e06a8b --- /dev/null +++ b/src/components/dashboard/settingsLayout/SettingsLayout.tsx @@ -0,0 +1,26 @@ +import { Heading } from "../heading/Heading"; +import { Tabs } from "../tabs/Tabs"; + +import type { ReactNode } from "react"; + +const tabs = [ + { + label: "General", + pathname: "/dashboard/settings", + }, +]; + +interface SettingsLayoutProps { + readonly title: string; + readonly children: ReactNode; +} + +export const SettingsLayout = ({ title, children }: SettingsLayoutProps) => ( +
    + + {title} + + + {children} +
    +); diff --git a/src/components/dashboard/tabs/Tabs.tsx b/src/components/dashboard/tabs/Tabs.tsx index 0a477103..f88d4daf 100644 --- a/src/components/dashboard/tabs/Tabs.tsx +++ b/src/components/dashboard/tabs/Tabs.tsx @@ -1,21 +1,29 @@ +import Link from "next/link"; +import { usePathname, useRouter } from "next/navigation"; import { twMerge } from "tailwind-merge"; import { Panel } from "./Panel"; import type { ChangeEvent } from "react"; +interface Tab { + readonly label: string; + readonly pathname: string; +} + interface TabsProps { - readonly labels: string[]; - readonly index: number; - readonly onChange: (index: number) => void; + readonly tabs: Tab[]; } -export const Tabs = ({ labels, index, onChange }: TabsProps) => { +export const Tabs = ({ tabs }: TabsProps) => { + const router = useRouter(); + const pathname = usePathname(); + const handleSelectChange = (event: ChangeEvent) => { - const value = Number(event.target.value); + const tab = tabs.find(({ pathname }) => pathname === event.target.value); - if (!Number.isNaN(value)) { - onChange(value); + if (tab) { + router.push(tab.pathname); } }; @@ -24,31 +32,31 @@ export const Tabs = ({ labels, index, onChange }: TabsProps) => { -
      - {labels.map((label, i) => ( -
    • + {tabs.map((tab) => ( + onChange(i)} > - {label} -
    • + {tab.label} + ))} -
    + ); }; diff --git a/src/pages/dashboard/settings.tsx b/src/pages/dashboard/settings.tsx deleted file mode 100644 index e4366cfb..00000000 --- a/src/pages/dashboard/settings.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { DashboardLayout } from "../../components/dashboard/layout/Layout"; -import { SettingsView } from "../../views/dashboard/settings/Settings"; - -import type { NextPage } from "next"; - -const Settings: NextPage = () => { - return ( - - - - ); -}; - -export default Settings; diff --git a/src/pages/dashboard/settings/index.tsx b/src/pages/dashboard/settings/index.tsx new file mode 100644 index 00000000..c448bbc2 --- /dev/null +++ b/src/pages/dashboard/settings/index.tsx @@ -0,0 +1,17 @@ +import { DashboardLayout } from "../../../components/dashboard/layout/Layout"; +import { SettingsLayout } from "../../../components/dashboard/settingsLayout/SettingsLayout"; +import { SettingsView } from "../../../views/dashboard/settings/Settings"; + +import type { NextPage } from "next"; + +const Settings: NextPage = () => { + return ( + + + + + + ); +}; + +export default Settings; diff --git a/src/views/dashboard/settings/Settings.tsx b/src/views/dashboard/settings/Settings.tsx index f58b75c0..1b94db0e 100644 --- a/src/views/dashboard/settings/Settings.tsx +++ b/src/views/dashboard/settings/Settings.tsx @@ -1,30 +1,54 @@ -import { useState } from "react"; +import { useSession } from "next-auth/react"; -import { GeneralTab } from "../../../components/dashboard/generalTab/GeneralTab"; import { Heading } from "../../../components/dashboard/heading/Heading"; -import { Tabs } from "../../../components/dashboard/tabs/Tabs"; - -const tabs = [{ label: "General", panel: }]; - -const labels = tabs.map(({ label }) => label); -const panels = tabs.map(({ panel }) => panel); +import { SettingsRow } from "../../../components/dashboard/settingsRow/SettingsRow"; +import { + updateUserEmailSchema, + updateUserNameSchema, + updateUserPasswordSchema, +} from "../../../utils/validation"; export const SettingsView = () => { - const [activeTab, setActiveTab] = useState(0); + const { data } = useSession(); - return ( -
    - - Settings - + const handleRowChange = (content: string) => { + console.log("saving...", content); + }; - + return ( +
    +
    + Profile +

    + This information will be displayed publicly so be careful what you + share. +

    +
    - {panels.map((panel, index) => ( - - {panel} - - ))} + {data?.user && ( +
      + + + +
    + )}
    ); }; From 7ff47daf54692d2585651f214a5bb7aa0e24a2b2 Mon Sep 17 00:00:00 2001 From: adipol1359 Date: Sun, 22 Jan 2023 13:51:44 +0100 Subject: [PATCH 08/11] fix: button variant not found --- src/components/modal/ConfirmModal.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/modal/ConfirmModal.tsx b/src/components/modal/ConfirmModal.tsx index f89a9167..1f606c34 100644 --- a/src/components/modal/ConfirmModal.tsx +++ b/src/components/modal/ConfirmModal.tsx @@ -1,6 +1,6 @@ import { QuestionMarkCircleIcon } from "@heroicons/react/24/outline"; -import { Button } from "../common/Button"; +import { Button, BUTTON_VARIANT } from "../common/Button"; import { BaseModal } from "./BaseModal"; @@ -27,7 +27,11 @@ export const ConfirmModal = ({

    Are you sure?

    {content}

    -
    - From 7688adb949ad9d9d092a1c0c0610d2ad2981ba58 Mon Sep 17 00:00:00 2001 From: adipol1359 Date: Sun, 22 Jan 2023 14:20:24 +0100 Subject: [PATCH 10/11] refactor: remove redundant components --- .../dashboard/generalTab/GeneralTab.tsx | 54 ------------------- src/components/dashboard/tabs/Panel.tsx | 10 ---- src/components/dashboard/tabs/Tabs.tsx | 4 -- 3 files changed, 68 deletions(-) delete mode 100644 src/components/dashboard/generalTab/GeneralTab.tsx delete mode 100644 src/components/dashboard/tabs/Panel.tsx diff --git a/src/components/dashboard/generalTab/GeneralTab.tsx b/src/components/dashboard/generalTab/GeneralTab.tsx deleted file mode 100644 index 15cdb528..00000000 --- a/src/components/dashboard/generalTab/GeneralTab.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { useSession } from "next-auth/react"; - -import { - updateUserEmailSchema, - updateUserNameSchema, - updateUserPasswordSchema, -} from "../../../utils/validation"; -import { Heading } from "../heading/Heading"; -import { SettingsRow } from "../settingsRow/SettingsRow"; - -export const GeneralTab = () => { - const { data } = useSession(); - - const handleRowChange = (content: string) => { - console.log("saving...", content); - }; - - return ( -
    -
    - Profile -

    - This information will be displayed publicly so be careful what you - share. -

    -
    - - {data?.user && ( -
      - - - -
    - )} -
    - ); -}; diff --git a/src/components/dashboard/tabs/Panel.tsx b/src/components/dashboard/tabs/Panel.tsx deleted file mode 100644 index 49b62198..00000000 --- a/src/components/dashboard/tabs/Panel.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import type { ReactNode } from "react"; - -interface PanelProps { - readonly index: number; - readonly value: number; - readonly children: ReactNode; -} - -export const Panel = ({ index, value, children }: PanelProps) => - index === value ? <>{children} : null; diff --git a/src/components/dashboard/tabs/Tabs.tsx b/src/components/dashboard/tabs/Tabs.tsx index f88d4daf..55710df7 100644 --- a/src/components/dashboard/tabs/Tabs.tsx +++ b/src/components/dashboard/tabs/Tabs.tsx @@ -2,8 +2,6 @@ import Link from "next/link"; import { usePathname, useRouter } from "next/navigation"; import { twMerge } from "tailwind-merge"; -import { Panel } from "./Panel"; - import type { ChangeEvent } from "react"; interface Tab { @@ -60,5 +58,3 @@ export const Tabs = ({ tabs }: TabsProps) => { ); }; - -Tabs.Panel = Panel; From 4bd7a696f21a06a72b8a33ef6bd4222488dd3b8d Mon Sep 17 00:00:00 2001 From: adipol1359 Date: Sun, 22 Jan 2023 14:20:41 +0100 Subject: [PATCH 11/11] refactor: change hook name --- src/components/dashboard/settingsRow/RowForm.tsx | 4 ++-- src/hooks/{useYupForm.ts => useZodForm.ts} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/hooks/{useYupForm.ts => useZodForm.ts} (93%) diff --git a/src/components/dashboard/settingsRow/RowForm.tsx b/src/components/dashboard/settingsRow/RowForm.tsx index 65263649..1eb095f4 100644 --- a/src/components/dashboard/settingsRow/RowForm.tsx +++ b/src/components/dashboard/settingsRow/RowForm.tsx @@ -1,6 +1,6 @@ import { twMerge } from "tailwind-merge"; -import { useYupForm } from "../../../hooks/useYupForm"; +import { useZodForm } from "../../../hooks/useZodForm"; import { onPromise } from "../../../utils/functions"; import { Button } from "../../common/Button"; import { Input } from "../../common/Input"; @@ -33,7 +33,7 @@ export const RowForm = ({ handleFormSubmit, register, formState: { errors }, - } = useYupForm( + } = useZodForm( schema, { onSubmit: ({ content }) => { diff --git a/src/hooks/useYupForm.ts b/src/hooks/useZodForm.ts similarity index 93% rename from src/hooks/useYupForm.ts rename to src/hooks/useZodForm.ts index 0163537e..ac5f1e31 100644 --- a/src/hooks/useYupForm.ts +++ b/src/hooks/useZodForm.ts @@ -13,7 +13,7 @@ interface Options { onInvalid?: SubmitErrorHandler>; } -export const useYupForm = ( +export const useZodForm = ( schema: T, { onSubmit, onInvalid }: Options, props?: Omit>, "resolver">,