38
common/resources/client/auth/requests/connect-social-with-password.ts
Executable file
38
common/resources/client/auth/requests/connect-social-with-password.ts
Executable file
@@ -0,0 +1,38 @@
|
||||
import {useMutation} from '@tanstack/react-query';
|
||||
import {UseFormReturn} from 'react-hook-form';
|
||||
import {BackendResponse} from '../../http/backend-response/backend-response';
|
||||
import {onFormQueryError} from '../../errors/on-form-query-error';
|
||||
import {useNavigate} from '../../utils/hooks/use-navigate';
|
||||
import {apiClient} from '../../http/query-client';
|
||||
import {useAuth} from '../use-auth';
|
||||
import {useBootstrapData} from '../../core/bootstrap-data/bootstrap-data-context';
|
||||
|
||||
interface Response extends BackendResponse {
|
||||
bootstrapData: string;
|
||||
}
|
||||
|
||||
export interface ConnectSocialPayload {
|
||||
password: string;
|
||||
}
|
||||
|
||||
export function useConnectSocialWithPassword(
|
||||
form: UseFormReturn<ConnectSocialPayload>,
|
||||
) {
|
||||
const navigate = useNavigate();
|
||||
const {getRedirectUri} = useAuth();
|
||||
const {setBootstrapData} = useBootstrapData();
|
||||
return useMutation({
|
||||
mutationFn: connect,
|
||||
onSuccess: response => {
|
||||
setBootstrapData(response.bootstrapData);
|
||||
navigate(getRedirectUri(), {replace: true});
|
||||
},
|
||||
onError: r => onFormQueryError(r, form),
|
||||
});
|
||||
}
|
||||
|
||||
function connect(payload: ConnectSocialPayload): Promise<Response> {
|
||||
return apiClient
|
||||
.post('secure/auth/social/connect', payload)
|
||||
.then(response => response.data);
|
||||
}
|
||||
25
common/resources/client/auth/requests/disconnect-social.ts
Executable file
25
common/resources/client/auth/requests/disconnect-social.ts
Executable file
@@ -0,0 +1,25 @@
|
||||
import {useMutation} from '@tanstack/react-query';
|
||||
import {BackendResponse} from '../../http/backend-response/backend-response';
|
||||
import {apiClient} from '../../http/query-client';
|
||||
import {showHttpErrorToast} from '../../utils/http/show-http-error-toast';
|
||||
|
||||
interface Response extends BackendResponse {
|
||||
//
|
||||
}
|
||||
|
||||
interface Payload {
|
||||
service: string;
|
||||
}
|
||||
|
||||
export function useDisconnectSocial() {
|
||||
return useMutation({
|
||||
mutationFn: disconnect,
|
||||
onError: err => showHttpErrorToast(err),
|
||||
});
|
||||
}
|
||||
|
||||
function disconnect(payload: Payload): Promise<Response> {
|
||||
return apiClient
|
||||
.post(`secure/auth/social/${payload.service}/disconnect`, payload)
|
||||
.then(response => response.data);
|
||||
}
|
||||
47
common/resources/client/auth/requests/logout.ts
Executable file
47
common/resources/client/auth/requests/logout.ts
Executable file
@@ -0,0 +1,47 @@
|
||||
import {useMutation} from '@tanstack/react-query';
|
||||
import {BackendResponse} from '../../http/backend-response/backend-response';
|
||||
import {useNavigate} from '../../utils/hooks/use-navigate';
|
||||
import {apiClient, queryClient} from '../../http/query-client';
|
||||
import {showHttpErrorToast} from '../../utils/http/show-http-error-toast';
|
||||
import {useAppearanceEditorMode} from '../../admin/appearance/commands/use-appearance-editor-mode';
|
||||
import {message} from '../../i18n/message';
|
||||
import {useBootstrapData} from '../../core/bootstrap-data/bootstrap-data-context';
|
||||
|
||||
interface Response extends BackendResponse {
|
||||
bootstrapData: string;
|
||||
}
|
||||
|
||||
const appearanceMessage = "Can't logout while in appearance editor.";
|
||||
|
||||
export function useLogout() {
|
||||
const navigate = useNavigate();
|
||||
const {isAppearanceEditorActive} = useAppearanceEditorMode();
|
||||
const {setBootstrapData} = useBootstrapData();
|
||||
return useMutation({
|
||||
mutationFn: () => (isAppearanceEditorActive ? noopLogout() : logout()),
|
||||
onSuccess: response => {
|
||||
// need to update bootstrap data in order for redirect to login page to work
|
||||
setBootstrapData(response.bootstrapData);
|
||||
queryClient.clear();
|
||||
navigate('/login');
|
||||
|
||||
// need to clear query client and then set bootstrap data again immediately,
|
||||
// because there's no way to clear everything except one in react query
|
||||
queryClient.clear();
|
||||
setBootstrapData(response.bootstrapData);
|
||||
},
|
||||
onError: err =>
|
||||
showHttpErrorToast(
|
||||
err,
|
||||
isAppearanceEditorActive ? message(appearanceMessage) : undefined,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
function logout(): Promise<Response> {
|
||||
return apiClient.post('auth/logout').then(r => r.data);
|
||||
}
|
||||
|
||||
function noopLogout() {
|
||||
return Promise.reject(appearanceMessage);
|
||||
}
|
||||
37
common/resources/client/auth/requests/reset-password.ts
Executable file
37
common/resources/client/auth/requests/reset-password.ts
Executable file
@@ -0,0 +1,37 @@
|
||||
import {useMutation} from '@tanstack/react-query';
|
||||
import {UseFormReturn} from 'react-hook-form';
|
||||
import {BackendResponse} from '../../http/backend-response/backend-response';
|
||||
import {onFormQueryError} from '../../errors/on-form-query-error';
|
||||
import {toast} from '../../ui/toast/toast';
|
||||
import {message} from '../../i18n/message';
|
||||
import {useNavigate} from '../../utils/hooks/use-navigate';
|
||||
import {apiClient} from '../../http/query-client';
|
||||
|
||||
interface Response extends BackendResponse {
|
||||
bootstrapData: string;
|
||||
}
|
||||
|
||||
export interface ResetPasswordPayload {
|
||||
email: string;
|
||||
password: string;
|
||||
password_confirmation: string;
|
||||
token: string;
|
||||
}
|
||||
|
||||
function reset(payload: ResetPasswordPayload): Promise<Response> {
|
||||
return apiClient
|
||||
.post('auth/reset-password', payload)
|
||||
.then(response => response.data);
|
||||
}
|
||||
|
||||
export function useResetPassword(form: UseFormReturn<ResetPasswordPayload>) {
|
||||
const navigate = useNavigate();
|
||||
return useMutation({
|
||||
mutationFn: reset,
|
||||
onSuccess: () => {
|
||||
navigate('/login', {replace: true});
|
||||
toast(message('Your password has been reset!'));
|
||||
},
|
||||
onError: r => onFormQueryError(r, form),
|
||||
});
|
||||
}
|
||||
37
common/resources/client/auth/requests/send-reset-password-email.ts
Executable file
37
common/resources/client/auth/requests/send-reset-password-email.ts
Executable file
@@ -0,0 +1,37 @@
|
||||
import {useMutation} from '@tanstack/react-query';
|
||||
import {UseFormReturn} from 'react-hook-form';
|
||||
import {BackendResponse} from '../../http/backend-response/backend-response';
|
||||
import {onFormQueryError} from '../../errors/on-form-query-error';
|
||||
import {toast} from '../../ui/toast/toast';
|
||||
import {useNavigate} from '../../utils/hooks/use-navigate';
|
||||
import {apiClient} from '../../http/query-client';
|
||||
|
||||
interface Response extends BackendResponse {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface SendPasswordResetEmailPayload {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export function useSendPasswordResetEmail(
|
||||
form: UseFormReturn<SendPasswordResetEmailPayload>,
|
||||
) {
|
||||
const navigate = useNavigate();
|
||||
return useMutation({
|
||||
mutationFn: sendResetPasswordEmail,
|
||||
onSuccess: response => {
|
||||
toast(response.message);
|
||||
navigate('/login');
|
||||
},
|
||||
onError: r => onFormQueryError(r, form),
|
||||
});
|
||||
}
|
||||
|
||||
function sendResetPasswordEmail(
|
||||
payload: SendPasswordResetEmailPayload,
|
||||
): Promise<Response> {
|
||||
return apiClient
|
||||
.post('auth/forgot-password', payload)
|
||||
.then(response => response.data);
|
||||
}
|
||||
57
common/resources/client/auth/requests/use-login.ts
Executable file
57
common/resources/client/auth/requests/use-login.ts
Executable file
@@ -0,0 +1,57 @@
|
||||
import {useMutation} from '@tanstack/react-query';
|
||||
import {UseFormReturn} from 'react-hook-form';
|
||||
import {BackendResponse} from '../../http/backend-response/backend-response';
|
||||
import {onFormQueryError} from '../../errors/on-form-query-error';
|
||||
import {useNavigate} from '../../utils/hooks/use-navigate';
|
||||
import {apiClient} from '../../http/query-client';
|
||||
import {useAuth} from '../use-auth';
|
||||
import {useBootstrapData} from '../../core/bootstrap-data/bootstrap-data-context';
|
||||
import {useCallback} from 'react';
|
||||
|
||||
interface LoginResponse extends BackendResponse {
|
||||
bootstrapData: string;
|
||||
two_factor: false;
|
||||
}
|
||||
interface TwoFactorResponse {
|
||||
two_factor: true;
|
||||
}
|
||||
|
||||
type Response = LoginResponse | TwoFactorResponse;
|
||||
|
||||
export interface LoginPayload {
|
||||
email: string;
|
||||
password: string;
|
||||
remember: boolean;
|
||||
token_name: string;
|
||||
}
|
||||
|
||||
export function useLogin(form: UseFormReturn<LoginPayload>) {
|
||||
const handleSuccess = useHandleLoginSuccess();
|
||||
return useMutation({
|
||||
mutationFn: login,
|
||||
onSuccess: response => {
|
||||
if (!response.two_factor) {
|
||||
handleSuccess(response);
|
||||
}
|
||||
},
|
||||
onError: r => onFormQueryError(r, form),
|
||||
});
|
||||
}
|
||||
|
||||
export function useHandleLoginSuccess() {
|
||||
const navigate = useNavigate();
|
||||
const {getRedirectUri} = useAuth();
|
||||
const {setBootstrapData} = useBootstrapData();
|
||||
|
||||
return useCallback(
|
||||
(response: LoginResponse) => {
|
||||
setBootstrapData(response.bootstrapData);
|
||||
navigate(getRedirectUri(), {replace: true});
|
||||
},
|
||||
[navigate, setBootstrapData, getRedirectUri],
|
||||
);
|
||||
}
|
||||
|
||||
function login(payload: LoginPayload): Promise<Response> {
|
||||
return apiClient.post('auth/login', payload).then(response => response.data);
|
||||
}
|
||||
45
common/resources/client/auth/requests/use-register.ts
Executable file
45
common/resources/client/auth/requests/use-register.ts
Executable file
@@ -0,0 +1,45 @@
|
||||
import {useMutation} from '@tanstack/react-query';
|
||||
import {UseFormReturn} from 'react-hook-form';
|
||||
import {BackendResponse} from '../../http/backend-response/backend-response';
|
||||
import {onFormQueryError} from '../../errors/on-form-query-error';
|
||||
import {useNavigate} from '../../utils/hooks/use-navigate';
|
||||
import {apiClient} from '../../http/query-client';
|
||||
import {useAuth} from '../use-auth';
|
||||
import {useBootstrapData} from '../../core/bootstrap-data/bootstrap-data-context';
|
||||
|
||||
interface Response extends BackendResponse {
|
||||
bootstrapData?: string;
|
||||
message?: string;
|
||||
status: 'success' | 'needs_email_verification';
|
||||
}
|
||||
|
||||
export interface RegisterPayload {
|
||||
email: string;
|
||||
password: string;
|
||||
password_confirmation: string;
|
||||
}
|
||||
|
||||
export function useRegister(form: UseFormReturn<RegisterPayload>) {
|
||||
const navigate = useNavigate();
|
||||
const {getRedirectUri} = useAuth();
|
||||
const {setBootstrapData} = useBootstrapData();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: register,
|
||||
onSuccess: response => {
|
||||
setBootstrapData(response.bootstrapData!);
|
||||
if (response.status === 'needs_email_verification') {
|
||||
navigate('/');
|
||||
} else {
|
||||
navigate(getRedirectUri(), {replace: true});
|
||||
}
|
||||
},
|
||||
onError: r => onFormQueryError(r, form),
|
||||
});
|
||||
}
|
||||
|
||||
function register(payload: RegisterPayload): Promise<Response> {
|
||||
return apiClient
|
||||
.post('auth/register', payload)
|
||||
.then(response => response.data);
|
||||
}
|
||||
30
common/resources/client/auth/requests/use-resend-verification-email.ts
Executable file
30
common/resources/client/auth/requests/use-resend-verification-email.ts
Executable file
@@ -0,0 +1,30 @@
|
||||
import {useMutation} from '@tanstack/react-query';
|
||||
import {BackendResponse} from '../../http/backend-response/backend-response';
|
||||
import {toast} from '../../ui/toast/toast';
|
||||
import {message} from '../../i18n/message';
|
||||
import {apiClient} from '../../http/query-client';
|
||||
import {showHttpErrorToast} from '../../utils/http/show-http-error-toast';
|
||||
|
||||
interface Response extends BackendResponse {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface ResendConfirmEmailPayload {
|
||||
email: string;
|
||||
}
|
||||
|
||||
export function useResendVerificationEmail() {
|
||||
return useMutation({
|
||||
mutationFn: (payload: ResendConfirmEmailPayload) => resendEmail(payload),
|
||||
onSuccess: () => {
|
||||
toast(message('Email sent'));
|
||||
},
|
||||
onError: err => showHttpErrorToast(err),
|
||||
});
|
||||
}
|
||||
|
||||
function resendEmail(payload: ResendConfirmEmailPayload): Promise<Response> {
|
||||
return apiClient
|
||||
.post('resend-email-verification', payload)
|
||||
.then(response => response.data);
|
||||
}
|
||||
107
common/resources/client/auth/requests/use-social-login.ts
Executable file
107
common/resources/client/auth/requests/use-social-login.ts
Executable file
@@ -0,0 +1,107 @@
|
||||
import {useCallback, useState} from 'react';
|
||||
import {toast} from '../../ui/toast/toast';
|
||||
import {useDisconnectSocial} from './disconnect-social';
|
||||
import {useTrans} from '../../i18n/use-trans';
|
||||
import {getBootstrapData} from '../../core/bootstrap-data/use-backend-bootstrap-data';
|
||||
import {useBootstrapData} from '../../core/bootstrap-data/bootstrap-data-context';
|
||||
|
||||
export type SocialService = 'google' | 'twitter' | 'facebook' | 'envato';
|
||||
|
||||
interface SocialMessageEvent {
|
||||
status?: 'SUCCESS' | 'ALREADY_LOGGED_IN' | 'REQUEST_PASSWORD' | 'ERROR';
|
||||
callbackData?: {
|
||||
bootstrapData?: string;
|
||||
errorMessage?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function useSocialLogin() {
|
||||
const {trans} = useTrans();
|
||||
const {setBootstrapData} = useBootstrapData();
|
||||
const disconnectSocial = useDisconnectSocial();
|
||||
|
||||
const [requestingPassword, setIsRequestingPassword] = useState(false);
|
||||
|
||||
const handleSocialLoginCallback = useCallback(
|
||||
(e: SocialMessageEvent) => {
|
||||
const {status, callbackData} = e;
|
||||
if (!status) return;
|
||||
switch (status.toUpperCase()) {
|
||||
case 'SUCCESS':
|
||||
if (callbackData?.bootstrapData) {
|
||||
setBootstrapData(callbackData.bootstrapData);
|
||||
}
|
||||
return e;
|
||||
case 'REQUEST_PASSWORD':
|
||||
setIsRequestingPassword(true);
|
||||
return e;
|
||||
case 'ERROR':
|
||||
const message: string =
|
||||
callbackData?.errorMessage ||
|
||||
trans({
|
||||
message: 'An error occurred. Please try again later',
|
||||
});
|
||||
toast.danger(message);
|
||||
return e;
|
||||
default:
|
||||
return e;
|
||||
}
|
||||
},
|
||||
[trans, setBootstrapData],
|
||||
);
|
||||
|
||||
return {
|
||||
requestingPassword,
|
||||
setIsRequestingPassword,
|
||||
loginWithSocial: async (serviceName: SocialService) => {
|
||||
const event = await openNewSocialAuthWindow(
|
||||
`secure/auth/social/${serviceName}/login`,
|
||||
);
|
||||
return handleSocialLoginCallback(event);
|
||||
},
|
||||
connectSocial: async (serviceNameOrUrl: SocialService | string) => {
|
||||
const url = serviceNameOrUrl.includes('/')
|
||||
? serviceNameOrUrl
|
||||
: `secure/auth/social/${serviceNameOrUrl}/connect`;
|
||||
const event = await openNewSocialAuthWindow(url);
|
||||
return handleSocialLoginCallback(event);
|
||||
},
|
||||
disconnectSocial,
|
||||
};
|
||||
}
|
||||
|
||||
const windowHeight = 550;
|
||||
const windowWidth = 650;
|
||||
let win: Window | null;
|
||||
|
||||
function openNewSocialAuthWindow(url: string): Promise<SocialMessageEvent> {
|
||||
const left = window.screen.width / 2 - windowWidth / 2;
|
||||
const top = window.screen.height / 2 - windowHeight / 2;
|
||||
|
||||
return new Promise(resolve => {
|
||||
win = window.open(
|
||||
url,
|
||||
'Authenticate Account',
|
||||
`menubar=0, location=0, toolbar=0, titlebar=0, status=0, scrollbars=1, width=${windowWidth}, height=${windowHeight}, left=${left}, top=${top}`,
|
||||
);
|
||||
|
||||
const messageListener = (e: MessageEvent) => {
|
||||
const baseUrl = getBootstrapData().settings.base_url;
|
||||
if (e.data.type === 'social-auth' && baseUrl.indexOf(e.origin) > -1) {
|
||||
resolve(e.data);
|
||||
window.removeEventListener('message', messageListener);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', messageListener);
|
||||
|
||||
// if user closes social login callback without interacting with it, remove message event listener
|
||||
const timer = setInterval(() => {
|
||||
if (!win || win.closed) {
|
||||
clearInterval(timer);
|
||||
resolve({});
|
||||
window.removeEventListener('message', messageListener);
|
||||
}
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
27
common/resources/client/auth/requests/use-validate-email-verification-otp.ts
Executable file
27
common/resources/client/auth/requests/use-validate-email-verification-otp.ts
Executable file
@@ -0,0 +1,27 @@
|
||||
import {useMutation} from '@tanstack/react-query';
|
||||
import {apiClient} from '../../http/query-client';
|
||||
import {UseFormReturn} from 'react-hook-form';
|
||||
import {onFormQueryError} from '@common/errors/on-form-query-error';
|
||||
|
||||
export interface ValidateEmailVerificationOtpPayload {
|
||||
code: string;
|
||||
}
|
||||
|
||||
export function useValidateEmailVerificationOtp(
|
||||
form: UseFormReturn<ValidateEmailVerificationOtpPayload>,
|
||||
) {
|
||||
return useMutation({
|
||||
mutationFn: (payload: ValidateEmailVerificationOtpPayload) =>
|
||||
validate(payload),
|
||||
onSuccess: () => {
|
||||
window.location.reload();
|
||||
},
|
||||
onError: err => onFormQueryError(err, form),
|
||||
});
|
||||
}
|
||||
|
||||
function validate(payload: ValidateEmailVerificationOtpPayload) {
|
||||
return apiClient
|
||||
.post('validate-email-verification-otp', payload)
|
||||
.then(response => response.data);
|
||||
}
|
||||
Reference in New Issue
Block a user