diff --git a/src/components/home/HomeTab.tsx b/src/components/home/HomeTab.tsx
index 88416ea8..d5e494ee 100644
--- a/src/components/home/HomeTab.tsx
+++ b/src/components/home/HomeTab.tsx
@@ -1,12 +1,21 @@
import React, { useRef } from 'react';
import styled from 'styled-components';
import { NavLink, useLocation } from 'react-router-dom';
-import palette from '../../lib/styles/palette';
-import { MdTrendingUp, MdAccessTime, MdMoreVert } from 'react-icons/md';
+import { themedPalette } from '../../lib/styles/themes';
+
+import {
+ MdTrendingUp,
+ MdAccessTime,
+ MdMoreVert,
+ MdArrowDropDown,
+} from 'react-icons/md';
import { useSpring, animated } from 'react-spring';
-import { mediaQuery } from '../../lib/styles/media';
+import media, { mediaQuery } from '../../lib/styles/media';
import useToggle from '../../lib/hooks/useToggle';
import HomeMobileHeadExtra from './HomeMobileHeadExtra';
+import TimeframePicker from './TimeframePicker';
+import { useTimeframe } from './hooks/useTimeframe';
+import { timeframes } from './utils/timeframeMap';
export type HomeTabProps = {};
@@ -15,7 +24,10 @@ function HomeTab(props: HomeTabProps) {
const isRecent = location.pathname === '/recent';
const [extra, toggle] = useToggle(false);
+ const [timeframePicker, toggleTimeframePicker] = useToggle(false);
const moreButtonRef = useRef
(null);
+ const timeframeRef = useRef(null);
+ const [timeframe] = useTimeframe();
const onClose = (e: React.MouseEvent) => {
if (!moreButtonRef.current) return;
@@ -28,6 +40,17 @@ function HomeTab(props: HomeTabProps) {
toggle();
};
+ const onCloseTimeframePicker = (e: React.MouseEvent) => {
+ if (!timeframeRef.current) return;
+ if (
+ e.target === timeframeRef.current ||
+ timeframeRef.current.contains(e.target as Node)
+ ) {
+ return;
+ }
+ toggleTimeframePicker();
+ };
+
const springStyle = useSpring({
left: isRecent ? '50%' : '0%',
config: {
@@ -38,23 +61,37 @@ function HomeTab(props: HomeTabProps) {
return (
-
- {
- return ['/', '/trending'].indexOf(location.pathname) !== -1;
- }}
- >
-
- 트렌딩
-
-
-
- 최신
-
-
-
+
+
+ {
+ return ['/', '/trending'].indexOf(location.pathname) !== -1;
+ }}
+ >
+
+ 트렌딩
+
+
+
+ 최신
+
+
+
+ {['/', '/trending'].includes(location.pathname) && (
+ <>
+
+ {timeframes.find((t) => t[0] === timeframe)![1]}{' '}
+
+
+
+ >
+ )}
+
@@ -72,17 +109,20 @@ const Wrapper = styled.div`
.more {
cursor: pointer;
font-size: 1.5rem;
- color: ${palette.gray6};
+ color: ${themedPalette.text3};
}
`;
const MobileMore = styled.div`
- display: none;
- ${mediaQuery(944)} {
- display: flex;
- align-items: center;
- justify-content: center;
- }
+ display: flex;
+ align-items: center;
+ justify-content: center;
+`;
+
+const Left = styled.div`
+ display: flex;
+ align-items: center;
+ position: relative;
`;
const Block = styled.div`
@@ -99,7 +139,7 @@ const Block = styled.div`
justify-content: center;
font-size: 1.125rem;
text-decoration: none;
- color: ${palette.gray6};
+ color: ${themedPalette.text3};
height: 3rem;
svg {
@@ -107,7 +147,7 @@ const Block = styled.div`
margin-right: 0.5rem;
}
&.active {
- color: ${palette.gray8};
+ color: ${themedPalette.text1};
font-weight: bold;
}
@@ -126,7 +166,37 @@ const Indicator = styled(animated.div)`
height: 2px;
position: absolute;
bottom: 0px;
- background: ${palette.gray8};
+ background: ${themedPalette.border1};
+`;
+
+const Selector = styled.div`
+ background: ${themedPalette.bg_element1};
+ height: 2rem;
+ width: 6rem;
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding-left: 0.5rem;
+ padding-right: 0.5rem;
+ font-weight: 600;
+ color: ${themedPalette.text2};
+ font-size: 0.875rem;
+ box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.05);
+ svg {
+ width: 1.5rem;
+ height: 1.5rem;
+ }
+ cursor: pointer;
+ @media (hover: hover) and (pointer: fine) {
+ &:hover {
+ opacity: 0.75;
+ }
+ }
+ ${media.medium} {
+ width: 5.25rem;
+ font-size: 0.75rem;
+ }
`;
export default HomeTab;
diff --git a/src/components/home/HomeTagWidget.tsx b/src/components/home/HomeTagWidget.tsx
index d22d949e..3268fbec 100644
--- a/src/components/home/HomeTagWidget.tsx
+++ b/src/components/home/HomeTagWidget.tsx
@@ -1,6 +1,7 @@
import React from 'react';
import styled from 'styled-components';
import HomeWidget from './HomeWidget';
+import { themedPalette } from '../../lib/styles/themes';
import palette from '../../lib/styles/palette';
import { Link } from 'react-router-dom';
import Skeleton from '../common/Skeleton';
@@ -13,7 +14,7 @@ function HomeTagWidget({ tags }: HomeTagWidgetProps) {
return (
- {tags.map(tag => (
+ {tags.map((tag) => (
-
# {tag}
@@ -53,7 +54,7 @@ const StyledWidget = styled(HomeWidget)`
}
}
li {
- color: ${palette.gray7};
+ color: ${themedPalette.text2};
line-height: 1.5;
font-size: 1rem;
}
@@ -61,10 +62,10 @@ const StyledWidget = styled(HomeWidget)`
margin-top: 0.25rem;
}
li.more {
- color: ${palette.gray6};
+ color: ${themedPalette.text3};
a {
&:hover {
- color: ${palette.gray5};
+ color: ${themedPalette.text3};
}
text-decoration: underline;
}
diff --git a/src/components/home/HomeWidget.tsx b/src/components/home/HomeWidget.tsx
index 0927954e..81a48228 100644
--- a/src/components/home/HomeWidget.tsx
+++ b/src/components/home/HomeWidget.tsx
@@ -1,5 +1,6 @@
import React from 'react';
import styled from 'styled-components';
+import { themedPalette } from '../../lib/styles/themes';
import palette from '../../lib/styles/palette';
export type HomeWidgetProps = {
@@ -22,7 +23,7 @@ const MainWidgetBlock = styled.section`
line-height: 1.5;
font-size: 0.875rem;
padding-bottom: 0.5rem;
- border-bottom: 1px solid ${palette.gray2};
+ border-bottom: 1px solid ${themedPalette.border4};
margin-top: 0;
margin-bottom: 1rem;
font-weight: bold;
diff --git a/src/components/home/TimeframePicker.tsx b/src/components/home/TimeframePicker.tsx
new file mode 100644
index 00000000..681974e4
--- /dev/null
+++ b/src/components/home/TimeframePicker.tsx
@@ -0,0 +1,112 @@
+import React from 'react';
+import styled from 'styled-components';
+import { themedPalette } from '../../lib/styles/themes';
+import { useTransition, animated } from 'react-spring';
+import OutsideClickHandler from 'react-outside-click-handler';
+import { useTimeframe } from './hooks/useTimeframe';
+import { timeframes } from './utils/timeframeMap';
+
+export type TimeframePickerProps = {
+ visible: boolean;
+ onClose: (e: React.MouseEvent) => void;
+};
+
+function TimeframePicker({ visible, onClose }: TimeframePickerProps) {
+ const transition = useTransition(visible, {
+ from: {
+ opacity: 0,
+ transform: 'scale(0.8)',
+ },
+ enter: {
+ opacity: 1,
+ transform: 'scale(1)',
+ },
+ leave: {
+ opacity: 0,
+ transform: 'scale(0.8)',
+ },
+ config: {
+ tension: 350,
+ friction: 26,
+ },
+ });
+
+ const [timeframe, setTimeframe] = useTimeframe();
+
+ return (
+ <>
+ {transition((styles, item) =>
+ item ? (
+
+
+
+
+ {timeframes.map(([value, text]) => (
+ - setTimeframe(value)}
+ className={value === timeframe ? 'active' : ''}
+ >
+ {text}
+
+ ))}
+
+
+
+
+ ) : null,
+ )}
+ >
+ );
+}
+
+const Aligner = styled.div`
+ position: absolute;
+ right: 0;
+ top: 100%;
+ z-index: 5;
+`;
+const Block = styled(animated.div)`
+ margin-top: 0.5rem;
+ width: 12rem;
+ box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1);
+ background: ${themedPalette.bg_element1};
+ color: ${themedPalette.text1};
+ transform-origin: top right;
+ ul {
+ list-style: none;
+ padding-left: 0;
+ margin: 0;
+ }
+ li {
+ cursor: pointer;
+ &:hover {
+ background: ${themedPalette.bg_element2};
+ }
+ font-weight: 600;
+
+ font-size: 0.875rem;
+ padding: 0.75rem 1rem;
+
+ &.active {
+ color: ${themedPalette.primary1};
+ }
+ }
+ li + li {
+ border-top: 1px solid ${themedPalette.border4};
+ }
+ .contact {
+ border-top: 1px solid #f1f3f5;
+ padding: 1rem;
+ h5 {
+ margin: 0;
+ font-size: 0.75rem;
+ }
+ .email {
+ color: ${themedPalette.text1};
+ font-size: 0.75rem;
+ }
+ }
+`;
+
+export default TimeframePicker;
diff --git a/src/components/home/hooks/useTimeframe.ts b/src/components/home/hooks/useTimeframe.ts
new file mode 100644
index 00000000..6a0ea438
--- /dev/null
+++ b/src/components/home/hooks/useTimeframe.ts
@@ -0,0 +1,16 @@
+import { useDispatch, useSelector } from 'react-redux';
+import { useMemo } from 'react';
+import { bindActionCreators } from 'redux';
+import home from '../../../modules/home';
+import { RootState } from '../../../modules';
+
+export function useTimeframe() {
+ const dispatch = useDispatch();
+ const actions = useMemo(
+ () => bindActionCreators(home.actions, dispatch),
+ [dispatch],
+ );
+ const timeframe = useSelector((state: RootState) => state.home.timeframe);
+
+ return [timeframe, actions.choose] as const;
+}
diff --git a/src/components/home/utils/timeframeMap.ts b/src/components/home/utils/timeframeMap.ts
new file mode 100644
index 00000000..d022e0b2
--- /dev/null
+++ b/src/components/home/utils/timeframeMap.ts
@@ -0,0 +1,10 @@
+import { Timeframe } from '../../../modules/home';
+
+export const timeframeMap: Record = {
+ day: '오늘',
+ week: '이번 주',
+ month: '이번 달',
+ year: '올해',
+};
+
+export const timeframes = Object.entries(timeframeMap) as [Timeframe, string][];
diff --git a/src/components/main/MainResponsive.tsx b/src/components/main/MainResponsive.tsx
index ae0d3205..67aed1c3 100644
--- a/src/components/main/MainResponsive.tsx
+++ b/src/components/main/MainResponsive.tsx
@@ -19,15 +19,9 @@ const Block = styled.div`
width: 1376px;
}
${mediaQuery(1440)} {
- width: 1280px;
+ width: 1024px;
}
- ${mediaQuery(1312)} {
- width: 912px;
- }
- ${mediaQuery(944)} {
- width: calc(100% - 2rem);
- }
- ${mediaQuery(767)} {
+ ${mediaQuery(1056)} {
width: calc(100% - 2rem);
}
`;
diff --git a/src/components/main/MainTemplate.tsx b/src/components/main/MainTemplate.tsx
index 08704cd8..0727a326 100644
--- a/src/components/main/MainTemplate.tsx
+++ b/src/components/main/MainTemplate.tsx
@@ -1,12 +1,5 @@
import React from 'react';
-import styled, { createGlobalStyle } from 'styled-components';
-import palette from '../../lib/styles/palette';
-
-const BackgroundStyle = createGlobalStyle`
- body {
- background: ${palette.gray0};
- }
-`;
+import styled from 'styled-components';
export type MainTemplateProps = {
children: React.ReactNode;
@@ -15,7 +8,6 @@ export type MainTemplateProps = {
function MainTemplate({ children }: MainTemplateProps) {
return (
<>
-
{children}
>
);
diff --git a/src/components/policy/policyData.ts b/src/components/policy/policyData.ts
index 5cb2c648..a6a2d673 100644
--- a/src/components/policy/policyData.ts
+++ b/src/components/policy/policyData.ts
@@ -18,22 +18,23 @@ const data = {
## 3. 수집하는 개인정보의 항목
회사는 회원가입, 서비스 이용 등을 위해 아래와 같은 개인정보를 수집하고 있습니다.
+
1. 수집항목
-필수 입력 : 이메일 혹은 소셜 계정 정보
-자동 수집항목 : IP 정보, MAC정보, 이용 기록, 접속 로그, 쿠키, 접속 기록 등
+사용자 입력
+- 이메일 또는 소셜 미디어 서비스 ID: 사용자의 구분
+- 이름: 콘텐츠에서 작성자의 정보를 보여주기 위함
+- 프로필 사진: 콘텐츠에서 작성자의 정보를 보여주기 위함
+
+자동 수집항목 : IP 정보, 이용 기록, 접속 로그, 쿠키, 접속 기록 등
+
2. 개인정보 수집방법: 홈페이지(회원 가입)
## 4. 개인정보의 파기절차 및 방법
-회사는 원칙적으로 개인정보 수집 및 이용목적이 달성된 후에는 해당 정보를 지체 없이 파기합니다.
-파기절차 및 방법은 다음과 같습니다.
-회사는 원칙적으로 개인정보의 수집 및 이용목적이 달성된 경우에는 지체없이 해당 개인정보를 파기합니다. 파기의 절차 및 방법은 다음과 같습니다.
+이용자는 로그인 후 [설정](https://velog.io/setting) 페이지에서 계정을 탈퇴할 수 있습니다.
+또는, 가입한 계정의 이메일을 사용하여 개인정보 관리 책임자(7조 참고)에게 이메일을 발송하여 탈퇴 요청을 할 수 있습니다.
### 파기절차
-이용자가 회원가입 등을 위해 입력한 정보는 목적 달성 후 별도의 DB에 옮겨져(종이의 경우 별도의 서류) 내부 방침 및 기타 관련 법령에 의한 정보보호 사유에 따라 일정기간(개인정보 보유 및 이용기간 참조) 저장된 후 파기됩니다. 동 개인정보는 법률에 의한 경우가 아니고서는 보유되는 이외의 다른 목적으로 이용되지 않습니다.
-
-### 파기방법
-전자적 파일 형태의 개인정보는 기록을 재생할 수 없는 기술적 방법을 사용하여 삭제합니다.
-종이에 출력된 개인정보는 분쇄기로 분쇄하거나 소각을 통하여 파기합니다.
+탈퇴처리가 진행되면 DB에 있는 계정정보와, 해당 계정으로 작성된 모든 게시글과 댓글이 삭제됩니다.
## 5. 개인정보 제공
회사는 이용자의 개인정보를 원칙적으로 외부에 제공하지 않습니다. 다만, 아래의 경우에는 예외로 합니다.
@@ -53,7 +54,8 @@ const data = {
기타 개인정보침해에 대한 신고나 상담이 필요한 경우에는 아래 회사에 문의하시기 바랍니다.
개인정보침해신고센터 (www.118.or.kr / 118)
-8. 개인정보 취급방침 변경에 관한 사항
+
+## 8. 개인정보 취급방침 변경에 관한 사항
이 개인정보 취급방침은 2018년 8월 23일부터 적용됩니다.
변경이전의 “정보보안 처리방침”을 과거이력 기록`,
terms: `# 서비스 이용약관
@@ -115,6 +117,7 @@ const data = {
1. "회원"의 게시물이 "정보통신망법" 및 "저작권법"등 관련법에 위반되는 내용을 포함하는 경우, 권리자는 관련법이 정한 절차에 따라 해당 게시물의 게시중단 및 삭제 등을 요청할 수 있으며, "회사"는 관련법에 따라 조치를 취하여야 합니다.
2. "회사"는 전항에 따른 권리자의 요청이 없는 경우라도 권리침해가 인정될 만한 사유가 있거나 기타 회사 정책 및 관련법에 위반되는 경우에는 관련법에 따라 해당 게시물에 대해 임시조치 등을 취할 수 있습니다.
3. 본 조에 따른 세부절차는 "정보통신망법" 및 "저작권법"이 규정한 범위 내에서 회사가 정한 게시중단요청서비스에 따릅니다.
+4. "회사"는 [커뮤니티 가이드라인](https://chafgames.notion.site/137fd2be2cdc8046b0f1fb8c93ac26d1?pvs=74)에 따라 게시물의 게시중단 및 삭제처리를 할 수 있습니다.
## 제 10조 권리의 귀속
diff --git a/src/components/post/JobPositions.tsx b/src/components/post/JobPositions.tsx
new file mode 100644
index 00000000..d9e88808
--- /dev/null
+++ b/src/components/post/JobPositions.tsx
@@ -0,0 +1,174 @@
+import { useQuery } from '@apollo/react-hooks';
+import React, { useEffect, useRef, useState } from 'react';
+import { JOB_POSITIONS, JobPosition } from '../../lib/graphql/ad';
+import styled from 'styled-components';
+import VelogResponsive from '../velog/VelogResponsive';
+import Typography from '../common/Typography';
+import { themedPalette } from '../../lib/styles/themes';
+import { ellipsis } from '../../lib/styles/utils';
+import media from '../../lib/styles/media';
+import gtag from '../../lib/gtag';
+import { getJobs, Job } from '../../lib/api/jobs';
+
+type Props = {
+ category: 'frontend' | 'backend' | 'mobile' | 'python' | 'node' | 'ai' | null;
+};
+
+function JobPositions({ category }: Props) {
+ const [isObserved, setIsObserved] = useState(false);
+ const [data, setData] = useState([]);
+
+ const ref = useRef(null);
+ const initializedRef = useRef(false);
+
+ useEffect(() => {
+ getJobs(category || 'general').then((jobs) => {
+ const shuffled = jobs.sort(() => Math.random() - 0.5);
+ const sliced = shuffled.slice(0, 3);
+ setData(sliced);
+ });
+ }, [category]);
+
+ useEffect(() => {
+ const observer = new IntersectionObserver(
+ (entries) => {
+ entries.forEach((entry) => {
+ if (entry.isIntersecting && !initializedRef.current) {
+ setIsObserved(true);
+ }
+ });
+ },
+ {
+ rootMargin: '300px',
+ threshold: 0,
+ },
+ );
+ if (!ref.current) return;
+ observer.observe(ref.current);
+ return () => {
+ observer.disconnect();
+ };
+ }, []);
+
+ const onClick = () => {
+ gtag('event', 'job_position_click');
+ };
+
+ useEffect(() => {
+ if (!isObserved) {
+ return;
+ }
+ gtag('event', 'job_position_view');
+ }, [isObserved]);
+
+ if (!data)
+ return (
+
+
+
+ );
+
+ return (
+
+
+
+ 관련 채용 정보
+
+ {data.map((jobPosition) => (
+
+
+
+
+
+
+
+
+
+
+ {jobPosition.name}
+
+ {jobPosition.summary}
+
+
+ ))}
+
+
+
+ );
+}
+
+const Block = styled(VelogResponsive)`
+ ${media.small} {
+ h4 {
+ padding-left: 1rem;
+ padding-right: 1rem;
+ }
+ }
+`;
+const Container = styled.div`
+ display: flex;
+ gap: 1rem;
+ a {
+ display: block;
+ color: inherit;
+ &:hover {
+ text-decoration: none;
+ color: inherit;
+ }
+ }
+ ${media.small} {
+ padding-left: 0.5rem;
+ padding-right: 0.5rem;
+ gap: 0.5rem;
+ overflow-x: auto;
+ overflow-y: hidden;
+ padding-bottom: 1rem;
+ }
+`;
+
+const Card = styled.div`
+ width: 33.33%;
+ ${media.small} {
+ flex-shrink: 0;
+ width: 60vw;
+ }
+`;
+
+const Thumbnail = styled.img`
+ width: 100%;
+ aspect-ratio: 400 / 292;
+ object-fit: cover;
+ border-radius: 4px;
+`;
+
+const Company = styled.div`
+ display: flex;
+ gap: 0.5rem;
+ img {
+ display: block;
+ width: 16px;
+ height: 16px;
+ }
+ font-size: 10px;
+ align-items: center;
+ color: ${themedPalette.text2};
+ ${ellipsis};
+ margin-bottom: 0.5rem;
+`;
+
+const JobTitle = styled.a`
+ font-size: 14px;
+ font-weight: 600;
+ line-height: 1.25;
+`;
+
+const JobDescription = styled.div`
+ margin-top: 8px;
+ color: ${themedPalette.text2};
+ font-size: 12px;
+ line-height: 1.5;
+`;
+
+export default JobPositions;
diff --git a/src/components/post/LinkedPostItem.tsx b/src/components/post/LinkedPostItem.tsx
index b9bf099d..71d992e3 100644
--- a/src/components/post/LinkedPostItem.tsx
+++ b/src/components/post/LinkedPostItem.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import styled, { css, keyframes } from 'styled-components';
-import palette from '../../lib/styles/palette';
+import { themedPalette } from '../../lib/styles/themes';
import { MdArrowBack, MdArrowForward } from 'react-icons/md';
import { LinkedPost } from '../../lib/graphql/post';
import { ellipsis } from '../../lib/styles/utils';
@@ -38,10 +38,10 @@ const Circle = styled.div<{ right?: boolean }>`
display: flex;
align-items: center;
justify-content: center;
- border: 1px solid ${palette.teal5};
+ border: 1px solid ${themedPalette.primary2};
font-size: 1.5rem;
- color: ${palette.teal5};
- ${props =>
+ color: ${themedPalette.primary2};
+ ${(props) =>
props.right
? css`
margin-left: 1rem;
@@ -53,7 +53,7 @@ const Circle = styled.div<{ right?: boolean }>`
const LinkedPostItemBlock = styled(PlainLink)<{ right?: boolean }>`
cursor: pointer;
- background: ${palette.gray0};
+ background: ${themedPalette.bg_element2};
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.06);
width: 100%;
padding-left: 1rem;
@@ -62,7 +62,7 @@ const LinkedPostItemBlock = styled(PlainLink)<{ right?: boolean }>`
display: flex;
align-items: center;
text-decoration: none;
- ${props =>
+ ${(props) =>
props.right &&
css`
flex-direction: row-reverse;
@@ -70,7 +70,7 @@ const LinkedPostItemBlock = styled(PlainLink)<{ right?: boolean }>`
&:hover {
${Circle} {
animation-duration: 0.35s;
- animation-name: ${props => (props.right ? bounceRight : bounceLeft)};
+ animation-name: ${(props) => (props.right ? bounceRight : bounceLeft)};
animation-fill-mode: forwards;
animation-timing-function: ease-out;
}
@@ -81,23 +81,23 @@ const Text = styled.div<{ right?: boolean }>`
flex: 1;
display: flex;
flex-direction: column;
- align-items: ${props => (props.right ? 'flex-end' : 'flex-start')};
+ align-items: ${(props) => (props.right ? 'flex-end' : 'flex-start')};
line-height: 1;
min-width: 0;
.description {
font-size: 0.75rem;
font-weight: bold;
- color: ${palette.gray7};
+ color: ${themedPalette.text2};
}
h3 {
- ${props =>
+ ${(props) =>
props.right &&
css`
text-align: right;
`};
width: 100%;
font-size: 1.125rem;
- color: ${palette.gray7};
+ color: ${themedPalette.text2};
line-height: 1.15;
margin: 0;
margin-top: 0.5rem;
diff --git a/src/components/post/MobileLikeButton.tsx b/src/components/post/MobileLikeButton.tsx
index 470b678f..3ebb8988 100644
--- a/src/components/post/MobileLikeButton.tsx
+++ b/src/components/post/MobileLikeButton.tsx
@@ -1,7 +1,8 @@
import React from 'react';
import styled, { css } from 'styled-components';
-import palette from '../../lib/styles/palette';
+import { themedPalette } from '../../lib/styles/themes';
import { LikeIcon } from '../../static/svg';
+import media from '../../lib/styles/media';
export type MobileLikeButtonProps = {
likes: number;
@@ -19,11 +20,12 @@ function MobileLikeButton({ likes, onToggle, liked }: MobileLikeButtonProps) {
}
const Button = styled.button<{ liked: boolean }>`
- background: white;
- border: 1px solid ${palette.gray5};
+ display: none;
+ align-items: center;
+ background: ${themedPalette.bg_element1};
+ border: 1px solid ${themedPalette.border2};
padding-left: 0.75rem;
padding-right: 0.75rem;
- display: flex;
align-items: center;
height: 1.5rem;
border-radius: 0.75rem;
@@ -32,23 +34,28 @@ const Button = styled.button<{ liked: boolean }>`
width: 0.75rem;
height: 0.75rem;
margin-right: 0.75rem;
- color: ${palette.gray5};
+ color: ${themedPalette.text3};
}
span {
font-size: 0.75rem;
font-weight: bold;
- color: ${palette.gray5};
+ color: ${themedPalette.text3};
}
- ${props =>
+ ${(props) =>
props.liked &&
css`
- border-color: ${palette.teal5};
- background: ${palette.teal5};
+ border-color: ${themedPalette.primary2};
+ background: ${themedPalette.primary2};
svg,
span {
color: white;
}
`}
+
+ ${media.medium} {
+ display: flex;
+ margin-left: 0.5rem;
+ }
`;
export default MobileLikeButton;
diff --git a/src/components/post/PostBanner.tsx b/src/components/post/PostBanner.tsx
new file mode 100644
index 00000000..49ed9633
--- /dev/null
+++ b/src/components/post/PostBanner.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import HorizontalBanner from '../../containers/post/HorizontalBanner';
+import PostCustomBanner from './PostCustomBanner';
+
+interface PostBannerProps {
+ isDisplayAd?: boolean;
+ customAd: { image: string; url: string } | null;
+}
+
+const PostBanner: React.FC = ({ isDisplayAd, customAd }) => {
+ if (customAd) {
+ return ;
+ }
+ return ;
+};
+
+export default PostBanner;
diff --git a/src/components/post/PostCommentItem.tsx b/src/components/post/PostCommentItem.tsx
index 2ba90466..d71763c8 100644
--- a/src/components/post/PostCommentItem.tsx
+++ b/src/components/post/PostCommentItem.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import styled, { css } from 'styled-components';
import { Comment } from '../../lib/graphql/post';
-import palette from '../../lib/styles/palette';
+import { themedPalette } from '../../lib/styles/themes';
import { formatDate } from '../../lib/utils';
import Typography from '../common/Typography';
import { PlusBoxIcon, MinusBoxIcon } from '../../static/svg';
@@ -10,15 +10,15 @@ import useBoolean from '../../lib/hooks/useBoolean';
import PostRepliesContainer from '../../containers/post/PostRepliesContainer';
import PostEditComment from '../../containers/post/PostEditComment';
import media from '../../lib/styles/media';
-import { Link } from 'react-router-dom';
import MarkdownRender from '../common/MarkdownRender';
import optimizeImage from '../../lib/optimizeImage';
+import VLink from '../common/VLink';
const PostCommentItemBlock = styled.div`
padding-top: 1.5rem;
padding-bottom: 1.5rem;
& + & {
- border-top: 1px solid ${palette.gray2};
+ border-top: 1px solid ${themedPalette.border4};
}
`;
const CommentHead = styled.div`
@@ -49,7 +49,7 @@ const CommentHead = styled.div`
.username {
font-size: 1rem;
font-weight: bold;
- color: ${palette.gray8};
+ color: ${themedPalette.text1};
${media.small} {
font-size: 0.875rem;
}
@@ -58,13 +58,13 @@ const CommentHead = styled.div`
text-decoration: none;
&:hover {
text-decoration: underline;
- color: ${palette.gray7};
+ color: ${themedPalette.text2};
}
}
}
.date {
margin-top: 0.5rem;
- color: ${palette.gray6};
+ color: ${themedPalette.text3};
font-size: 0.875rem;
${media.small} {
font-size: 0.75rem;
@@ -78,11 +78,11 @@ const CommentHead = styled.div`
font-size: 0.75rem;
}
- color: ${palette.gray6};
+ color: ${themedPalette.text3};
span {
cursor: pointer;
&:hover {
- color: ${palette.gray5};
+ color: ${themedPalette.text3};
text-decoration: underline;
}
}
@@ -101,10 +101,10 @@ const CommentText = styled.div<{ deleted: boolean }>`
}
}
- ${props =>
+ ${(props) =>
props.deleted &&
css`
- color: ${palette.gray6};
+ color: ${themedPalette.text3};
font-style: italic;
`}
`;
@@ -114,14 +114,14 @@ const CommentFoot = styled.div`
const TogglerBlock = styled.div`
display: inline-flex;
align-items: center;
- color: ${palette.teal6};
+ color: ${themedPalette.primary1};
font-weight: bold;
svg {
margin-right: 0.5rem;
}
cursor: pointer;
&:hover {
- color: ${palette.teal5};
+ color: ${themedPalette.primary2};
}
`;
@@ -129,6 +129,7 @@ export interface PostCommentItemProps {
comment: Comment;
ownComment: boolean;
onRemove: (id: string) => any;
+ ownPost: boolean;
}
interface TogglerProps {
@@ -152,6 +153,7 @@ const PostCommentItem: React.FC = ({
comment,
ownComment,
onRemove,
+ ownPost,
}) => {
const { id, user, created_at, text, replies_count, deleted, level } = comment;
const [open, onToggleOpen] = useBoolean(false);
@@ -160,13 +162,13 @@ const PostCommentItem: React.FC = ({
// hides comment where it is deleted and its every reply is also deleted
if (deleted && replies_count === 0) return null;
- const velogLink = `/@${user && user.username}`;
+ const velogLink = `/@${user?.username}/posts`;
return (
-
+
= ({
)}
alt="comment-user-thumbnail"
/>
-
+
{user ? (
-
{user.username}
+
{user.profile.display_name}
) : (
'알 수 없음'
)}
@@ -192,6 +194,11 @@ const PostCommentItem: React.FC
= ({
onRemove(id)}>삭제
)}
+ {ownPost && !(ownComment && !editing) && (
+
+ onRemove(id)}>삭제
+
+ )}
{editing ? (
= ({
{level < 2 && (
)}
- {open && }
+ {open && (
+
+ )}
);
diff --git a/src/components/post/PostCommentsList.tsx b/src/components/post/PostCommentsList.tsx
index 4ca8a70b..b7016728 100644
--- a/src/components/post/PostCommentsList.tsx
+++ b/src/components/post/PostCommentsList.tsx
@@ -9,23 +9,28 @@ export interface PostCommentsListProps {
comments: Comment[];
currentUserId: null | string;
onRemove: (id: string) => any;
+ ownPost: boolean;
}
const PostCommentsList: React.FC = ({
comments,
currentUserId,
onRemove,
+ ownPost,
}) => {
return (
- {comments.map(comment => (
-
- ))}
+ {comments
+ .filter((comment) => !!comment.user?.profile)
+ .map((comment) => (
+
+ ))}
);
};
diff --git a/src/components/post/PostCommentsTemplate.tsx b/src/components/post/PostCommentsTemplate.tsx
index 64aa2cdf..97f426cc 100644
--- a/src/components/post/PostCommentsTemplate.tsx
+++ b/src/components/post/PostCommentsTemplate.tsx
@@ -1,12 +1,12 @@
import * as React from 'react';
import styled from 'styled-components';
import VelogResponsive from '../velog/VelogResponsive';
-import palette from '../../lib/styles/palette';
+import { themedPalette } from '../../lib/styles/themes';
import media from '../../lib/styles/media';
const PostCommentsTemplateBlock = styled(VelogResponsive)`
margin-top: 3rem;
- color: ${palette.gray8};
+ color: ${themedPalette.text1};
h4 {
font-size: 1.125rem;
line-height: 1.5;
diff --git a/src/components/post/PostCommentsWrite.tsx b/src/components/post/PostCommentsWrite.tsx
index 2c88fc45..074d0270 100644
--- a/src/components/post/PostCommentsWrite.tsx
+++ b/src/components/post/PostCommentsWrite.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
import styled from 'styled-components';
import TextareaAutosize from 'react-textarea-autosize';
import Button from '../common/Button';
-import palette from '../../lib/styles/palette';
+import { themedPalette } from '../../lib/styles/themes';
import { customFont } from '../../lib/styles/utils';
import media from '../../lib/styles/media';
@@ -17,21 +17,22 @@ const StyledTextarea = styled(TextareaAutosize)`
padding: 1rem;
padding-bottom: 1.5rem;
outline: none;
- border: 1px solid ${palette.gray2};
+ border: 1px solid ${themedPalette.border4};
margin-bottom: 1.5rem;
width: 100%;
border-radius: 4px;
min-height: 6.125rem;
font-size: 1rem;
${customFont};
- color: ${palette.gray9};
+ color: ${themedPalette.text1};
&::placeholder {
- color: ${palette.gray5};
+ color: ${themedPalette.text3};
}
line-height: 1.75;
${media.small} {
margin-bottom: 1rem;
}
+ background: ${themedPalette.bg_element1};
`;
export interface PostCommentsWriteProps {
@@ -58,7 +59,7 @@ const PostCommentsWrite: React.FC = ({
/>
{onCancel && (
-