mirror of
https://github.com/TurTaskProject/TurTaskWeb.git
synced 2025-12-19 05:54:07 +01:00
Merge pull request #12 from TurTaskProject/feature/user-authentication
Fix Google Authentication and Add placeholder navbar and login-signup
This commit is contained in:
commit
af7cd4a6f7
@ -46,19 +46,19 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
'django.contrib.sites',
|
||||||
'tasks',
|
|
||||||
|
|
||||||
|
'tasks',
|
||||||
'users',
|
'users',
|
||||||
'rest_framework',
|
|
||||||
'corsheaders',
|
'corsheaders',
|
||||||
|
|
||||||
'django.contrib.sites',
|
|
||||||
'allauth',
|
'allauth',
|
||||||
'allauth.account',
|
'allauth.account',
|
||||||
'allauth.socialaccount',
|
'allauth.socialaccount',
|
||||||
'allauth.socialaccount.providers.google',
|
'allauth.socialaccount.providers.google',
|
||||||
|
|
||||||
|
'rest_framework',
|
||||||
'dj_rest_auth',
|
'dj_rest_auth',
|
||||||
'dj_rest_auth.registration',
|
'dj_rest_auth.registration',
|
||||||
'rest_framework.authtoken',
|
'rest_framework.authtoken',
|
||||||
@ -70,10 +70,10 @@ REST_FRAMEWORK = {
|
|||||||
|
|
||||||
],
|
],
|
||||||
'DEFAULT_AUTHENTICATION_CLASSES': [
|
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||||
|
'rest_framework.authentication.BasicAuthentication',
|
||||||
|
'rest_framework.authentication.TokenAuthentication',
|
||||||
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
||||||
'dj_rest_auth.jwt_auth.JWTCookieAuthentication',
|
'dj_rest_auth.jwt_auth.JWTCookieAuthentication',
|
||||||
'rest_framework.authentication.BasicAuthentication',
|
|
||||||
'rest_framework.authentication.SessionAuthentication',
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ CORS_ALLOWED_ORIGINS = [
|
|||||||
"http://localhost:5173",
|
"http://localhost:5173",
|
||||||
]
|
]
|
||||||
|
|
||||||
CSRF_TRUSTED_ORIGINS = ["http://*"]
|
CSRF_TRUSTED_ORIGINS = ["http://localhost:5173"]
|
||||||
|
|
||||||
CORS_ORIGIN_WHITELIST = ["*"]
|
CORS_ORIGIN_WHITELIST = ["*"]
|
||||||
|
|
||||||
|
|||||||
@ -24,7 +24,7 @@ class CustomUserSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CustomUser
|
model = CustomUser
|
||||||
fields = ('email', 'username', 'password')
|
fields = ('email', 'password')
|
||||||
extra_kwargs = {'password': {'write_only': True}}
|
extra_kwargs = {'password': {'write_only': True}}
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from rest_framework_simplejwt import views as jwt_views
|
from rest_framework_simplejwt import views as jwt_views
|
||||||
from .views import ObtainTokenPairWithCustomView, CustomUserCreate, GreetingView, GoogleLogin
|
from .views import ObtainTokenPairWithCustomView, CustomUserCreate, GreetingView, GoogleLogin, GoogleRetrieveUserInfo
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('user/create/', CustomUserCreate.as_view(), name="create_user"),
|
path('user/create/', CustomUserCreate.as_view(), name="create_user"),
|
||||||
@ -9,4 +9,5 @@ urlpatterns = [
|
|||||||
path('token/custom_obtain/', ObtainTokenPairWithCustomView.as_view(), name='token_create_custom'),
|
path('token/custom_obtain/', ObtainTokenPairWithCustomView.as_view(), name='token_create_custom'),
|
||||||
path('hello/', GreetingView.as_view(), name='hello_world'),
|
path('hello/', GreetingView.as_view(), name='hello_world'),
|
||||||
path('dj-rest-auth/google/', GoogleLogin.as_view(), name="google_login"),
|
path('dj-rest-auth/google/', GoogleLogin.as_view(), name="google_login"),
|
||||||
|
path('auth/google/', GoogleRetrieveUserInfo.as_view())
|
||||||
]
|
]
|
||||||
@ -1,16 +1,25 @@
|
|||||||
"""This module defines API views for authentication, user creation, and a simple hello message."""
|
"""This module defines API views for authentication, user creation, and a simple hello message."""
|
||||||
|
|
||||||
from django.shortcuts import render
|
import json
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from django.contrib.auth.hashers import make_password
|
||||||
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from dj_rest_auth.registration.views import SocialLoginView
|
from rest_framework_simplejwt.tokens import RefreshToken
|
||||||
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
|
|
||||||
from .adapter import CustomGoogleOAuth2Adapter
|
|
||||||
from .serializers import MyTokenObtainPairSerializer, CustomUserSerializer
|
|
||||||
|
|
||||||
from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
|
from allauth.socialaccount.providers.google.views import GoogleOAuth2Adapter
|
||||||
|
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
|
||||||
|
|
||||||
|
from dj_rest_auth.registration.views import SocialLoginView
|
||||||
|
|
||||||
|
from .serializers import MyTokenObtainPairSerializer, CustomUserSerializer
|
||||||
|
from .managers import CustomAccountManager
|
||||||
|
from .models import CustomUser
|
||||||
|
|
||||||
|
|
||||||
class ObtainTokenPairWithCustomView(APIView):
|
class ObtainTokenPairWithCustomView(APIView):
|
||||||
"""
|
"""
|
||||||
@ -78,5 +87,47 @@ class GoogleLogin(SocialLoginView):
|
|||||||
"""
|
"""
|
||||||
# permission_classes = (AllowAny,)
|
# permission_classes = (AllowAny,)
|
||||||
adapter_class = GoogleOAuth2Adapter
|
adapter_class = GoogleOAuth2Adapter
|
||||||
client_class = OAuth2Client
|
# client_class = OAuth2Client
|
||||||
# callback_url = 'http://localhost:8000/accounts/google/login/callback/'
|
# callback_url = 'http://localhost:8000/accounts/google/login/callback/'
|
||||||
|
|
||||||
|
|
||||||
|
class GoogleRetrieveUserInfo(APIView):
|
||||||
|
"""
|
||||||
|
Retrieve user information from Google and create a user if not exists.
|
||||||
|
"""
|
||||||
|
def post(self, request):
|
||||||
|
access_token = request.data.get("token")
|
||||||
|
|
||||||
|
user_info = self.get_google_user_info(access_token)
|
||||||
|
|
||||||
|
if 'error' in user_info:
|
||||||
|
error_message = 'Wrong Google token or the token has expired.'
|
||||||
|
return Response({'message': error_message, 'error': user_info['error']})
|
||||||
|
|
||||||
|
user = self.get_or_create_user(user_info)
|
||||||
|
token = RefreshToken.for_user(user)
|
||||||
|
|
||||||
|
response = {
|
||||||
|
'username': user.username,
|
||||||
|
'access_token': str(token.access_token),
|
||||||
|
'refresh_token': str(token),
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response(response)
|
||||||
|
|
||||||
|
def get_google_user_info(self, access_token):
|
||||||
|
url = 'https://www.googleapis.com/oauth2/v2/userinfo'
|
||||||
|
payload = {'access_token': access_token}
|
||||||
|
response = requests.get(url, params=payload)
|
||||||
|
return json.loads(response.text)
|
||||||
|
|
||||||
|
def get_or_create_user(self, user_info):
|
||||||
|
try:
|
||||||
|
user = CustomUser.objects.get(email=user_info['email'])
|
||||||
|
except CustomUser.DoesNotExist:
|
||||||
|
user = CustomUser()
|
||||||
|
user.username = user_info['email']
|
||||||
|
user.password = make_password(CustomAccountManager().make_random_password())
|
||||||
|
user.email = user_info['email']
|
||||||
|
user.save()
|
||||||
|
return user
|
||||||
@ -16,6 +16,7 @@
|
|||||||
"@material-ui/icons": "^4.11.3",
|
"@material-ui/icons": "^4.11.3",
|
||||||
"@mui/icons-material": "^5.14.15",
|
"@mui/icons-material": "^5.14.15",
|
||||||
"@mui/material": "^5.14.15",
|
"@mui/material": "^5.14.15",
|
||||||
|
"@mui/system": "^5.14.15",
|
||||||
"@react-oauth/google": "^0.11.1",
|
"@react-oauth/google": "^0.11.1",
|
||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"bootstrap": "^5.3.2",
|
"bootstrap": "^5.3.2",
|
||||||
|
|||||||
@ -23,6 +23,9 @@ dependencies:
|
|||||||
'@mui/material':
|
'@mui/material':
|
||||||
specifier: ^5.14.15
|
specifier: ^5.14.15
|
||||||
version: 5.14.15(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0)
|
version: 5.14.15(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.33)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@mui/system':
|
||||||
|
specifier: ^5.14.15
|
||||||
|
version: 5.14.15(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.33)(react@18.2.0)
|
||||||
'@react-oauth/google':
|
'@react-oauth/google':
|
||||||
specifier: ^0.11.1
|
specifier: ^0.11.1
|
||||||
version: 0.11.1(react-dom@18.2.0)(react@18.2.0)
|
version: 0.11.1(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
|||||||
@ -1,38 +1,18 @@
|
|||||||
// import './App.css';
|
|
||||||
// import { Routes, Route, Link } from "react-router-dom";
|
|
||||||
// import Login from "./components/login";
|
|
||||||
// import TestAuth from './components/testAuth';
|
|
||||||
|
|
||||||
// function App() {
|
|
||||||
// return (
|
|
||||||
// <div className="App">
|
|
||||||
// <nav>
|
|
||||||
// <Link className={"nav-link"} to={"/"}>Home</Link>
|
|
||||||
// <Link className={"nav-link"} to={"/login"}>Login</Link>
|
|
||||||
// <Link className={"nav-link"} to={"/testAuth"}>testAuth</Link>
|
|
||||||
// </nav>
|
|
||||||
// <Routes>
|
|
||||||
// <Route exact path={"/login"} element={Login} />
|
|
||||||
// <Route exact path={"/testAuth"} element={TestAuth} />
|
|
||||||
// <Route path={"/"} render={() => <h1>This is Home page!</h1>} />
|
|
||||||
// </Routes>
|
|
||||||
// </div>
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export default App;
|
|
||||||
|
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import {BrowserRouter, Route, Routes, Link} from "react-router-dom";
|
import { BrowserRouter, Route, Routes, Link } from 'react-router-dom';
|
||||||
import Login from "./components/login";
|
|
||||||
import TestAuth from './components/testAuth';
|
import TestAuth from './components/testAuth';
|
||||||
import Signup from './components/signup';
|
import IconSideNav from './components/IconSideNav';
|
||||||
import IconSideNav from "./components/IconSideNav";
|
import AuthenticantionPage from './components/authentication/AuthenticationPage';
|
||||||
|
import SignUpPage from './components/authentication/SignUpPage';
|
||||||
|
import NavBar from './components/Nav/Navbar';
|
||||||
|
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<div className="App">
|
<div className="App">
|
||||||
|
<NavBar/>
|
||||||
<nav>
|
<nav>
|
||||||
<Link className={"nav-link"} to={"/"}>Home</Link>
|
<Link className={"nav-link"} to={"/"}>Home</Link>
|
||||||
<Link className={"nav-link"} to={"/login"}>Login</Link>
|
<Link className={"nav-link"} to={"/login"}>Login</Link>
|
||||||
@ -41,14 +21,14 @@ const App = () => {
|
|||||||
</nav>
|
</nav>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path={"/"} render={() => <h1>This is Home page!</h1>} />
|
<Route path={"/"} render={() => <h1>This is Home page!</h1>} />
|
||||||
<Route path="/login" element={<Login/>}/>
|
<Route path="/login" element={<AuthenticantionPage/>}/>
|
||||||
<Route path="/signup" element={<Signup/>}/>
|
<Route path="/signup" element={<SignUpPage/>}/>
|
||||||
<Route path="/testAuth" element={<TestAuth/>}/>
|
<Route path="/testAuth" element={<TestAuth/>}/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
{/* <div>
|
||||||
<IconSideNav />
|
<IconSideNav />
|
||||||
</div>
|
</div> */}
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,13 +62,12 @@ const apiUserLogout = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Function for Google login
|
// Function for Google login
|
||||||
const googleLogin = async (accesstoken) => {
|
const googleLogin = async (token) => {
|
||||||
axios.defaults.withCredentials = true
|
axios.defaults.withCredentials = true
|
||||||
let res = await axios.post(
|
let res = await axios.post(
|
||||||
"http://localhost:8000/api/dj-rest-auth/google/",
|
"http://localhost:8000/api/auth/google/",
|
||||||
{
|
{
|
||||||
access_token: accesstoken,
|
token: token,
|
||||||
id_token: accesstoken,
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
// console.log('service google login res: ', res);
|
// console.log('service google login res: ', res);
|
||||||
|
|||||||
177
frontend/src/components/Nav/Navbar.jsx
Normal file
177
frontend/src/components/Nav/Navbar.jsx
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import AppBar from '@mui/material/AppBar';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Toolbar from '@mui/material/Toolbar';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import Menu from '@mui/material/Menu';
|
||||||
|
import MenuIcon from '@mui/icons-material/Menu';
|
||||||
|
import Container from '@mui/material/Container';
|
||||||
|
import Avatar from '@mui/material/Avatar';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import Tooltip from '@mui/material/Tooltip';
|
||||||
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
|
import AdbIcon from '@mui/icons-material/Adb';
|
||||||
|
|
||||||
|
const pages = {
|
||||||
|
TestAuth: '/testAuth',
|
||||||
|
|
||||||
|
};
|
||||||
|
const settings = {
|
||||||
|
Profile: '/profile',
|
||||||
|
Account: '/account',
|
||||||
|
Dashboard: '/dashboard',
|
||||||
|
Logout: '/logout',
|
||||||
|
};
|
||||||
|
|
||||||
|
function NavBar() {
|
||||||
|
const [anchorElNav, setAnchorElNav] = React.useState(null);
|
||||||
|
const [anchorElUser, setAnchorElUser] = React.useState(null);
|
||||||
|
|
||||||
|
const handleOpenNavMenu = (event) => {
|
||||||
|
setAnchorElNav(event.currentTarget);
|
||||||
|
};
|
||||||
|
const handleOpenUserMenu = (event) => {
|
||||||
|
setAnchorElUser(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseNavMenu = () => {
|
||||||
|
setAnchorElNav(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseUserMenu = () => {
|
||||||
|
setAnchorElUser(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AppBar position="static">
|
||||||
|
<Container maxWidth="xl">
|
||||||
|
<Toolbar disableGutters>
|
||||||
|
<AdbIcon sx={{ display: { xs: 'none', md: 'flex' }, mr: 1 }} />
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
noWrap
|
||||||
|
component="a"
|
||||||
|
href="/"
|
||||||
|
sx={{
|
||||||
|
mr: 2,
|
||||||
|
display: { xs: 'none', md: 'flex' },
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '.3rem',
|
||||||
|
color: 'inherit',
|
||||||
|
textDecoration: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
LOGO
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box sx={{ flexGrow: 1, display: { xs: 'flex', md: 'none' } }}>
|
||||||
|
<IconButton
|
||||||
|
size="large"
|
||||||
|
aria-label="account of current user"
|
||||||
|
aria-controls="menu-appbar"
|
||||||
|
aria-haspopup="true"
|
||||||
|
onClick={handleOpenNavMenu}
|
||||||
|
color="inherit"
|
||||||
|
>
|
||||||
|
<MenuIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Menu
|
||||||
|
id="menu-appbar"
|
||||||
|
anchorEl={anchorElNav}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'bottom',
|
||||||
|
horizontal: 'left',
|
||||||
|
}}
|
||||||
|
keepMounted
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'left',
|
||||||
|
}}
|
||||||
|
open={Boolean(anchorElNav)}
|
||||||
|
onClose={handleCloseNavMenu}
|
||||||
|
sx={{
|
||||||
|
display: { xs: 'block', md: 'none' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.entries(pages).map(([page, path]) => (
|
||||||
|
<MenuItem key={page} onClick={handleCloseNavMenu}>
|
||||||
|
<Link to={path} className="nav-link">
|
||||||
|
<Typography textAlign="center">{page}</Typography>
|
||||||
|
</Link>
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
</Box>
|
||||||
|
<AdbIcon sx={{ display: { xs: 'flex', md: 'none' }, mr: 1 }} />
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
noWrap
|
||||||
|
component="a"
|
||||||
|
href="#app-bar-with-responsive-menu"
|
||||||
|
sx={{
|
||||||
|
mr: 2,
|
||||||
|
display: { xs: 'flex', md: 'none' },
|
||||||
|
flexGrow: 1,
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontWeight: 700,
|
||||||
|
letterSpacing: '.3rem',
|
||||||
|
color: 'inherit',
|
||||||
|
textDecoration: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
LOGO
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
|
||||||
|
{Object.entries(pages).map(([page, path]) => (
|
||||||
|
<Button
|
||||||
|
key={page}
|
||||||
|
component={Link} // Use the Link component
|
||||||
|
to={path} // Specify the target path
|
||||||
|
onClick={handleCloseNavMenu}
|
||||||
|
sx={{ my: 2, color: 'white', display: 'block' }}
|
||||||
|
>
|
||||||
|
{page}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ flexGrow: 0 }}>
|
||||||
|
<Tooltip title="Open settings">
|
||||||
|
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
|
||||||
|
<Avatar alt="Bullet" src="https://us-tuna-sounds-images.voicemod.net/f322631f-689a-43ac-81ab-17a70f27c443-1692187175560.png" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Menu
|
||||||
|
sx={{ mt: '45px' }}
|
||||||
|
id="menu-appbar"
|
||||||
|
anchorEl={anchorElUser}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'right',
|
||||||
|
}}
|
||||||
|
keepMounted
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: 'top',
|
||||||
|
horizontal: 'right',
|
||||||
|
}}
|
||||||
|
open={Boolean(anchorElUser)}
|
||||||
|
onClose={handleCloseUserMenu}
|
||||||
|
>
|
||||||
|
{Object.entries(settings).map(([setting, path]) => (
|
||||||
|
<MenuItem key={setting} onClick={handleCloseUserMenu}>
|
||||||
|
<Link to={path} className="nav-link">
|
||||||
|
<Typography textAlign="center">{setting}</Typography>
|
||||||
|
</Link>
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
</Box>
|
||||||
|
</Toolbar>
|
||||||
|
</Container>
|
||||||
|
</AppBar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default NavBar;
|
||||||
215
frontend/src/components/authentication/AuthenticationPage.jsx
Normal file
215
frontend/src/components/authentication/AuthenticationPage.jsx
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { useGoogleLogin } from '@react-oauth/google';
|
||||||
|
|
||||||
|
import Avatar from '@mui/material/Avatar';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import CssBaseline from '@mui/material/CssBaseline';
|
||||||
|
import TextField from '@mui/material/TextField';
|
||||||
|
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||||
|
import Checkbox from '@mui/material/Checkbox';
|
||||||
|
import Link from '@mui/material/Link';
|
||||||
|
import Divider from '@mui/material/Divider';
|
||||||
|
import Paper from '@mui/material/Paper';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Grid from '@mui/material/Grid';
|
||||||
|
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
||||||
|
|
||||||
|
import axiosapi from '../../api/axiosapi';
|
||||||
|
|
||||||
|
|
||||||
|
function Copyright(props) {
|
||||||
|
return (
|
||||||
|
<Typography variant="body2" color="text.secondary" align="center" {...props}>
|
||||||
|
{'Copyright © '}
|
||||||
|
<Link color="inherit" href="https://mui.com/">
|
||||||
|
Your Website
|
||||||
|
</Link>{' '}
|
||||||
|
{new Date().getFullYear()}
|
||||||
|
{'.'}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const defaultTheme = createTheme();
|
||||||
|
|
||||||
|
export default function SignInSide() {
|
||||||
|
|
||||||
|
const Navigate = useNavigate();
|
||||||
|
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [username, setUsername] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
|
||||||
|
const handleUsernameChange = (event) => {
|
||||||
|
setUsername(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEmailChange = (event) => {
|
||||||
|
setEmail(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePasswordChange = (event) => {
|
||||||
|
setPassword(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// Send a POST request to the authentication API
|
||||||
|
axiosapi.apiUserLogin({
|
||||||
|
email: email,
|
||||||
|
username: username,
|
||||||
|
password: password
|
||||||
|
}).then(res => {
|
||||||
|
// On successful login, store tokens and set the authorization header
|
||||||
|
localStorage.setItem('access_token', res.data.access);
|
||||||
|
localStorage.setItem('refresh_token', res.data.refresh);
|
||||||
|
axiosapi.axiosInstance.defaults.headers['Authorization'] = "Bearer " + res.data.access;
|
||||||
|
Navigate('/');
|
||||||
|
}).catch(err => {
|
||||||
|
console.log('Login failed'); // Handle login failure
|
||||||
|
console.log(err)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseGoogle = async (response) => {
|
||||||
|
// Handle Google login response
|
||||||
|
let googleResponse = await axiosapi.googleLogin(response.access_token);
|
||||||
|
console.log('Google Response:\n', googleResponse);
|
||||||
|
|
||||||
|
if (googleResponse.status === 200) {
|
||||||
|
// Store Google login tokens and set the authorization header on success
|
||||||
|
localStorage.setItem('access_token', googleResponse.data.access_token);
|
||||||
|
localStorage.setItem('refresh_token', googleResponse.data.refresh_token);
|
||||||
|
axiosapi.axiosInstance.defaults.headers['Authorization'] = "Bearer " + googleResponse.data.access_token;
|
||||||
|
Navigate('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const googleLoginImplicit = useGoogleLogin({
|
||||||
|
// flow: 'auth-code',
|
||||||
|
onSuccess: async (response) => {
|
||||||
|
console.log(response);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const loginResponse = await axiosapi.googleLogin(response.access_token);
|
||||||
|
if (loginResponse && loginResponse.data) {
|
||||||
|
const { access_token, refresh_token } = loginResponse.data;
|
||||||
|
|
||||||
|
// Save the tokens in localStorage
|
||||||
|
localStorage.setItem('access_token', access_token);
|
||||||
|
localStorage.setItem('refresh_token', refresh_token);
|
||||||
|
Navigate('/');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error with the POST request:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: errorResponse => console.log(errorResponse),
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeProvider theme={defaultTheme}>
|
||||||
|
<Grid container component="main" sx={{ height: '100vh' }}>
|
||||||
|
<CssBaseline />
|
||||||
|
<Grid
|
||||||
|
item
|
||||||
|
xs={false}
|
||||||
|
sm={4}
|
||||||
|
md={7}
|
||||||
|
sx={{
|
||||||
|
backgroundImage: 'url(https://source.unsplash.com/random?wallpapers)',
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
backgroundColor: (t) =>
|
||||||
|
t.palette.mode === 'light' ? t.palette.grey[50] : t.palette.grey[900],
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Grid item xs={12} sm={8} md={5} component={Paper} elevation={6} square>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
my: 8,
|
||||||
|
mx: 4,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}>
|
||||||
|
<LockOutlinedIcon />
|
||||||
|
</Avatar>
|
||||||
|
<Typography component="h1" variant="h5">
|
||||||
|
Sign in
|
||||||
|
</Typography>
|
||||||
|
<Box component="form" noValidate onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||||
|
<TextField
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
id="email"
|
||||||
|
label="Email Address"
|
||||||
|
name="email"
|
||||||
|
autoComplete="email"
|
||||||
|
autoFocus
|
||||||
|
onChange={handleEmailChange}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
name="password"
|
||||||
|
label="Password"
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
autoComplete="current-password"
|
||||||
|
onChange={handlePasswordChange}
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Checkbox value="remember" color="primary" />}
|
||||||
|
label="Remember me"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
fullWidth
|
||||||
|
variant="contained"
|
||||||
|
sx={{ mt: 3, mb: 2 }}
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</Button>
|
||||||
|
<Divider>OR</Divider>
|
||||||
|
<Box py={2}>
|
||||||
|
<Button
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
color="secondary"
|
||||||
|
onClick={() => googleLoginImplicit()}
|
||||||
|
>
|
||||||
|
Sign in with Google 🚀
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Grid container>
|
||||||
|
<Grid item xs>
|
||||||
|
<Link href="#" variant="body2">
|
||||||
|
Forgot password?
|
||||||
|
</Link>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<Link href="#" variant="body2">
|
||||||
|
{"Don't have an account? Sign Up"}
|
||||||
|
</Link>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Copyright sx={{ mt: 5 }} />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
48
frontend/src/components/authentication/IsAuthenticated.jsx
Normal file
48
frontend/src/components/authentication/IsAuthenticated.jsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import axiosapi from './axiosapi';
|
||||||
|
|
||||||
|
function IsAuthenticated() {
|
||||||
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const checkAuthentication = async () => {
|
||||||
|
const access_token = localStorage.getItem('access_token');
|
||||||
|
const refresh_token = localStorage.getItem('refresh_token');
|
||||||
|
|
||||||
|
if (access_token && refresh_token) {
|
||||||
|
const isAccessTokenExpired = checkIfAccessTokenExpired(access_token);
|
||||||
|
|
||||||
|
if (!isAccessTokenExpired) {
|
||||||
|
setIsAuthenticated(true);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
// Attempt to refresh the access token using the refresh token
|
||||||
|
const response = await axiosapi.refreshAccessToken(refresh_token);
|
||||||
|
if (response.status === 200) {
|
||||||
|
const newAccessToken = response.data.access_token;
|
||||||
|
localStorage.setItem('access_token', newAccessToken);
|
||||||
|
setIsAuthenticated(true);
|
||||||
|
} else {
|
||||||
|
setIsAuthenticated(false);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setIsAuthenticated(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setIsAuthenticated(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkAuthentication();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const checkIfAccessTokenExpired = (accessToken) => {
|
||||||
|
// Need to change logic again!
|
||||||
|
return !accessToken;
|
||||||
|
};
|
||||||
|
|
||||||
|
return isAuthenticated;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IsAuthenticated;
|
||||||
151
frontend/src/components/authentication/SignUpPage.jsx
Normal file
151
frontend/src/components/authentication/SignUpPage.jsx
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import axiosapi from '../../api/axiosapi';
|
||||||
|
|
||||||
|
import Avatar from '@mui/material/Avatar';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import CssBaseline from '@mui/material/CssBaseline';
|
||||||
|
import TextField from '@mui/material/TextField';
|
||||||
|
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||||
|
import Checkbox from '@mui/material/Checkbox';
|
||||||
|
import Link from '@mui/material/Link';
|
||||||
|
import Grid from '@mui/material/Grid';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import Container from '@mui/material/Container';
|
||||||
|
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
||||||
|
|
||||||
|
|
||||||
|
function Copyright(props) {
|
||||||
|
return (
|
||||||
|
<Typography variant="body2" color="text.secondary" align="center" {...props}>
|
||||||
|
{'Copyright © '}
|
||||||
|
<Link color="inherit" href="https://mui.com/">
|
||||||
|
Your Website
|
||||||
|
</Link>{' '}
|
||||||
|
{new Date().getFullYear()}
|
||||||
|
{'.'}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultTheme = createTheme();
|
||||||
|
|
||||||
|
export default function SignUp() {
|
||||||
|
|
||||||
|
const Navigate = useNavigate();
|
||||||
|
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
email: '',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
});
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsSubmitting(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
axiosapi.createUser(formData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating user:', error);
|
||||||
|
setError('Registration failed. Please try again.');
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
Navigate('/login');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setFormData({ ...formData, [name]: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeProvider theme={defaultTheme}>
|
||||||
|
<Container component="main" maxWidth="xs">
|
||||||
|
<CssBaseline />
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
marginTop: 8,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Avatar sx={{ m: 1, bgcolor: 'secondary.main' }}>
|
||||||
|
<LockOutlinedIcon />
|
||||||
|
</Avatar>
|
||||||
|
<Typography component="h1" variant="h5">
|
||||||
|
Sign up
|
||||||
|
</Typography>
|
||||||
|
<Box component="form" noValidate onSubmit={handleSubmit} sx={{ mt: 3 }}>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<TextField
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
id="email"
|
||||||
|
label="Email Address"
|
||||||
|
name="email"
|
||||||
|
autoComplete="email"
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<TextField
|
||||||
|
autoComplete="username"
|
||||||
|
name="Username"
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
id="Username"
|
||||||
|
label="Username"
|
||||||
|
autoFocus
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<TextField
|
||||||
|
required
|
||||||
|
fullWidth
|
||||||
|
name="password"
|
||||||
|
label="Password"
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
autoComplete="new-password"
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Checkbox value="allowExtraEmails" color="primary" />}
|
||||||
|
label="I want to receive inspiration, marketing promotions and updates via email."
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
fullWidth
|
||||||
|
variant="contained"
|
||||||
|
sx={{ mt: 3, mb: 2 }}
|
||||||
|
>
|
||||||
|
Sign Up
|
||||||
|
</Button>
|
||||||
|
<Grid container justifyContent="flex-end">
|
||||||
|
<Grid item>
|
||||||
|
<Link href="#" variant="body2">
|
||||||
|
Already have an account? Sign in
|
||||||
|
</Link>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Copyright sx={{ mt: 5 }} />
|
||||||
|
</Container>
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,161 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import Avatar from '@material-ui/core/Avatar';
|
|
||||||
import Button from '@material-ui/core/Button';
|
|
||||||
import CssBaseline from '@material-ui/core/CssBaseline';
|
|
||||||
import TextField from '@material-ui/core/TextField';
|
|
||||||
import Typography from '@material-ui/core/Typography';
|
|
||||||
import { makeStyles } from '@material-ui/core/styles';
|
|
||||||
import Container from '@material-ui/core/Container';
|
|
||||||
import axiosapi from '../api/axiosapi';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { useGoogleLogin } from '@react-oauth/google';
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
|
||||||
// Styles for various elements
|
|
||||||
paper: {
|
|
||||||
marginTop: theme.spacing(8),
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
avatar: {
|
|
||||||
margin: theme.spacing(1),
|
|
||||||
backgroundColor: theme.palette.secondary.main,
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
width: '100%',
|
|
||||||
marginTop: theme.spacing(1),
|
|
||||||
},
|
|
||||||
submit: {
|
|
||||||
margin: theme.spacing(3, 0, 2),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
export default function Login() {
|
|
||||||
const history = useNavigate();
|
|
||||||
const classes = useStyles();
|
|
||||||
|
|
||||||
const [email, setEmail] = useState("");
|
|
||||||
const [username, setUsername] = useState("");
|
|
||||||
const [password, setPassword] = useState("");
|
|
||||||
|
|
||||||
const handleUsernameChange = (event) => {
|
|
||||||
// Update the 'username' state when the input field changes
|
|
||||||
setUsername(event.target.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleEmailChange = (event) => {
|
|
||||||
// Update the 'email' state when the email input field changes
|
|
||||||
setEmail(event.target.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handlePasswordChange = (event) => {
|
|
||||||
// Update the 'password' state when the password input field changes
|
|
||||||
setPassword(event.target.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSubmit = (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
// Send a POST request to the authentication API
|
|
||||||
axiosapi.apiUserLogin({
|
|
||||||
email: email,
|
|
||||||
username: username,
|
|
||||||
password: password
|
|
||||||
}).then(res => {
|
|
||||||
// On successful login, store tokens and set the authorization header
|
|
||||||
localStorage.setItem('access_token', res.data.access);
|
|
||||||
localStorage.setItem('refresh_token', res.data.refresh);
|
|
||||||
axiosapi.axiosInstance.defaults.headers['Authorization'] = "Bearer " + res.data.access;
|
|
||||||
history.push('/testAuth');
|
|
||||||
}).catch(err => {
|
|
||||||
console.log('Login failed'); // Handle login failure
|
|
||||||
console.log(err)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const responseGoogle = async (response) => {
|
|
||||||
// Handle Google login response
|
|
||||||
let googleResponse = await axiosapi.googleLogin(response.access_token);
|
|
||||||
console.log('Google Response:\n', googleResponse);
|
|
||||||
|
|
||||||
if (googleResponse.status === 200) {
|
|
||||||
// Store Google login tokens and set the authorization header on success
|
|
||||||
localStorage.setItem('access_token', googleResponse.data.access_token);
|
|
||||||
localStorage.setItem('refresh_token', googleResponse.data.refresh_token);
|
|
||||||
axiosapi.axiosInstance.defaults.headers['Authorization'] = "Bearer " + googleResponse.data.access_token;
|
|
||||||
history.push('/testAuth');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const googleLoginflow = useGoogleLogin({
|
|
||||||
onSuccess: async tokenResponse => {
|
|
||||||
console.log(tokenResponse);
|
|
||||||
responseGoogle(tokenResponse);
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container component="main" maxWidth="xs">
|
|
||||||
<CssBaseline />
|
|
||||||
<div className={classes.paper}>
|
|
||||||
<Avatar className={classes.avatar} />
|
|
||||||
<Typography component="h1" variant="h5">
|
|
||||||
Sign in
|
|
||||||
</Typography>
|
|
||||||
<form className={classes.form} noValidate onSubmit={handleSubmit}>
|
|
||||||
<TextField
|
|
||||||
variant="outlined"
|
|
||||||
margin="normal"
|
|
||||||
required
|
|
||||||
fullWidth
|
|
||||||
id="email"
|
|
||||||
label="Email"
|
|
||||||
name="email"
|
|
||||||
autoComplete="email"
|
|
||||||
autoFocus
|
|
||||||
onChange={handleEmailChange}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
variant="outlined"
|
|
||||||
margin="normal"
|
|
||||||
required
|
|
||||||
fullWidth
|
|
||||||
id="username"
|
|
||||||
label="Username"
|
|
||||||
name="username"
|
|
||||||
autoComplete="username"
|
|
||||||
autoFocus
|
|
||||||
onChange={handleUsernameChange}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
variant="outlined"
|
|
||||||
margin="normal"
|
|
||||||
required
|
|
||||||
fullWidth
|
|
||||||
name="password"
|
|
||||||
label="Password"
|
|
||||||
type="password"
|
|
||||||
id="password"
|
|
||||||
autoComplete="current-password"
|
|
||||||
onChange={handlePasswordChange}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
fullWidth
|
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
className={classes.submit}
|
|
||||||
>
|
|
||||||
Sign In
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
|
|
||||||
<button onClick={() => googleLoginflow()}>
|
|
||||||
Sign in with Google 🚀{' '}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</Container>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -4,7 +4,7 @@ import Button from '@material-ui/core/Button';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
function TestAuth() {
|
function TestAuth() {
|
||||||
let history = useNavigate();
|
let Navigate = useNavigate();
|
||||||
|
|
||||||
const [message, setMessage] = useState("");
|
const [message, setMessage] = useState("");
|
||||||
|
|
||||||
@ -22,19 +22,19 @@ function TestAuth() {
|
|||||||
const logout = () => {
|
const logout = () => {
|
||||||
// Log out the user, clear tokens, and navigate to the "/testAuth" route
|
// Log out the user, clear tokens, and navigate to the "/testAuth" route
|
||||||
axiosapi.apiUserLogout();
|
axiosapi.apiUserLogout();
|
||||||
history('/testAuth');
|
Navigate('/testAuth');
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{message !== "" && (
|
{message !== "" && (
|
||||||
<div>
|
<div>
|
||||||
<h1>Hello!</h1>
|
<h1 class="text-xl font-bold">Login! Hello!</h1>
|
||||||
<h2>{message}</h2>
|
<h2>{message}</h2>
|
||||||
<Button variant="contained" onClick={logout}>Logout</Button>
|
<Button variant="contained" onClick={logout}>Logout</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{message === "" && <h1>Need to sign in</h1>}
|
{message === "" && <h1 class="text-xl font-bold">Need to sign in, No authentication found</h1>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import { GoogleLogin, GoogleOAuthProvider} from '@react-oauth/google';
|
import { GoogleOAuthProvider} from '@react-oauth/google';
|
||||||
|
|
||||||
const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID
|
const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID
|
||||||
|
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root")).render(
|
ReactDOM.createRoot(document.getElementById("root")).render(
|
||||||
<GoogleOAuthProvider clientId={GOOGLE_CLIENT_ID}>
|
<GoogleOAuthProvider clientId={GOOGLE_CLIENT_ID}>
|
||||||
<App />
|
<App />
|
||||||
</GoogleOAuthProvider>
|
</GoogleOAuthProvider>
|
||||||
);
|
);
|
||||||
@ -8,4 +8,7 @@ djangorestframework>=3.14
|
|||||||
markdown>=3.5
|
markdown>=3.5
|
||||||
django-filter>=23.3
|
django-filter>=23.3
|
||||||
djangorestframework-simplejwt>=5.3
|
djangorestframework-simplejwt>=5.3
|
||||||
django-cors-headers>=4.3
|
django-cors-headers>=4.3
|
||||||
|
google_api_python_client>=2.1
|
||||||
|
google_auth_oauthlib>=1.1
|
||||||
|
google-auth-httplib2>=0.1
|
||||||
Loading…
Reference in New Issue
Block a user