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
7 changes: 6 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,24 @@ jobs:
BUMP_RULE: ${{ (github.event_name == 'workflow_dispatch' && inputs.WORKFLOW_PHASE || 'dev') == 'dev' && '--mode development' || '' }}
AWS_S3_PYCONKR_FRONTEND_BUCKET: ${{ (github.event_name == 'workflow_dispatch' && inputs.WORKFLOW_PHASE || 'dev') == 'dev' && secrets.AWS_S3_PYCONKR_FRONTEND_BUCKET_DEV || secrets.AWS_S3_PYCONKR_FRONTEND_BUCKET_PROD }}
AWS_S3_PYCONKR_ADMIN_BUCKET: ${{ (github.event_name == 'workflow_dispatch' && inputs.WORKFLOW_PHASE || 'dev') == 'dev' && secrets.AWS_S3_PYCONKR_ADMIN_BUCKET_DEV || secrets.AWS_S3_PYCONKR_ADMIN_BUCKET_PROD }}
AWS_S3_PYCONKR_PARTICIPANT_PORTAL_BUCKET: ${{ (github.event_name == 'workflow_dispatch' && inputs.WORKFLOW_PHASE || 'dev') == 'dev' && secrets.AWS_S3_PYCONKR_PARTICIPANT_PORTAL_BUCKET_DEV || secrets.AWS_S3_PYCONKR_PARTICIPANT_PORTAL_BUCKET_PROD }}
AWS_CLOUDFRONT_PYCONKR_FRONTEND_DISTRIBUTION_ID: ${{ (github.event_name == 'workflow_dispatch' && inputs.WORKFLOW_PHASE || 'dev') == 'dev' && secrets.AWS_CLOUDFRONT_PYCONKR_FRONTEND_DISTRIBUTION_ID_DEV || secrets.AWS_CLOUDFRONT_PYCONKR_FRONTEND_DISTRIBUTION_ID_PROD }}
AWS_CLOUDFRONT_PYCONKR_ADMIN_DISTRIBUTION_ID: ${{ (github.event_name == 'workflow_dispatch' && inputs.WORKFLOW_PHASE || 'dev') == 'dev' && secrets.AWS_CLOUDFRONT_PYCONKR_ADMIN_DISTRIBUTION_ID_DEV || secrets.AWS_CLOUDFRONT_PYCONKR_ADMIN_DISTRIBUTION_ID_PROD }}
AWS_CLOUDFRONT_PYCONKR_PARTICIPANT_PORTAL_DISTRIBUTION_ID: ${{ (github.event_name == 'workflow_dispatch' && inputs.WORKFLOW_PHASE || 'dev') == 'dev' && secrets.AWS_CLOUDFRONT_PYCONKR_PARTICIPANT_PORTAL_DISTRIBUTION_ID_DEV || secrets.AWS_CLOUDFRONT_PYCONKR_PARTICIPANT_PORTAL_DISTRIBUTION_ID_PROD }}

strategy:
matrix:
application: [pyconkr, pyconkr-admin]
application: [pyconkr, pyconkr-admin, pyconkr-participant-portal]
include:
- application: pyconkr
aws_s3_bucket_key: AWS_S3_PYCONKR_FRONTEND_BUCKET
aws_cloudfront_distribution_key: AWS_CLOUDFRONT_PYCONKR_FRONTEND_DISTRIBUTION_ID
- application: pyconkr-admin
aws_s3_bucket_key: AWS_S3_PYCONKR_ADMIN_BUCKET
aws_cloudfront_distribution_key: AWS_CLOUDFRONT_PYCONKR_ADMIN_DISTRIBUTION_ID
- application: pyconkr-participant-portal
aws_s3_bucket_key: AWS_S3_PYCONKR_PARTICIPANT_PORTAL_BUCKET
aws_cloudfront_distribution_key: AWS_CLOUDFRONT_PYCONKR_PARTICIPANT_PORTAL_DISTRIBUTION_ID

steps:
- uses: actions/checkout@master
Expand Down
47 changes: 47 additions & 0 deletions apps/pyconkr-participant-portal/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<!doctype html>
<html lang="en">
<head>
<meta charSet="UTF-8" />
<base href="/" />
<link rel="icon" href="/favicon.ico" sizes="32x32">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/favicon-180.png">

<meta name="theme-color" content="#fff" />
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#fff" />
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#fff" />

<meta name="msapplication-navbutton-color" content="#fff" />
<meta name="msapplication-TileColor" content="#fff" />
<meta name="msapplication-TileImage" content="/favicon-192.png" />
<meta name="application-name" content="PyCon KR" />
<meta name="apple-mobile-web-app-title" content="PyCon KR" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="mobile-web-app-capable" content="yes" />
<!-- https://developers.google.com/web/fundamentals/web-app-manifest/ -->
<link rel="manifest" href="/site.webmanifest" />

<meta name="viewport" content="width=device-width,
height=device-height,
target-densitydpi=device-dpi,
initial-scale=1.0,
minimum-scale=1.0,
maximum-scale=1.0,
user-scalable=0,
user-scalable=no,
shrink-to-fit=no" />
<meta name="author" content="PyCon Korea Organizing Team" />
<meta name="description" content="PyCon Korea Participant Portal" />
<meta name="keywords" content="PyCon, Python, Conference, Korea" />
<meta name="google" content="notranslate" />
<meta name="googlebot" content="index, follow" />
<meta name="robots" content="index, follow" />

<title>PyCon Korea Participant Portal</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./src/main.tsx"></script>
</body>
</html>
13 changes: 13 additions & 0 deletions apps/pyconkr-participant-portal/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "@apps/pyconkr-participant-portal",
"dependencies": {
"@frontend/common": "workspace:*",
"@frontend/shop": "workspace:*"
},
"devDependencies": {
"vite": "^6.3.5",
"vite-plugin-mdx": "^3.6.1",
"vite-plugin-mkcert": "^1.17.8",
"vite-plugin-svgr": "^4.3.0"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
1 change: 1 addition & 0 deletions apps/pyconkr-participant-portal/public/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions apps/pyconkr-participant-portal/public/site.webmanifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "PyCon Korea Participant Portal",
"icons": [
{
"src": "favicon-192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "favicon-512.png",
"type": "image/png",
"sizes": "512x512",
"purpose": "maskable"
},
{
"src": "favicon-512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"id": "/",
"start_url": "/",
"scope": "/",
"display": "standalone"
}
22 changes: 22 additions & 0 deletions apps/pyconkr-participant-portal/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as React from "react";
import { Navigate, Route, Routes } from "react-router-dom";

import { Layout } from "./components/layout.tsx";
import { LandingPage } from "./components/pages/home.tsx";
import { ProfileEditor } from "./components/pages/profile_editor.tsx";
import { SessionEditor } from "./components/pages/session_editor";
import { SignInPage } from "./components/pages/signin.tsx";
import { SponsorEditor } from "./components/pages/sponsor_editor";

export const App: React.FC = () => (
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<LandingPage />} />
<Route path="/signin" element={<SignInPage />} />
<Route path="/user" element={<ProfileEditor />} />
<Route path="/sponsor/:id" element={<SponsorEditor />} />
<Route path="/session/:sessionId" element={<SessionEditor />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Route>
</Routes>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import * as Common from "@frontend/common";
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, TextField } from "@mui/material";
import { enqueueSnackbar, OptionsObject } from "notistack";
import * as React from "react";

import { useAppContext } from "../../contexts/app_context";

type ChangePasswordDialogProps = {
open: boolean;
onClose: () => void;
};

type PasswordFormDataType = {
old_password: string;
new_password: string;
new_password_confirm: string;
};

export const ChangePasswordDialog: React.FC<ChangePasswordDialogProps> = ({ open, onClose }) => {
const formRef = React.useRef<HTMLFormElement>(null);
const { language } = useAppContext();
const participantPortalClient = Common.Hooks.BackendParticipantPortalAPI.useParticipantPortalClient();
const changePasswordMutation = Common.Hooks.BackendParticipantPortalAPI.useChangePasswordMutation(participantPortalClient);

const addSnackbar = (c: string | React.ReactNode, variant: OptionsObject["variant"]) =>
enqueueSnackbar(c, { variant, anchorOrigin: { vertical: "bottom", horizontal: "center" } });

const titleStr = language === "ko" ? "비밀번호 변경" : "Change Password";
const prevPasswordLabel = language === "ko" ? "이전 비밀번호" : "Previous Password";
const newPasswordLabel = language === "ko" ? "새 비밀번호" : "New Password";
const confirmPasswordLabel = language === "ko" ? "새 비밀번호 확인" : "Confirm New Password";
const cancelStr = language === "ko" ? "취소" : "Cancel";
const submitStr = language === "ko" ? "수정" : "Apply changes";
const passwordChangedStr = language === "ko" ? "비밀번호가 성공적으로 변경되었습니다." : "Password changed successfully.";

const handleSubmit = () => {
if (!Common.Utils.isFormValid(formRef.current)) return;

const formData = Common.Utils.getFormValue<PasswordFormDataType>({ form: formRef.current });
changePasswordMutation.mutate(formData, {
onSuccess: () => {
addSnackbar(passwordChangedStr, "success");
onClose();
},
onError: (error) => {
console.error("Change password failed:", error);

let errorMessage = error instanceof Error ? error.message : "An unknown error occurred.";
if (error instanceof Common.BackendAPIs.BackendAPIClientError) errorMessage = error.message;

addSnackbar(errorMessage, "error");
},
});
};

const disabled = changePasswordMutation.isPending;

return (
<Dialog open={open} maxWidth="sm" fullWidth>
<DialogTitle children={titleStr} />
<DialogContent>
<form ref={formRef}>
<Stack spacing={2} sx={{ my: 1 }}>
<TextField fullWidth disabled={disabled} type="password" name="old_password" label={prevPasswordLabel} />
<TextField fullWidth disabled={disabled} type="password" name="new_password" label={newPasswordLabel} />
<TextField fullWidth disabled={disabled} type="password" name="new_password_confirm" label={confirmPasswordLabel} />
</Stack>
</form>
</DialogContent>
<DialogActions>
<Button loading={disabled} onClick={onClose} color="error" children={cancelStr} />
<Button loading={disabled} onClick={handleSubmit} color="primary" variant="contained" children={submitStr} />
</DialogActions>
</Dialog>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import * as Common from "@frontend/common";
import { Button, Chip, Dialog, DialogActions, DialogContent, DialogTitle, Typography } from "@mui/material";
import { enqueueSnackbar, OptionsObject } from "notistack";
import * as React from "react";

import { useAppContext } from "../../contexts/app_context";

type ModificationAuditCancelConfirmDialogProps = {
modificationAuditId: string;
open: boolean;
onClose: () => void;
};

export const ModificationAuditCancelConfirmDialog: React.FC<ModificationAuditCancelConfirmDialogProps> = ({ open, onClose, modificationAuditId }) => {
const reasonInputRef = React.useRef<HTMLInputElement>(null);
const { language } = useAppContext();
const participantPortalClient = Common.Hooks.BackendParticipantPortalAPI.useParticipantPortalClient();
const cancelModificationAuditMutation = Common.Hooks.BackendParticipantPortalAPI.useCancelModificationAuditMutation(participantPortalClient);

const addSnackbar = (c: string | React.ReactNode, variant: OptionsObject["variant"]) =>
enqueueSnackbar(c, { variant, anchorOrigin: { vertical: "bottom", horizontal: "center" } });

const titleStr = language === "ko" ? "수정 요청 철회 확인" : "Confirm Withdrawal of Modification Request";
const content =
language === "ko" ? (
<Typography variant="body1" gutterBottom>
제출하신 수정 요청을 철회하시겠습니까?
<br />
철회 후에는 다시 수정 요청을 하셔야 합니다.
<br />
계속하시려면 <Chip label="철회" color="error" size="small" /> 버튼을 클릭해 주세요.
</Typography>
) : (
<Typography>
Are you sure you want to withdraw your modification request?
<br />
After withdrawal, you will need to submit a new modification request.
<br />
To continue, please click the <Chip label="Cancel" color="error" size="small" /> button below.
</Typography>
);
// const reasonStr = language === "ko" ? "철회 사유 (선택)" : "Reason for Withdrawal (Optional)";
const submitStr = language === "ko" ? "철회" : "Withdraw Request";
const cancelStr = language === "ko" ? "취소" : "Cancel";
const successStr = language === "ko" ? "수정 요청이 철회되었습니다." : "Modification request has been canceled.";

const onClick = () => {
cancelModificationAuditMutation.mutate(
{
id: modificationAuditId,
reason: reasonInputRef.current?.value || "",
},
{
onSuccess: () => {
addSnackbar(successStr, "success");
onClose();
},
onError: (error) => {
console.error("Canceling ModAudit failed:", error);

let errorMessage = error instanceof Error ? error.message : "An unknown error occurred.";
if (error instanceof Common.BackendAPIs.BackendAPIClientError) errorMessage = error.message;

addSnackbar(errorMessage, "error");
},
}
);
};

const disabled = cancelModificationAuditMutation.isPending;

return (
<Dialog open={open} maxWidth="sm" fullWidth>
<DialogTitle children={titleStr} />
<DialogContent>
{content}
{/* <br />
<TextField ref={reasonInputRef} disabled={disabled} label={reasonStr} fullWidth variant="filled" /> */}
</DialogContent>
<DialogActions>
<Button disabled={disabled} onClick={onClose} color="error" children={cancelStr} />
<Button disabled={disabled} onClick={onClick} color="error" children={submitStr} variant="contained" />
</DialogActions>
</Dialog>
);
};
Loading