mirror of
https://github.com/TurTaskProject/TurTaskWeb.git
synced 2025-12-19 05:54:07 +01:00
Merge branch 'main' into feature/dashboard
This commit is contained in:
commit
9379e71ce8
@ -52,6 +52,7 @@ INSTALLED_APPS = [
|
|||||||
'tasks',
|
'tasks',
|
||||||
'users',
|
'users',
|
||||||
'authentications',
|
'authentications',
|
||||||
|
'dashboard',
|
||||||
|
|
||||||
'corsheaders',
|
'corsheaders',
|
||||||
'drf_spectacular',
|
'drf_spectacular',
|
||||||
|
|||||||
@ -27,4 +27,5 @@ urlpatterns = [
|
|||||||
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
|
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
|
||||||
path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
|
path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
|
||||||
path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
|
path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
|
||||||
|
path('api/', include('dashboard.urls')),
|
||||||
]
|
]
|
||||||
3
backend/dashboard/admin.py
Normal file
3
backend/dashboard/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
backend/dashboard/apps.py
Normal file
6
backend/dashboard/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class DashboardConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'dashboard'
|
||||||
0
backend/dashboard/migrations/__init__.py
Normal file
0
backend/dashboard/migrations/__init__.py
Normal file
7
backend/dashboard/serializers.py
Normal file
7
backend/dashboard/serializers.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from .models import UserStats
|
||||||
|
|
||||||
|
class UserStatsSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = UserStats
|
||||||
|
fields = ['health', 'gold', 'experience', 'strength', 'intelligence', 'endurance', 'perception', 'luck', 'level']
|
||||||
3
backend/dashboard/tests.py
Normal file
3
backend/dashboard/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
6
backend/dashboard/urls.py
Normal file
6
backend/dashboard/urls.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from .views import DashboardStatsAPIView
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('dashboard/stats/', DashboardStatsAPIView.as_view(), name='dashboard-stats'),
|
||||||
|
]
|
||||||
58
backend/dashboard/views.py
Normal file
58
backend/dashboard/views.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
from django.db.models import Count
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from tasks.models import Todo, RecurrenceTask
|
||||||
|
|
||||||
|
class DashboardStatsAPIView(APIView):
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
user = request.user
|
||||||
|
|
||||||
|
# Calculate task usage statistics
|
||||||
|
todo_count = Todo.objects.filter(user=user).count()
|
||||||
|
recurrence_task_count = RecurrenceTask.objects.filter(user=user).count()
|
||||||
|
|
||||||
|
# Calculate how many tasks were completed in the last 7 days
|
||||||
|
completed_todo_count_last_week = Todo.objects.filter(user=user, completed=True, last_update__gte=timezone.now() - timezone.timedelta(days=7)).count()
|
||||||
|
completed_recurrence_task_count_last_week = RecurrenceTask.objects.filter(user=user, completed=True, last_update__gte=timezone.now() - timezone.timedelta(days=7)).count()
|
||||||
|
|
||||||
|
# Calculate subtask completion rate
|
||||||
|
total_subtasks = Todo.objects.filter(user=user).aggregate(total=Count('subtask__id'))['total']
|
||||||
|
completed_subtasks = Todo.objects.filter(user=user, subtask__completed=True).aggregate(total=Count('subtask__id'))['total']
|
||||||
|
|
||||||
|
# Calculate overall completion rate
|
||||||
|
total_tasks = todo_count + recurrence_task_count
|
||||||
|
completed_tasks = completed_todo_count_last_week + completed_recurrence_task_count_last_week
|
||||||
|
overall_completion_rate = (completed_tasks / total_tasks) * 100 if total_tasks > 0 else 0
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'todo_count': todo_count,
|
||||||
|
'recurrence_task_count': recurrence_task_count,
|
||||||
|
'completed_todo_count_last_week': completed_todo_count_last_week,
|
||||||
|
'completed_recurrence_task_count_last_week': completed_recurrence_task_count_last_week,
|
||||||
|
'total_subtasks': total_subtasks,
|
||||||
|
'completed_subtasks': completed_subtasks,
|
||||||
|
'overall_completion_rate': overall_completion_rate,
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
# Handle incoming data from the POST request
|
||||||
|
# Update the necessary information based on the data
|
||||||
|
|
||||||
|
task_id = request.data.get('task_id')
|
||||||
|
is_completed = request.data.get('is_completed')
|
||||||
|
|
||||||
|
try:
|
||||||
|
task = Todo.objects.get(id=task_id, user=request.user)
|
||||||
|
task.completed = is_completed
|
||||||
|
task.save()
|
||||||
|
return Response({'message': 'Task completion status updated successfully'}, status=status.HTTP_200_OK)
|
||||||
|
except Todo.DoesNotExist:
|
||||||
|
return Response({'error': 'Task not found'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 4.2.6 on 2023-11-17 16:40
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('tasks', '0013_alter_recurrencetask_recurrence_rule'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='recurrencetask',
|
||||||
|
name='completed',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='todo',
|
||||||
|
name='completed',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -18,7 +18,6 @@ class Task(models.Model):
|
|||||||
:param title: Title of the task.
|
:param title: Title of the task.
|
||||||
:param notes: Optional additional notes for the task.
|
:param notes: Optional additional notes for the task.
|
||||||
:param tags: Associated tags for the task.
|
:param tags: Associated tags for the task.
|
||||||
:param completed: A boolean field indicating whether the task is completed.
|
|
||||||
:param importance: The importance of the task (range: 1 to 5)
|
:param importance: The importance of the task (range: 1 to 5)
|
||||||
:param difficulty: The difficulty of the task (range: 1 to 5).
|
:param difficulty: The difficulty of the task (range: 1 to 5).
|
||||||
:param challenge: Associated challenge (optional).
|
:param challenge: Associated challenge (optional).
|
||||||
@ -62,12 +61,14 @@ class Todo(Task):
|
|||||||
NOT_IMPORTANT_URGENT = 3, 'Not Important & Urgent'
|
NOT_IMPORTANT_URGENT = 3, 'Not Important & Urgent'
|
||||||
NOT_IMPORTANT_NOT_URGENT = 4, 'Not Important & Not Urgent'
|
NOT_IMPORTANT_NOT_URGENT = 4, 'Not Important & Not Urgent'
|
||||||
|
|
||||||
|
completed = models.BooleanField(default=False)
|
||||||
priority = models.PositiveSmallIntegerField(choices=EisenhowerMatrix.choices, default=EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT)
|
priority = models.PositiveSmallIntegerField(choices=EisenhowerMatrix.choices, default=EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
class RecurrenceTask(Task):
|
class RecurrenceTask(Task):
|
||||||
|
completed = models.BooleanField(default=False)
|
||||||
recurrence_rule = models.CharField()
|
recurrence_rule = models.CharField()
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
|
|||||||
@ -16,6 +16,9 @@
|
|||||||
"@dnd-kit/utilities": "^3.2.2",
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@emotion/react": "^11.11.1",
|
"@emotion/react": "^11.11.1",
|
||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^6.4.2",
|
||||||
|
"@fortawesome/free-brands-svg-icons": "^6.4.2",
|
||||||
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@fullcalendar/core": "^6.1.9",
|
"@fullcalendar/core": "^6.1.9",
|
||||||
"@fullcalendar/daygrid": "^6.1.9",
|
"@fullcalendar/daygrid": "^6.1.9",
|
||||||
"@fullcalendar/interaction": "^6.1.9",
|
"@fullcalendar/interaction": "^6.1.9",
|
||||||
|
|||||||
@ -23,6 +23,15 @@ dependencies:
|
|||||||
'@emotion/styled':
|
'@emotion/styled':
|
||||||
specifier: ^11.11.0
|
specifier: ^11.11.0
|
||||||
version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.37)(react@18.2.0)
|
version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.37)(react@18.2.0)
|
||||||
|
'@fortawesome/fontawesome-svg-core':
|
||||||
|
specifier: ^6.4.2
|
||||||
|
version: 6.4.2
|
||||||
|
'@fortawesome/free-brands-svg-icons':
|
||||||
|
specifier: ^6.4.2
|
||||||
|
version: 6.4.2
|
||||||
|
'@fortawesome/react-fontawesome':
|
||||||
|
specifier: ^0.2.0
|
||||||
|
version: 0.2.0(@fortawesome/fontawesome-svg-core@6.4.2)(react@18.2.0)
|
||||||
'@fullcalendar/core':
|
'@fullcalendar/core':
|
||||||
specifier: ^6.1.9
|
specifier: ^6.1.9
|
||||||
version: 6.1.9
|
version: 6.1.9
|
||||||
@ -855,6 +864,39 @@ packages:
|
|||||||
resolution: {integrity: sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==}
|
resolution: {integrity: sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@fortawesome/fontawesome-common-types@6.4.2:
|
||||||
|
resolution: {integrity: sha512-1DgP7f+XQIJbLFCTX1V2QnxVmpLdKdzzo2k8EmvDOePfchaIGQ9eCHj2up3/jNEbZuBqel5OxiaOJf37TWauRA==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
requiresBuild: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@fortawesome/fontawesome-svg-core@6.4.2:
|
||||||
|
resolution: {integrity: sha512-gjYDSKv3TrM2sLTOKBc5rH9ckje8Wrwgx1CxAPbN5N3Fm4prfi7NsJVWd1jklp7i5uSCVwhZS5qlhMXqLrpAIg==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
requiresBuild: true
|
||||||
|
dependencies:
|
||||||
|
'@fortawesome/fontawesome-common-types': 6.4.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@fortawesome/free-brands-svg-icons@6.4.2:
|
||||||
|
resolution: {integrity: sha512-LKOwJX0I7+mR/cvvf6qIiqcERbdnY+24zgpUSouySml+5w8B4BJOx8EhDR/FTKAu06W12fmUIcv6lzPSwYKGGg==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
requiresBuild: true
|
||||||
|
dependencies:
|
||||||
|
'@fortawesome/fontawesome-common-types': 6.4.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@fortawesome/react-fontawesome@0.2.0(@fortawesome/fontawesome-svg-core@6.4.2)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@fortawesome/fontawesome-svg-core': ~1 || ~6
|
||||||
|
react: '>=16.3'
|
||||||
|
dependencies:
|
||||||
|
'@fortawesome/fontawesome-svg-core': 6.4.2
|
||||||
|
prop-types: 15.8.1
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@fullcalendar/core@6.1.9:
|
/@fullcalendar/core@6.1.9:
|
||||||
resolution: {integrity: sha512-eeG+z9BWerdsU9Ac6j16rpYpPnE0wxtnEHiHrh/u/ADbGTR3hCOjCD9PxQOfhOTHbWOVs7JQunGcksSPu5WZBQ==}
|
resolution: {integrity: sha512-eeG+z9BWerdsU9Ac6j16rpYpPnE0wxtnEHiHrh/u/ADbGTR3hCOjCD9PxQOfhOTHbWOVs7JQunGcksSPu5WZBQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import { loadFull } from "tsparticles";
|
|||||||
import refreshAccessToken from "./refreshAcesstoken";
|
import refreshAccessToken from "./refreshAcesstoken";
|
||||||
import axiosapi from "../../api/AuthenticationApi";
|
import axiosapi from "../../api/AuthenticationApi";
|
||||||
import { useAuth } from "../../hooks/authentication/IsAuthenticated";
|
import { useAuth } from "../../hooks/authentication/IsAuthenticated";
|
||||||
|
import { FcGoogle } from "react-icons/fc";
|
||||||
|
|
||||||
|
|
||||||
function LoginPage() {
|
function LoginPage() {
|
||||||
const Navigate = useNavigate();
|
const Navigate = useNavigate();
|
||||||
@ -208,7 +210,7 @@ function LoginPage() {
|
|||||||
className="btn btn-outline btn-secondary w-full "
|
className="btn btn-outline btn-secondary w-full "
|
||||||
onClick={() => googleLoginImplicit()}
|
onClick={() => googleLoginImplicit()}
|
||||||
>
|
>
|
||||||
Login with Google
|
<FcGoogle className="rounded-full bg-white"/>Login with Google
|
||||||
</button>
|
</button>
|
||||||
{/* Forgot Password Link */}
|
{/* Forgot Password Link */}
|
||||||
<div className="justify-left">
|
<div className="justify-left">
|
||||||
|
|||||||
@ -1,36 +1,29 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import axiosapi from "../../api/AuthenticationApi";
|
import axiosapi from "../../api/AuthenticationApi";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
import Particles from "react-tsparticles";
|
||||||
|
import { loadFull } from "tsparticles";
|
||||||
|
import { FcGoogle } from "react-icons/fc";
|
||||||
|
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 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) {
|
function Copyright(props) {
|
||||||
return (
|
return (
|
||||||
<Typography variant="body2" color="text.secondary" align="center" {...props}>
|
<div className="text-center text-sm text-gray-500" {...props}>
|
||||||
{"Copyright © "}
|
{"Copyright © "}
|
||||||
<Link color="inherit" href="https://github.com/TurTaskProject/TurTaskWeb">
|
<a
|
||||||
|
href="https://github.com/TurTaskProject/TurTaskWeb"
|
||||||
|
className="text-blue-500 hover:underline"
|
||||||
|
>
|
||||||
TurTask
|
TurTask
|
||||||
</Link>{" "}
|
</a>{" "}
|
||||||
{new Date().getFullYear()}
|
{new Date().getFullYear()}
|
||||||
{"."}
|
{"."}
|
||||||
</Typography>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultTheme = createTheme();
|
|
||||||
|
|
||||||
export default function SignUp() {
|
export default function SignUp() {
|
||||||
const Navigate = useNavigate();
|
const Navigate = useNavigate();
|
||||||
|
|
||||||
@ -42,7 +35,7 @@ export default function SignUp() {
|
|||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
const handleSubmit = async e => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
@ -58,86 +51,194 @@ export default function SignUp() {
|
|||||||
Navigate("/login");
|
Navigate("/login");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = e => {
|
const handleChange = (e) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
setFormData({ ...formData, [name]: value });
|
setFormData({ ...formData, [name]: value });
|
||||||
};
|
};
|
||||||
|
{
|
||||||
|
/* Particles Loader*/
|
||||||
|
}
|
||||||
|
const particlesInit = useCallback(async (engine) => {
|
||||||
|
console.log(engine);
|
||||||
|
await loadFull(engine);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const particlesLoaded = useCallback(async (container) => {
|
||||||
|
console.log(container);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const googleLoginImplicit = useGoogleLogin({
|
||||||
|
flow: "auth-code",
|
||||||
|
redirect_uri: "postmessage",
|
||||||
|
onSuccess: async (response) => {
|
||||||
|
try {
|
||||||
|
const loginResponse = await axiosapi.googleLogin(response.code);
|
||||||
|
if (loginResponse && loginResponse.data) {
|
||||||
|
const { access_token, refresh_token } = loginResponse.data;
|
||||||
|
|
||||||
|
localStorage.setItem("access_token", access_token);
|
||||||
|
localStorage.setItem("refresh_token", refresh_token);
|
||||||
|
setIsAuthenticated(true);
|
||||||
|
Navigate("/");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error with the POST request:", error);
|
||||||
|
setIsAuthenticated(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (errorResponse) => console.log(errorResponse),
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={defaultTheme}>
|
<div
|
||||||
<Container component="main" maxWidth="xs">
|
data-theme="night"
|
||||||
<CssBaseline />
|
className="h-screen flex items-center justify-center"
|
||||||
<Box
|
>
|
||||||
sx={{
|
{/* Particles Container */}
|
||||||
marginTop: 8,
|
<div style={{ width: "0%", height: "100vh" }}>
|
||||||
display: "flex",
|
<Particles
|
||||||
flexDirection: "column",
|
id="particles"
|
||||||
alignItems: "center",
|
init={particlesInit}
|
||||||
}}>
|
loaded={particlesLoaded}
|
||||||
<Avatar sx={{ m: 1, bgcolor: "secondary.main" }}>
|
className="-z-10"
|
||||||
<LockOutlinedIcon />
|
options={{
|
||||||
</Avatar>
|
fpsLimit: 240,
|
||||||
<Typography component="h1" variant="h5">
|
interactivity: {
|
||||||
Sign up
|
events: {
|
||||||
</Typography>
|
onClick: {
|
||||||
<Box component="form" noValidate onSubmit={handleSubmit} sx={{ mt: 3 }}>
|
enable: true,
|
||||||
<Grid container spacing={2}>
|
mode: "push",
|
||||||
<Grid item xs={12}>
|
},
|
||||||
<TextField
|
onHover: {
|
||||||
required
|
enable: true,
|
||||||
fullWidth
|
mode: "repulse",
|
||||||
id="email"
|
},
|
||||||
label="Email Address"
|
resize: true,
|
||||||
name="email"
|
},
|
||||||
autoComplete="email"
|
modes: {
|
||||||
onChange={handleChange}
|
push: {
|
||||||
/>
|
quantity: 4,
|
||||||
</Grid>
|
},
|
||||||
<Grid item xs={12}>
|
repulse: {
|
||||||
<TextField
|
distance: 200,
|
||||||
autoComplete="username"
|
duration: 0.4,
|
||||||
name="Username"
|
},
|
||||||
required
|
},
|
||||||
fullWidth
|
},
|
||||||
id="Username"
|
particles: {
|
||||||
label="Username"
|
color: {
|
||||||
autoFocus
|
value: "#023020",
|
||||||
onChange={handleChange}
|
},
|
||||||
/>
|
links: {
|
||||||
</Grid>
|
color: "#228B22",
|
||||||
<Grid item xs={12}>
|
distance: 150,
|
||||||
<TextField
|
enable: true,
|
||||||
required
|
opacity: 0.5,
|
||||||
fullWidth
|
width: 1,
|
||||||
name="password"
|
},
|
||||||
label="Password"
|
move: {
|
||||||
type="password"
|
direction: "none",
|
||||||
id="password"
|
enable: true,
|
||||||
autoComplete="new-password"
|
outModes: {
|
||||||
onChange={handleChange}
|
default: "bounce",
|
||||||
/>
|
},
|
||||||
</Grid>
|
random: false,
|
||||||
<Grid item xs={12}>
|
speed: 4,
|
||||||
<FormControlLabel
|
straight: false,
|
||||||
control={<Checkbox value="allowExtraEmails" color="primary" />}
|
},
|
||||||
label="I want to receive inspiration, marketing promotions and updates via email."
|
number: {
|
||||||
/>
|
density: {
|
||||||
</Grid>
|
enable: true,
|
||||||
</Grid>
|
area: 800,
|
||||||
<Button type="submit" fullWidth variant="contained" sx={{ mt: 3, mb: 2 }}>
|
},
|
||||||
Sign Up
|
value: 50,
|
||||||
</Button>
|
},
|
||||||
<Grid container justifyContent="flex-end">
|
opacity: {
|
||||||
<Grid item>
|
value: 0.5,
|
||||||
<Link href="#" variant="body2">
|
},
|
||||||
Already have an account? Sign in
|
shape: {
|
||||||
</Link>
|
type: "circle",
|
||||||
</Grid>
|
},
|
||||||
</Grid>
|
size: {
|
||||||
</Box>
|
value: { min: 6, max: 8 },
|
||||||
</Box>
|
},
|
||||||
<Copyright sx={{ mt: 5 }} />
|
},
|
||||||
</Container>
|
detectRetina: true,
|
||||||
</ThemeProvider>
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-1/4 h-1 flex items-center justify-center">
|
||||||
|
<div className="w-96 bg-neutral rounded-lg p-8 shadow-md space-y-4 z-10">
|
||||||
|
{/* Register Form */}
|
||||||
|
<h2 className="text-3xl font-bold text-center">Signup</h2>
|
||||||
|
{/* Email Input */}
|
||||||
|
<div className="form-control ">
|
||||||
|
<label className="label" htmlFor="email">
|
||||||
|
<p className="text-bold">
|
||||||
|
Email<span className="text-red-500 text-bold">*</span>
|
||||||
|
</p>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="input"
|
||||||
|
type="email"
|
||||||
|
id="email"
|
||||||
|
placeholder="Enter your email"
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* Username Input */}
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label" htmlFor="Username">
|
||||||
|
<p className="text-bold">
|
||||||
|
Username<span className="text-red-500 text-bold">*</span>
|
||||||
|
</p>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="input"
|
||||||
|
type="text"
|
||||||
|
id="Username"
|
||||||
|
placeholder="Enter your username"
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* Password Input */}
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label" htmlFor="password">
|
||||||
|
<p className="text-bold">
|
||||||
|
Password<span className="text-red-500 text-bold">*</span>
|
||||||
|
</p>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="input"
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
placeholder="Enter your password"
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<br></br>
|
||||||
|
|
||||||
|
{/* Login Button */}
|
||||||
|
<button className="btn btn-success w-full " onClick={handleSubmit}>
|
||||||
|
Signup
|
||||||
|
</button>
|
||||||
|
<div className="divider">OR</div>
|
||||||
|
{/* Login with Google Button */}
|
||||||
|
<button
|
||||||
|
className="btn btn-outline btn-secondary w-full "
|
||||||
|
onClick={() => googleLoginImplicit()}
|
||||||
|
>
|
||||||
|
<FcGoogle className="rounded-full bg-white"/>Login with Google
|
||||||
|
</button>
|
||||||
|
{/* Already have an account? */}
|
||||||
|
<div className="text-blue-500 flex justify-center text-sm">
|
||||||
|
<a href="login">
|
||||||
|
Already have an account?
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<Copyright />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,7 +39,7 @@ function NavBar() {
|
|||||||
</label>
|
</label>
|
||||||
<ul
|
<ul
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className="mt-3 z-[1] p-2 shadow menu menu-sm dropdown-content bg-base-100 rounded-box w-52">
|
className="mt-3 z-[10] p-2 shadow menu menu-sm dropdown-content bg-base-100 rounded-box w-52">
|
||||||
<li>
|
<li>
|
||||||
<a href={settings.Profile} className="justify-between">
|
<a href={settings.Profile} className="justify-between">
|
||||||
Profile
|
Profile
|
||||||
|
|||||||
39
frontend/src/components/signup/Signup.jsx
Normal file
39
frontend/src/components/signup/Signup.jsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { faGoogle, faGithub } from '@fortawesome/free-brands-svg-icons';
|
||||||
|
|
||||||
|
function Signup() {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center h-screen">
|
||||||
|
<div className="flex flex-col items-center bg-white p-10 rounded-lg shadow-md">
|
||||||
|
<h1 className="text-4xl font-semibold mb-6">Create your account</h1>
|
||||||
|
<p className="text-gray-700 mb-6 text-lg">
|
||||||
|
Start spending more time on your own table.
|
||||||
|
</p>
|
||||||
|
<div className='font-bold'>
|
||||||
|
<div className="mb-4">
|
||||||
|
<button className="flex items-center justify-center bg-blue-500 text-white px-14 py-3 rounded-lg">
|
||||||
|
<span className="mr-3"><FontAwesomeIcon icon={faGoogle} /></span>
|
||||||
|
Sign Up with Google
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<button className="flex items-center justify-center bg-gray-800 text-white px-14 py-3 rounded-lg">
|
||||||
|
<span className="mr-3"><FontAwesomeIcon icon={faGithub} /></span>
|
||||||
|
Sign Up with Github
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button className="bg-green-500 text-white px-14 py-3 rounded-lg">
|
||||||
|
Sign Up with your email.
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Signup;
|
||||||
Loading…
Reference in New Issue
Block a user