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: allow user to change his password/email/name #20

Closed
wants to merge 13 commits into from
2 changes: 1 addition & 1 deletion src/components/common/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const Input = forwardRef<
<label className="block text-sm font-medium text-gray-700">
{children}
</label>
<div className="relative mt-1 rounded-md shadow-sm">
<div className="relative rounded-md shadow-sm">
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should check login and register page and other places where input is used, to check if something is illegible

<input
ref={ref}
{...props}
Expand Down
37 changes: 37 additions & 0 deletions src/components/dashboard/generalTab/GeneralTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useSession } from "next-auth/react";

import { Heading } from "../heading/Heading";

import { UserSettingsRow } from "./UserSettingsRow";

export const GeneralTab = () => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tab?

const { data } = useSession();

return (
<section className="mt-8">
<hgroup>
<Heading level={3}>Profile</Heading>
<p className="max-w-2xl text-sm text-gray-500">
This information will be displayed publicly so be careful what you
share.
</p>
</hgroup>

{data?.user && (
<ul className="mt-6 divide-y divide-gray-300 border-y border-gray-300">
<UserSettingsRow
name="Name"
type="text"
initValue={data.user.name ?? ""}
/>
<UserSettingsRow
name="E-mail"
type="email"
initValue={data.user.email}
/>
<UserSettingsRow name="Passowrd" type="password" />
</ul>
)}
</section>
);
};
59 changes: 59 additions & 0 deletions src/components/dashboard/generalTab/UserSettingsRow.tsx
Original file line number Diff line number Diff line change
@@ -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 = ({
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use memo here, please check CONTRIBUTING for component structure

name,
type,
initValue,
}: UserSettingsRowProps) => {
const [value, setValue] = useState(initValue);
const [editMode, setEditMode] = useState(false);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isEditMode or isEditing


const handleSaveButtonClick = () => {
console.log("saving...");
setEditMode(false);
};

return (
<SettingsRow
leftSection={name}
middleSection={
<>
{editMode ? (
<Input
type={type}
value={value}
onChange={(event) => setValue(event.target.value)}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handling form should be done with react-hook-form as with other forms in our app, then we will able to easily use validation

/>
) : (
value
)}
</>
}
rightSection={
<>
{editMode ? (
<Button type="button" onClick={handleSaveButtonClick}>
Save
</Button>
) : (
<SettingsRow.Action onClick={() => setEditMode(true)}>
Edit
</SettingsRow.Action>
)}
</>
}
/>
);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's basically going on here? We should stick with our row structure and modify content from props, but all content and probably it should be determined by isEditing mode flag

};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why everything is in generalTab? I think we should create settings folder for components related to this view specifically

32 changes: 32 additions & 0 deletions src/components/dashboard/heading/Heading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { twMerge } from "tailwind-merge";

import type { HTMLAttributes } from "react";

const variants = {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be enum probably

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;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why level is optional? And why we are passing 1 as default value?

}> &
HTMLAttributes<HTMLHeadingElement>;

export const Heading = ({ level = 1, className, ...props }: HeadingProps) => {
const Tag = `h${level}` as const;

return (
<Tag
className={twMerge(
"font-medium leading-6 text-gray-900",
variants[level],
className,
)}
{...props}
/>
);
};
16 changes: 16 additions & 0 deletions src/components/dashboard/settingsRow/Action.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { ReactNode } from "react";

interface ActionProps {
readonly onClick?: () => void;
readonly children: ReactNode;
}

export const Action = ({ onClick, children }: ActionProps) => (
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we have Button component? Is it different?

<button
type="button"
className="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"
onClick={onClick}
>
{children}
</button>
);
27 changes: 27 additions & 0 deletions src/components/dashboard/settingsRow/SettingsRow.tsx
Original file line number Diff line number Diff line change
@@ -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 = ({
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

put this component in /settings folder

leftSection,
middleSection,
rightSection,
}: SettingsRowProps) => (
<li className="py-4 text-sm sm:grid sm:h-16 sm:grid-cols-3 sm:gap-4 sm:py-0">
<div className="flex items-center font-medium text-gray-500">
{leftSection}
</div>
<div className="mt-1 flex items-center sm:col-span-2 sm:mt-0">
<div className="grow text-gray-800">{middleSection}</div>
<div className="ml-2.5 flex-shrink-0 font-medium">{rightSection}</div>
</div>
</li>
);

SettingsRow.Action = Action;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why that way?

10 changes: 10 additions & 0 deletions src/components/dashboard/tabs/Panel.tsx
Original file line number Diff line number Diff line change
@@ -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) =>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For what we need this component?

index === value ? <>{children}</> : null;
56 changes: 56 additions & 0 deletions src/components/dashboard/tabs/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -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;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why index? We should have something like id

}

export const Tabs = ({ labels, index, onChange }: TabsProps) => {
const handleSelectChange = (event: ChangeEvent<HTMLSelectElement>) => {
const value = Number(event.target.value);

if (!Number.isNaN(value)) {
onChange(value);
}
};

return (
<>
<select
className="block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-indigo-600 focus:outline-none focus:ring-indigo-600 sm:text-sm md:hidden"
aria-label="Select a tab"
value={index}
onChange={handleSelectChange}
>
{labels.map((label, i) => (
<option key={i} value={i}>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't pass index as a key

{label}
</option>
))}
</select>
<ul className="hidden space-x-8 border-b border-gray-200 md:flex">
{labels.map((label, i) => (
<li
key={i}
className={twMerge(
"cursor-pointer whitespace-nowrap border-b-2 py-4 px-1 text-sm font-medium",
i === index
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably we should change active tab based on url param:
/settings -> general active
/settings/notifications -> notifications active etc.

? "border-indigo-600 text-indigo-600"
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700",
)}
onClick={() => onChange(i)}
>
{label}
</li>
))}
</ul>
</>
);
};

Tabs.Panel = Panel;
9 changes: 4 additions & 5 deletions src/views/dashboard/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -127,9 +128,7 @@ export const HomeView = () => {

<div className="mt-8">
<div className="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
<h2 className="text-lg font-medium leading-6 text-gray-900">
Overview
</h2>
<Heading level={3}>Overview</Heading>
<div className="mt-4 grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
{DASHBOARD_CARDS.map((card, index) => (
<Tile
Expand All @@ -141,9 +140,9 @@ export const HomeView = () => {
</div>

<section className="mx-auto mt-10 max-w-6xl sm:px-6 lg:mt-12 lg:px-8">
<h2 className="px-4 text-lg font-medium leading-6 text-gray-900 sm:px-0">
<Heading level={3} className="px-4 sm:px-0">
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our headings order is completely inappropriate here

Recent syncs
</h2>
</Heading>
{syncs.length ? (
<div className="mt-4">
<SyncsList
Expand Down
5 changes: 2 additions & 3 deletions src/views/dashboard/device/Device.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { toast } from "react-hot-toast";
import EmptyDeviceIcon from "public/svg/empty-device.svg";

import { Empty } from "../../../components/common/Empty";
import { Heading } from "../../../components/dashboard/heading/Heading";
import { AddDeviceModal } from "../../../components/modal/device/AddDeviceModal";
import { DeviceTile } from "../../../components/tile/deviceTile/DeviceTile";
import { useGenericLoader } from "../../../hooks/useGenericLoader";
Expand Down Expand Up @@ -60,9 +61,7 @@ export const DeviceView = () => {
onAdd={onAdd}
/>
<section className="mx-auto mt-8 max-w-6xl px-4 sm:px-6 lg:mt-12 lg:px-8">
<h2 className="text-lg font-medium leading-6 text-gray-900">
Your device
</h2>
<Heading level={3}>Your device</Heading>

{device ? (
<div className="mt-8">
Expand Down
5 changes: 2 additions & 3 deletions src/views/dashboard/feeds/Feeds.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -69,9 +70,7 @@ export const FeedsView = () => {
/>
<section className="mx-auto mt-8 max-w-6xl px-4 sm:px-6 lg:mt-12 lg:px-8">
<div className="flex w-full items-center justify-between">
<h2 className="text-lg font-medium leading-6 text-gray-900">
All feeds
</h2>
<Heading level={3}>All feeds</Heading>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why it's level 3 for example? I guess that our main title should be h1 indeed

<Button onClick={() => setIsAddModalOpen(true)}>
<PlusIcon className="h-6 w-6" /> Add feed
</Button>
Expand Down
29 changes: 26 additions & 3 deletions src/views/dashboard/settings/Settings.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,30 @@
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: <GeneralTab /> }];

const labels = tabs.map(({ label }) => label);
const panels = tabs.map(({ panel }) => panel);

export const SettingsView = () => {
const [activeTab, setActiveTab] = useState(0);

return (
<div className="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
<h1 className="mt-8 text-lg font-bold">Settings view coming soon...</h1>
</div>
<section className="mx-auto mt-8 max-w-6xl px-4 sm:px-6 lg:mt-12 lg:px-8">
<Heading level={2} className="mb-8">
Settings
</Heading>

<Tabs labels={labels} index={activeTab} onChange={setActiveTab} />
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's labels? they should be named tabs I guess


{panels.map((panel, index) => (
<Tabs.Panel key={index} index={index} value={activeTab}>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't use index, what's value?

{panel}
</Tabs.Panel>
))}
</section>
);
};