Skip to content
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
22 changes: 22 additions & 0 deletions src/api/users/getAlias.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { apiClient } from '../index';

export interface AliasChoice {
aliasName: string;
categoryName: string;
imageUrl: string;
color: string;
}

export interface GetAliasResponse {
success: boolean;
code: number;
message: string;
data: {
aliasChoices: AliasChoice[];
};
}

export const getAlias = async (): Promise<GetAliasResponse> => {
const response = await apiClient.get<GetAliasResponse>('/users/alias');
return response.data;
};
19 changes: 19 additions & 0 deletions src/api/users/postNickname.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { apiClient } from '../index';

export interface PostNicknameRequest {
nickname: string;
}

export interface PostNicknameResponse {
isSuccess: boolean;
code: number;
message: string;
data: { isVerified: boolean };
}

export const postNickname = async (nickname: string): Promise<PostNicknameResponse> => {
const response = await apiClient.post<PostNicknameResponse>('/users/nickname', {
nickname,
});
return response.data;
};
20 changes: 20 additions & 0 deletions src/api/users/postSignup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { apiClient } from '../index';

export interface PostSignupRequest {
aliasName: string;
nickName: string;
}

export interface PostSignupResponse {
success: boolean;
code: number;
message: string;
data: {
userId: number;
};
}

export const postSignup = async (data: PostSignupRequest): Promise<PostSignupResponse> => {
const response = await apiClient.post<PostSignupResponse>('/users/signup', data);
return response.data;
};
50 changes: 26 additions & 24 deletions src/pages/login/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,32 @@ import KaKao from '../../assets/login/kakao.svg';
import Google from '../../assets/login/google.svg';
import { Wrapper } from '@/components/common/Wrapper';

const Login = () => {
const handleKakaoLogin = () => {
// 직접 카카오 로그인 URL로 리다이렉션
window.location.href = `${import.meta.env.VITE_API_BASE_URL}/oauth2/authorization/kakao`;
};

const handleGoogleLogin = () => {
// 직접 구글 로그인 URL로 리다이렉션
window.location.href = `${import.meta.env.VITE_API_BASE_URL}/oauth2/authorization/google`;
};

return (
<Wrapper>
<img src={logo} />
<ButtonBox>
<SocialButton onClick={handleKakaoLogin} bg="#fee500">
<img src={KaKao} /> 카카오계정 로그인
</SocialButton>
<SocialButton onClick={handleGoogleLogin} bg="#fefefe">
<img src={Google} /> 구글계정 로그인
</SocialButton>
</ButtonBox>
</Wrapper>
);
};

const ButtonBox = styled.div`
display: flex;
flex-direction: column;
Expand Down Expand Up @@ -35,28 +61,4 @@ const SocialButton = styled.div<{ bg: string }>`
cursor: pointer;
`;

const Login = () => {
const handleKakaoLogin = () => {
return;
};

const handleGoogleLogin = () => {
return;
};

return (
<Wrapper>
<img src={logo} />
<ButtonBox>
<SocialButton onClick={handleKakaoLogin} bg="#fee500">
<img src={KaKao} /> 카카오계정 로그인
</SocialButton>
<SocialButton onClick={handleGoogleLogin} bg="#fefefe">
<img src={Google} /> 구글계정 로그인
</SocialButton>
</ButtonBox>
</Wrapper>
);
};

export default Login;
18 changes: 16 additions & 2 deletions src/pages/signup/Signup.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import styled from '@emotion/styled';

export const Container = styled.div`
display: flex;
position: relative;
flex-direction: column;
background-color: var(--color-black-main);
min-width: 360px;
Expand All @@ -11,6 +12,17 @@ export const Container = styled.div`
padding: 96px 20px 0 20px;
gap: 12px;

.errorMessage {
position: absolute;
top: 220px;
left: 24px;

color: var(--color-text-warning_red, #ff9496);
font-size: var(--string-size-small03, 12px);
font-weight: var(--string-weight-regular, 400);
line-height: normal;
}

.title {
margin-top: 40px;
color: var(--color-white);
Expand Down Expand Up @@ -164,7 +176,7 @@ export const Container = styled.div`
}
`;

export const InputBox = styled.div`
export const InputBox = styled.div<{ hasError?: boolean }>`
display: flex;
flex-direction: row;
justify-content: space-between;
Expand All @@ -175,9 +187,11 @@ export const InputBox = styled.div`
border-radius: 12px;
padding: 12px;
background-color: var(--color-darkgrey-dark);
border: 1px solid ${props => (props.hasError ? '#FF9496' : 'none')};
transition: border-color 0.2s ease;
`;

export const StyledInput = styled.input`
export const StyledInput = styled.input<{ hasError?: boolean }>`
flex: 1;
background: none;
border: none;
Expand Down
21 changes: 16 additions & 5 deletions src/pages/signup/SignupDone.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,45 @@
import { useNavigate } from 'react-router-dom';
import { useNavigate, useLocation } from 'react-router-dom';
import { Container } from './Signup.styled';
import leftarrow from '../../assets/common/leftArrow.svg';
import art from '../../assets/genre/art.svg';
import TitleHeader from '../../components/common/TitleHeader';

const SignupDone = () => {
const navigate = useNavigate();
const location = useLocation();

// SignupGenre에서 전달된 데이터 받기
const { nickName, aliasName } = location.state || {};

const handleBackClick = () => {
navigate(-1);
navigate('/signup/genre');
};

const handleNextClick = () => {
navigate('/feed');
};

// state가 없으면 이전 페이지로 이동
if (!nickName || !aliasName) {
navigate('/signup/nickname');
return null;
}

return (
<Container>
<TitleHeader
leftIcon={<img src={leftarrow} alt="뒤로가기" />}
onLeftClick={handleBackClick}
/>
<div className="title">안녕하세요, 희용희용님</div>
<div className="title">안녕하세요, {nickName}님</div>
<div className="subtitle">이제 Thip에서 활동할 준비를 모두 마쳤어요!</div>
<div className="content">
<div className="userInfo">
<div className="profile">
<img src={art} />
</div>
<div className="username">희용희용</div>
<div className="subname">예술가</div>
<div className="username">{nickName}</div>
<div className="subname">{aliasName}</div>
</div>
<div className="startBtn" onClick={handleNextClick}>
지금 바로 Thip 시작하기
Expand Down
104 changes: 93 additions & 11 deletions src/pages/signup/SignupGenre.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,111 @@
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useNavigate, useLocation } from 'react-router-dom';
import { Container } from './Signup.styled';
import leftarrow from '../../assets/common/leftArrow.svg';
import TitleHeader from '../../components/common/TitleHeader';
import type { Genre } from '@/types/genre';
import { postSignup } from '@/api/users/postSignup';
import { apiClient } from '@/api/index';

const SignupGenre = () => {
const [genres, setGenres] = useState<Genre[]>([]);
const [selectedId, setSelectedId] = useState<string | null>(null);
const [genres, setGenres] = useState<
Array<{
id: string;
title: string;
subTitle: string;
iconUrl: string;
color: string;
}>
>([]);
const [selectedAlias, setSelectedAlias] = useState<{
id: string;
subTitle: string;
} | null>(null);
const navigate = useNavigate();
const location = useLocation();

// SignupNickname에서 넘어온 nickname 받기
const nickname = location.state?.nickname;

// 쿠키에서 Authorization 토큰 추출
const getAuthTokenFromCookie = () => {
console.log('=== 쿠키 디버깅 ===');
console.log('현재 페이지 URL:', window.location.href);
console.log('현재 도메인:', window.location.hostname);
console.log('전체 쿠키:', document.cookie);

const cookies = document.cookie.split(';');
console.log('분리된 쿠키들:', cookies);

for (const cookie of cookies) {
const [name, value] = cookie.trim().split('=');
console.log('쿠키 이름:', name, '값:', value);
if (name === 'Authorization') {
console.log('Authorization 토큰 발견:', value);
return value;
}
}

console.log('Authorization 토큰을 찾을 수 없습니다.');
console.log('가능한 원인: 도메인 불일치, 경로 불일치, 쿠키 만료');
return null;
};

// 토큰을 헤더에 설정
const setAuthTokenToHeader = (token: string) => {
// localStorage에 저장 (페이지 새로고침 시에도 유지)
localStorage.setItem('authToken', token);

// apiClient 기본 헤더에 설정
apiClient.defaults.headers.common['Authorization'] = `Bearer ${token}`;
};

useEffect(() => {
fetch('/genres.json')
.then(res => res.json())
.then((data: Genre[]) => setGenres(data))
.then(data => setGenres(data))
.catch(console.error);
}, []);

const handleBackClick = () => {
navigate(-1);
};

const handleNextClick = () => {
if (!selectedId) return;
navigate('/signupdone', { state: { genreId: selectedId } });
const handleNextClick = async () => {
if (!selectedAlias || !nickname) return;

// 쿠키에서 토큰 추출
const authToken = getAuthTokenFromCookie();
if (!authToken) {
console.log('쿠키에서 Authorization 토큰을 찾을 수 없습니다.');
console.log('토큰이 없어 회원가입을 진행할 수 없습니다.');
return; // 토큰이 없으면 함수 종료하여 페이지에 머무름
}

// 토큰을 헤더에 설정
setAuthTokenToHeader(authToken);
console.log('Authorization 토큰을 헤더에 설정했습니다.');

try {
const result = await postSignup({
aliasName: selectedAlias.subTitle,
nickName: nickname,
});

if (result.success) {
console.log('회원가입 성공! 사용자 ID:', result.data.userId);
// 회원가입 완료 페이지로 이동
navigate('/signupdone', {
state: {
aliasName: selectedAlias.subTitle,
nickName: nickname,
},
});
} else {
console.error('회원가입 실패:', result.message);
}
} catch (error) {
console.error('회원가입 중 오류 발생:', error);
}
};

return (
Expand All @@ -34,7 +116,7 @@ const SignupGenre = () => {
rightButton={<div className="next">다음</div>}
onLeftClick={handleBackClick}
onRightClick={handleNextClick}
isNextActive={!!selectedId}
isNextActive={!!selectedAlias}
/>
<Container>
<div className="title">관심있는 장르를 선택해주세요.</div>
Expand All @@ -44,8 +126,8 @@ const SignupGenre = () => {
{genres.map(g => (
<div
key={g.id}
className={`genreCard ${g.id === selectedId ? 'active' : ''}`}
onClick={() => setSelectedId(g.id)}
className={`genreCard ${g.id === selectedAlias?.id ? 'active' : ''}`}
onClick={() => setSelectedAlias({ id: g.id, subTitle: g.subTitle })}
>
<img className="bg" src={g.iconUrl} alt={g.title} />
<div className="textbox">
Expand Down
Loading