-
Notifications
You must be signed in to change notification settings - Fork 1
[FE-Feat] 로그인 및 캘린더 인증 로직을 beforeLoad로 이전 #430
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
7fab2d7
54597fd
750abb8
eb42a18
cd4e2a8
3fd6811
2b2d112
658a665
cd58984
9143c5a
5497415
1c7c29f
c4a44fd
91e989f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ node_modules | |
| dist | ||
| dist-ssr | ||
| *.local | ||
| .env* | ||
|
|
||
| # Editor directories and files | ||
| .vscode/* | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,23 +1,10 @@ | ||
| import { useQueryClient } from '@tanstack/react-query'; | ||
| import { createFileRoute, useNavigate } from '@tanstack/react-router'; | ||
| import { useEffect } from 'react'; | ||
| import { createFileRoute, redirect } from '@tanstack/react-router'; | ||
|
|
||
| import { calendarKeys } from '@/features/my-calendar/api/keys'; | ||
|
|
||
| const Redirect = () => { | ||
| const queryClient = useQueryClient(); | ||
| const navigate = useNavigate(); | ||
|
|
||
| useEffect(() => { | ||
| (async () => { | ||
| await queryClient.invalidateQueries({ queryKey: calendarKeys.all }); | ||
| navigate({ to: '/my-calendar' }); | ||
| })(); | ||
| }, [queryClient, navigate]); | ||
|
|
||
| return null; | ||
| }; | ||
|
|
||
| export const Route = createFileRoute('/oauth/redirect/calendar/')({ | ||
| component: Redirect, | ||
| beforeLoad: async ({ context }) => { | ||
| await context.queryClient.invalidateQueries({ queryKey: calendarKeys.all }); | ||
| throw redirect({ to: '/my-calendar' }); | ||
| }, | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,24 +1,18 @@ | ||
| import { createFileRoute, useSearch } from '@tanstack/react-router'; | ||
| import { useEffect } from 'react'; | ||
| import { createFileRoute } from '@tanstack/react-router'; | ||
|
|
||
| import { useJWTMutation } from '@/features/login/api/mutations'; | ||
| import { jwtMutation } from '@/features/login/api/mutations'; | ||
| import { getLastRoutePath } from '@/utils/route'; | ||
|
|
||
| const Redirect = () => { | ||
| const { loginMutate } = useJWTMutation(); | ||
| const lastPath = getLastRoutePath(); | ||
| const params: { code: string } = useSearch({ from: '/oauth/redirect/login/' }); | ||
| const { code } = params; | ||
|
|
||
| useEffect(() => { | ||
| if (code) { | ||
| loginMutate({ code, lastPath }); | ||
| } | ||
| }, [code, loginMutate, lastPath]); | ||
|
|
||
| return null; | ||
| type SearchWithCode = { | ||
| code?: string; | ||
| }; | ||
|
|
||
| export const Route = createFileRoute('/oauth/redirect/login/')({ | ||
| component: Redirect, | ||
| beforeLoad: async ({ search }: { search: SearchWithCode }) => { | ||
| const lastPath = getLastRoutePath(); | ||
| const { loginMutate } = jwtMutation(); | ||
| if (search.code) { | ||
| await loginMutate({ code: search.code, lastPath }); | ||
| } | ||
| }, | ||
| }); | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,54 @@ | ||||||||||||||||||||||
| type MutationFn<TArgs, TRes> = (args: TArgs) => Promise<TRes>; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| interface RedirectMutationOptions<TArgs, TRes> { | ||||||||||||||||||||||
| mutationFn: MutationFn<TArgs, TRes>; | ||||||||||||||||||||||
| onSuccess?: (response: TRes, args: TArgs) => unknown; | ||||||||||||||||||||||
| onError?: (error: unknown, args: TArgs) => unknown; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| type Mutation<TArgs> = { | ||||||||||||||||||||||
| mutate: (args: TArgs) => void; | ||||||||||||||||||||||
| mutateAsync: (args: TArgs) => Promise<void>; | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| let globalHandleError: (error: unknown) => unknown; | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Initialize the global error handler to prevent runtime errors. The global error handler is declared but not initialized, which could cause issues if -let globalHandleError: (error: unknown) => unknown;
+let globalHandleError: (error: unknown) => unknown = () => {
+ // Default no-op handler to prevent runtime errors
+};Or alternatively, add proper null checking: - globalHandleError?.(error);
+ if (globalHandleError) {
+ globalHandleError(error);
+ }
🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const isRedirectError = (error: unknown): boolean => ( | ||||||||||||||||||||||
| typeof error === 'object' && error !== null | ||||||||||||||||||||||
| && 'isRedirect' in error && error.isRedirect === true | ||||||||||||||||||||||
| ); | ||||||||||||||||||||||
|
Comment on lines
+16
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve type safety of the redirect error detection. The -const isRedirectError = (error: unknown): boolean => (
- typeof error === 'object' && error !== null
- && 'isRedirect' in error && error.isRedirect === true
-);
+const isRedirectError = (error: unknown): error is { isRedirect: true } => (
+ typeof error === 'object' &&
+ error !== null &&
+ 'isRedirect' in error &&
+ (error as { isRedirect: unknown }).isRedirect === true
+);This provides:
📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export const setDefaultMutationErrorHandler = (fn: (error: unknown) => void) => { | ||||||||||||||||||||||
| globalHandleError = fn; | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * 리액트 컴포넌트 외부에서 사용할 수 있는 뮤테이션 함수 생성기. | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| export const createMutation = <TArgs, TRes>( | ||||||||||||||||||||||
| options: RedirectMutationOptions<TArgs, TRes>, | ||||||||||||||||||||||
| ): Mutation<TArgs> => { | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const mutateAsync = async (args: TArgs) => { | ||||||||||||||||||||||
| try { | ||||||||||||||||||||||
| const response = await options.mutationFn(args); | ||||||||||||||||||||||
| options.onSuccess?.(response, args); | ||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||
| if (isRedirectError(error)) throw error; | ||||||||||||||||||||||
| globalHandleError?.(error); | ||||||||||||||||||||||
| options.onError?.(error, args); | ||||||||||||||||||||||
| throw error; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const mutate = (args: TArgs) => { | ||||||||||||||||||||||
| options.mutationFn(args) | ||||||||||||||||||||||
| .then(res => options.onSuccess?.(res, args)) | ||||||||||||||||||||||
| .catch(err => { | ||||||||||||||||||||||
| globalHandleError?.(err); | ||||||||||||||||||||||
| options.onError?.(err, args); | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just ask; |
||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return { mutate, mutateAsync }; | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export * from './createMutation'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling and validation for the beforeLoad lifecycle.
The
beforeLoadfunction lacks proper error handling and validation:search.codeexists and is a valid string before proceeding with the mutation.loginMutatethrows an error that isn't a redirect, it will cause the route loading to fail without proper user feedback.SearchWithCodetype allowscodeto be undefined, but the logic assumes it exists.export const Route = createFileRoute('/oauth/redirect/login/')({ - beforeLoad: async ({ search }: { search: SearchWithCode }) => { + beforeLoad: async ({ search }: { search: SearchWithCode }) => { + if (!search.code) { + throw redirect({ to: '/login' }); + } + const lastPath = getLastRoutePath(); const { loginMutate } = jwtMutation(); - if (search.code) { - await loginMutate({ code: search.code, lastPath }); - } + + try { + await loginMutate({ code: search.code, lastPath }); + } catch (error) { + // Let redirect errors bubble up, handle others + if (error && typeof error === 'object' && 'isRedirect' in error) { + throw error; + } + throw redirect({ to: '/login' }); + } }, });📝 Committable suggestion
🤖 Prompt for AI Agents