Skip to content

Commit 07da51d

Browse files
committed
feat: 백엔드 API와의 세션 동기화를 위한 쿠키 설정 및 관련 컴포넌트 추가
1 parent 52b331a commit 07da51d

12 files changed

Lines changed: 96 additions & 8 deletions

File tree

apps/pyconkr-2025/src/components/pages/sign_in.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { DevSetCookieButton } from "@frontend/common/components";
2+
import { useBackendContext } from "@frontend/common/hooks/useAPI";
13
import { useShopClient, useSignInWithSNSMutation, useUserStatus } from "@frontend/shop/hooks";
24
import { AccountCircleOutlined, Google } from "@mui/icons-material";
35
import { Backdrop, Button, ButtonProps, CircularProgress, Stack, Typography } from "@mui/material";
@@ -15,6 +17,7 @@ type PageeStateType = {
1517

1618
export const ShopSignInPage: FC = Suspense.with({ fallback: <CircularProgress /> }, () => {
1719
const { setAppContext, language } = useAppContext();
20+
const { backendApiAbsoluteDomain, backendApiSessionCookieName } = useBackendContext();
1821
const [state, setState] = useState<PageeStateType>({ openBackdrop: false });
1922
const navigate = useNavigate();
2023
const shopAPIClient = useShopClient();
@@ -92,6 +95,15 @@ export const ShopSignInPage: FC = Suspense.with({ fallback: <CircularProgress />
9295
<PageLayout spacing={6}>
9396
<Typography variant="h4" sx={{ textAlign: "center", fontWeight: "bolder" }} children={signInTitleStr} />
9497
<Stack spacing={1} sx={{ width: "100%", maxWidth: "400px" }}>
98+
{import.meta.env.DEV && (
99+
<DevSetCookieButton
100+
backendDomain={backendApiAbsoluteDomain ?? ""}
101+
cookieName={backendApiSessionCookieName ?? ""}
102+
cookieValue={shopAPIClient.getSessionId() ?? ""}
103+
>
104+
[localhost] 세션 쿠키 동기화 (로그인 전 클릭)
105+
</DevSetCookieButton>
106+
)}
95107
{btnProps.map((props, index) => (
96108
<Button key={index} {...commonBtnProps} {...props} />
97109
))}

apps/pyconkr-2025/src/main.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ const CommonOptions: ContextOptions = {
5353
debug: IS_DEBUG_ENV,
5454
baseUrl: ".",
5555
backendApiDomain,
56+
backendApiAbsoluteDomain: import.meta.env.VITE_PYCONKR_BACKEND_API_DOMAIN,
57+
backendApiCSRFCookieName: import.meta.env.VITE_PYCONKR_BACKEND_CSRF_COOKIE_NAME,
58+
backendApiSessionCookieName: import.meta.env.VITE_PYCONKR_BACKEND_SESSION_COOKIE_NAME,
5659
backendApiTimeout: 10000,
5760
mdxComponents: PyConKRMDXComponents,
5861
};

apps/pyconkr-2026/src/components/pages/sign_in.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { DevSetCookieButton } from "@frontend/common/components";
2+
import { useBackendContext } from "@frontend/common/hooks/useAPI";
13
import { useShopClient, useSignInWithSNSMutation, useUserStatus } from "@frontend/shop/hooks";
24
import { AccountCircleOutlined, Google } from "@mui/icons-material";
35
import { Backdrop, Button, ButtonProps, CircularProgress, Stack, Typography } from "@mui/material";
@@ -15,6 +17,7 @@ type PageeStateType = {
1517

1618
export const ShopSignInPage: FC = Suspense.with({ fallback: <CircularProgress /> }, () => {
1719
const { setAppContext, language } = useAppContext();
20+
const { backendApiAbsoluteDomain, backendApiSessionCookieName } = useBackendContext();
1821
const [state, setState] = useState<PageeStateType>({ openBackdrop: false });
1922
const navigate = useNavigate();
2023
const shopAPIClient = useShopClient();
@@ -92,6 +95,15 @@ export const ShopSignInPage: FC = Suspense.with({ fallback: <CircularProgress />
9295
<PageLayout spacing={6}>
9396
<Typography variant="h4" sx={{ textAlign: "center", fontWeight: "bolder" }} children={signInTitleStr} />
9497
<Stack spacing={1} sx={{ width: "100%", maxWidth: "400px" }}>
98+
{import.meta.env.DEV && (
99+
<DevSetCookieButton
100+
backendDomain={backendApiAbsoluteDomain ?? ""}
101+
cookieName={backendApiSessionCookieName ?? ""}
102+
cookieValue={shopAPIClient.getSessionId() ?? ""}
103+
>
104+
[localhost] 세션 쿠키 동기화 (로그인 전 클릭)
105+
</DevSetCookieButton>
106+
)}
95107
{btnProps.map((props, index) => (
96108
<Button key={index} {...commonBtnProps} {...props} />
97109
))}

apps/pyconkr-2026/src/main.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,10 @@ const CommonOptions: ContextOptions = {
5353
debug: IS_DEBUG_ENV,
5454
baseUrl: ".",
5555
backendApiDomain,
56-
backendApiTimeout: 10000,
56+
backendApiAbsoluteDomain: import.meta.env.VITE_PYCONKR_BACKEND_API_DOMAIN,
5757
backendApiCSRFCookieName: import.meta.env.VITE_PYCONKR_BACKEND_CSRF_COOKIE_NAME,
58+
backendApiSessionCookieName: import.meta.env.VITE_PYCONKR_BACKEND_SESSION_COOKIE_NAME,
59+
backendApiTimeout: 10000,
5860
mdxComponents: PyConKRMDXComponents,
5961
};
6062

packages/common/src/apis/client.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,14 @@ export class BackendAPIClient {
8989
readonly language: supportedLanguages;
9090
readonly baseURL: string;
9191
protected readonly csrfCookieName: string;
92+
protected readonly sessionCookieName: string;
9293
private readonly backendAPI: AxiosInstance;
9394

9495
constructor(
9596
baseURL: string,
9697
timeout: number,
9798
csrfCookieName: string = "csrftoken",
99+
sessionCookieName: string = "sessionid",
98100
withCredentials: boolean = false,
99101
language: supportedLanguages = "ko"
100102
) {
@@ -105,6 +107,7 @@ export class BackendAPIClient {
105107
this.language = language;
106108
this.baseURL = baseURL;
107109
this.csrfCookieName = csrfCookieName;
110+
this.sessionCookieName = sessionCookieName;
108111
this.backendAPI = axios.create({
109112
baseURL,
110113
timeout,
@@ -127,6 +130,10 @@ export class BackendAPIClient {
127130
return getCookie(this.csrfCookieName);
128131
}
129132

133+
getSessionId(): string | undefined {
134+
return getCookie(this.sessionCookieName);
135+
}
136+
130137
_safe_request_without_payload(requestFunc: AxiosRequestWithoutPayload): AxiosRequestWithoutPayload {
131138
return async <T = unknown, Resp = AxiosResponse<T>, D = unknown>(url: string, config?: AxiosRequestConfig<D>) => {
132139
try {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { Button } from "@mui/material";
2+
import { FC, PropsWithChildren } from "react";
3+
import { flushSync } from "react-dom";
4+
import { createRoot } from "react-dom/client";
5+
6+
export const DevSetCookieButton: FC<
7+
PropsWithChildren<{
8+
backendDomain: string;
9+
cookieName: string;
10+
cookieValue: string;
11+
}>
12+
> = ({ backendDomain, cookieName, cookieValue, children }) => {
13+
const handleClick = () => {
14+
if (!backendDomain || !cookieName || !cookieValue) {
15+
alert(
16+
"dev cookie sync: backendDomain/cookieName/cookieValue 중 비어 있는 값이 있습니다. (페이지를 잠시 둘러본 뒤 다시 시도하면 cookie 값이 확보될 수 있습니다.)"
17+
);
18+
return;
19+
}
20+
const container = document.createElement("div");
21+
document.body.appendChild(container);
22+
const root = createRoot(container);
23+
flushSync(() =>
24+
root.render(
25+
<form method="POST" action={`${backendDomain}/dev/set-cookie/`} target="_blank">
26+
<input type="hidden" name="name" defaultValue={cookieName} />
27+
<input type="hidden" name="value" defaultValue={cookieValue} />
28+
<input type="hidden" name="domain" defaultValue=".pycon.kr" />
29+
<input type="hidden" name="secure" defaultValue="true" />
30+
<input type="hidden" name="samesite" defaultValue="None" />
31+
<input type="hidden" name="max_age" defaultValue="1209600" />
32+
</form>
33+
)
34+
);
35+
const form = container.querySelector("form");
36+
if (!form) throw new Error("DevSetCookieButton: form element not found after flushSync render");
37+
form.submit();
38+
root.unmount();
39+
container.remove();
40+
};
41+
42+
return (
43+
<Button variant="outlined" color="warning" onClick={handleClick} sx={{ textTransform: "none" }}>
44+
{children}
45+
</Button>
46+
);
47+
};

packages/common/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export { AutoTextLinking } from "./auto_text_linking";
22
export { CenteredPage } from "./centered_page";
33
export { CommonContextProvider } from "./common_context";
4+
export { DevSetCookieButton } from "./dev_set_cookie_button";
45
export { DndFileInput } from "./dnd_file_input";
56
export { ErrorFallback } from "./error_handler";
67
export { FallbackImage } from "./fallback_image";

packages/common/src/contexts/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ export type ContextOptions = {
66
baseUrl: string;
77
debug?: boolean;
88
backendApiDomain: string;
9+
backendApiAbsoluteDomain?: string;
910
backendApiTimeout: number;
1011
backendApiCSRFCookieName?: string;
12+
backendApiSessionCookieName?: string;
1113
mdxComponents?: MDXComponents;
1214
};
1315

@@ -17,6 +19,8 @@ export const context = createContext<ContextOptions>({
1719
baseUrl: "",
1820
debug: false,
1921
backendApiDomain: "",
22+
backendApiAbsoluteDomain: "",
2023
backendApiTimeout: 10000,
2124
backendApiCSRFCookieName: "",
25+
backendApiSessionCookieName: "",
2226
});

packages/common/src/hooks/useAPI.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const useBackendContext = () => {
2020

2121
export const useBackendClient = () => {
2222
const { language, backendApiDomain, backendApiTimeout } = useBackendContext();
23-
return new BackendAPIClient(backendApiDomain, backendApiTimeout, "", false, language);
23+
return new BackendAPIClient(backendApiDomain, backendApiTimeout, "", "", false, language);
2424
};
2525

2626
export const useFlattenSiteMapQuery = (client: BackendAPIClient) =>

packages/common/src/hooks/useAdminAPI.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ const MUTATION_KEYS = {
6363
};
6464

6565
export const useBackendAdminClient = () => {
66-
const { backendApiDomain, backendApiTimeout, backendApiCSRFCookieName } = useBackendContext();
67-
return new BackendAPIClient(backendApiDomain, backendApiTimeout, backendApiCSRFCookieName, true);
66+
const { backendApiDomain, backendApiTimeout, backendApiCSRFCookieName, backendApiSessionCookieName } = useBackendContext();
67+
return new BackendAPIClient(backendApiDomain, backendApiTimeout, backendApiCSRFCookieName, backendApiSessionCookieName, true);
6868
};
6969

7070
export const useSignedInUserQuery = (client: BackendAPIClient) =>

0 commit comments

Comments
 (0)