Skip to content
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] 튜토리얼 로직 추가 #779

Merged
merged 6 commits into from
Dec 13, 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
8 changes: 1 addition & 7 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
.vscode
.DS_Store
.vscode/extensions.json
.vscode/settings.json
.idea
frontend/techpick/.env.production
frontend/techpick/.env.development
frontend/techpick-extension/.env.development
frontend/techpick-extension/.env.production
.idea
17 changes: 10 additions & 7 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,6 @@ logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
Expand All @@ -37,3 +30,13 @@ dist-ssr
*.njsproj
*.sln
*.sw?

# Build output
dist/

# Dependencies
node_modules/

# ENV
.env.development
.env.production
2 changes: 2 additions & 0 deletions frontend/techpick/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.1",
"@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-visually-hidden": "^1.1.0",
"@sentry/nextjs": "8",
"@tanstack/react-query": "^5.59.0",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 23 additions & 7 deletions frontend/techpick/src/app/(signed)/mypage/page.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,6 @@ export const myPageLayoutStyle = style({
backgroundColor: colorVars.gold2,
});

export const buttonSectionLayout = style({
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center',
padding: '8px',
});

export const logoutButtonStyle = style({
width: '120px',
height: '32px',
Expand All @@ -28,4 +21,27 @@ export const logoutButtonStyle = style({
':hover': {
backgroundColor: colorVars.red3,
},

':focus': {
backgroundColor: colorVars.red3,
},
});

export const myPageContentContainerLayoutStyle = style({
display: 'flex',
alignItems: 'start',
justifyContent: 'space-between',
});

export const tutorialReplaySwitchLayoutStyle = style({
display: 'flex',
alignItems: 'center',
gap: '8px',
padding: '16px 0',
});

export const tutorialReplaySwitchLabelStyle = style({
fontSize: '12px',
cursor: 'pointer',
flexShrink: 0,
});
33 changes: 24 additions & 9 deletions frontend/techpick/src/app/(signed)/mypage/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
import { postLogout } from '@/apis/postLogout';
import MyPageContentContainer from '@/components/MyPage/MyPageContentContainer';
import MyPageShareFolderContent from '@/components/MyPage/MyPageShareFolderContent';
import { TutorialReplaySwitch } from '@/components/TutorialReplaySwitch';
import { ROUTES } from '@/constants';
import {
buttonSectionLayout,
logoutButtonStyle,
myPageContentContainerLayoutStyle,
myPageLayoutStyle,
tutorialReplaySwitchLabelStyle,
tutorialReplaySwitchLayoutStyle,
} from './page.css';

export default function MyPage() {
Expand All @@ -22,17 +25,29 @@ export default function MyPage() {

return (
<div className={myPageLayoutStyle}>
<MyPageContentContainer title="내 계정">
<div>내 계정 정보</div>
</MyPageContentContainer>
<div className={myPageContentContainerLayoutStyle}>
<MyPageContentContainer title="내 계정">
<div style={{ display: 'flex' }}>
<div>내 계정 정보</div>
<button className={logoutButtonStyle} onClick={handleLogout}>
로그아웃
</button>
</div>
</MyPageContentContainer>
<div className={tutorialReplaySwitchLayoutStyle}>
<label
htmlFor="tutorial-replay-switch"
className={tutorialReplaySwitchLabelStyle}
>
튜토리얼 다시 보기
</label>
<TutorialReplaySwitch labelTargetId="tutorial-replay-switch" />
</div>
</div>

<MyPageContentContainer title="공개된 폴더">
<MyPageShareFolderContent />
</MyPageContentContainer>
<div className={buttonSectionLayout}>
<button className={logoutButtonStyle} onClick={handleLogout}>
로그아웃
</button>
</div>
</div>
);
}
3 changes: 3 additions & 0 deletions frontend/techpick/src/app/(signed)/recommend/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useEffect, useState } from 'react';
import { getSuggestionRankingPicks } from '@/apis/getSuggestionRankingPicks';
import { FolderContentLayout } from '@/components/FolderContentLayout';
import { RecommendedPickCarousel } from '@/components/RecommendedPickCarousel/RecommendedPickCarousel';
import { TutorialDialog } from '@/components/TutorialDialog';
import {
useClearSelectedPickIdsOnMount,
useFetchTagList,
Expand Down Expand Up @@ -57,6 +58,8 @@ export default function RecommendPage() {

return (
<FolderContentLayout>
<TutorialDialog />

<div className={recommendSectionLayoutStyle}>
<h1 className={recommendPageTitleStyle}>🔥HOT TREND!🔥</h1>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ export const folderTreeHeaderTitleLayout = style({
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
height: '30px',
});
150 changes: 150 additions & 0 deletions frontend/techpick/src/components/TutorialDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
'use client';

import { useEffect, useRef, useState } from 'react';
import Image from 'next/image';
import * as Dialog from '@radix-ui/react-dialog';
import * as Tabs from '@radix-ui/react-tabs';
import * as VisuallyHidden from '@radix-ui/react-visually-hidden';
import { IS_TUTORIAL_SEEN_LOCAL_STORAGE_KEY } from '@/constants';
import { useDisclosure, useLocalStorage } from '@/hooks';
import { Gap } from './Gap';
import {
dialogCloseButtonStyle,
dialogContent,
overlayStyle,
pointTextStyle,
tabContentDescriptionStyle,
tabContentStyle,
tabListStyle,
tabTriggerButtonStyle,
tabTriggerLayoutStyle,
} from './tutorialDialog.css';

const tutorialStepList = ['tutorial-step-1', 'tutorial-step-2'] as const;
type TutorialStepType = (typeof tutorialStepList)[number];
obvoso marked this conversation as resolved.
Show resolved Hide resolved

export function TutorialDialog() {
const [tutorialStep, setTutorialStep] = useState<TutorialStepType>(
tutorialStepList[0]
);
const prevButtonRef = useRef<HTMLButtonElement>(null);
const closeButtonRef = useRef<HTMLButtonElement>(null);
const { isOpen, onClose, onOpen } = useDisclosure();

const {
storedValue: isTutorialSeen,
setValue: setIsTutorialSeen,
isStoredValueLoad,
} = useLocalStorage(IS_TUTORIAL_SEEN_LOCAL_STORAGE_KEY, false);

const onValueChange = (value: string) => {
setTutorialStep(value as TutorialStepType);
};

const handleMouseEnter = (ref: React.RefObject<HTMLButtonElement>) => {
ref.current?.focus();
};

const onCloseTutorial = () => {
setIsTutorialSeen(true);
onClose();
};

useEffect(
function openTutorialForFirstTimeUser() {
if (isStoredValueLoad && !isTutorialSeen) {
onOpen();
}
},
[isStoredValueLoad, isTutorialSeen, onOpen]
);

return (
<Dialog.Root open={isOpen} modal>
<Dialog.Portal>
<Dialog.Overlay className={overlayStyle} />
<Dialog.Content className={dialogContent}>
<VisuallyHidden.Root>
<Dialog.Title>튜토리얼</Dialog.Title>
<Dialog.Description>튜토리얼 설명입니다.</Dialog.Description>
</VisuallyHidden.Root>

<Tabs.Root
value={tutorialStep}
onValueChange={onValueChange}
activationMode={'manual'}
className={tabContentStyle}
>
<Tabs.Content value={tutorialStepList[0]}>
<p className={tabContentDescriptionStyle}>
📌 <span className={pointTextStyle}>추천 페이지</span>에서
원하는 걸<span className={pointTextStyle}> 저장</span>할 수
있어요!
</p>

<Gap verticalSize="gap16" />

<Image
src={'/video/recommendPickMove.gif'}
alt="GIF 설명"
unoptimized
width={659}
height={389}
priority={true}
/>
</Tabs.Content>

<Tabs.Content value={tutorialStepList[1]}>
<p className={tabContentDescriptionStyle}>
<span className={pointTextStyle}>저장한 북마크</span>를 쉽게
<span className={pointTextStyle}> 이동</span>할 수 있어요!
</p>

<Gap verticalSize="gap16" />

<Image
src={'/video/multiSelectPickMove.gif'}
alt="GIF 설명"
unoptimized
width={659}
height={389}
/>
</Tabs.Content>

<Tabs.List className={tabListStyle}>
{tutorialStep === tutorialStepList[0] ? (
<Tabs.Trigger
className={tabTriggerButtonStyle}
value={tutorialStepList[1]}
asChild
>
<button>다음</button>
</Tabs.Trigger>
) : (
<div className={tabTriggerLayoutStyle}>
<Tabs.Trigger
className={tabTriggerButtonStyle}
value={tutorialStepList[0]}
ref={prevButtonRef}
onMouseEnter={() => handleMouseEnter(prevButtonRef)}
asChild
>
<button>이전</button>
</Tabs.Trigger>
<button
onClick={onCloseTutorial}
ref={closeButtonRef}
onMouseEnter={() => handleMouseEnter(closeButtonRef)}
className={dialogCloseButtonStyle}
>
종료
</button>
</div>
)}
</Tabs.List>
</Tabs.Root>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}
32 changes: 32 additions & 0 deletions frontend/techpick/src/components/TutorialReplaySwitch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use client';

import * as Switch from '@radix-ui/react-switch';
import { IS_TUTORIAL_SEEN_LOCAL_STORAGE_KEY } from '@/constants';
import { useLocalStorage } from '@/hooks';
import { switchRoot, switchThumb } from './tutorialReplaySwitch.css';

export function TutorialReplaySwitch({
labelTargetId,
}: TutorialReplaySwitchProps) {
const { setValue: setIsTutorialSeen } = useLocalStorage(
IS_TUTORIAL_SEEN_LOCAL_STORAGE_KEY,
false
);

return (
<Switch.Root
className={switchRoot}
defaultChecked={false}
onCheckedChange={(isTutorialSeen) => {
setIsTutorialSeen(!isTutorialSeen);
}}
id={labelTargetId}
>
<Switch.Thumb className={switchThumb} />
</Switch.Root>
);
}

interface TutorialReplaySwitchProps {
labelTargetId?: string;
}
Loading
Loading