Skip to content

Feature/notifications #493

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

Merged
merged 7 commits into from
Feb 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 100 additions & 44 deletions src/components/base/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useRef, useCallback } from 'react';
import styled from 'styled-components';
import { SearchIcon2 } from '../../static/svg';
import styled, { css } from 'styled-components';
import { NotificationIcon, SearchIcon3 } from '../../static/svg';
import RoundButton from '../common/RoundButton';
import MainResponsive from '../main/MainResponsive';
import useHeader from './hooks/useHeader';
Expand All @@ -11,19 +11,21 @@ import { Link } from 'react-router-dom';
import media from '../../lib/styles/media';
import HeaderLogo from './HeaderLogo';
import { themedPalette } from '../../lib/styles/themes';
import ThemeToggleButton from './ThemeToggleButton';
import { useSelector } from 'react-redux';
import { RootState } from '../../modules';
import VLink from '../common/VLink';
import { useDispatch } from 'react-redux';
import { showAuthModal } from '../../modules/core';
import { useQuery } from '@apollo/react-hooks';
import { NOTIFICATION_COUNT } from '../../lib/graphql/notification';

export type MainHeaderProps = {};

function Header(props: MainHeaderProps) {
const dispatch = useDispatch();
const { data: notificationCountData } = useQuery(NOTIFICATION_COUNT);

const { user, onLoginClick, onLogout, customHeader } = useHeader();
const [userMenu, toggleUserMenu] = useToggle(false);
const ref = useRef<HTMLDivElement>(null);
const themeReady = useSelector(
(state: RootState) => state.darkMode.systemTheme !== 'not-ready',
);

const onOutsideClick = useCallback(
(e: React.MouseEvent) => {
Expand All @@ -34,6 +36,15 @@ function Header(props: MainHeaderProps) {
[toggleUserMenu],
);

const onClickNotification = (event: React.MouseEvent<HTMLAnchorElement>) => {
if (!user) {
event.preventDefault();
dispatch(showAuthModal('LOGIN'));
return;
}
};

const notificationCount = notificationCountData?.notificationCount ?? 0;
const urlForSearch = customHeader.custom
? `/search?username=${customHeader.username}`
: '/search';
Expand All @@ -46,44 +57,48 @@ function Header(props: MainHeaderProps) {
userLogo={customHeader.userLogo}
username={customHeader.username}
/>
<Right>
<NotificationButton to="/notifications" onClick={onClickNotification}>
{user && notificationCount !== 0 && (
<NotificationCounter
isSingle={Math.floor(notificationCount / 10) === 0}
>
{Math.min(99, notificationCount)}
</NotificationCounter>
)}
<NotificationIcon />
</NotificationButton>
<SearchButton to={urlForSearch}>
<SearchIcon3 />
</SearchButton>
{user ? (
<>
<RoundButton
border
color="darkGray"
style={{ marginRight: '1.25rem' }}
to="/write"
className="write-button"
>
새 글 작성
</RoundButton>

{user ? (
<Right>
{themeReady && <ThemeToggleButton />}
<SearchButton to={urlForSearch}>
<SearchIcon2 />
</SearchButton>
<RoundButton
border
color="darkGray"
style={{ marginRight: '1.25rem' }}
to="/write"
className="write-button"
>
새 글 작성
</RoundButton>

<div ref={ref}>
<HeaderUserIcon user={user} onClick={toggleUserMenu} />
</div>
<HeaderUserMenu
onClose={onOutsideClick}
onLogout={onLogout}
username={user.username}
visible={userMenu}
/>
</Right>
) : (
<Right>
{themeReady && <ThemeToggleButton />}
<SearchButton to={urlForSearch}>
<SearchIcon2 />
</SearchButton>
<div ref={ref}>
<HeaderUserIcon user={user} onClick={toggleUserMenu} />
</div>
<HeaderUserMenu
onClose={onOutsideClick}
onLogout={onLogout}
username={user.username}
visible={userMenu}
/>
</>
) : (
<RoundButton color="darkGray" onClick={onLoginClick}>
로그인
</RoundButton>
</Right>
)}
)}
</Right>
</Inner>
</Block>
);
Expand Down Expand Up @@ -114,12 +129,53 @@ const SearchButton = styled(Link)`
background: ${themedPalette.slight_layer};
}
svg {
width: 1.125rem;
height: 1.125rem;
width: 24px;
height: 24px;
}
margin-right: 0.5rem;
`;

const NotificationButton = styled(VLink)`
position: relative;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
width: 2.5rem;
height: 2.5rem;
outline: none;
border-radius: 50%;
color: ${themedPalette.text1};
cursor: pointer;
margin-right: 4px;
&:hover {
background: ${themedPalette.slight_layer};
}
svg {
width: 24px;
height: 24px;
}
`;

const NotificationCounter = styled.div<{ isSingle: boolean }>`
position: absolute;
top: 3px;
right: -3px;
padding: 1px 4px;
font-weight: 500;
font-size: 11px;
background-color: var(--primary1);
color: var(--button-text);
border-radius: 100px;

${(props) =>
props.isSingle &&
css`
right: 4px;
`}
`;

const Inner = styled(MainResponsive)`
height: 100%;
display: flex;
Expand Down
4 changes: 3 additions & 1 deletion src/components/base/HeaderUserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ const HeaderUserMenu: React.FC<HeaderUserMenuProps> = ({
</div>
<HeaderUserMenuItem to="/saves">임시 글</HeaderUserMenuItem>
<HeaderUserMenuItem to="/lists/liked">읽기 목록</HeaderUserMenuItem>
<HeaderUserMenuItem to="/setting">설정</HeaderUserMenuItem>
<HeaderUserMenuItem to="/setting" isMigrated={true}>
설정
</HeaderUserMenuItem>
<HeaderUserMenuItem onClick={onLogout}>로그아웃</HeaderUserMenuItem>
</div>
</HeaderUserMenuBlock>
Expand Down
16 changes: 14 additions & 2 deletions src/components/common/VLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,24 @@ type Props = {
className?: string;
children?: React.ReactNode;
style?: React.CSSProperties;
onClick?: (event: React.MouseEvent<HTMLAnchorElement>) => void;
};

function VLink({ to, children, className = '', style }: Props) {
function VLink({ to, children, className = '', style, onClick }: Props) {
const url = `${process.env.REACT_APP_CLIENT_V3_HOST}${to}`;

const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
if (!onClick) return;
onClick(event);
};

return (
<Link href={url} className={className} style={style}>
<Link
href={url}
className={className}
style={style}
onClick={(event) => handleClick(event)}
>
{children}
</Link>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/post/PostCustomBanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const onClick = () => {
const PostCustomBanner: React.FC<PostCustomBannerProps> = ({ image, url }) => {
return (
<PostCustomBannerBlock onClick={onClick}>
<a href={url} target="_blank">
<a href={url} target="_blank" rel="noopener noreferrer">
<img src={image} alt="post-custom-banner" />
</a>
</PostCustomBannerBlock>
Expand Down
1 change: 0 additions & 1 deletion src/containers/post/PostRepliesContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ const PostRepliesContainer: React.FC<PostRepliesProps> = ({
[onToggleAskRemove],
);

console.log(replies);
if (replies.loading || !replies.data) {
return null;
}
Expand Down
1 change: 0 additions & 1 deletion src/containers/post/PostViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ import MobileLikeButton from '../../components/post/MobileLikeButton';
import RelatedPost from './RelatedPost';
import optimizeImage from '../../lib/optimizeImage';
import { useSetShowFooter } from '../../components/velog/VelogPageTemplate';
import HorizontalBanner from './HorizontalBanner';
import gtag from '../../lib/gtag';
import FollowButton from '../../components/common/FollowButton';
import { BANNER_ADS } from '../../lib/graphql/ad';
Expand Down
7 changes: 7 additions & 0 deletions src/lib/graphql/notification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { gql } from 'apollo-boost';

export const NOTIFICATION_COUNT = gql`
query NotificationCount {
notificationCount
}
`;
5 changes: 5 additions & 0 deletions src/static/svg/icon-notification.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/static/svg/icon-search-3.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion src/static/svg/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ export { ReactComponent as TwitterIcon } from './icon-twitter.svg';
export { ReactComponent as ShareIcon2 } from './icon-share-2.svg';
export { ReactComponent as SearchIcon } from './icon-search.svg'; // iconmonstr-magnifier-3
export { ReactComponent as SearchIcon2 } from './icon-search-2.svg';
export { ReactComponent as SearchIcon3 } from './icon-search-3.svg';
export { ReactComponent as VelogIcon } from './velog-icon.svg';
export { ReactComponent as CheckIcon } from './icon-check.svg';

export { ReactComponent as NotificationIcon } from './icon-notification.svg';
export { ReactComponent as MoonIcon } from './icon-moon.svg';
export { ReactComponent as SunIcon } from './icon-sun.svg';