JoinCircleView: enhance UI/UX with improved messaging, animations, and layout adjustments

This commit is contained in:
Mo Tarbin
2026-08-08 16:01:10 -04:00
parent 1ddf2539e0
commit e05aa2d169

View File

@@ -7,9 +7,20 @@ import { useUserProfile } from '../../queries/UserQueries'
import { useNotification } from '../../service/NotificationProvider' import { useNotification } from '../../service/NotificationProvider'
import { JoinCircle } from '../../utils/Fetcher' import { JoinCircle } from '../../utils/Fetcher'
import { clearPendingInvite, setPendingInvite } from '../../utils/PendingInvite' import { clearPendingInvite, setPendingInvite } from '../../utils/PendingInvite'
import AuthShell from '../Authorization/AuthShell'
import { authButtonSx } from '../Authorization/authStyles' import { authButtonSx } from '../Authorization/authStyles'
import AcknowledgmentModal from '../Modals/Inputs/AcknowledgmentModal' import AcknowledgmentModal from '../Modals/Inputs/AcknowledgmentModal'
import { CircleVignette } from '../Onboarding/OnboardingVignettes'
const EASE = 'cubic-bezier(0.22, 1, 0.36, 1)'
const enter = (delay = 0) => ({
animation: `joinCircleIn 520ms ${EASE} ${delay}ms both`,
'@keyframes joinCircleIn': {
from: { opacity: 0, transform: 'translateY(12px)' },
to: { opacity: 1, transform: 'none' },
},
'@media (prefers-reduced-motion: reduce)': { animation: 'none' },
})
const JoinCircleView = () => { const JoinCircleView = () => {
const { data: userProfile, isLoading: isProfileLoading } = useUserProfile() const { data: userProfile, isLoading: isProfileLoading } = useUserProfile()
@@ -38,8 +49,10 @@ const JoinCircleView = () => {
clearPendingInvite() clearPendingInvite()
if (resp.ok) { if (resp.ok) {
showAcknowledgment( showAcknowledgment(
'Your join request has been sent successfully! The circle admin will need to approve your request before you can access the circle and its chores. You will receive a notification once your request is approved.', 'Your request has been sent. A circle admin will need to approve ' +
'Join Request Sent!', "it before you can access the circle and its chores. We'll " +
"notify you when it's approved.",
'Request sent',
() => navigate('/chores'), () => navigate('/chores'),
'Got it', 'Got it',
'success', 'success',
@@ -57,7 +70,7 @@ const JoinCircleView = () => {
.catch(() => { .catch(() => {
setIsJoining(false) setIsJoining(false)
clearPendingInvite() clearPendingInvite()
showError('Failed to join circle, please try again') showError('Could not send your join request. Please try again.')
}) })
}, [code, navigate, showAcknowledgment, showError]) }, [code, navigate, showAcknowledgment, showError])
@@ -89,14 +102,14 @@ const JoinCircleView = () => {
/> />
) )
let title = "You've been invited to a circle" let title = "You're invited to join a circle"
let subtitle = null let subtitle = null
let body = null let body = null
if (!code) { if (!code) {
title = 'Invite link is incomplete' title = 'Invite link is incomplete'
subtitle = subtitle =
"This link doesn't include an invite code. Ask whoever invited you to send the link again." 'This invite link is missing a code. Ask the person who invited you to send a new link.'
body = ( body = (
<Button <Button
fullWidth fullWidth
@@ -111,7 +124,7 @@ const JoinCircleView = () => {
// better to offer sign-in than to spin forever. // better to offer sign-in than to spin forever.
} else if (!isAuthenticated || (!isProfileLoading && !userProfile)) { } else if (!isAuthenticated || (!isProfileLoading && !userProfile)) {
subtitle = subtitle =
'You need a Donetick account to join. Sign in or create one — well send your join request as soon as youre in.' "Sign in or create a Donetick account to continue. We'll send your join request once you're signed in."
body = ( body = (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
{inviteCodeField} {inviteCodeField}
@@ -136,7 +149,7 @@ const JoinCircleView = () => {
</Box> </Box>
) )
} else if (isProfileLoading || isJoining) { } else if (isProfileLoading || isJoining) {
title = 'Joining circle' title = 'Sending your request'
subtitle = 'Sending your request…' subtitle = 'Sending your request…'
body = ( body = (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 2 }}> <Box sx={{ display: 'flex', justifyContent: 'center', py: 2 }}>
@@ -144,19 +157,19 @@ const JoinCircleView = () => {
</Box> </Box>
) )
} else { } else {
subtitle = `Hi ${ subtitle =
userProfile?.displayName || userProfile?.username `Hi ${userProfile?.displayName || userProfile?.username}. ` +
}, joining gives you access to this circle's tasks and members.` "Send a request to share this circle's chores with its members."
body = ( body = (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
<Typography <Typography
level='body-sm' level='body-sm'
sx={{ textAlign: 'center', color: 'text.secondary' }} sx={{ textAlign: 'center', color: 'text.secondary' }}
> >
A circle admin approves your request before you get access. A circle admin will review your request before you get access.
</Typography> </Typography>
<Button fullWidth size='lg' sx={authButtonSx} onClick={submitJoin}> <Button fullWidth size='lg' sx={authButtonSx} onClick={submitJoin}>
Join circle Send join request
</Button> </Button>
<Button <Button
fullWidth fullWidth
@@ -176,10 +189,72 @@ const JoinCircleView = () => {
} }
return ( return (
<AuthShell title={title} subtitle={subtitle} showLogo> <Box
{body} component='main'
sx={{
minHeight: 'calc(100dvh - var(--safe-area-inset-top, 0px))',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
px: 3,
pb: 'calc(var(--safe-area-inset-bottom, 0px) + 24px)',
bgcolor: 'background.body',
}}
>
<Box
sx={{
width: '100%',
maxWidth: 420,
my: 'auto',
display: 'flex',
flexDirection: 'column',
}}
>
<Box sx={{ mb: 2, ...enter(0) }}>
<CircleVignette />
</Box>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
textAlign: 'center',
gap: 1.5,
mb: 4,
...enter(60),
}}
>
<Typography
level='h1'
sx={{
fontSize: '2rem',
fontWeight: 700,
letterSpacing: '-0.02em',
textWrap: 'balance',
}}
>
{title}
</Typography>
{subtitle && (
<Typography
level='body-md'
sx={{
color: 'text.secondary',
maxWidth: '34ch',
textWrap: 'pretty',
}}
>
{subtitle}
</Typography>
)}
</Box>
<Box sx={{ ...enter(120) }}>{body}</Box>
</Box>
<AcknowledgmentModal config={ackModalConfig} /> <AcknowledgmentModal config={ackModalConfig} />
</AuthShell> </Box>
) )
} }