Skip to content

Commit f089128

Browse files
committed
Show UserLogo when user visits velog page
1 parent b590902 commit f089128

File tree

7 files changed

+144
-33
lines changed

7 files changed

+144
-33
lines changed

src/components/base/Header.tsx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import useToggle from '../../lib/hooks/useToggle';
99
import HeaderUserMenu from './HeaderUserMenu';
1010
import { logout } from '../../lib/api/auth';
1111
import storage from '../../lib/storage';
12+
import { UserLogo } from '../../modules/header';
13+
import { Link } from 'react-router-dom';
1214

1315
const HeaderBlock = styled.div<{
1416
floating: boolean;
@@ -50,15 +52,32 @@ interface HeaderProps {
5052
floatingMargin: number;
5153
onLoginClick: () => void;
5254
user: CurrentUser | null;
55+
custom: boolean;
56+
userLogo: UserLogo | null;
57+
velogUsername: string | null;
5358
}
5459

5560
const { useCallback } = React;
5661

62+
const CustomLogo = styled(Link)``;
63+
64+
const createFallbackTitle = (username: string | null) => {
65+
if (!username) return null;
66+
const lastChar = username.slice(-1).toLowerCase();
67+
if (lastChar === 's') {
68+
return `${username}' velog`;
69+
}
70+
return `${username}'s velog`;
71+
};
72+
5773
const Header: React.SFC<HeaderProps> = ({
5874
floating,
5975
floatingMargin,
6076
onLoginClick,
6177
user,
78+
custom,
79+
userLogo,
80+
velogUsername,
6281
}) => {
6382
const [userMenu, toggleUserMenu] = useToggle(false);
6483

@@ -78,7 +97,17 @@ const Header: React.SFC<HeaderProps> = ({
7897
>
7998
<div className="wrapper">
8099
<div className="brand">
81-
<Logo />
100+
{custom ? (
101+
userLogo ? (
102+
<CustomLogo to="/">
103+
{userLogo.title
104+
? userLogo.title
105+
: createFallbackTitle(velogUsername)}
106+
</CustomLogo>
107+
) : null
108+
) : (
109+
<Logo />
110+
)}
82111
</div>
83112
<div className="right">
84113
{user ? (

src/components/base/HeaderLogo.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as React from 'react';
2+
import styled from 'styled-components';
3+
import { Logo } from '../../static/svg';
4+
import { UserLogo } from '../../modules/header';
5+
6+
const HeaderLogoBlock = styled.div``;
7+
8+
export interface HeaderLogoProps {
9+
custom: boolean;
10+
userLogo: UserLogo | null;
11+
velogUsername: string | null;
12+
}
13+
14+
const HeaderLogo: React.FC<HeaderLogoProps> = props => {
15+
return <HeaderLogoBlock />;
16+
};
17+
18+
export default HeaderLogo;

src/containers/base/HeaderContainer.tsx

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,30 @@ import { getScrollTop } from '../../lib/utils';
44
import { RootState } from '../../modules';
55
import { connect } from 'react-redux';
66
import { showAuthModal } from '../../modules/core';
7-
import { CurrentUser } from '../../lib/graphql/user';
87

98
const { useEffect, useRef, useState, useCallback } = React;
109

10+
const mapStateToProps = (state: RootState) => ({
11+
user: state.core.user,
12+
custom: state.header.custom,
13+
userLogo: state.header.userLogo,
14+
velogUsername: state.header.velogUsername,
15+
});
16+
17+
const mapDispatchToProps = {
18+
showAuthModal,
19+
};
1120
interface OwnProps {}
12-
interface StateProps {
13-
user: CurrentUser | null;
14-
}
15-
interface DispatchProps {
16-
showAuthModal: typeof showAuthModal;
17-
}
21+
type StateProps = ReturnType<typeof mapStateToProps>;
22+
type DispatchProps = typeof mapDispatchToProps;
1823
type HeaderContainerProps = OwnProps & StateProps & DispatchProps;
1924

2025
const HeaderContainer: React.SFC<HeaderContainerProps> = ({
2126
showAuthModal,
2227
user,
28+
custom,
29+
userLogo,
30+
velogUsername,
2331
}) => {
2432
const lastY = useRef(0);
2533
const direction = useRef<null | 'UP' | 'DOWN'>(null);
@@ -79,13 +87,14 @@ const HeaderContainer: React.SFC<HeaderContainerProps> = ({
7987
floatingMargin={floatingMargin}
8088
onLoginClick={onLoginClick}
8189
user={user}
90+
custom={custom}
91+
userLogo={userLogo}
92+
velogUsername={velogUsername}
8293
/>
8394
);
8495
};
8596

8697
export default connect<StateProps, DispatchProps, OwnProps, RootState>(
87-
state => ({
88-
user: state.core.user,
89-
}),
90-
{ showAuthModal },
98+
mapStateToProps,
99+
mapDispatchToProps,
91100
)(HeaderContainer);

src/containers/velog/ConfigLoader.tsx

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,54 @@ import React, { useEffect } from 'react';
22
import { Query, QueryResult } from 'react-apollo';
33
import { connect } from 'react-redux';
44
import { GET_VELOG_CONFIG, VelogConfig } from '../../lib/graphql/user';
5-
import { setUserLogo } from '../../modules/header';
6-
7-
export interface ConfigLoaderProps {
8-
username: string;
9-
}
5+
import { setUserLogo, setCustom, setVelogUsername } from '../../modules/header';
6+
import { RootState } from '../../modules';
107

118
interface ConfigEffectProps {
129
velogConfig: VelogConfig;
13-
setUserLogo: typeof setUserLogo;
10+
onConfigChange: (config: VelogConfig) => any;
1411
}
1512

1613
const ConfigEffect: React.FC<ConfigEffectProps> = ({
1714
velogConfig,
18-
setUserLogo,
15+
onConfigChange,
1916
}) => {
2017
useEffect(() => {
21-
setUserLogo(velogConfig);
22-
}, [setUserLogo, velogConfig]);
18+
onConfigChange(velogConfig);
19+
}, [onConfigChange, velogConfig]);
2320
return null;
2421
};
2522

26-
const ConfigEffectContainer = connect(
27-
() => ({}),
28-
{ setUserLogo },
29-
)(ConfigEffect);
23+
const mapDispatchToProps = {
24+
setUserLogo,
25+
setCustom,
26+
setVelogUsername,
27+
};
28+
29+
type OwnProps = {
30+
username: string;
31+
};
32+
type StateProps = {};
33+
type DispatchProps = typeof mapDispatchToProps;
34+
export type ConfigLoaderProps = OwnProps & StateProps & DispatchProps;
35+
36+
const ConfigLoader: React.FC<ConfigLoaderProps> = ({
37+
username,
38+
setUserLogo,
39+
setCustom,
40+
setVelogUsername,
41+
}) => {
42+
useEffect(() => {
43+
setCustom(true);
44+
return () => {
45+
setCustom(false);
46+
};
47+
}, [setCustom]);
48+
49+
useEffect(() => {
50+
setVelogUsername(username);
51+
}, [setVelogUsername, username]);
3052

31-
const ConfigLoader: React.FC<ConfigLoaderProps> = ({ username }) => {
3253
return (
3354
<Query query={GET_VELOG_CONFIG} variables={{ username }}>
3455
{({
@@ -41,10 +62,18 @@ const ConfigLoader: React.FC<ConfigLoaderProps> = ({ username }) => {
4162
}
4263
if (error || loading) return null;
4364
if (!data) return null;
44-
return <ConfigEffectContainer velogConfig={data.velog_config} />;
65+
return (
66+
<ConfigEffect
67+
velogConfig={data.velog_config}
68+
onConfigChange={setUserLogo}
69+
/>
70+
);
4571
}}
4672
</Query>
4773
);
4874
};
4975

50-
export default ConfigLoader;
76+
export default connect<StateProps, DispatchProps, OwnProps, RootState>(
77+
() => ({}),
78+
mapDispatchToProps,
79+
)(ConfigLoader);

src/containers/velog/__tests__/ConfigLoader.test.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import * as React from 'react';
2-
import { render, waitForDomChange } from 'react-testing-library';
32
import ConfigLoader, { ConfigLoaderProps } from '../ConfigLoader';
43
import { MockedProvider } from 'react-apollo/test-utils';
54
import { GET_VELOG_CONFIG } from '../../../lib/graphql/user';
@@ -8,7 +7,9 @@ import waitUntil from '../../../lib/waitUntil';
87

98
describe('ConfigLoader', () => {
109
const setup = (props: Partial<ConfigLoaderProps> = {}) => {
11-
const initialProps: ConfigLoaderProps = { username: 'velopert' };
10+
const initialProps: Partial<ConfigLoaderProps> & { username: string } = {
11+
username: 'velopert',
12+
};
1213
const mocks = [
1314
{
1415
request: {
@@ -36,7 +37,17 @@ describe('ConfigLoader', () => {
3637
it('renders properly', () => {
3738
setup();
3839
});
39-
it('loads GET_VELOG_CONFIG and dispatches setUserLogo', async () => {
40+
it('dispatches SET_VELOG_USERNAME action', () => {
41+
const { store } = setup();
42+
expect(store.getState().header.velogUsername).toBe('velopert');
43+
});
44+
it('dispatches SET_CUSTOM action', () => {
45+
const { store, unmount } = setup();
46+
expect(store.getState().header.custom).toBeTruthy();
47+
unmount();
48+
expect(store.getState().header.custom).toBeFalsy();
49+
});
50+
it('loads GET_VELOG_CONFIG and dispatches SET_USER_LOGO action', async () => {
4051
const { store } = setup();
4152
const userLogo = store.getState().header.userLogo;
4253
await waitUntil(() => store.getState().header.userLogo !== userLogo);

src/modules/__tests__/header.test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import header, { setCustom, setUserLogo } from '../header';
1+
import header, { setCustom, setUserLogo, setVelogUsername } from '../header';
22

33
describe('header redux module', () => {
44
describe('reducer', () => {
55
const initialState = {
66
custom: false,
77
userLogo: null,
8+
velogUsername: null,
89
};
910
it('should have initialState', () => {
1011
const state = header(undefined, {});
@@ -23,5 +24,9 @@ describe('header redux module', () => {
2324
);
2425
expect(state.userLogo).toHaveProperty('title', 'SAMPLE');
2526
});
27+
it('handles SET_VELOG_USER', () => {
28+
let state = header(initialState, setVelogUsername('velopert'));
29+
expect(state.velogUsername).toBe('velopert');
30+
});
2631
});
2732
});

src/modules/header.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,33 @@ import { createStandardAction } from 'typesafe-actions';
33

44
const SET_CUSTOM = 'header/SET_CUSTOM';
55
const SET_USER_LOGO = 'header/SET_USER_LOGO';
6+
const SET_VELOG_USERNAME = 'header/SET_VELOG_USERNAME';
67

7-
type UserLogo = {
8+
export type UserLogo = {
89
title: string | null;
910
logo_image: string | null;
1011
};
1112

1213
export interface HeaderState {
1314
custom: boolean;
1415
userLogo: null | UserLogo;
16+
velogUsername: string | null;
1517
}
1618

1719
export const setCustom = createStandardAction(SET_CUSTOM)<boolean>();
1820
export const setUserLogo = createStandardAction(SET_USER_LOGO)<UserLogo>();
21+
export const setVelogUsername = createStandardAction(SET_VELOG_USERNAME)<
22+
string
23+
>();
1924

2025
type SetCustom = ReturnType<typeof setCustom>;
2126
type SetUserLogo = ReturnType<typeof setUserLogo>;
27+
type SetVelogUsername = ReturnType<typeof setVelogUsername>;
2228

2329
const initialState: HeaderState = {
2430
custom: false,
2531
userLogo: null,
32+
velogUsername: null,
2633
};
2734

2835
const header = createReducer(
@@ -33,6 +40,9 @@ const header = createReducer(
3340
[SET_USER_LOGO]: (state, { payload }: SetUserLogo) => {
3441
return updateKey(state, 'userLogo', payload);
3542
},
43+
[SET_VELOG_USERNAME]: (state, { payload }: SetVelogUsername) => {
44+
return updateKey(state, 'velogUsername', payload);
45+
},
3646
},
3747
initialState,
3848
);

0 commit comments

Comments
 (0)