feat: Enhance Signup and Chores functionality

- Added query invalidation on signup to refresh user profile data.
- Improved MyChores component to ensure data is loaded before rendering.
- Introduced dynamic sidepanel configuration with drag-and-drop functionality.
- Created TasksByAssigneeCard to visualize tasks assigned to users.
- Updated MFASettings and Settings components for better structure and usability.
- Implemented SettingsOverview for a comprehensive settings navigation experience.
- Added SidepanelSettings for customizing sidepanel card visibility and order.
- Refactored NavBar to include user profile avatar and improved layout.
This commit is contained in:
Mo Tarbin
2025-08-29 00:50:54 -04:00
parent 6b5a874db5
commit cf4d0565c5
19 changed files with 2077 additions and 188 deletions

View File

@@ -1,5 +1,5 @@
// create boilerplate for ResetPasswordView:
import LogoSVG from '@/assets/logo.svg'
import Logo from '../../Logo'
import {
Box,
Button,
@@ -80,16 +80,12 @@ const ForgotPasswordView = () => {
<Container
component='main'
maxWidth='xs'
// make content center in the middle of the page:
>
<Box
sx={{
marginTop: 4,
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
@@ -104,102 +100,83 @@ const ForgotPasswordView = () => {
padding: 2,
borderRadius: '8px',
boxShadow: 'md',
minHeight: '70vh',
justifyContent: 'space-between',
justifyItems: 'center',
}}
>
<Box>
<img src={LogoSVG} alt='logo' width='128px' height='128px' />
{/* <Logo /> */}
<Typography level='h2'>
Done
<span
style={{
color: '#06b6d4',
}}
>
tick
</span>
</Typography>
</Box>
{/* HERE */}
<Box sx={{ textAlign: 'center' }}></Box>
<Logo />
<Typography level='h2'>
Done
<span style={{ color: '#06b6d4' }}>tick</span>
</Typography>
{resetStatusOk === null && (
<form onSubmit={handleSubmit}>
<div className='grid gap-6'>
<Typography level='body2' gutterBottom>
Enter your email, and we'll send you a link to get into your
account.
</Typography>
<FormControl error={emailError !== null}>
<Input
placeholder='Email'
type='email'
variant='soft'
fullWidth
size='lg'
value={email}
onChange={handleEmailChange}
error={emailError !== null}
onKeyDown={e => {
if (e.key === 'Enter') {
e.preventDefault()
handleSubmit()
}
}}
/>
<FormHelperText>{emailError}</FormHelperText>
</FormControl>
<Box>
<Button
variant='solid'
size='lg'
fullWidth
sx={{
mb: 1,
}}
onClick={handleSubmit}
>
Reset Password
</Button>
<Button
fullWidth
size='lg'
variant='soft'
sx={{
width: '100%',
border: 'moccasin',
borderRadius: '8px',
}}
onClick={() => {
navigate('/login')
}}
color='neutral'
>
Back to Login
</Button>
</Box>
</div>
</form>
)}
{resetStatusOk != null && (
<>
<Box mt={-30}>
<Typography level='body-md'>
if there is an account associated with the email you entered,
you will receive an email with instructions on how to reset
your
</Typography>
</Box>
<Typography level='body2' sx={{ textAlign: 'center', mt: 2, mb: 3 }}>
Enter your email, and we'll send you a link to get into your
account.
</Typography>
<FormControl error={emailError !== null} sx={{ width: '100%', mb: 2 }}>
<Input
placeholder='Email'
type='email'
variant='soft'
fullWidth
size='lg'
value={email}
onChange={handleEmailChange}
error={emailError !== null}
onKeyDown={e => {
if (e.key === 'Enter') {
e.preventDefault()
handleSubmit()
}
}}
/>
<FormHelperText>{emailError}</FormHelperText>
</FormControl>
<Button
variant='soft'
variant='solid'
size='lg'
sx={{ position: 'relative', bottom: '0' }}
fullWidth
sx={{ mb: 2 }}
onClick={handleSubmit}
>
Reset Password
</Button>
<Button
fullWidth
size='lg'
variant='plain'
sx={{
width: '100%',
border: 'moccasin',
borderRadius: '8px',
}}
onClick={() => {
navigate('/login')
}}
color='neutral'
>
Back to Login
</Button>
</>
)}
{resetStatusOk != null && (
<>
<Typography level='body-md' sx={{ textAlign: 'center', mt: 2, mb: 3 }}>
If there is an account associated with the email you entered,
you will receive an email with instructions on how to reset
your password.
</Typography>
<Button
variant='solid'
size='lg'
fullWidth
onClick={() => {
navigate('/login')
}}
>
Go to Login
</Button>

View File

@@ -1,3 +1,4 @@
import { Browser } from '@capacitor/browser'
import { Capacitor } from '@capacitor/core'
import { Device } from '@capacitor/device'
// import { GoogleAuth } from '@codetrix-studio/capacitor-google-auth'
@@ -251,19 +252,52 @@ const LoginView = () => {
return randomState
}
const handleAuthentikLogin = () => {
const handleAuthentikLogin = async () => {
const authentikAuthorizeUrl = resource?.identity_provider?.auth_url
const state = generateRandomState()
const params = new URLSearchParams({
response_type: 'code',
client_id: resource?.identity_provider?.client_id,
redirect_uri: `${window.location.origin}/auth/oauth2`,
scope: 'openid profile email', // Your scopes
state: generateRandomState(),
})
console.log('redirect', `${authentikAuthorizeUrl}?${params.toString()}`)
if (Capacitor.isNativePlatform()) {
// For mobile devices, use a custom URL scheme for the redirect
const redirectUri = 'donetick://auth/oauth2'
const params = new URLSearchParams({
response_type: 'code',
client_id: resource?.identity_provider?.client_id,
redirect_uri: redirectUri,
scope: 'openid profile email',
state: state,
})
window.location.href = `${authentikAuthorizeUrl}?${params.toString()}`
const authUrl = `${authentikAuthorizeUrl}?${params.toString()}`
console.log('Opening OAuth in browser:', authUrl)
try {
// Open OAuth flow in system browser
await Browser.open({ url: authUrl })
// Note: The OAuth callback will be handled by deep link handling
// You'll need to implement deep link handling to catch the redirect
// and extract the authorization code
} catch (error) {
console.error('Failed to open OAuth browser:', error)
showError({
title: 'OAuth Error',
message: 'Failed to open authentication browser',
})
}
} else {
// For web platforms, use the current approach
const params = new URLSearchParams({
response_type: 'code',
client_id: resource?.identity_provider?.client_id,
redirect_uri: `${window.location.origin}/auth/oauth2`,
scope: 'openid profile email',
state: state,
})
console.log('redirect', `${authentikAuthorizeUrl}?${params.toString()}`)
window.location.href = `${authentikAuthorizeUrl}?${params.toString()}`
}
}
return (

View File

@@ -11,6 +11,7 @@ import {
} from '@mui/joy'
import React from 'react'
import { useNavigate } from 'react-router-dom'
import { useQueryClient } from '@tanstack/react-query'
import Logo from '../../Logo'
import { useNotification } from '../../service/NotificationProvider'
import { login, signUp } from '../../utils/Fetcher'
@@ -19,6 +20,7 @@ const SignupView = () => {
const [username, setUsername] = React.useState('')
const [password, setPassword] = React.useState('')
const Navigate = useNavigate()
const queryClient = useQueryClient()
const [displayName, setDisplayName] = React.useState('')
const [email, setEmail] = React.useState('')
const [usernameError, setUsernameError] = React.useState('')
@@ -32,11 +34,11 @@ const SignupView = () => {
response.json().then(res => {
localStorage.setItem('ca_token', res.token)
localStorage.setItem('ca_expiration', res.expire)
setTimeout(() => {
// TODO: not sure if there is a race condition here
// but on first sign up it renavigates to login.
Navigate('/chores')
}, 500)
// Invalidate user profile queries to ensure fresh data
queryClient.invalidateQueries(['userProfile'])
Navigate('/chores')
})
} else {
console.log('Login failed', response)