first commit
Some checks failed
Build / run (push) Has been cancelled

This commit is contained in:
maher
2025-10-29 11:42:25 +01:00
commit 703f50a09d
4595 changed files with 385164 additions and 0 deletions

View 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);
}

View 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);
}

View 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);
}

View 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),
});
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
});
}

View 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);
}