Skip to content

Commit

Permalink
[Style] 모립세트 페이지 퍼블리싱 (#226)
Browse files Browse the repository at this point in the history
* [style] 사이드바 모립세트 뷰 추가 (#220)

* [style] 모립세트 경로 추가 (#220)

* [style] tab 스타일 변경사항 반영 (#220)

* [style] 모립세트 페이지 퍼블리싱 (#220)

* [ Style ] 모립세트 컴포넌트 구현 (#225)

* refactor: SideBoxTemporary 컴포넌트 삭제

* feat: MoribSetCOntainer 컴포넌트 구현

* style: 허용할 사이트 영역 홀,짝수번째 배경화면 다르게 수정

* feat: 취소 버튼 온클릭 핸들러 추가

* refactor: MoribSetContainer가 열린 상태에서 다른 요소 클릭 가능하게 수정

* feat: 배열 별도의 상수 파일로 분리 및 기능 구현

* refactor: 아이콘 url도 ALLOW_SITE_LIST에서 관리

* codereview: 대원 코드리뷰 반영

* refactor: 모립세트 워딩 허용서비스 세트로 수정

* codereview: 대원, 상아 코드리뷰 반영

* feat: 툴팁 생성

* refactor: 타이머페이지 툴팁 삭제

* refacotr: 툴팁 isAllowedServiceVisible이 true일 때만 화면에 등장하도록 수정

* codereview: BoxAllowedService에서 PopoverAllowedService로 네이밍 변경

* [ Style ] 설정 뷰 내의 경고 모달 뷰 구현 (#227)

* feat: alert모달 내 버튼 공통 컴포넌트 생성 (#222)

* feat: ModalContentsAlert 생성 (#222)

* feat: input창 에러일 때 추가

* fix: 필요없는 코드 삭제 (#222)

* refactor: 약간의 로직분리 .. (#222)

* code review: user email prop 추가, 모달 컴포넌트 하나의 객체로 export 하도록 수정

* style: 스타일 누락된 것 추가

* refactor: userEmail props 추가 누락된 부분 수정

* [ Style ] 온보딩 뷰 구현 (#228)

* refactor: home/ not found 페이지에 쓰이는 사이드바 컴포넌트 리팩토링 (#224)

- 세팅모달, 기존 SidebarHome에서 쓰이는 컴포넌트 구조 배럴파일 방식으로 변경
- HomePage, NotFoundePage 두 곳에서 쓰이므로 shared로 위치 이동
- Sidebar에서 쓰이는 svg는 Sidebar 폴더 아래로 이동

* refactor: 홈페이지 구조 absolute를 사용해서 변경 (#224)

* feat: 온보딩 페이지 라우팅 (#224)

* refactor: 설정 모달 위치 변경 (#224)

* refactor: 사이드바 위치 변경 (#224)

* refactor: 레이아웃 생성 및 적용 (#224)

* refactor: 폰트 적용 방식 자동완성 될 수 있도록 변경 (#224)

* feat: 사용하는 아이콘 다운로드 및 export 설정 (#224)

* feat: StartStep, FieldStep 구현 (#224)

* feat: ServiceStep 구현 및 export (#224)

* chore: 상수 분리 및 컴포넌트 디자인 일부 수정 (#224)

* feat: useFunnel 훅 생성 및 온보딩 페이지에 적용 (#224)

* feat: Step 컴포넌트 컨벤션에 맞게 네이밍 변경 (#224)

* feat: 온보딩 페이지 퍼블리싱 (#224)

* feat: input 상태 ServiceStep 컴포넌트에 연결 (#224)

* style: StepField 컴포넌트 화면 너비에 따라 패딩값 일부 수정 (#224)

* feat: ServiceStep 버튼에 disabled 속성 추가 및 엔터 눌렀을 때 입력되게 끔 로직 추가 (#224)

* feat: svg 아이콘 폴더 shared로 변경, assets 아래 페이지별 폴더를 만들어 관리 (#224)

* feat: 바뀐 svg들의 위치에 맞게 import 경로 변경 및 BoxAllowedService 버튼에 홈으로 이동하는 로직 추가 (#224)

* style: 반응형 고려하여 홈 페이지 컴포넌트 일부 스타일 수정 (#224)

- 미완성입니다요

* refactor: truncate 활용해서 스타일 재적용 (#224)

* refactor: StepField.tsx p태그에서 h2태그로 변경 (#224)

* chore: 브라우저 리스트 업데이트하면서 일부 패키지 버전업 (#224)

* refactor: StepService.tsx 인풋 상태 input -> inputURL로 수정 (#224)

* [Refactor] home 페이지 폴더구조 변경 (#231)

* feat: home 페이지 폴더구조 변경 (#230)

* feat: 공통적으로 쓰이는 BoxTodo, ButtonTogoToggle -> shared로 이동 (#230)

* feat: shared로 이동한 컴포넌트 경로 변경 (#230)

* refactor: ModalContents 폴더 구조 변경 (#230)

* feat: import 문 구분을 위해 폴더 구조 배럴파일 구조로 변경 (#230)

* feat: commit 누락된 부분 저장 (#230)

* feat: 홈페이지 import 문 수정 (#230)

* refactor: atoms 컴포넌트 용도에 맞게 폴더 이동 및 네이밍 컨벤션에 맞게 변경 (#230)

* refactor: 바뀐 컴포넌트 경로에 맞게 import문 수정 (#230)

* refactor: 홈에서만 쓰이는 hook 폴더 이동 및 훅을 사용하는 컴포넌트의 import문 변경 (#230)

* refactor: ButtonSVG 사용 불필요하여 삭제 (#230)

* refactor: 상아 피드백 반영해서 UI 분류 페이지 제거 모든 컴포넌트 depth 1로 고정 (#230)

* refactor: hook import 경로 수정 (#230)

* refactor: 로그인 페이지 구조 변경 및 불필요한 templates 레거시 코드 삭제 (#230) (#232)

* [Refactor] 타이머 페이지 폴더 구조 변경 (#233)

* refactor: timer 컴포넌트/훅 컨벤션에 맞게 정리 및 불필요 컴포넌트/훅 삭제 (#230)

* refactor: 불필요한 templates 컴포넌트 삭제 (#230)

* Merge branch 'develop' of github.com:morib-in/Morib-Client into refactor/#230/timer-folder-structure

* [Refactor] 온보딩 페이지 폴더구조 변경 (#234)

* refactor: 온보딩 페이지 컴포넌트 폴더 삭제 및 컴포넌트명으로 폴더 생성하여 적용 (#230)

* refactor: 상아 피드백 반영해서 컴포넌트 depth 1로 고정 (#230)

* [style] 컬러팔레트 네이밍 추가 및 색상 공통 컴포넌트 생성 (#220)

* [refactor] 색상 네이밍에 맞춰 컬러 팔레트 수정 (#220)

* [feat] url 중복 유효성 검사 로직 추가 (#220)

* [refactor] 드롭다운 클릭 시 이벤트 전파 방지 (#220)

* [style] 카테고리 내 허용서비스 컴포넌트 분리 (#220)

* [code review] 코드리뷰 반영 (#220)

* [style] moribSet -> allowedService 네이밍 변경 (#220)

* [chore] 리베이스 충돌 해결 (#220)

* [style] sideBar 삭제 후 Layout 적용 (#220)

* [code review] 코드리뷰 반영 (#220)

* [style] 사이드바 홈, 허용서비스뷰 추가 (#220)

* [style] 드롭다운 이벤트 전파 방지 적용 (#220)

---------

Co-authored-by: 김건휘 <66071954+KIMGEONHWI@users.noreply.github.com>
Co-authored-by: Hanseo Kim <108131226+seueooo@users.noreply.github.com>
Co-authored-by: suwonthugger <127329855+suwonthugger@users.noreply.github.com>
  • Loading branch information
4 people authored Dec 29, 2024
1 parent c137655 commit a88a4a7
Show file tree
Hide file tree
Showing 25 changed files with 771 additions and 9 deletions.
134 changes: 134 additions & 0 deletions src/pages/MoribSetPage/AllowedServicePage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { useEffect, useRef, useState } from 'react';

import ModalWrapper, { ModalWrapperRef } from '@/shared/components/ModalWrapper';

import useClickOutside from '@/shared/hooks/useClickOutside';

import BellIcon from '@/shared/assets/svgs/bell.svg?react';
import FriendSettingIcon from '@/shared/assets/svgs/friend_setting.svg?react';

import ModalContentsFriends from '@/pages/HomePage/ModalContentsFriends/ModalContentsFriends';

import BoxMakeAllowedService from './components/Box/BoxMakeAllowedService';
import BoxRecommendService from './components/Box/BoxRecommendService';
import CategoryAllowedService from './components/CategoryAllowedService';
import { AllowedService, UrlInfo } from './types';

const AllowedServicePage = () => {
const friendsModalRef = useRef<ModalWrapperRef>(null);
const friendModalContentRef = useRef<HTMLDivElement>(null);

const [allowedServices, setAllowedServices] = useState<AllowedService[]>([
{
id: Date.now(),
allowedServiceName: '',
selectedColor: 'bg-gray-bg-07',
urlList: [],
},
]);
const [activeAllowedServiceId, setActiveAllowedServiceId] = useState<number>(allowedServices[0].id);

const handleAddAllowedService = () => {
const newSet: AllowedService = {
id: Date.now(),
allowedServiceName: '',
selectedColor: 'bg-gray-bg-07',
urlList: [],
};
setAllowedServices((prev) => [...prev, newSet]);
setActiveAllowedServiceId(newSet.id);
};

const handleUpdateAllowedService = <T extends keyof AllowedService>(id: number, key: T, value: AllowedService[T]) => {
setAllowedServices((prev) =>
prev.map((set) =>
set.id === id
? {
...set,
[key]: value,
}
: set,
),
);
};

const handleDeleteAllowedService = (id: number) => {
const updatedAllowedServices = allowedServices.filter((allowedService) => allowedService.id !== id);
if (updatedAllowedServices.length === 0) {
return;
}
setAllowedServices(updatedAllowedServices);
};

const handleAddUrlToAllowedService = (urlInfo: UrlInfo) => {
setAllowedServices((prev) =>
prev.map((set) =>
set.id === activeAllowedServiceId
? {
...set,
urlList: [...set.urlList, urlInfo],
}
: set,
),
);
};

useEffect(() => {
if (allowedServices.length > 0 && !allowedServices.find((set) => set.id === activeAllowedServiceId)) {
setActiveAllowedServiceId(allowedServices[allowedServices.length - 1].id);
}
}, [allowedServices, activeAllowedServiceId]);

const activeAllowedService = allowedServices.find((set) => set.id === activeAllowedServiceId);

const handleOpenFriendsModal = () => {
friendsModalRef.current?.open();
};
const handleCloseFriendsModal = () => {
friendsModalRef.current?.close();
};

useClickOutside(friendModalContentRef, handleCloseFriendsModal);

return (
<div className="flex h-screen w-screen bg-gray-bg-01">
<div className={`absolute right-[13.4rem] top-[5.4rem] flex gap-[0.8rem]`}>
<button onClick={handleOpenFriendsModal}>
<FriendSettingIcon className="rounded-[1.6rem] hover:bg-gray-bg-04 active:bg-gray-bg-05" />
</button>
<button>
<BellIcon className="rounded-[1.6rem] hover:bg-gray-bg-04 active:bg-gray-bg-05" />
</button>
</div>

<div className="my-[4.2rem] flex">
<CategoryAllowedService
allowedServices={allowedServices}
activeAllowedServiceId={activeAllowedServiceId}
setActiveAllowedServiceId={setActiveAllowedServiceId}
addAllowedService={handleAddAllowedService}
deleteAllowedService={handleDeleteAllowedService}
/>

<div className="ml-[4.2rem] mt-[6.8rem]">
{activeAllowedService && (
<BoxMakeAllowedService
allowedService={activeAllowedService}
updateAllowedService={(key, value) => handleUpdateAllowedService(activeAllowedServiceId, key, value)}
/>
)}

<div className="mt-[4.2rem]">
<BoxRecommendService addUrlToAllowedService={handleAddUrlToAllowedService} />
</div>
</div>
</div>

<ModalWrapper ref={friendsModalRef} backdrop={true}>
<ModalContentsFriends ref={friendModalContentRef} />
</ModalWrapper>
</div>
);
};

export default AllowedServicePage;
71 changes: 71 additions & 0 deletions src/pages/MoribSetPage/components/Box/BoxAllowedServiceItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import Dropdown from '@/shared/components/Dropdown';

import IconMeatBall from '@/shared/assets/svgs/todo_meatball_default.svg?react';

import { AllowedService } from './../../types';

interface BoxAllowedServiceItemProps {
allowedService: AllowedService;
activeAllowedServiceId: number | null;
setActiveAllowedServiceId: (id: number) => void;
deleteAllowedService: (id: number) => void;
maxIconsToShow: number;
}

const BoxAllowedServiceItem = ({
allowedService,
activeAllowedServiceId,
setActiveAllowedServiceId,
deleteAllowedService,
maxIconsToShow,
}: BoxAllowedServiceItemProps) => {
const additionalIconsCount =
allowedService.urlList.length > maxIconsToShow ? allowedService.urlList.length - maxIconsToShow : 0;

return (
<div
key={allowedService.id}
className={`mb-[0.8rem] h-[8rem] w-[36.6rem] flex-col items-start justify-end rounded-[8px] bg-gray-bg-01 p-[1.4rem] ${activeAllowedServiceId === allowedService.id ? 'border-[1px] border-mint-01' : ''}`}
onClick={() => setActiveAllowedServiceId(allowedService.id)}
>
<div className="flex content-between items-center self-stretch">
<div className="flex w-[30.8rem] items-center gap-[0.6rem]">
<div className={`h-[1.4rem] w-[1.4rem] rounded-[70px] ${allowedService.selectedColor}`} />
<h2
className={`w-[28.8rem] truncate body-semibold-16 ${allowedService.allowedServiceName ? 'text-white' : 'text-gray-03'}`}
>
{allowedService.allowedServiceName || '모립세트 이름을 입력해주세요.'}
</h2>
</div>

<Dropdown.Root>
<Dropdown.Trigger>
<IconMeatBall className="cursor-pointer hover:rounded-full hover:bg-gray-bg-05" />
</Dropdown.Trigger>
<Dropdown.Content boxShadow="shadow-none" className="absolute left-[-10rem] w-[12.4rem] overflow-x-hidden">
<Dropdown.Item
label="할 일 삭제"
textColor="red"
onClick={() => {
deleteAllowedService(allowedService.id);
}}
/>
</Dropdown.Content>
</Dropdown.Root>
</div>

<div className="mt-[0.4rem] flex items-center gap-[0.6rem]">
{allowedService.urlList.slice(0, maxIconsToShow).map(({ faviconUrl }, url) => (
<img key={url} src={faviconUrl} alt="favicon" className="h-[2rem] w-[2rem] rounded-full" />
))}
{additionalIconsCount > 0 && (
<div className="body-detail-reg-12 flex h-[2rem] w-[2rem] items-center justify-center rounded-[57px] bg-date-active text-white">
+{additionalIconsCount}
</div>
)}
</div>
</div>
);
};

export default BoxAllowedServiceItem;
28 changes: 28 additions & 0 deletions src/pages/MoribSetPage/components/Box/BoxEachUrlInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ReactNode } from 'react';

import { UrlInfo } from './../../types';

interface BoxEachUrlInfoProps {
urlInfo: UrlInfo;
children: ReactNode;
}

const BoxEachUrlInfo = ({ urlInfo, children }: BoxEachUrlInfoProps) => {
return (
<>
<div className="body-med-16 flex w-[19.4rem] items-center truncate pl-[1rem] text-left text-white">
<img src={urlInfo.faviconUrl} alt="favicon" className="mr-[0.6rem] h-[2rem] w-[2rem]" />
<p className="truncate">{urlInfo.siteName}</p>
</div>
<div className="body-reg-16 w-[31rem] text-left text-gray-04">
<p className="truncate">{urlInfo.page}</p>
</div>
<div className="body-reg-16 w-[79.6rem] text-left text-gray-04">
<p className="truncate">{urlInfo.url}</p>
</div>
<div> {children} </div>
</>
);
};

export default BoxEachUrlInfo;
101 changes: 101 additions & 0 deletions src/pages/MoribSetPage/components/Box/BoxMakeAllowedService.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { ChangeEvent, useState } from 'react';

import ArrowSVGBtn from '@/shared/components/Button/ButtonArrowSVG/ButtonArrowSVG';
import CategoryTabSelect from '@/shared/components/CategoryTabSelect';

import { Direction } from '@/shared/types/global';

import { CATEGORY_MODALTABS } from '@/shared/constants/tabSelections';

import { AllowedService } from './../../types';
import ColorPallete from './../ColorPallete';
import InputAllowedServiceUrl from './../Input/InputAllowedServiceUrl';
import TableAllowedService from './../TableAllowedService';
import BoxUrlList from './BoxUrlList';

interface BoxMakeAllowedServiceProps {
allowedService: AllowedService;
updateAllowedService: <T extends keyof AllowedService>(key: T, value: AllowedService[T]) => void;
}

const BoxMakeAllowedService = ({ allowedService, updateAllowedService }: BoxMakeAllowedServiceProps) => {
const { allowedServiceName, selectedColor, urlList } = allowedService;
const [isPaletteOpen, setIsPaletteOpen] = useState(false);
const [selectedTabId, setSelectedTabId] = useState(CATEGORY_MODALTABS[0].id);

const handleColorPalleteOpen = () => {
setIsPaletteOpen((prev) => !prev);
};

const handleColorSelect = (color: string) => {
updateAllowedService('selectedColor', color);
setIsPaletteOpen(false);
};

const handleMoribNameChange = (e: ChangeEvent<HTMLInputElement>) => {
updateAllowedService('allowedServiceName', e.target.value);
};

const handleTabChange = (tab: number) => {
setSelectedTabId(tab);
};
const handleDeleteUrlInfo = (urlToDelete: string) => {
const updatedUrlList = urlList.filter((urlInfo) => urlInfo.url !== urlToDelete);
updateAllowedService('urlList', updatedUrlList);
};

const addUrl = (url: string) => {
try {
const domain = new URL(url).hostname;
const faviconUrl = `https://www.google.com/s2/favicons?domain=${domain}`;

updateAllowedService('urlList', [
...urlList,
{
siteName: domain.replace(/^www\./, '').replace(/\.[a-z]{2,}$/, ''),
page: domain, //api로 받은 값
url,
faviconUrl,
},
]);
} catch (error) {
console.error('Invalid URL:', error);
}
};

return (
<div className="relative flex w-[132rem] flex-col items-start gap-[2rem]">
<div className="flex items-center gap-[1.4rem]">
<div className="flex items-center gap-[0.6rem]">
<div className={`h-[3rem] w-[3rem] rounded-[31px] ${selectedColor}`} />
<ArrowSVGBtn direction={Direction.DOWN} onClick={handleColorPalleteOpen} />
</div>
<input
value={allowedServiceName}
onChange={handleMoribNameChange}
placeholder="모립세트 이름을 입력해주세요."
className="title-bold-36 placeholder-text-gray-03 w-[114rem] flex-shrink-0 bg-transparent text-white focus:outline-none"
/>
</div>

<ColorPallete isOpen={isPaletteOpen} onSelectColor={handleColorSelect} />

<div className="flex items-center gap-[0.3rem] self-stretch">
<CategoryTabSelect tabs={CATEGORY_MODALTABS} handleTabChange={handleTabChange} selectedTabId={selectedTabId} />
</div>

{selectedTabId === CATEGORY_MODALTABS[0].id && (
<>
<div className="relative w-full">
<InputAllowedServiceUrl urlList={urlList} addUrl={addUrl} />
</div>
<TableAllowedService urlList={urlList}>
<BoxUrlList urlList={urlList} onDelete={handleDeleteUrlInfo} />
</TableAllowedService>
</>
)}
</div>
);
};

export default BoxMakeAllowedService;
Loading

0 comments on commit a88a4a7

Please sign in to comment.