-
-
Notifications
You must be signed in to change notification settings - Fork 5
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
Changes from 4 commits
cf4f7f5
845c576
9244bbf
af87779
020018b
f8e9889
44e6c5a
a85ae88
353a217
7ff47da
31bc8b5
7688adb
4bd7a69
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 = () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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> | ||
); | ||
}; |
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 = ({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use |
||
name, | ||
type, | ||
initValue, | ||
}: UserSettingsRowProps) => { | ||
const [value, setValue] = useState(initValue); | ||
const [editMode, setEditMode] = useState(false); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
const handleSaveButtonClick = () => { | ||
console.log("saving..."); | ||
setEditMode(false); | ||
}; | ||
|
||
return ( | ||
<SettingsRow | ||
leftSection={name} | ||
middleSection={ | ||
<> | ||
{editMode ? ( | ||
<Input | ||
type={type} | ||
value={value} | ||
onChange={(event) => setValue(event.target.value)} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handling form should be done with |
||
/> | ||
) : ( | ||
value | ||
)} | ||
</> | ||
} | ||
rightSection={ | ||
<> | ||
{editMode ? ( | ||
<Button type="button" onClick={handleSaveButtonClick}> | ||
Save | ||
</Button> | ||
) : ( | ||
<SettingsRow.Action onClick={() => setEditMode(true)}> | ||
Edit | ||
</SettingsRow.Action> | ||
)} | ||
</> | ||
} | ||
/> | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why everything is in |
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 = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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} | ||
/> | ||
); | ||
}; |
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) => ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||
); |
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 = ({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. put this component in |
||
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why that way? |
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) => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For what we need this component? |
||
index === value ? <>{children}</> : null; |
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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}> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably we should change active tab based on url param: |
||
? "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; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 = () => { | |
|
||
<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 | ||
|
@@ -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"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 = () => { | |
/> | ||
<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> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||
|
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} /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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}> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't use index, what's value? |
||
{panel} | ||
</Tabs.Panel> | ||
))} | ||
</section> | ||
); | ||
}; |
There was a problem hiding this comment.
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