Skip to content

Commit a8dc19a

Browse files
committed
feat: 참가자 포탈 App 추가
1 parent 4f49b10 commit a8dc19a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2214
-1
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charSet="UTF-8" />
5+
<base href="/" />
6+
<link rel="icon" href="/favicon.ico" sizes="32x32">
7+
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
8+
<link rel="apple-touch-icon" href="/favicon-180.png">
9+
10+
<meta name="theme-color" content="#fff" />
11+
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#fff" />
12+
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#fff" />
13+
14+
<meta name="msapplication-navbutton-color" content="#fff" />
15+
<meta name="msapplication-TileColor" content="#fff" />
16+
<meta name="msapplication-TileImage" content="/favicon-192.png" />
17+
<meta name="application-name" content="PyCon KR" />
18+
<meta name="apple-mobile-web-app-title" content="PyCon KR" />
19+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
20+
<meta name="apple-mobile-web-app-capable" content="yes" />
21+
<meta name="mobile-web-app-capable" content="yes" />
22+
<!-- https://developers.google.com/web/fundamentals/web-app-manifest/ -->
23+
<link rel="manifest" href="/site.webmanifest" />
24+
25+
<meta name="viewport" content="width=device-width,
26+
height=device-height,
27+
target-densitydpi=device-dpi,
28+
initial-scale=1.0,
29+
minimum-scale=1.0,
30+
maximum-scale=1.0,
31+
user-scalable=0,
32+
user-scalable=no,
33+
shrink-to-fit=no" />
34+
<meta name="author" content="PyCon Korea Organizing Team" />
35+
<meta name="description" content="PyCon Korea Participant Portal" />
36+
<meta name="keywords" content="PyCon, Python, Conference, Korea" />
37+
<meta name="google" content="notranslate" />
38+
<meta name="googlebot" content="index, follow" />
39+
<meta name="robots" content="index, follow" />
40+
41+
<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=d3945eccce7debf0942f885e90a71f97"></script>
42+
<script src="https://cdn.iamport.kr/v1/iamport.js"></script>
43+
44+
<title>PyCon Korea Participant Portal</title>
45+
</head>
46+
<body>
47+
<div id="root"></div>
48+
<script type="module" src="./src/main.tsx"></script>
49+
</body>
50+
</html>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "@apps/pyconkr-participant-portal",
3+
"dependencies": {
4+
"@frontend/common": "workspace:*",
5+
"@frontend/shop": "workspace:*"
6+
},
7+
"devDependencies": {
8+
"vite": "^6.3.5",
9+
"vite-plugin-mdx": "^3.6.1",
10+
"vite-plugin-mkcert": "^1.17.8",
11+
"vite-plugin-svgr": "^4.3.0"
12+
}
13+
}
3.78 KB
Loading
4.27 KB
Loading
15.2 KB
Loading
1.35 KB
Binary file not shown.
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "PyCon Korea Participant Portal",
3+
"icons": [
4+
{
5+
"src": "favicon-192.png",
6+
"type": "image/png",
7+
"sizes": "192x192"
8+
},
9+
{
10+
"src": "favicon-512.png",
11+
"type": "image/png",
12+
"sizes": "512x512",
13+
"purpose": "maskable"
14+
},
15+
{
16+
"src": "favicon-512.png",
17+
"type": "image/png",
18+
"sizes": "512x512"
19+
}
20+
],
21+
"id": "/",
22+
"start_url": "/",
23+
"scope": "/",
24+
"display": "standalone"
25+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import * as React from "react";
2+
import { Navigate, Route, Routes } from "react-router-dom";
3+
4+
import { Layout } from "./components/layout.tsx";
5+
import { LandingPage } from "./components/pages/home.tsx";
6+
import { ProfileEditor } from "./components/pages/profile_editor.tsx";
7+
import { SessionEditor } from "./components/pages/session_editor";
8+
import { SignInPage } from "./components/pages/signin.tsx";
9+
import { SponsorEditor } from "./components/pages/sponsor_editor";
10+
11+
export const App: React.FC = () => (
12+
<Routes>
13+
<Route element={<Layout />}>
14+
<Route path="/" element={<LandingPage />} />
15+
<Route path="/signin" element={<SignInPage />} />
16+
<Route path="/user" element={<ProfileEditor />} />
17+
<Route path="/sponsor/:id" element={<SponsorEditor />} />
18+
<Route path="/session/:id" element={<SessionEditor />} />
19+
<Route path="*" element={<Navigate to="/" replace />} />
20+
</Route>
21+
</Routes>
22+
);
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import * as Common from "@frontend/common";
2+
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, TextField } from "@mui/material";
3+
import { enqueueSnackbar, OptionsObject } from "notistack";
4+
import * as React from "react";
5+
6+
import { useAppContext } from "../../contexts/app_context";
7+
8+
type ChangePasswordDialogProps = {
9+
open: boolean;
10+
onClose: () => void;
11+
};
12+
13+
type PasswordFormDataType = {
14+
old_password: string;
15+
new_password: string;
16+
new_password_confirm: string;
17+
};
18+
19+
export const ChangePasswordDialog: React.FC<ChangePasswordDialogProps> = ({ open, onClose }) => {
20+
const formRef = React.useRef<HTMLFormElement>(null);
21+
const { language } = useAppContext();
22+
const participantPortalClient = Common.Hooks.BackendParticipantPortalAPI.useParticipantPortalClient();
23+
const changePasswordMutation = Common.Hooks.BackendParticipantPortalAPI.useChangePasswordMutation(participantPortalClient);
24+
25+
const addSnackbar = (c: string | React.ReactNode, variant: OptionsObject["variant"]) =>
26+
enqueueSnackbar(c, { variant, anchorOrigin: { vertical: "bottom", horizontal: "center" } });
27+
28+
const titleStr = language === "ko" ? "비밀번호 변경" : "Change Password";
29+
const prevPasswordLabel = language === "ko" ? "이전 비밀번호" : "Previous Password";
30+
const newPasswordLabel = language === "ko" ? "새 비밀번호" : "New Password";
31+
const confirmPasswordLabel = language === "ko" ? "새 비밀번호 확인" : "Confirm New Password";
32+
const cancelStr = language === "ko" ? "취소" : "Cancel";
33+
const submitStr = language === "ko" ? "수정" : "Apply changes";
34+
const passwordChangedStr = language === "ko" ? "비밀번호가 성공적으로 변경되었습니다." : "Password changed successfully.";
35+
36+
const handleSubmit = () => {
37+
if (!Common.Utils.isFormValid(formRef.current)) return;
38+
39+
const formData = Common.Utils.getFormValue<PasswordFormDataType>({ form: formRef.current });
40+
changePasswordMutation.mutate(formData, {
41+
onSuccess: () => {
42+
addSnackbar(passwordChangedStr, "success");
43+
onClose();
44+
},
45+
onError: (error) => {
46+
console.error("Change password failed:", error);
47+
48+
let errorMessage = error instanceof Error ? error.message : "An unknown error occurred.";
49+
if (error instanceof Common.BackendAPIs.BackendAPIClientError) errorMessage = error.message;
50+
51+
addSnackbar(errorMessage, "error");
52+
},
53+
});
54+
};
55+
56+
const disabled = changePasswordMutation.isPending;
57+
58+
return (
59+
<Dialog open={open} maxWidth="sm" fullWidth>
60+
<DialogTitle children={titleStr} />
61+
<DialogContent>
62+
<form ref={formRef}>
63+
<Stack spacing={2} sx={{ my: 1 }}>
64+
<TextField fullWidth disabled={disabled} type="password" name="old_password" label={prevPasswordLabel} />
65+
<TextField fullWidth disabled={disabled} type="password" name="new_password" label={newPasswordLabel} />
66+
<TextField fullWidth disabled={disabled} type="password" name="new_password_confirm" label={confirmPasswordLabel} />
67+
</Stack>
68+
</form>
69+
</DialogContent>
70+
<DialogActions>
71+
<Button loading={disabled} onClick={onClose} color="error" children={cancelStr} />
72+
<Button loading={disabled} onClick={handleSubmit} color="primary" variant="contained" children={submitStr} />
73+
</DialogActions>
74+
</Dialog>
75+
);
76+
};

0 commit comments

Comments
 (0)