Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,26 @@ import { ShoppingCart } from "@mui/icons-material";
import { Badge, badgeClasses, IconButton, styled } from "@mui/material";
import { ErrorBoundary, Suspense } from "@suspensive/react";
import { FC } from "react";
import { useNavigate } from "react-router-dom";
import { Link as RouterLink } from "react-router-dom";

type InnerCartBadgeButtonPropType = {
loading?: boolean;
count?: number;
};

// `as typeof IconButton`으로 styled가 잃어버린 polymorphic 타입(component/to)을 복원한다.
const ColoredIconButton = styled(IconButton)(({ theme }) => ({
color: theme.palette.primary.nonFocus,
"&:hover": { color: theme.palette.primary.dark },
"&:active": { color: theme.palette.primary.main },
transition: "color 0.4s ease, background-color 0.4s ease",
}));
})) as typeof IconButton;

const InnerCartBadge = styled(Badge)({ [`& .${badgeClasses.badge}`]: { top: "-12px", right: "-3px" } });

const InnerCartBadgeButton: FC<InnerCartBadgeButtonPropType> = ({ loading, count }) => {
const navigate = useNavigate();

return (
<ColoredIconButton loading={loading} onClick={() => navigate("/store/cart")}>
<ColoredIconButton loading={loading} component={RouterLink} to="/store/cart">
<ShoppingCart />
{count !== undefined && count > 0 && <InnerCartBadge badgeContent={count} color="primary" overlap="circular" />}
</ColoredIconButton>
Expand Down
19 changes: 7 additions & 12 deletions apps/pyconkr-2025/src/components/layout/SignInButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useShopClient, useSignOutMutation, useUserStatus } from "@frontend/shop
import { Login, Logout } from "@mui/icons-material";
import { Button, Stack } from "@mui/material";
import { ErrorBoundary, Suspense } from "@suspensive/react";
import { useNavigate } from "react-router-dom";
import { Link as RouterLink } from "react-router-dom";

import { useAppContext } from "@apps/pyconkr-2025/contexts/app_context";

Expand All @@ -23,24 +23,20 @@ const InnerSignInButtonImpl: React.FC<InnerSignInButtonImplPropType> = ({
isMainPath = true,
onClose,
}) => {
const navigate = useNavigate();
const { language } = useAppContext();

const signInBtnStr = language === "ko" ? "로그인" : "Sign In";
const signOutBtnStr = language === "ko" ? "로그아웃" : "Sign Out";

const handleClick = () => {
if (signedIn) {
onSignOut?.();
} else {
onClose?.();
navigate("/account/sign-in");
}
};
// 로그인 상태에 따라: 로그아웃은 클릭 액션, 로그인은 Ctrl+클릭(새 탭)이 동작하도록 Link로 이동.
const navProps = signedIn
? { onClick: () => onSignOut?.() }
: ({ component: RouterLink, to: "/account/sign-in", onClick: () => onClose?.() } as const);

if (isMobile) {
return (
<Button
{...navProps}
variant="text"
sx={{
color: isMainPath ? "white" : "rgba(18, 109, 127, 0.9)",
Expand All @@ -56,7 +52,6 @@ const InnerSignInButtonImpl: React.FC<InnerSignInButtonImplPropType> = ({
},
}}
loading={loading}
onClick={handleClick}
>
<Stack direction="row" alignItems="center" sx={{ gap: "3px" }}>
{signedIn ? <Logout fontSize="small" /> : <Login fontSize="small" />}
Expand All @@ -68,10 +63,10 @@ const InnerSignInButtonImpl: React.FC<InnerSignInButtonImplPropType> = ({

return (
<Button
{...navProps}
variant="text"
sx={({ palette }) => ({ color: palette.primary.dark })}
loading={loading}
onClick={() => (signedIn ? onSignOut?.() : navigate("/account/sign-in"))}
children={signedIn ? signOutBtnStr : signInBtnStr}
/>
);
Expand Down
14 changes: 4 additions & 10 deletions apps/pyconkr-2026/src/components/layout/CartBadgeButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ShoppingCart } from "@mui/icons-material";
import { Badge, badgeClasses, IconButton, styled } from "@mui/material";
import { ErrorBoundary, Suspense } from "@suspensive/react";
import { FC } from "react";
import { useNavigate } from "react-router-dom";
import { Link as RouterLink } from "react-router-dom";

type CartBadgeButtonProps = { onClose?: () => void };

Expand All @@ -12,27 +12,21 @@ type InnerCartBadgeButtonPropType = CartBadgeButtonProps & {
count?: number;
};

// `as typeof IconButton`으로 styled가 잃어버린 polymorphic 타입(component/to)을 복원한다.
const ColoredIconButton = styled(IconButton)(({ theme }) => ({
color: theme.palette.primary.nonFocus,
"&:hover": { color: theme.palette.primary.dark },
"&:active": { color: theme.palette.primary.main },
transition: "color 0.4s ease, background-color 0.4s ease",
}));
})) as typeof IconButton;

const InnerCartBadge = styled(Badge)({ [`& .${badgeClasses.badge}`]: { top: "-12px", right: "-3px" } });

const InnerCartBadgeButton: FC<InnerCartBadgeButtonPropType> = ({ loading, count, onClose }) => {
const navigate = useNavigate();

if (!loading && (count === undefined || count <= 0)) return null;

const handleClick = () => {
onClose?.();
navigate("/store/cart");
};

return (
<ColoredIconButton loading={loading} onClick={handleClick}>
<ColoredIconButton loading={loading} component={RouterLink} to="/store/cart" onClick={() => onClose?.()}>
<ShoppingCart />
{count !== undefined && count > 0 && <InnerCartBadge badgeContent={count} color="primary" overlap="circular" />}
</ColoredIconButton>
Expand Down
10 changes: 4 additions & 6 deletions apps/pyconkr-2026/src/components/layout/UserMenuButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { AccountCircle, Login, Logout, Receipt } from "@mui/icons-material";
import { Button, Divider, IconButton, ListItemIcon, ListItemText, Menu, MenuItem, styled, Typography } from "@mui/material";
import { ErrorBoundary, Suspense } from "@suspensive/react";
import { FC, MouseEvent, useState } from "react";
import { useNavigate } from "react-router-dom";
import { Link as RouterLink } from "react-router-dom";

import { useAppContext } from "@apps/pyconkr-2026/contexts/app_context";

Expand Down Expand Up @@ -42,7 +42,6 @@ type InnerUserMenuButtonPropType = UserMenuButtonProps & {
};

const InnerUserMenuButton: FC<InnerUserMenuButtonPropType> = ({ loading, user, onSignOut, onClose, showLabel }) => {
const navigate = useNavigate();
const { language } = useAppContext();
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
const open = Boolean(anchorEl);
Expand All @@ -54,10 +53,9 @@ const InnerUserMenuButton: FC<InnerUserMenuButtonPropType> = ({ loading, user, o
const orderHistoryLabel = language === "ko" ? "결제 내역" : "Order History";
const signOutLabel = language === "ko" ? "로그아웃" : "Sign Out";

const goTo = (path: string) => {
const closeAll = () => {
handleMenuClose();
onClose?.();
navigate(path);
};

const handleSignOut = () => {
Expand Down Expand Up @@ -102,7 +100,7 @@ const InnerUserMenuButton: FC<InnerUserMenuButtonPropType> = ({ loading, user, o
</Typography>
</UserNameItem>,
<Divider key="divider" sx={{ my: 0.5 }} />,
<MenuItem key="orders" onClick={() => goTo("/store/order-histories")}>
<MenuItem key="orders" component={RouterLink} to="/store/order-histories" onClick={closeAll}>
<ListItemIcon>
<Receipt fontSize="small" />
</ListItemIcon>
Expand All @@ -116,7 +114,7 @@ const InnerUserMenuButton: FC<InnerUserMenuButtonPropType> = ({ loading, user, o
</MenuItem>,
]
) : (
<MenuItem onClick={() => goTo("/account/sign-in")}>
<MenuItem component={RouterLink} to="/account/sign-in" onClick={closeAll}>
<ListItemIcon>
<Login fontSize="small" />
</ListItemIcon>
Expand Down
22 changes: 9 additions & 13 deletions apps/pyconkr-participant-portal/src/components/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AccountCircle } from "@mui/icons-material";
import { AppBar, ButtonBase, CircularProgress, IconButton, Menu, MenuItem, Stack, styled, Toolbar, Tooltip, Typography } from "@mui/material";
import { ErrorBoundary, Suspense } from "@suspensive/react";
import { FC, MouseEventHandler, useState } from "react";
import { Link, Outlet, useNavigate } from "react-router-dom";
import { Link, Outlet } from "react-router-dom";

import { LOCAL_STORAGE_LANGUAGE_KEY } from "@apps/pyconkr-participant-portal/consts/local_stroage";
import { useAppContext } from "@apps/pyconkr-participant-portal/contexts/app_context";
Expand Down Expand Up @@ -59,7 +59,6 @@ type ProfileMenuButtonState = {
};

const InnerProfileMenuButton: FC<ProfileMenuButtonProps> = ({ loading, signedIn }) => {
const navigate = useNavigate();
const participantPortalClient = useParticipantPortalClient();
const [btnState, setBtnState] = useState<ProfileMenuButtonState>({});
const openMenu: MouseEventHandler<HTMLButtonElement> = (evt) => setBtnState((ps) => ({ ...ps, anchorEl: evt.currentTarget }));
Expand All @@ -70,16 +69,9 @@ const InnerProfileMenuButton: FC<ProfileMenuButtonProps> = ({ loading, signedIn
const signOutStr = language === "ko" ? "로그아웃" : "Sign Out";
const editProfileStr = language === "ko" ? "프로필 편집" : "Edit Profile";

const goToProfileEditor = () => {
navigate("/user");
closeMenu();
};
const goToSignIn = () => navigate("/signin");
const signOutMutation = useSignOutMutation(participantPortalClient);
const onSignInOutClick = () => {
if (signedIn) signOutMutation.mutate();
else goToSignIn();

const handleSignOut = () => {
signOutMutation.mutate();
closeMenu();
};

Expand All @@ -89,8 +81,12 @@ const InnerProfileMenuButton: FC<ProfileMenuButtonProps> = ({ loading, signedIn
<IconButton children={loading ? <CircularProgress size={24} /> : <AccountCircle />} disabled={loading} onClick={openMenu} />
</Tooltip>
<Menu open={!!btnState.anchorEl} anchorEl={btnState.anchorEl} onClose={closeMenu}>
{signedIn && <MenuItem children={editProfileStr} onClick={goToProfileEditor} />}
<MenuItem children={signedIn ? signOutStr : signInStr} onClick={onSignInOutClick} />
{signedIn && <MenuItem children={editProfileStr} component={Link} to="/user" onClick={closeMenu} />}
{signedIn ? (
<MenuItem children={signOutStr} onClick={handleSignOut} />
) : (
<MenuItem children={signInStr} component={Link} to="/signin" onClick={closeMenu} />
)}
</Menu>
</>
);
Expand Down
10 changes: 5 additions & 5 deletions apps/pyconkr-participant-portal/src/components/pages/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
} from "@mui/material";
import { ErrorBoundary, Suspense } from "@suspensive/react";
import { CSSProperties, FC, useState } from "react";
import { useNavigate } from "react-router-dom";
import { Link as RouterLink } from "react-router-dom";
import { isEmpty } from "remeda";

import { ErrorPage } from "@apps/pyconkr-participant-portal/components/elements/error_page";
Expand Down Expand Up @@ -97,7 +97,6 @@ type InnerLandingPageState = {
};

const InnerLandingPage: FC = () => {
const navigate = useNavigate();
const { language } = useAppContext();
const isMobile = useMediaQuery((theme) => theme.breakpoints.down("sm"));
const participantPortalAPIClient = useParticipantPortalClient();
Expand Down Expand Up @@ -150,7 +149,7 @@ const InnerLandingPage: FC = () => {
<Typography variant="body1" children={emailStr} />
</Stack>
<Stack sx={{ width: "100%", maxWidth: "8rem" }}>
<Button variant="contained" size="small" onClick={() => navigate("/user")} children={editProfileStr} />
<Button variant="contained" size="small" component={RouterLink} to="/user" children={editProfileStr} />
</Stack>
</Stack>
</Stack>
Expand Down Expand Up @@ -178,8 +177,9 @@ const InnerLandingPage: FC = () => {
return (
<ListItem key={audit.id} disablePadding sx={{ cursor: "pointer", border: "1px solid #ccc" }}>
<ListItemButton
component={RouterLink}
to={navigateTo}
children={<ListItemText primary={audit.str_repr} secondary={TranslatedAuditState[audit.status][language]} />}
onClick={() => navigate(navigateTo)}
/>
</ListItem>
);
Expand All @@ -196,7 +196,7 @@ const InnerLandingPage: FC = () => {
<List>
{sessions.map((s) => (
<ListItem key={s.id} disablePadding sx={{ cursor: "pointer", border: "1px solid #ccc" }}>
<ListItemButton children={<ListItemText primary={s.title} />} onClick={() => navigate(`/session/${s.id}`)} />
<ListItemButton component={RouterLink} to={`/session/${s.id}`} children={<ListItemText primary={s.title} />} />
</ListItem>
))}
</List>
Expand Down