Skip to content
This repository was archived by the owner on Mar 19, 2021. It is now read-only.

Commit db14195

Browse files
committed
feat(features/settings): add settings page with displayName change logic
HOW-68
1 parent 537ce4e commit db14195

File tree

12 files changed

+234
-34
lines changed

12 files changed

+234
-34
lines changed

babel.config.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
module.exports = {
22
presets: ["react-app"],
3-
plugins: ["react-hot-loader/babel", "styled-components"],
3+
plugins: [
4+
"react-hot-loader/babel",
5+
"styled-components",
6+
"@babel/proposal-optional-chaining",
7+
],
48
env: {
59
development: {
610
plugins: [

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@
7373
"browser-cookies": "^1.2.0",
7474
"case-sensitive-paths-webpack-plugin": "2.2.0",
7575
"commitizen": "^3.0.7",
76-
"connected-react-router": "^6.3.1",
7776
"css-loader": "1.0.0",
7877
"cz-customizable": "^5.3.0",
7978
"date-fns": "^1.30.1",
@@ -158,5 +157,8 @@
158157
"webpack-manifest-plugin": "2.0.4",
159158
"webpackbar": "^3.1.5",
160159
"workbox-webpack-plugin": "3.6.3"
160+
},
161+
"devDependencies": {
162+
"@babel/plugin-proposal-optional-chaining": "^7.2.0"
161163
}
162164
}

src/api/account.js

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,48 @@
11
// @flow
22
import { request } from "@features/common"
33

4-
type RegisterData = {
4+
export type CurrentUser = {|
5+
id: number,
6+
displayName: string,
7+
email: string,
8+
|}
9+
10+
export type ExternalUser = {|
11+
id: number,
12+
displayName: string,
13+
|}
14+
15+
export type User = ExternalUser | CurrentUser
16+
17+
type RegisterData = {|
518
email: string,
619
password: string,
7-
}
20+
|}
821
/**
922
* https://github.com/howtocards/frontend/tree/master/mock-server/server#post-account-create-user-account
1023
*/
1124
const createAccount = (registerData: RegisterData): Promise<number> =>
1225
request("POST", "/account/", { body: registerData })
1326

27+
export type Settings = {|
28+
displayName: string | null,
29+
gravatarEmail: string | null,
30+
|}
31+
32+
export type UpdateSettings = {|
33+
displayName?: string,
34+
|}
35+
36+
const updateSettings = (
37+
data: UpdateSettings,
38+
): Promise<{ settings: Settings }> =>
39+
request("PUT", "/account/settings/", { body: data })
40+
41+
const getSettings = (): Promise<{ settings: Settings }> =>
42+
request("GET", "/account/settings/")
43+
1444
export const accountApi = {
1545
createAccount,
46+
updateSettings,
47+
getSettings,
1648
}

src/features/settings/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// @flow
2+
export * from "./templates"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// @flow
2+
export { SettingsTemplate } from "./settings"
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// @flow
2+
import * as React from "react"
3+
// TODO: remove styles
4+
import styled from "styled-components"
5+
6+
import { CommonContentTemplate } from "@features/common"
7+
import { Container, Card, H1 } from "@howtocards/ui"
8+
9+
type Props = {
10+
children: React.Node,
11+
title?: string,
12+
}
13+
14+
export const SettingsTemplate = ({ children, title }: Props) => (
15+
<CommonContentTemplate>
16+
<Container>
17+
<Main>
18+
<Card>
19+
{title && <H1>{title}</H1>}
20+
{children}
21+
</Card>
22+
</Main>
23+
</Container>
24+
</CommonContentTemplate>
25+
)
26+
27+
SettingsTemplate.defaultProps = {
28+
title: undefined,
29+
}
30+
31+
const Main = styled.main`
32+
display: flex;
33+
flex-direction: column;
34+
margin-top: 2rem;
35+
flex-grow: 1;
36+
align-items: center;
37+
38+
${Card} {
39+
max-width: 60rem;
40+
width: 100%;
41+
}
42+
43+
${H1} {
44+
margin-top: 0;
45+
margin-bottom: 0;
46+
}
47+
`

src/features/users/model/current.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import {
77
type Store,
88
combine,
99
} from "effector"
10+
import { type User } from "@api/account"
1011
import { createFetching, type Fetching } from "@lib/fetching"
1112
import { $cardsRegistry, cardsToObject } from "@features/cards"
1213
// TODO: fix type reexport
1314
import { type Card } from "../../cards/types"
1415
import { usersApi } from "../api"
15-
import { type User } from "../types"
1616

1717
export const pageMounted = createEvent<{ userId: number }>()
1818

@@ -27,7 +27,7 @@ type Cards = {
2727
created: Card[],
2828
}
2929
const loadCards: Effect<number, Cards, void> = createEffect()
30-
export const cardsFetching: Fetching<User, void> = createFetching(
30+
export const cardsFetching: Fetching<Cards, void> = createFetching(
3131
loadCards,
3232
"loading",
3333
)

src/features/users/types.js

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +0,0 @@
1-
export type CurrentUser = {
2-
id: number,
3-
displayName: string,
4-
email: string,
5-
}
6-
7-
export type ExternalUser = {
8-
id: number,
9-
displayName: string,
10-
}
11-
12-
export type User = ExternalUser | CurrentUser

src/pages/settings/page.js

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,48 @@
11
// @flow
22
import * as React from "react"
3+
import styled from "styled-components"
34

4-
export const SettingsPage = () => <div />
5+
import { SettingsTemplate } from "@features/settings"
6+
import { H4 } from "@howtocards/ui"
7+
import { pageMounted, pageUnmounted } from "./store"
8+
9+
export const SettingsPage = () => {
10+
React.useEffect(() => (pageMounted(), pageUnmounted))
11+
12+
return (
13+
<SettingsTemplate title="Personal settings">
14+
<NameSection />
15+
<AvatarSection />
16+
</SettingsTemplate>
17+
)
18+
}
19+
20+
const NameSection = () => {
21+
return (
22+
<FormSection title="Display name">
23+
Display name form here with some logic
24+
</FormSection>
25+
)
26+
}
27+
28+
const AvatarSection = () => {
29+
return (
30+
<FormSection title="Avatar">
31+
Avatar changing form with some logic
32+
</FormSection>
33+
)
34+
}
35+
36+
type FormSectionProps = {
37+
title: string,
38+
children: React.Node,
39+
}
40+
41+
const FormSection = ({ title, children }: FormSectionProps) => (
42+
<>
43+
<H4>{title}</H4>
44+
<ContentBlock>{children}</ContentBlock>
45+
</>
46+
)
47+
48+
const ContentBlock = styled.div``

src/pages/settings/store.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,78 @@
11
// @flow
2+
import {
3+
createEvent,
4+
createStore,
5+
type Store,
6+
createEffect,
7+
forward,
8+
sample,
9+
combine,
10+
createStoreObject,
11+
} from "effector"
12+
import { accountApi, type Settings } from "@api/account"
13+
14+
/**
15+
* 1. load user settings object
16+
* 2. mark page ready
17+
*/
18+
19+
type InputEvent = SyntheticEvent<HTMLInputElement>
20+
type ButtonEvent = SyntheticEvent<HTMLButtonElement>
21+
22+
export const pageMounted = createEvent<void>()
23+
export const pageUnmounted = createEvent<void>()
24+
25+
export const nameChanged = createEvent<InputEvent>()
26+
export const nameSubmitted = createEvent<ButtonEvent>()
27+
28+
export const avaChangePressed = createEvent<ButtonEvent>()
29+
export const avaEmailChanged = createEvent<InputEvent>()
30+
export const avaEmailSubmitted = createEvent<ButtonEvent>()
31+
32+
const loadSettings = createEffect()
33+
const saveSettings = createEffect()
34+
35+
export const $settings: Store<?Settings> = createStore(null)
36+
export const $isSettingsReady = $settings.map<boolean>(Boolean)
37+
38+
// Stores for inputs
39+
40+
export const $name: Store<string> = createStore("")
41+
export const $avaEmail: Store<string> = createStore("")
42+
43+
forward({
44+
from: pageMounted,
45+
to: loadSettings,
46+
})
47+
48+
loadSettings.use(accountApi.getSettings)
49+
saveSettings.use(accountApi.updateSettings)
50+
51+
$settings
52+
.on(loadSettings.done, (_, { result }) => result.settings)
53+
.on(saveSettings.done, (_, { result }) => result.settings)
54+
.reset(pageMounted, pageUnmounted)
55+
56+
$name
57+
.on($settings.updates, (_, settings) => settings?.displayName || "")
58+
.on(nameChanged, (_, event) => event.currentTarget.value)
59+
.reset(pageUnmounted)
60+
.watch(nameSubmitted, (displayName) => {
61+
saveSettings({ displayName })
62+
})
63+
64+
$avaEmail
65+
.on($settings.updates, (_, settings) => settings?.gravatarEmail || "")
66+
.on(avaEmailChanged, (_, event) => event.currentTarget.value)
67+
.reset(pageUnmounted)
68+
.watch(avaEmailSubmitted, (_gravatarEmail) => {
69+
// HOW-62
70+
// saveSettings({ gravatarEmail })
71+
})
72+
73+
$settings.watch((settings) => console.log("SETTINGS", settings))
74+
$isSettingsReady.watch((settingsReady) =>
75+
console.log("settingsReady", settingsReady),
76+
)
77+
$name.watch((name) => console.log("name", name))
78+
$avaEmail.watch((avaEmail) => console.log("avaEmail", avaEmail))

0 commit comments

Comments
 (0)