Skip to content

Commit

Permalink
refactor: 언어 관련 기능 및 UI 적용
Browse files Browse the repository at this point in the history
- LanguageProvider context를 통해 동적 언어 변경
- LanguageFilter 컴포넌트에서 선택된 언어에 따라 언어가 변경되도록 구현
  • Loading branch information
KimJi-An committed Nov 4, 2024
1 parent 054d717 commit bbaa072
Show file tree
Hide file tree
Showing 12 changed files with 90 additions and 38 deletions.
33 changes: 33 additions & 0 deletions src/components/providers/Language.provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { userLocalStorage } from '@/utils/storage';
import { createContext, ReactNode, useContext, useEffect, useState } from 'react';

type LanguageContextType = {
language: string | null;
setLanguage: (language: string | null) => void;
};

const LanguageContext = createContext<LanguageContextType | undefined>(undefined);

export const LanguageProvider = ({ children }: { children: ReactNode }) => {
const [language, setLanguage] = useState<string | null>(() => userLocalStorage.getLanguage());

useEffect(() => {
const changeLanguage = () => {
const updatedLanguage = userLocalStorage.getLanguage();
setLanguage(updatedLanguage);
};

window.addEventListener('storage', changeLanguage);
return () => window.removeEventListener('storage', changeLanguage);
}, []);

return <LanguageContext.Provider value={{ language, setLanguage }}>{children}</LanguageContext.Provider>;
};

export const useLanguage = () => {
const context = useContext(LanguageContext);
if (!context) {
throw new Error('useLanguage must be used within a LanguageProvider');
}
return context;
};
12 changes: 5 additions & 7 deletions src/components/providers/User.provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,25 @@ import { createContext, useContext, useEffect, useState } from 'react';
import { UserData } from '@/types';
import { userLocalStorage } from '@/utils/storage';

interface UserContextType {
type UserContextType = {
user: UserData | undefined;
setUser: React.Dispatch<React.SetStateAction<UserData | undefined>>;
}
};

export const UserContext = createContext<UserContextType | undefined>(undefined);

export const UserProvider = ({ children }: { children: ReactNode }) => {
const [user, setUser] = useState<UserData | undefined>(() => userLocalStorage.getUser());

useEffect(() => {
const changeStorage = () => {
const changeUser = () => {
const updatedUser = userLocalStorage.getUser();
setUser(updatedUser);
};

window.addEventListener('storage', changeStorage);
window.addEventListener('storage', changeUser);

return () => {
window.removeEventListener('storage', changeStorage);
};
return () => window.removeEventListener('storage', changeUser);
}, []);

return <UserContext.Provider value={{ user, setUser }}>{children}</UserContext.Provider>;
Expand Down
11 changes: 7 additions & 4 deletions src/components/providers/index.provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ import Modals from '../common/Modal/Modals';
import { QueryClientProvider } from '@tanstack/react-query';
import { queryClient } from '@/apis/instance';
import { UserProvider } from './User.provider';
import { LanguageProvider } from './Language.provider';

export default function AppProviders({ children }: { children: ReactNode }) {
return (
<GlobalStylesProvider>
<QueryClientProvider client={queryClient}>
<ModalsProvider>
<UserProvider>
{children}
<Modals />
</UserProvider>
<LanguageProvider>
<UserProvider>
{children}
<Modals />
</UserProvider>
</LanguageProvider>
</ModalsProvider>
</QueryClientProvider>
</GlobalStylesProvider>
Expand Down
2 changes: 1 addition & 1 deletion src/features/applicants/ApplicantList/ApplicantList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default function ApplicantList({ applicantList }: Props) {
지원자 목록
</Typo>
<Typo element="span" size="16px">
{applicantList.length}
{applicantList.length}
</Typo>
</Flex>
<ApplicantsTable applicantList={applicantList} />
Expand Down
17 changes: 13 additions & 4 deletions src/features/layout/Header/components/LanguageFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import theme from '@/assets/theme';
import { Select, Icon, List } from '@/components/common';
import useGlobalSelect from '@/components/common/Select/hooks/useGlobalSelect';
import { useLanguage } from '@/components/providers/Language.provider';
import { responsiveStyle } from '@/utils/responsive';

type LanguageOptionType = {
value: string;
text: string;
};

const triggerStyle = {
minWidth: '80px',
fontSize: '16px',
Expand Down Expand Up @@ -30,18 +35,22 @@ const languageOptions = [
];

export default function LanguageFilter() {
const { selectedOption, handleSelect } = useGlobalSelect(languageOptions[0]);
const { language, setLanguage } = useLanguage();

const changeLanguage = (option: LanguageOptionType) => {
setLanguage(option.value);
};

return (
<Select.Root>
<Select.Trigger icon={<Icon.Arrow.DownBlue />} css={triggerStyle}>
{selectedOption.text}
{languageOptions.find((opt) => opt.value === language)?.text}
</Select.Trigger>
<Select.Content>
<List
items={languageOptions}
renderItem={(option) => (
<Select.Option key={option.value} value={option.value} onClick={() => handleSelect(option)}>
<Select.Option key={option.value} value={option.value} onClick={() => changeLanguage(option)}>
{option.text}
</Select.Option>
)}
Expand Down
11 changes: 8 additions & 3 deletions src/features/recruitments/RecruitmentInfo/RecruitmentInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@ import {
infoGroupStyle,
recruitmentFlexStyle,
} from './RecruitmentInfo.styles';
import { useLanguage } from '@/components/providers/Language.provider';

type Props = Pick<RecruitmentItem, 'image' | 'companyName' | 'koreanTitle' | 'area' | 'salary'>;
type Props = Omit<RecruitmentItem, 'recruitmentId' | 'workHours' | 'hiring'>;

export default function RecruitmentInfo({ image, companyName, koreanTitle, vietnameseTitle, area, salary }: Props) {
const { language } = useLanguage();

const title = language === 'korean' ? koreanTitle : vietnameseTitle;

export default function RecruitmentInfo({ image, companyName, koreanTitle, area, salary }: Props) {
return (
<Flex justifyContent="space-between" alignItems="center" gap={{ y: '100px' }} css={recruitmentFlexStyle}>
<ImageWrapper>
Expand All @@ -24,7 +29,7 @@ export default function RecruitmentInfo({ image, companyName, koreanTitle, area,
<Flex css={infoFlexStyle}>
<Typo color="blue">{companyName}</Typo>
<Typo element="h3" size="20px">
{koreanTitle}
{title}
</Typo>
<Flex css={infoGroupStyle}>
<Flex alignItems="center" gap={{ x: '12px' }}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useNavigate, useParams } from 'react-router-dom';
import ROUTE_PATH from '@/routes/path';
import { useCloseRecruitment } from '@/apis/recruitments/hooks/useCloseRecruitment';
import { useState } from 'react';
import { useLanguage } from '@/components/providers/Language.provider';

type Props = {
recruitmentList: RecruitmentItem[];
Expand All @@ -15,6 +16,7 @@ export default function RecruitmentsTable({ recruitmentList }: Props) {
const { companyId } = useParams();
const mutation = useCloseRecruitment();
const [closedRecruitment, setClosedRecruitment] = useState<{ [key: number]: boolean }>({});
const { language } = useLanguage();

const handleApplicantClick = (companyId: string, recruitmentId: number) => {
navigate(
Expand All @@ -30,10 +32,6 @@ export default function RecruitmentsTable({ recruitmentList }: Props) {
[recruitmentId]: true,
}));
},
onError: () => {
// TODO: 에러 처리 결정
alert('마감 실패');
},
});
};

Expand All @@ -58,7 +56,7 @@ export default function RecruitmentsTable({ recruitmentList }: Props) {
{recruitment.companyName}
</Typo>
<Typo element="p" size="16px">
{recruitment.koreanTitle}
{language === 'vietnamese' ? recruitment.vietnameseTitle : recruitment.koreanTitle}
</Typo>
</Flex>
<Flex css={buttonGroupStyle}>
Expand Down
1 change: 0 additions & 1 deletion src/features/registerVisa/VisaRegistrationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ export default function VisaRegistrationForm() {
<Modal
textChildren="등록이 완료되었습니다."
buttonChildren={<Button onClick={closeModal}>확인</Button>}
/* onClose 부분 추후 수정 예정 */
onClose={closeModal}
/>
)}
Expand Down
2 changes: 1 addition & 1 deletion src/pages/applicants/Applicants.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const recruitment = {
recruitmentId: 1,
image: CompanyImage,
koreanTitle: '쿠팡 유성점에서 아르바이트 모집합니다.',
vietnameseTitle: '',
vietnameseTitle: 'Coupang đang tuyển dụng làm việc bán thời gian tại chi nhánh Yuseong.',
companyName: '쿠팡 유성점',
salary: 50000000,
workHours: '',
Expand Down
1 change: 1 addition & 0 deletions src/pages/applicants/Applicants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default function Applicants({ recruitment, applicantList }: MyApplicantPr
image={recruitmentData.image}
companyName={recruitmentData.companyName}
koreanTitle={recruitmentData.koreanTitle}
vietnameseTitle={recruitmentData.vietnameseTitle}
area={recruitmentData.area}
salary={recruitmentData.salary}
/>
Expand Down
6 changes: 3 additions & 3 deletions src/pages/myCompany/MyCompany.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const recruitmentList = [
recruitmentId: 1,
image: CompanyLogo,
koreanTitle: '쿠팡 유성점에서 아르바이트 모집합니다.',
vietnameseTitle: '',
vietnameseTitle: 'Coupang đang tuyển dụng làm việc bán thời gian tại chi nhánh Yuseong.',
companyName: '쿠팡 유성점',
salary: 100000,
workHours: '',
Expand All @@ -25,7 +25,7 @@ export const recruitmentList = [
recruitmentId: 2,
image: CompanyLogo,
koreanTitle: '쿠팡 유성점에서 아르바이트 모집합니다.',
vietnameseTitle: '',
vietnameseTitle: 'Coupang đang tuyển dụng làm việc bán thời gian tại chi nhánh Yuseong.',
companyName: '쿠팡 유성점',
salary: 100000,
workHours: '',
Expand All @@ -36,7 +36,7 @@ export const recruitmentList = [
recruitmentId: 3,
image: CompanyLogo,
koreanTitle: '쿠팡 유성점에서 아르바이트 모집합니다.',
vietnameseTitle: '',
vietnameseTitle: 'Coupang đang tuyển dụng làm việc bán thời gian tại chi nhánh Yuseong.',
companyName: '쿠팡 유성점',
salary: 100000,
workHours: '',
Expand Down
24 changes: 15 additions & 9 deletions src/utils/storage.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
import { UserData } from '@/types';

const USER_KEY = 'user';
const TOKEN_KEY = 'token';
const STORAGE_KEYS = {
USER: 'user',
TOKEN: 'token',
LANGUAGE: 'language',
};

export const userLocalStorage = {
getUser: (): UserData | undefined => {
const user = localStorage.getItem(USER_KEY);
const user = localStorage.getItem(STORAGE_KEYS.USER);
return user ? JSON.parse(user) : undefined;
},
setUser: (user: UserData) => {
localStorage.setItem(USER_KEY, JSON.stringify(user));
localStorage.setItem(STORAGE_KEYS.USER, JSON.stringify(user));
},
removeUser: () => {
localStorage.removeItem(USER_KEY);
},
getToken: (): string | null => {
return localStorage.getItem(TOKEN_KEY);
localStorage.removeItem(STORAGE_KEYS.USER);
},

removeToken: () => {
localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem(STORAGE_KEYS.TOKEN);
},

getLanguage: (): string | null => {
const language = localStorage.getItem(STORAGE_KEYS.LANGUAGE);
return language ? JSON.parse(language).state.selectedOption.value : null;
},
};

0 comments on commit bbaa072

Please sign in to comment.