μλλ μ 리λ GitHub README.md νμΌμ λλ€. λΆνμν μ€λ³΅μ μ κ±°νκ³ λ Όλ¦¬μ νλ¦μ λ°λΌ λ΄μ©μ μ¬κ΅¬μ±νμ΅λλ€.
COMAtchingμ μ¬μ©μμ μ·¨λ―Έ, μ±ν₯, μ νΈλλ₯Ό κΈ°λ°μΌλ‘ μ΅μ μ λ§€μΉμ μ 곡νλ μΉ μ ν리μΌμ΄μ
μ
λλ€.
μ΄ νλ‘μ νΈμμ νλ‘ νΈμλλ₯Ό λ΄λΉνμΌλ©°, μ£Όμ κΈ°μ μ€νμΌλ‘ React, Vanilla CSS, Recoil, Firebase λ±μ μ¬μ©νμ΅λλ€.
-
μΉ΄μΉ΄μ€ λ‘κ·ΈμΈ
- μ¬μ©μκ° μΉ΄μΉ΄μ€ λ‘κ·ΈμΈμ ν΅ν΄ μμ½κ² μ μν μ μμ΅λλ€.
-
μ¬μ©μ μ·¨λ―Έ μ ν
- μ¬μ©μκ° μ·¨λ―Έ λ° νλμ μ ννμ¬ λ§μΆ€ν νλ‘νμ μμ±ν©λλ€.
-
λ§€μΉ κ²°κ³Ό νμΈ
- μ νλ 쑰건(μ·¨λ―Έ, λμ΄ λ±)μ λ°λΌ μ΅μ μ λ§€μΉ κ²°κ³Όλ₯Ό νμΈν©λλ€.
-
λ°μ΄μ 컀νλκΈ° κΈ°λ₯
- μ¬μ©μκ° λ²νΌμ λλκ·Ένμ¬ μ§κ΄μ μΌλ‘ λ§€μΉμ μ§ννκ³ μ‘°κ±΄μ νμΈν μ μμ΅λλ€.
-
λΈλΌμ°μ 리λλ μ μ²λ¦¬
- μΉ΄μΉ΄μ€ν‘κ³Ό κ°μ μΈμ± λΈλΌμ°μ μμ λ§ν¬λ₯Ό μ΄ κ²½μ° ν¬λ‘¬ λ± μΈλΆ λΈλΌμ°μ λ‘ λ¦¬λλ μ λ©λλ€.
-
κ΄λ¦¬μ νμ΄μ§
- κ΄λ¦¬μ μ μ© νμ΄μ§μμ μΆ©μ μμ²μ μ€μκ°μΌλ‘ νμΈ λ° μ²λ¦¬ν μ μμ΅λλ€.
Firebase Hostingμ ν΅ν΄ COMAtchingμ λ°°ν¬νμ΅λλ€.
- React: μ»΄ν¬λνΈ κΈ°λ° μ¬μ©μ μΈν°νμ΄μ€
- Vanilla CSS: μΌκ΄λ λμμΈκ³Ό κΉλν μ€νμΌλ§
- Recoil: κΈλ‘λ² μν κ΄λ¦¬ λΌμ΄λΈλ¬λ¦¬
- Axios: μλ²μμ ν¨μ¨μ μΈ ν΅μ (API μμ² λ° μλ΅ μ²λ¦¬)
- SockJS & Stomp.js: μ€μκ° WebSocket ν΅μ μ μν λΌμ΄λΈλ¬λ¦¬
- Firebase Hosting: μμ μ μ΄κ³ λΉ λ₯Έ μΉ μλΉμ€ λ°°ν¬
src/
βββ pages/ # μ£Όμ νμ΄μ§ μ»΄ν¬λνΈ
β βββ Mainpage.jsx # λ©μΈ νμ΄μ§
β βββ Hobbyform.jsx # μ·¨λ―Έ μ ν νμ΄μ§
β βββ Matchresult.jsx # λ§€μΉ κ²°κ³Ό νμ΄μ§
β βββ Adminpage_unlogin.jsx # κ΄λ¦¬μ λ‘κ·ΈμΈ νμ΄μ§
β βββ User_info_page.jsx # μ¬μ©μ μ 보 νμ΄μ§
β
βββ components/ # μ¬μ¬μ© κ°λ₯ν UI μ»΄ν¬λνΈ
β βββ AdminRequestList.jsx # κ΄λ¦¬μ μμ² λ¦¬μ€νΈ
β
βββ recoil/ # Recoil μν κ΄λ¦¬
β βββ userState.js # μ¬μ©μ μν κ΄λ¦¬
β βββ adminRequests.js # κ΄λ¦¬μ μμ² μν
β βββ MatchResultState.js # λ§€μΉ κ²°κ³Ό μν
β
βββ App.js # λ©μΈ λ£¨νΈ μ»΄ν¬λνΈ
βββ axiosConfig.js # Axios μ€μ νμΌ
βββ ...
SockJSμ Stomp.jsλ₯Ό μ¬μ©ν΄ μ€μκ° WebSocket ν΅μ μ ꡬννμ΅λλ€. μ΄λ₯Ό ν΅ν΄ μΆ©μ μμ² λ° μΉμΈ/μ·¨μ μ΄λ²€νΈκ° μ€μκ°μΌλ‘ λ°μλ©λλ€.
-
μΆ©μ μμ² μ€μκ° κ΅¬λ
- μλ²μ
/topic/chargeRequestsλ₯Ό ꡬλ ν΄ μλ‘μ΄ μμ²μ μ€μκ°μΌλ‘ μ λ°μ΄νΈν©λλ€.
- μλ²μ
-
μΉμΈ λ° μ·¨μ μ²λ¦¬
/topic/approvalUpdateμ/topic/cancelUpdateλ₯Ό ꡬλ νμ¬ μμ² μνλ₯Ό μ¦μ λ³κ²½ν©λλ€.
-
ν ν° κΈ°λ° μΈμ¦
- μΏ ν€μμ
Authorizationν ν°μ κ°μ Έμ WebSocket μ°κ²° μ μΈμ¦ν©λλ€.
- μΏ ν€μμ
useEffect(() => {
const socket = new SockJS("https://cuk.comatching.site/wss");
const client = Stomp.over(socket);
const token = getTokenFromCookie();
if (!token) {
navigate("/adminlogin");
return;
}
client.connect({ Authorization: `Bearer ${token}` }, (frame) => {
console.log("Connected: " + frame);
client.subscribe("/topic/chargeRequests", (message) => {
const chargeRequests = JSON.parse(message.body);
setRequests((prev) => [
...prev,
...chargeRequests.filter((req) => !prev.some((r) => r.userId === req.userId)),
]);
});
client.subscribe("/topic/approvalUpdate", (message) => {
const userId = message.body;
setRequests((prev) => prev.filter((req) => req.userId !== userId));
});
});
}, []);function getTokenFromCookie() {
const name = "Authorization=";
const decodedCookie = decodeURIComponent(document.cookie);
const ca = decodedCookie.split(";");
for (let i = 0; i < ca.length; i++) {
let c = ca[i].trim();
if (c.indexOf(name) === 0) {
return c.substring(name.length, c.length);
}
}
return "";
}Recoilμ μ¬μ©ν΄ μ μ μνλ₯Ό κ΄λ¦¬ν©λλ€.
import { atom } from "recoil";
export const userState = atom({
key: "userState",
default: {
username: "",
age: "",
mbti: "",
hobby: [],
point: 0,
},
});νλ‘μ νΈμμ μλ²μμ ν¨μ¨μ μ΄κ³ μμ ν ν΅μ μ μν΄ Axiosλ₯Ό μ€μ νμμΌλ©°, μ μ μμ² λ° μλ΅ μΈν°μ ν°λ₯Ό ν΅ν΄ μλ¬λ₯Ό μ€μμμ μ²λ¦¬νλλ‘ κ΅¬ννμ΅λλ€.
-
Axios μΈμ€ν΄μ€ μ€μ
baseURLμ ν΅ν΄ κ³΅ν΅ μλ² μ£Όμλ₯Ό μ€μ ν©λλ€.- μΏ ν€μμ μμΈμ€ ν ν°μ κ°μ Έμ Authorization ν€λμ μλμΌλ‘ μΆκ°ν©λλ€.
- νμ μ μλ²λ‘λΆν° μλ‘μ΄ ν ν°μ μμ νλ©΄ μΏ ν€λ₯Ό μ λ°μ΄νΈν©λλ€.
-
μ μ μλ¬ μ²λ¦¬
- 401 μΈμ¦ μλ¬: μΈμ λ§λ£ μ μΏ ν€λ₯Ό μμ νκ³ λ©μΈ νμ΄μ§λ‘ 리λ€μ΄λ νΈν©λλ€.
- 403 κΆν μλ¬: κΆνμ΄ μλ μμ μ λν΄ μλ¦Όμ νμν©λλ€.
- 404 μμ μμ: μμ²ν μμμ μ°Ύμ μ μμ λ μ¬μ©μμκ² μλ¦Όμ μ 곡ν©λλ€.
- 500 μλ² μλ¬: μλ² μ€λ₯ λ°μ μ μλ΄ λ©μμ§λ₯Ό μΆλ ₯ν©λλ€.
- λ€νΈμν¬ μλ¬: μλ²κ° λ€μ΄λμκ±°λ λ€νΈμν¬ λ¬Έμ λ°μ μ μ¬μ©μμκ² μλ¦Όμ νμν©λλ€.
- μΏ ν€μμ Authorization ν ν°μ κ°μ Έμ ν€λμ μΆκ°ν©λλ€.
- ν ν°μ΄ μλ κ²½μ° λ‘κ·ΈμΈ νμ΄μ§λ‘ 리λ€μ΄λ νΈλ©λλ€.
instance.interceptors.request.use(
(config) => {
const accessToken = Cookies.get("Authorization");
if (!accessToken) {
window.location.href = "/";
return Promise.reject(new Error("No access token found"));
}
config.headers.Authorization = `Bearer ${accessToken}`;
return config;
},
(error) => Promise.reject(error)
);- μλ²μμ μλ‘μ΄ ν ν°μ μ 곡νλ©΄ μΏ ν€λ₯Ό μ λ°μ΄νΈν©λλ€.
instance.interceptors.response.use(
(response) => {
const newAccessToken = response.headers["authorization"];
if (newAccessToken && newAccessToken.startsWith("Bearer ")) {
const tokenWithoutBearer = newAccessToken.slice(7);
Cookies.remove("Authorization", { path: "/" });
Cookies.set("Authorization", tokenWithoutBearer, { path: "/" });
}
return response;
},
async (error) => {
if (!error.response) {
alert("μλ²μ μ°κ²°ν μ μμ΅λλ€. λ€μ μλν΄ μ£ΌμΈμ.");
return Promise.reject(error);
}
const { status, data } = error.response;
// μ μ μλ¬ μ²λ¦¬
if (status === 401 && (data.code === "SEC-001" || data.code === "SEC-002")) {
Cookies.remove("Authorization");
alert("μΈμ
μ΄ λ§λ£λμμ΅λλ€. λ€μ λ‘κ·ΈμΈν΄ μ£ΌμΈμ.");
window.location.href = "/";
} else if (status === 403) {
alert("ν΄λΉ μμ
μ λν κΆνμ΄ μμ΅λλ€.");
} else if (status === 404) {
alert("μμ²ν μμμ΄ μ‘΄μ¬νμ§ μμ΅λλ€.");
} else if (status === 500) {
alert("μλ² μ€λ₯κ° λ°μνμ΅λλ€. μ μ ν λ€μ μλν΄ μ£ΌμΈμ.");
}
return Promise.reject(error);
}
);-
μμ²:
- Axios μΈμ€ν΄μ€κ°
AuthorizationμΏ ν€λ₯Ό νμΈνκ³ ν€λμ μΆκ°ν©λλ€. - ν ν°μ΄ μμΌλ©΄ λ©μΈ νμ΄μ§(
/)λ‘ λ¦¬λ€μ΄λ νΈν©λλ€.
- Axios μΈμ€ν΄μ€κ°
-
μλ΅:
- μλ² μλ΅μμ μλ‘μ΄ ν ν°μ΄ μ λ¬λλ©΄ κΈ°μ‘΄ μΏ ν€λ₯Ό μμ νκ³ μ ν ν°μΌλ‘ μ λ°μ΄νΈν©λλ€.
- μλ¬κ° λ°μνλ©΄ μν μ½λμ λ°λΌ μ¬μ©μμκ² μ μ ν μλ΄ λ©μμ§λ₯Ό μΆλ ₯ν©λλ€.
- μΈμ λ§λ£: μΏ ν€ μμ λ° λ‘κ·ΈμΈ νμ΄μ§λ‘ μ΄λ
- κΆν μ€λ₯: μ¬μ©μ μλ¦Ό (403)
- μμ μμ: μλ¦Ό λ©μμ§ μΆλ ₯ (404)
- μλ² μ€λ₯: μ¬μ©μμκ² μ€λ₯ μλ΄ (500)
- λ€νΈμν¬ μλ¬: μλ² μ°κ²° λΆκ° λ©μμ§ μΆλ ₯
μ΄ μ€μ μ ν΅ν΄ Axios μμ²κ³Ό μλ΅μ ν¨μ¨μ μΌλ‘ κ΄λ¦¬νκ³ μλ¬λ₯Ό μ μμμ μ²λ¦¬νμ¬ μ¬μ©μ κ²½νμ κ°μ νμ΅λλ€. π
- SockJS: WebSocket μ°κ²° λ° λΈλΌμ°μ νΈνμ±
- Stomp.js: WebSocket λ©μμ§ μ μ‘ λ° κ΅¬λ
- Clipboard API: ν΄λ¦½λ³΄λμ ν μ€νΈ λ³΅μ¬ κΈ°λ₯
- Axios: API ν΅μ
- Recoil: μν κ΄λ¦¬ λΌμ΄λΈλ¬λ¦¬
μ΄ λ¬Έμλ νλ‘μ νΈμ μ£Όμ κΈ°λ₯κ³Ό κΈ°μ μ ꡬνμ λͺ ννκ³ κ°κ²°νκ² μ€λͺ ν©λλ€.
μ΅μ’ λ°°ν¬ λ§ν¬: COMAtching π


