mirror of
https://github.com/logto-io/logto.git
synced 2025-02-03 21:48:55 -05:00
refactor(ui): refactor social signin callback flow (#967)
* refactor(ui): refactor social signin callback flow refactor social signin callback flow * fix(ui): change file structure change file structure
This commit is contained in:
parent
6b53ed5f4f
commit
7015d81378
8 changed files with 74 additions and 46 deletions
|
@ -15,6 +15,7 @@ import Register from './pages/Register';
|
||||||
import SecondarySignIn from './pages/SecondarySignIn';
|
import SecondarySignIn from './pages/SecondarySignIn';
|
||||||
import SignIn from './pages/SignIn';
|
import SignIn from './pages/SignIn';
|
||||||
import SocialRegister from './pages/SocialRegister';
|
import SocialRegister from './pages/SocialRegister';
|
||||||
|
import SocialSignInCallback from './pages/SocialSignInCallback';
|
||||||
import getSignInExperienceSettings from './utils/sign-in-experience';
|
import getSignInExperienceSettings from './utils/sign-in-experience';
|
||||||
|
|
||||||
import './scss/normalized.scss';
|
import './scss/normalized.scss';
|
||||||
|
@ -54,7 +55,7 @@ const App = () => {
|
||||||
<Route path="/" element={<Navigate replace to="/sign-in" />} />
|
<Route path="/" element={<Navigate replace to="/sign-in" />} />
|
||||||
<Route path="/sign-in" element={<SignIn />} />
|
<Route path="/sign-in" element={<SignIn />} />
|
||||||
<Route path="/sign-in/consent" element={<Consent />} />
|
<Route path="/sign-in/consent" element={<Consent />} />
|
||||||
<Route path="/sign-in/callback/:connector" element={<SignIn />} />
|
<Route path="/sign-in/callback/:connector" element={<SocialSignInCallback />} />
|
||||||
<Route path="/sign-in/:method" element={<SecondarySignIn />} />
|
<Route path="/sign-in/:method" element={<SecondarySignIn />} />
|
||||||
<Route path="/register" element={<Register />} />
|
<Route path="/register" element={<Register />} />
|
||||||
<Route path="/register/:method" element={<Register />} />
|
<Route path="/register/:method" element={<Register />} />
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import useSocialSignInListener from '@/hooks/use-social-signin-listener';
|
import useNativeMessageListener from '@/hooks/use-native-message-listener';
|
||||||
|
|
||||||
import SocialSignInList from '../SocialSignInList';
|
import SocialSignInList from '../SocialSignInList';
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ type Props = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const PrimarySocialSignIn = ({ className }: Props) => {
|
const PrimarySocialSignIn = ({ className }: Props) => {
|
||||||
useSocialSignInListener();
|
useNativeMessageListener();
|
||||||
|
|
||||||
return <SocialSignInList className={className} />;
|
return <SocialSignInList className={className} />;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import { fireEvent, waitFor } from '@testing-library/react';
|
import { fireEvent, waitFor } from '@testing-library/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
|
||||||
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||||
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
|
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
|
||||||
import { socialConnectors, mockSignInExperienceSettings } from '@/__mocks__/logto';
|
import { socialConnectors, mockSignInExperienceSettings } from '@/__mocks__/logto';
|
||||||
import * as socialSignInApi from '@/apis/social';
|
import * as socialSignInApi from '@/apis/social';
|
||||||
import { generateState, storeState } from '@/hooks/utils';
|
|
||||||
|
|
||||||
import SecondarySocialSignIn, { defaultSize } from '.';
|
import SecondarySocialSignIn, { defaultSize } from '.';
|
||||||
|
|
||||||
|
@ -17,10 +16,6 @@ describe('SecondarySocialSignIn', () => {
|
||||||
.spyOn(socialSignInApi, 'invokeSocialSignIn')
|
.spyOn(socialSignInApi, 'invokeSocialSignIn')
|
||||||
.mockResolvedValue({ redirectTo: `${mockOrigin}/callback` });
|
.mockResolvedValue({ redirectTo: `${mockOrigin}/callback` });
|
||||||
|
|
||||||
const signInWithSocialSpy = jest
|
|
||||||
.spyOn(socialSignInApi, 'signInWithSocial')
|
|
||||||
.mockResolvedValue({ redirectTo: `${mockOrigin}/callback` });
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
/* eslint-disable @silverhand/fp/no-mutation */
|
/* eslint-disable @silverhand/fp/no-mutation */
|
||||||
// @ts-expect-error mock global object
|
// @ts-expect-error mock global object
|
||||||
|
@ -126,34 +121,4 @@ describe('SecondarySocialSignIn', () => {
|
||||||
expect(logtoNativeSdk?.getPostMessage).toBeCalled();
|
expect(logtoNativeSdk?.getPostMessage).toBeCalled();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('callback validation and signIn with social', async () => {
|
|
||||||
const state = generateState();
|
|
||||||
storeState(state, 'github');
|
|
||||||
|
|
||||||
/* eslint-disable @silverhand/fp/no-mutating-methods */
|
|
||||||
Object.defineProperty(window, 'location', {
|
|
||||||
value: {
|
|
||||||
href: `/sign-in/callback?state=${state}&code=foo`,
|
|
||||||
search: `?state=${state}&code=foo`,
|
|
||||||
pathname: '/sign-in/callback',
|
|
||||||
assign: jest.fn(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
/* eslint-enable @silverhand/fp/no-mutating-methods */
|
|
||||||
|
|
||||||
renderWithPageContext(
|
|
||||||
<SettingsProvider>
|
|
||||||
<MemoryRouter initialEntries={['/sign-in/callback/github']}>
|
|
||||||
<Routes>
|
|
||||||
<Route path="/sign-in/callback/:connector" element={<SecondarySocialSignIn />} />
|
|
||||||
</Routes>
|
|
||||||
</MemoryRouter>
|
|
||||||
</SettingsProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(signInWithSocialSpy).toBeCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React, { useMemo, useState, useRef } from 'react';
|
import React, { useMemo, useState, useRef } from 'react';
|
||||||
|
|
||||||
|
import useNativeMessageListener from '@/hooks/use-native-message-listener';
|
||||||
import usePlatform from '@/hooks/use-platform';
|
import usePlatform from '@/hooks/use-platform';
|
||||||
import useSocial from '@/hooks/use-social';
|
import useSocial from '@/hooks/use-social';
|
||||||
import useSocialSignInListener from '@/hooks/use-social-signin-listener';
|
|
||||||
|
|
||||||
import SocialSignInDropdown from '../SocialSignInDropdown';
|
import SocialSignInDropdown from '../SocialSignInDropdown';
|
||||||
import SocialSignInIconList from '../SocialSignInIconList';
|
import SocialSignInIconList from '../SocialSignInIconList';
|
||||||
|
@ -20,7 +20,7 @@ const SecondarySocialSignIn = ({ className }: Props) => {
|
||||||
const moreButtonRef = useRef<HTMLButtonElement>(null);
|
const moreButtonRef = useRef<HTMLButtonElement>(null);
|
||||||
const { isMobile } = usePlatform();
|
const { isMobile } = usePlatform();
|
||||||
|
|
||||||
useSocialSignInListener();
|
useNativeMessageListener();
|
||||||
|
|
||||||
const isCollapsed = socialConnectors.length > defaultSize;
|
const isCollapsed = socialConnectors.length > defaultSize;
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,10 @@ const useSocialCallbackHandler = () => {
|
||||||
// Web/Mobile-Web redirect to sign-in/callback page to login
|
// Web/Mobile-Web redirect to sign-in/callback page to login
|
||||||
if (platform === 'web') {
|
if (platform === 'web') {
|
||||||
navigate(
|
navigate(
|
||||||
new URL(`${location.origin}/sign-in/callback/${connectorId}/${window.location.search}`),
|
{
|
||||||
|
pathname: `/sign-in/callback/${connectorId}`,
|
||||||
|
search: window.location.search,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
replace: true,
|
replace: true,
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { signInWithSocial } from '@/apis/social';
|
||||||
import { parseQueryParameters } from '@/utils';
|
import { parseQueryParameters } from '@/utils';
|
||||||
|
|
||||||
import useApi, { ErrorHandlers } from './use-api';
|
import useApi, { ErrorHandlers } from './use-api';
|
||||||
import useNativeMessageListener from './use-native-message-listener';
|
|
||||||
import { PageContext } from './use-page-context';
|
import { PageContext } from './use-page-context';
|
||||||
import { stateValidation } from './utils';
|
import { stateValidation } from './utils';
|
||||||
|
|
||||||
|
@ -16,8 +15,6 @@ const useSocialSignInListener = () => {
|
||||||
const parameters = useParams();
|
const parameters = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useNativeMessageListener();
|
|
||||||
|
|
||||||
const signInWithSocialErrorHandlers: ErrorHandlers = useMemo(
|
const signInWithSocialErrorHandlers: ErrorHandlers = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
'user.identity_not_exists': (error) => {
|
'user.identity_not_exists': (error) => {
|
||||||
|
@ -58,7 +55,7 @@ const useSocialSignInListener = () => {
|
||||||
|
|
||||||
// Social Sign-In Callback Handler
|
// Social Sign-In Callback Handler
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!location.pathname.includes('/sign-in/callback') || !parameters.connector) {
|
if (!parameters.connector) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
49
packages/ui/src/pages/SocialSignInCallback/index.test.tsx
Normal file
49
packages/ui/src/pages/SocialSignInCallback/index.test.tsx
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import { waitFor } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
||||||
|
|
||||||
|
import renderWithPageContext from '@/__mocks__/RenderWithPageContext';
|
||||||
|
import SettingsProvider from '@/__mocks__/RenderWithPageContext/SettingsProvider';
|
||||||
|
import * as socialSignInApi from '@/apis/social';
|
||||||
|
import { generateState, storeState } from '@/hooks/utils';
|
||||||
|
|
||||||
|
import SocialCallback from '.';
|
||||||
|
|
||||||
|
const origin = 'http://localhost:3000';
|
||||||
|
|
||||||
|
describe('SocialCallbackPage with code', () => {
|
||||||
|
const signInWithSocialSpy = jest
|
||||||
|
.spyOn(socialSignInApi, 'signInWithSocial')
|
||||||
|
.mockResolvedValue({ redirectTo: `/sign-in` });
|
||||||
|
|
||||||
|
it('callback validation and signIn with social', async () => {
|
||||||
|
const state = generateState();
|
||||||
|
storeState(state, 'github');
|
||||||
|
|
||||||
|
/* eslint-disable @silverhand/fp/no-mutating-methods */
|
||||||
|
Object.defineProperty(window, 'location', {
|
||||||
|
value: {
|
||||||
|
origin,
|
||||||
|
href: `/sign-in/callback?state=${state}&code=foo`,
|
||||||
|
search: `?state=${state}&code=foo`,
|
||||||
|
pathname: '/sign-in/callback',
|
||||||
|
assign: jest.fn(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
/* eslint-enable @silverhand/fp/no-mutating-methods */
|
||||||
|
|
||||||
|
renderWithPageContext(
|
||||||
|
<SettingsProvider>
|
||||||
|
<MemoryRouter initialEntries={['/sign-in/callback/github']}>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/sign-in/callback/:connector" element={<SocialCallback />} />
|
||||||
|
</Routes>
|
||||||
|
</MemoryRouter>
|
||||||
|
</SettingsProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(signInWithSocialSpy).toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
13
packages/ui/src/pages/SocialSignInCallback/index.tsx
Normal file
13
packages/ui/src/pages/SocialSignInCallback/index.tsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import useSocialSignInListener from '@/hooks/use-social-sign-in-listener';
|
||||||
|
|
||||||
|
import SignIn from '../SignIn';
|
||||||
|
|
||||||
|
const SocialSignInCallback = () => {
|
||||||
|
useSocialSignInListener();
|
||||||
|
|
||||||
|
return <SignIn />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SocialSignInCallback;
|
Loading…
Add table
Reference in a new issue