Skip to content

Commit a79ce99

Browse files
authored
Merge pull request #119 from wafflestudio/dev
dev -> main 반영
2 parents 5fd2d14 + fa3a46f commit a79ce99

21 files changed

Lines changed: 616 additions & 66 deletions

src/contexts/AuthProvider.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,13 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
7373
if (restoredUser) {
7474
setUser(restoredUser);
7575
setIsAuthenticated(true);
76+
} else if (TokenService.getToken()) {
77+
try {
78+
const user = await auth.getUser();
79+
setUser(user); setIsAuthenticated(true);
80+
} catch {
81+
TokenService.clearTokens();
82+
}
7683
}
7784
} catch (e) {
7885
console.error(e);

src/contexts/SearchContext.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ export const SearchProvider = ({ children }: { children: ReactNode }) => {
5252
[],
5353
);
5454

55-
const emptySearchResults = () => {
55+
const emptySearchResults = useCallback(() => {
5656
setSearchResults(null);
57-
};
57+
}, []);
5858

5959
// useEffect(() => {
6060
// fetchSearchResult(query, page, size);

src/pages/MyPage.tsx

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,26 @@ import Loading from "@/widgets/Loading";
2323
import defaultProfile from "/assets/defaultProfile.png";
2424
import BottomNav from "@/widgets/BottomNav";
2525

26+
// 이름 길이 제한: 한글 1자 = 2, 그 외 1자 = 1 → 상한 20 (한글 10자 / 영어 20자)
27+
const MAX_NAME_WEIGHT = 20;
28+
29+
const isKoreanChar = (char: string) => /[---]/.test(char);
30+
31+
const getNameWeight = (value: string) =>
32+
[...value].reduce((acc, char) => acc + (isKoreanChar(char) ? 2 : 1), 0);
33+
34+
// 가중치 상한을 넘지 않도록 문자열을 잘라낸다
35+
const truncateToWeight = (value: string, max: number) => {
36+
let weight = 0;
37+
let result = "";
38+
for (const char of value) {
39+
weight += isKoreanChar(char) ? 2 : 1;
40+
if (weight > max) break;
41+
result += char;
42+
}
43+
return result;
44+
};
45+
2646
const ProfileCard = ({ onClickInterest }: { onClickInterest: () => void }) => {
2747
const { user, updateUsername, setProfileImg } = useAuth();
2848
const { interestCategories } = useUserData();
@@ -40,6 +60,9 @@ const ProfileCard = ({ onClickInterest }: { onClickInterest: () => void }) => {
4060
const [isEditmode, setIsEditmode] = useState<boolean>(false);
4161
const navigate = useNavigate();
4262

63+
const nameWeight = getNameWeight(username);
64+
const isNameMax = nameWeight >= MAX_NAME_WEIGHT;
65+
4366
const handleImageError = () => {
4467
setProfilePreviewUrl(defaultProfile);
4568
};
@@ -105,20 +128,38 @@ const ProfileCard = ({ onClickInterest }: { onClickInterest: () => void }) => {
105128
</div>
106129
<div className={styles.nameEmailCol}>
107130
{isEditmode ? (
108-
<input
109-
className={styles.nameInput}
110-
type="text"
111-
value={username}
112-
placeholder="이름을 입력하세요"
113-
onChange={(e) => setUsername(e.currentTarget.value)}
114-
onKeyDown={(e: React.KeyboardEvent) => {
115-
if (e.key === "Enter" && !e.nativeEvent.isComposing) {
116-
e.stopPropagation();
117-
e.preventDefault();
118-
handleChangesSave();
131+
<div className={styles.nameInputWrapper}>
132+
<input
133+
className={styles.nameInput}
134+
type="text"
135+
value={username}
136+
placeholder="이름을 입력하세요"
137+
onChange={(e) =>
138+
setUsername(
139+
truncateToWeight(e.currentTarget.value, MAX_NAME_WEIGHT),
140+
)
119141
}
120-
}}
121-
/>
142+
onKeyDown={(e: React.KeyboardEvent) => {
143+
if (e.key === "Enter" && !e.nativeEvent.isComposing) {
144+
e.stopPropagation();
145+
e.preventDefault();
146+
handleChangesSave();
147+
}
148+
}}
149+
/>
150+
<div className={styles.nameHintRow}>
151+
<span
152+
className={`${styles.nameHint} ${isNameMax ? styles.nameHintMax : ""}`}
153+
>
154+
한글 10자 · 영어 20자 이내로 입력해주세요
155+
</span>
156+
<span
157+
className={`${styles.nameCounter} ${isNameMax ? styles.nameHintMax : ""}`}
158+
>
159+
{nameWeight}/{MAX_NAME_WEIGHT}
160+
</span>
161+
</div>
162+
</div>
122163
) : (
123164
<span className={styles.nameText}>{user?.username}</span>
124165
)}

src/pages/auth/Home.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Home.tsx
2-
import { useNavigate } from "react-router-dom";
2+
import { Navigate, useNavigate } from "react-router-dom";
3+
import { useAuth } from "@contexts/AuthProvider";
34
import logo from "/assets/logo.png";
45
import styles from "@styles/Home.module.css";
56

@@ -15,6 +16,13 @@ const SOCIAL_LOGIN_ENTRY = {
1516

1617
export default function Home() {
1718
const navigate = useNavigate();
19+
const { isAuthenticated, isLoading } = useAuth();
20+
21+
// 세션 복원 중에는 판단을 보류
22+
if (isLoading) return null;
23+
24+
// 이미 로그인된 사용자는 /main 으로
25+
if (isAuthenticated) return <Navigate to="/main" replace />;
1826

1927
const toLogin = () => navigate("/auth/login");
2028
const toSignUp = () => navigate("/auth/signup");

src/pages/auth/Login/SocialLoginHandler.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,9 @@ const LoginHandler = () => {
2020
const run = async () => {
2121
try {
2222
await completeSocialLogin(accessToken);
23-
navigate(
24-
isNewUser ? "/auth/signup?step=onboarding" : "/auth/complete",
25-
{
26-
replace: true,
27-
},
28-
);
23+
navigate(isNewUser ? "/auth/signup?step=onboarding" : "/main", {
24+
replace: true,
25+
});
2926
} catch (e) {
3027
console.error(e);
3128
navigate("/auth/login", { replace: true });

src/pages/search/Search.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const SearchView = () => {
2828
setPage,
2929
setSize,
3030
fetchSearchResult,
31+
emptySearchResults,
3132
searchResults,
3233
searchLoading,
3334
} = useSearch();
@@ -56,10 +57,13 @@ const SearchView = () => {
5657
const fetchData = async () => {
5758
if (query.trim()) {
5859
await fetchSearchResult(query, page, size);
60+
} else {
61+
// 빈 검색어로 들어오면 이전 검색 결과를 비움
62+
emptySearchResults();
5963
}
6064
};
6165
fetchData();
62-
}, [fetchSearchResult, query, page, size]);
66+
}, [fetchSearchResult, emptySearchResults, query, page, size]);
6367

6468
useEffect(() => {
6569
const mq = window.matchMedia("(max-width: 576px)");

src/pages/search/SearchToolbar.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const SearchToolbar = ({ viewMode, setViewMode }: SearchToolbarProps) => {
4444
<div className={styles.inputWrapper}>
4545
<input
4646
type="text"
47+
maxLength={50}
4748
className={styles.searchInput}
4849
placeholder="검색어를 입력해주세요"
4950
value={queryState}

src/router/AppRoutes.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { Navigate, Route, Routes } from "react-router-dom";
22
import Home from "../pages/auth/Home";
33
import Login from "../pages/auth/Login/Login";
44
import LoginHandler from "../pages/auth/Login/SocialLoginHandler";
5-
import CompleteSignUp from "../pages/auth/OnBoarding/CompleteSignUp";
65
import EmailSignUp from "../pages/auth/Signup/EmailSignUp";
76
import CalendarView from "../pages/CalendarView";
87
import MainDay from "../pages/MainDay";
@@ -23,7 +22,7 @@ export default function AppRoutes() {
2322
<Route path="/" element={<Home />} />
2423
<Route path="/auth/login" element={<Login />} />
2524
<Route path="/auth/signup" element={<EmailSignUp />} />
26-
<Route path="/auth/complete" element={<CompleteSignUp />} />
25+
{/* <Route path="/auth/complete" element={<CompleteSignUp />} /> */}
2726

2827
{/* OAuth Redirect */}
2928
<Route path="/auth/callback" element={<LoginHandler />} />

src/styles/CardView.module.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
Roboto, "Noto Sans KR", sans-serif;
1414
text-align: left;
1515
}
16+
17+
/* fullWidth prop이 켜지면 340px 제한 해제 */
18+
.cardWrapper.fullWidth {
19+
max-width: none;
20+
}
1621
.mobileDateOrg,
1722
.mobileRow {
1823
padding: 0px;

src/styles/DetailView.module.css

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@
1111
sans-serif;
1212

1313
overflow-y: auto;
14+
15+
/* body에 걸린 user-select: none(전역)이 상속되어 선택이 막히므로
16+
상세 패널 안에서는 텍스트 선택·복사를 다시 허용한다. */
17+
user-select: text;
18+
-webkit-user-select: text;
19+
-webkit-touch-callout: default;
1420
}
1521

1622
.thumbnail {
@@ -119,7 +125,11 @@
119125
font-size: 15px;
120126
line-height: 1.6;
121127
color: #333333;
122-
white-space: pre-wrap;
128+
/* 렌더링된 HTML이므로 pre-wrap을 쓰면 태그 사이 들여쓰기·줄바꿈이
129+
그대로 보여 레이아웃이 깨진다. 공백은 normal로 collapse시킨다. */
130+
white-space: normal;
131+
overflow-wrap: break-word;
132+
word-break: break-word;
123133
margin-bottom: 40px;
124134
}
125135
.contentText :global(img) {
@@ -128,6 +138,11 @@
128138
height: auto !important;
129139
display: block !important;
130140
}
141+
.contentText :global(a) {
142+
color: #2563eb;
143+
text-decoration: underline;
144+
word-break: break-all;
145+
}
131146

132147
.locationRow {
133148
display: flex;

0 commit comments

Comments
 (0)