From ebd4b293e964af4e0111ab32d2d697db7ada7202 Mon Sep 17 00:00:00 2001 From: Abdalhamid Alhamad Date: Thu, 16 Jan 2025 13:35:22 +0300 Subject: [PATCH] feat: client testing api , add ouath2 --- client/package-lock.json | 11 ++++ client/package.json | 1 + client/src/api/client.ts | 10 +++- client/src/components/auth/AppleLogin.tsx | 69 ++++++++++++++++++++++ client/src/components/auth/GoogleLogin.tsx | 40 +++++++++++++ client/src/components/auth/LoginForm.tsx | 47 ++------------- client/src/contexts/AuthContext.tsx | 16 ++--- client/src/enums/grantType.enum.ts | 6 ++ client/src/enums/index.ts | 1 + client/src/types/auth.ts | 10 ++++ client/vite.config.ts | 1 + 11 files changed, 160 insertions(+), 52 deletions(-) create mode 100644 client/src/components/auth/AppleLogin.tsx create mode 100644 client/src/components/auth/GoogleLogin.tsx create mode 100644 client/src/enums/grantType.enum.ts create mode 100644 client/src/enums/index.ts diff --git a/client/package-lock.json b/client/package-lock.json index b27ef11..03a0ab0 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -13,6 +13,7 @@ "@fontsource/roboto": "^5.1.1", "@mui/icons-material": "^6.3.1", "@mui/material": "^6.3.1", + "@react-oauth/google": "^0.12.1", "axios": "^1.7.9", "react": "^18.3.1", "react-apple-signin-auth": "^1.1.0", @@ -1447,6 +1448,16 @@ "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": { "version": "4.30.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.0.tgz", diff --git a/client/package.json b/client/package.json index 3d7e22e..4901c48 100644 --- a/client/package.json +++ b/client/package.json @@ -15,6 +15,7 @@ "@fontsource/roboto": "^5.1.1", "@mui/icons-material": "^6.3.1", "@mui/material": "^6.3.1", + "@react-oauth/google": "^0.12.1", "axios": "^1.7.9", "react": "^18.3.1", "react-apple-signin-auth": "^1.1.0", diff --git a/client/src/api/client.ts b/client/src/api/client.ts index 3e52f27..9467611 100644 --- a/client/src/api/client.ts +++ b/client/src/api/client.ts @@ -1,8 +1,10 @@ import axios from 'axios'; +import { LoginRequest } from '../types/auth'; import { CreateJuniorRequest, JuniorTheme } from '../types/junior'; 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 // Helper function to get auth header @@ -75,11 +77,13 @@ export const authApi = { 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', { - grantType: 'PASSWORD', + grantType, email, password, + appleToken, + googleToken, fcmToken: 'web-client-token', // Required by API signature: 'web-login', // Required by API }), diff --git a/client/src/components/auth/AppleLogin.tsx b/client/src/components/auth/AppleLogin.tsx new file mode 100644 index 0000000..4abaca8 --- /dev/null +++ b/client/src/components/auth/AppleLogin.tsx @@ -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 ( + { + 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 */ + /> + ); +}; diff --git a/client/src/components/auth/GoogleLogin.tsx b/client/src/components/auth/GoogleLogin.tsx new file mode 100644 index 0000000..f7c6ab9 --- /dev/null +++ b/client/src/components/auth/GoogleLogin.tsx @@ -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 ( + + { + onSuccess(credentialResponse); + }} + onError={() => { + onError('Login failed. Please check your credentials.'); + }} + /> + + ); +}; diff --git a/client/src/components/auth/LoginForm.tsx b/client/src/components/auth/LoginForm.tsx index 286cb3d..193c7f7 100644 --- a/client/src/components/auth/LoginForm.tsx +++ b/client/src/components/auth/LoginForm.tsx @@ -1,8 +1,10 @@ import { Alert, Box, Button, Container, Paper, TextField, Typography } from '@mui/material'; import React, { useState } from 'react'; -import AppleSignin from 'react-apple-signin-auth'; import { useNavigate } from 'react-router-dom'; import { useAuth } from '../../contexts/AuthContext'; +import { GrantType } from '../../enums'; +import { AppleLogin } from './AppleLogin'; +import { GoogleLogin } from './GoogleLogin'; export const LoginForm = () => { const { login } = useAuth(); const navigate = useNavigate(); @@ -19,7 +21,7 @@ export const LoginForm = () => { setLoading(true); try { - await login(formData.email, formData.password); + await login({ email: formData.email, password: formData.password, grantType: GrantType.PASSWORD }); navigate('/dashboard'); } catch (err) { setError(err instanceof Error ? err.message : 'Login failed. Please check your credentials.'); @@ -35,7 +37,6 @@ export const LoginForm = () => { [name]: value, })); }; - console.log(process.env); return ( { > signup - - { - 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 */ - /> + + diff --git a/client/src/contexts/AuthContext.tsx b/client/src/contexts/AuthContext.tsx index 4021e64..a19aa46 100644 --- a/client/src/contexts/AuthContext.tsx +++ b/client/src/contexts/AuthContext.tsx @@ -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 { User, LoginResponse } from '../types/auth'; +import { LoginRequest, LoginResponse, User } from '../types/auth'; interface AuthContextType { isAuthenticated: boolean; user: User | null; - login: (email: string, password: string) => Promise; + login: (loginRequest: LoginRequest) => Promise; logout: () => void; register: (countryCode: string, phoneNumber: string) => Promise; verifyOtp: (countryCode: string, phoneNumber: string, otp: string) => Promise; @@ -27,9 +27,9 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children const [isAuthenticated, setIsAuthenticated] = useState(false); const [user, setUser] = useState(null); - const login = useCallback(async (email: string, password: string) => { + const login = useCallback(async (loginRequest: LoginRequest) => { try { - const response = await authApi.login(email, password); + const response = await authApi.login(loginRequest); const loginData = response.data.data as LoginResponse; setUser(loginData.user); // Store tokens @@ -76,7 +76,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children console.log('Access token:', accessToken); // Store token in localStorage immediately localStorage.setItem('accessToken', accessToken); - setRegistrationData(prev => ({ ...prev, token: accessToken })); + setRegistrationData((prev) => ({ ...prev, token: accessToken })); return accessToken; } catch (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) => { try { await authApi.setEmail(email); - setRegistrationData(prev => ({ ...prev, email })); + setRegistrationData((prev) => ({ ...prev, email })); } catch (error) { console.error('Setting email failed:', error); throw error; @@ -112,7 +112,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children register, verifyOtp, setEmail, - setPasscode + setPasscode, }; return {children}; diff --git a/client/src/enums/grantType.enum.ts b/client/src/enums/grantType.enum.ts new file mode 100644 index 0000000..f4d19d3 --- /dev/null +++ b/client/src/enums/grantType.enum.ts @@ -0,0 +1,6 @@ +export enum GrantType { + PASSWORD = 'PASSWORD', + APPLE = 'APPLE', + GOOGLE = 'GOOGLE', + BIOMETRIC = 'BIOMETRIC', +} diff --git a/client/src/enums/index.ts b/client/src/enums/index.ts new file mode 100644 index 0000000..69dc8be --- /dev/null +++ b/client/src/enums/index.ts @@ -0,0 +1 @@ +export * from './grantType.enum'; diff --git a/client/src/types/auth.ts b/client/src/types/auth.ts index 524a8ef..8c2b38e 100644 --- a/client/src/types/auth.ts +++ b/client/src/types/auth.ts @@ -1,3 +1,5 @@ +import { GrantType } from '../enums'; + export interface User { id: string; email: string; @@ -15,3 +17,11 @@ export interface LoginResponse { refreshToken: string; user: User; } + +export interface LoginRequest { + email?: string; + password?: string; + grantType: GrantType; + googleToken?: string; + appleToken?: string; +} diff --git a/client/vite.config.ts b/client/vite.config.ts index 35c1795..43e4018 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -9,6 +9,7 @@ export default defineConfig(({ mode }) => { define: { '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.GOOGLE_WEB_CLIENT_ID': JSON.stringify(env.GOOGLE_WEB_CLIENT_ID), }, plugins: [react()], };