mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2025-08-26 06:09:41 +00:00
feat: client testing api , add ouath2
This commit is contained in:
11
client/package-lock.json
generated
11
client/package-lock.json
generated
@ -13,6 +13,7 @@
|
|||||||
"@fontsource/roboto": "^5.1.1",
|
"@fontsource/roboto": "^5.1.1",
|
||||||
"@mui/icons-material": "^6.3.1",
|
"@mui/icons-material": "^6.3.1",
|
||||||
"@mui/material": "^6.3.1",
|
"@mui/material": "^6.3.1",
|
||||||
|
"@react-oauth/google": "^0.12.1",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-apple-signin-auth": "^1.1.0",
|
"react-apple-signin-auth": "^1.1.0",
|
||||||
@ -1447,6 +1448,16 @@
|
|||||||
"url": "https://opencollective.com/popperjs"
|
"url": "https://opencollective.com/popperjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@react-oauth/google": {
|
||||||
|
"version": "0.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-oauth/google/-/google-0.12.1.tgz",
|
||||||
|
"integrity": "sha512-qagsy22t+7UdkYAiT5ZhfM4StXi9PPNvw0zuwNmabrWyMKddczMtBIOARflbaIj+wHiQjnMAsZmzsUYuXeyoSg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0",
|
||||||
|
"react-dom": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.30.0",
|
"version": "4.30.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.0.tgz",
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"@fontsource/roboto": "^5.1.1",
|
"@fontsource/roboto": "^5.1.1",
|
||||||
"@mui/icons-material": "^6.3.1",
|
"@mui/icons-material": "^6.3.1",
|
||||||
"@mui/material": "^6.3.1",
|
"@mui/material": "^6.3.1",
|
||||||
|
"@react-oauth/google": "^0.12.1",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-apple-signin-auth": "^1.1.0",
|
"react-apple-signin-auth": "^1.1.0",
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { LoginRequest } from '../types/auth';
|
||||||
import { CreateJuniorRequest, JuniorTheme } from '../types/junior';
|
import { CreateJuniorRequest, JuniorTheme } from '../types/junior';
|
||||||
import { CreateTaskRequest, TaskStatus, TaskSubmission } from '../types/task';
|
import { CreateTaskRequest, TaskStatus, TaskSubmission } from '../types/task';
|
||||||
|
|
||||||
const API_BASE_URL = 'http://79.72.0.143';
|
// const API_BASE_URL = 'http://79.72.0.143';
|
||||||
|
const API_BASE_URL = 'http://localhost:5001';
|
||||||
const AUTH_TOKEN = btoa('zod-digital:Zod2025'); // Base64 encode credentials
|
const AUTH_TOKEN = btoa('zod-digital:Zod2025'); // Base64 encode credentials
|
||||||
|
|
||||||
// Helper function to get auth header
|
// Helper function to get auth header
|
||||||
@ -75,11 +77,13 @@ export const authApi = {
|
|||||||
return apiClient.post('/api/auth/register/set-passcode', { passcode });
|
return apiClient.post('/api/auth/register/set-passcode', { passcode });
|
||||||
},
|
},
|
||||||
|
|
||||||
login: (email: string, password: string) =>
|
login: ({ grantType, email, password, appleToken, googleToken }: LoginRequest) =>
|
||||||
apiClient.post('/api/auth/login', {
|
apiClient.post('/api/auth/login', {
|
||||||
grantType: 'PASSWORD',
|
grantType,
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
|
appleToken,
|
||||||
|
googleToken,
|
||||||
fcmToken: 'web-client-token', // Required by API
|
fcmToken: 'web-client-token', // Required by API
|
||||||
signature: 'web-login', // Required by API
|
signature: 'web-login', // Required by API
|
||||||
}),
|
}),
|
||||||
|
69
client/src/components/auth/AppleLogin.tsx
Normal file
69
client/src/components/auth/AppleLogin.tsx
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import AppleSignInButton from 'react-apple-signin-auth';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { useAuth } from '../../contexts/AuthContext';
|
||||||
|
import { GrantType } from '../../enums';
|
||||||
|
|
||||||
|
interface LoginProps {
|
||||||
|
setError: (error: string) => void;
|
||||||
|
setLoading: (loading: boolean) => void;
|
||||||
|
}
|
||||||
|
export const AppleLogin = ({ setError, setLoading }: LoginProps) => {
|
||||||
|
const { login } = useAuth();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const onError = (err: any) => {
|
||||||
|
setError(err instanceof Error ? err.message : 'Login failed. Please check your credentials.');
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSuccess = async (response: any) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
await login({ grantType: GrantType.APPLE, appleToken: response.authorization.id_token });
|
||||||
|
navigate('/dashboard');
|
||||||
|
} catch (error) {
|
||||||
|
setError(error instanceof Error ? error.message : 'Login failed. Please check your credentials.');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppleSignInButton
|
||||||
|
/** Auth options passed to AppleID.auth.init() */
|
||||||
|
authOptions={{
|
||||||
|
/** Client ID - eg: 'com.example.com' */
|
||||||
|
clientId: process?.env.REACT_APP_APPLE_CLIENT_ID!,
|
||||||
|
|
||||||
|
scope: 'email name',
|
||||||
|
/** Requested scopes, seperated by spaces - eg: 'email name' */
|
||||||
|
/** Apple's redirectURI - must be one of the URIs you added to the serviceID - the undocumented trick in apple docs is that you should call auth from a page that is listed as a redirectURI, localhost fails */
|
||||||
|
redirectURI: process?.env.REACT_APP_APPLE_REDIRECT_URI!,
|
||||||
|
|
||||||
|
state: 'default',
|
||||||
|
|
||||||
|
/** Uses popup auth instead of redirection */
|
||||||
|
usePopup: true,
|
||||||
|
}} // REQUIRED
|
||||||
|
/** General props */
|
||||||
|
uiType="dark"
|
||||||
|
/** className */
|
||||||
|
className="apple-auth-btn"
|
||||||
|
/** Removes default style tag */
|
||||||
|
noDefaultStyle={false}
|
||||||
|
/** Allows to change the button's children, eg: for changing the button text */
|
||||||
|
buttonExtraChildren="Continue with Apple"
|
||||||
|
/** Extra controlling props */
|
||||||
|
/** Called upon signin success in case authOptions.usePopup = true -- which means auth is handled client side */
|
||||||
|
onSuccess={(response: any) => {
|
||||||
|
onSuccess(response);
|
||||||
|
}} // default = undefined
|
||||||
|
/** Called upon signin error */
|
||||||
|
onError={(error: any) => onError(error)} // default = undefined
|
||||||
|
/** Skips loading the apple script if true */
|
||||||
|
skipScript={false} // default = undefined
|
||||||
|
/** Apple image props */
|
||||||
|
|
||||||
|
/** render function - called with all props - can be used to fully customize the UI by rendering your own component */
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
40
client/src/components/auth/GoogleLogin.tsx
Normal file
40
client/src/components/auth/GoogleLogin.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { GoogleLogin as GoogleApiLogin, GoogleOAuthProvider } from '@react-oauth/google';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { useAuth } from '../../contexts/AuthContext';
|
||||||
|
import { GrantType } from '../../enums';
|
||||||
|
interface LoginProps {
|
||||||
|
setError: (error: string) => void;
|
||||||
|
setLoading: (loading: boolean) => void;
|
||||||
|
}
|
||||||
|
export const GoogleLogin = ({ setError, setLoading }: LoginProps) => {
|
||||||
|
const { login } = useAuth();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const onError = (err: any) => {
|
||||||
|
setError(err instanceof Error ? err.message : 'Login failed. Please check your credentials.');
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSuccess = async (response: any) => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
await login({ grantType: GrantType.GOOGLE, googleToken: response.credential });
|
||||||
|
navigate('/dashboard');
|
||||||
|
} catch (error) {
|
||||||
|
setError(error instanceof Error ? error.message : 'Login failed. Please check your credentials.');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<GoogleOAuthProvider clientId={process.env.GOOGLE_WEB_CLIENT_ID!}>
|
||||||
|
<GoogleApiLogin
|
||||||
|
onSuccess={(credentialResponse) => {
|
||||||
|
onSuccess(credentialResponse);
|
||||||
|
}}
|
||||||
|
onError={() => {
|
||||||
|
onError('Login failed. Please check your credentials.');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</GoogleOAuthProvider>
|
||||||
|
);
|
||||||
|
};
|
@ -1,8 +1,10 @@
|
|||||||
import { Alert, Box, Button, Container, Paper, TextField, Typography } from '@mui/material';
|
import { Alert, Box, Button, Container, Paper, TextField, Typography } from '@mui/material';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import AppleSignin from 'react-apple-signin-auth';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useAuth } from '../../contexts/AuthContext';
|
import { useAuth } from '../../contexts/AuthContext';
|
||||||
|
import { GrantType } from '../../enums';
|
||||||
|
import { AppleLogin } from './AppleLogin';
|
||||||
|
import { GoogleLogin } from './GoogleLogin';
|
||||||
export const LoginForm = () => {
|
export const LoginForm = () => {
|
||||||
const { login } = useAuth();
|
const { login } = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -19,7 +21,7 @@ export const LoginForm = () => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await login(formData.email, formData.password);
|
await login({ email: formData.email, password: formData.password, grantType: GrantType.PASSWORD });
|
||||||
navigate('/dashboard');
|
navigate('/dashboard');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : 'Login failed. Please check your credentials.');
|
setError(err instanceof Error ? err.message : 'Login failed. Please check your credentials.');
|
||||||
@ -35,7 +37,6 @@ export const LoginForm = () => {
|
|||||||
[name]: value,
|
[name]: value,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
console.log(process.env);
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -138,44 +139,8 @@ export const LoginForm = () => {
|
|||||||
>
|
>
|
||||||
signup
|
signup
|
||||||
</Button>
|
</Button>
|
||||||
|
<AppleLogin setError={setError} setLoading={setLoading} />
|
||||||
<AppleSignin
|
<GoogleLogin setError={setError} setLoading={setLoading} />
|
||||||
/** Auth options passed to AppleID.auth.init() */
|
|
||||||
authOptions={{
|
|
||||||
/** Client ID - eg: 'com.example.com' */
|
|
||||||
clientId: process?.env.REACT_APP_APPLE_CLIENT_ID!,
|
|
||||||
|
|
||||||
scope: 'email name',
|
|
||||||
/** Requested scopes, seperated by spaces - eg: 'email name' */
|
|
||||||
/** Apple's redirectURI - must be one of the URIs you added to the serviceID - the undocumented trick in apple docs is that you should call auth from a page that is listed as a redirectURI, localhost fails */
|
|
||||||
redirectURI: process?.env.REACT_APP_APPLE_REDIRECT_URI!,
|
|
||||||
|
|
||||||
state: 'default',
|
|
||||||
|
|
||||||
/** Uses popup auth instead of redirection */
|
|
||||||
usePopup: true,
|
|
||||||
}} // REQUIRED
|
|
||||||
/** General props */
|
|
||||||
uiType="dark"
|
|
||||||
/** className */
|
|
||||||
className="apple-auth-btn"
|
|
||||||
/** Removes default style tag */
|
|
||||||
noDefaultStyle={false}
|
|
||||||
/** Allows to change the button's children, eg: for changing the button text */
|
|
||||||
buttonExtraChildren="Continue with Apple"
|
|
||||||
/** Extra controlling props */
|
|
||||||
/** Called upon signin success in case authOptions.usePopup = true -- which means auth is handled client side */
|
|
||||||
onSuccess={(response: any) => {
|
|
||||||
console.log(response);
|
|
||||||
}} // default = undefined
|
|
||||||
/** Called upon signin error */
|
|
||||||
onError={(error: any) => console.error(error)} // default = undefined
|
|
||||||
/** Skips loading the apple script if true */
|
|
||||||
skipScript={false} // default = undefined
|
|
||||||
/** Apple image props */
|
|
||||||
|
|
||||||
/** render function - called with all props - can be used to fully customize the UI by rendering your own component */
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Container>
|
</Container>
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import React, { createContext, useContext, useState, useCallback } from 'react';
|
import React, { createContext, useCallback, useContext, useState } from 'react';
|
||||||
import { authApi } from '../api/client';
|
import { authApi } from '../api/client';
|
||||||
import { User, LoginResponse } from '../types/auth';
|
import { LoginRequest, LoginResponse, User } from '../types/auth';
|
||||||
|
|
||||||
interface AuthContextType {
|
interface AuthContextType {
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
user: User | null;
|
user: User | null;
|
||||||
login: (email: string, password: string) => Promise<void>;
|
login: (loginRequest: LoginRequest) => Promise<void>;
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
register: (countryCode: string, phoneNumber: string) => Promise<void>;
|
register: (countryCode: string, phoneNumber: string) => Promise<void>;
|
||||||
verifyOtp: (countryCode: string, phoneNumber: string, otp: string) => Promise<string>;
|
verifyOtp: (countryCode: string, phoneNumber: string, otp: string) => Promise<string>;
|
||||||
@ -27,9 +27,9 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||||
const [user, setUser] = useState<User | null>(null);
|
const [user, setUser] = useState<User | null>(null);
|
||||||
|
|
||||||
const login = useCallback(async (email: string, password: string) => {
|
const login = useCallback(async (loginRequest: LoginRequest) => {
|
||||||
try {
|
try {
|
||||||
const response = await authApi.login(email, password);
|
const response = await authApi.login(loginRequest);
|
||||||
const loginData = response.data.data as LoginResponse;
|
const loginData = response.data.data as LoginResponse;
|
||||||
setUser(loginData.user);
|
setUser(loginData.user);
|
||||||
// Store tokens
|
// Store tokens
|
||||||
@ -76,7 +76,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
console.log('Access token:', accessToken);
|
console.log('Access token:', accessToken);
|
||||||
// Store token in localStorage immediately
|
// Store token in localStorage immediately
|
||||||
localStorage.setItem('accessToken', accessToken);
|
localStorage.setItem('accessToken', accessToken);
|
||||||
setRegistrationData(prev => ({ ...prev, token: accessToken }));
|
setRegistrationData((prev) => ({ ...prev, token: accessToken }));
|
||||||
return accessToken;
|
return accessToken;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('OTP verification failed:', error);
|
console.error('OTP verification failed:', error);
|
||||||
@ -87,7 +87,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
const setEmail = useCallback(async (email: string) => {
|
const setEmail = useCallback(async (email: string) => {
|
||||||
try {
|
try {
|
||||||
await authApi.setEmail(email);
|
await authApi.setEmail(email);
|
||||||
setRegistrationData(prev => ({ ...prev, email }));
|
setRegistrationData((prev) => ({ ...prev, email }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Setting email failed:', error);
|
console.error('Setting email failed:', error);
|
||||||
throw error;
|
throw error;
|
||||||
@ -112,7 +112,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
register,
|
register,
|
||||||
verifyOtp,
|
verifyOtp,
|
||||||
setEmail,
|
setEmail,
|
||||||
setPasscode
|
setPasscode,
|
||||||
};
|
};
|
||||||
|
|
||||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||||
|
6
client/src/enums/grantType.enum.ts
Normal file
6
client/src/enums/grantType.enum.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export enum GrantType {
|
||||||
|
PASSWORD = 'PASSWORD',
|
||||||
|
APPLE = 'APPLE',
|
||||||
|
GOOGLE = 'GOOGLE',
|
||||||
|
BIOMETRIC = 'BIOMETRIC',
|
||||||
|
}
|
1
client/src/enums/index.ts
Normal file
1
client/src/enums/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './grantType.enum';
|
@ -1,3 +1,5 @@
|
|||||||
|
import { GrantType } from '../enums';
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
id: string;
|
id: string;
|
||||||
email: string;
|
email: string;
|
||||||
@ -15,3 +17,11 @@ export interface LoginResponse {
|
|||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
user: User;
|
user: User;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LoginRequest {
|
||||||
|
email?: string;
|
||||||
|
password?: string;
|
||||||
|
grantType: GrantType;
|
||||||
|
googleToken?: string;
|
||||||
|
appleToken?: string;
|
||||||
|
}
|
||||||
|
@ -9,6 +9,7 @@ export default defineConfig(({ mode }) => {
|
|||||||
define: {
|
define: {
|
||||||
'process.env.REACT_APP_APPLE_CLIENT_ID': JSON.stringify(env.REACT_APP_APPLE_CLIENT_ID),
|
'process.env.REACT_APP_APPLE_CLIENT_ID': JSON.stringify(env.REACT_APP_APPLE_CLIENT_ID),
|
||||||
'process.env.REACT_APP_APPLE_REDIRECT_URI': JSON.stringify(env.REACT_APP_APPLE_REDIRECT_URI),
|
'process.env.REACT_APP_APPLE_REDIRECT_URI': JSON.stringify(env.REACT_APP_APPLE_REDIRECT_URI),
|
||||||
|
'process.env.GOOGLE_WEB_CLIENT_ID': JSON.stringify(env.GOOGLE_WEB_CLIENT_ID),
|
||||||
},
|
},
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user