Skip to content

Commit 24cb84b

Browse files
committed
feat: enhance AuthModal with exit animations and state management
- Introduced state management for modal visibility and exit animations in AuthGate. - Updated AuthModal to accept new props for exit animation handling. - Refactored Modal component to apply animations via inline styles, improving flexibility. - Added new keyframes for fade-out and zoom-out animations to enhance user experience.
1 parent 476d46d commit 24cb84b

File tree

4 files changed

+114
-9
lines changed

4 files changed

+114
-9
lines changed

src/frontend/src/AuthGate.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,37 @@ export default function AuthGate({ children }: { children: React.ReactNode }) {
5050
// eslint-disable-next-line react-hooks/exhaustive-deps
5151
}, [isAuthenticated, coderAuthDone]);
5252

53+
// State to control modal visibility and exit animation
54+
const [showAuthModal, setShowAuthModal] = useState(false);
55+
const [isExiting, setIsExiting] = useState(false);
56+
57+
// Update showAuthModal when authentication status changes
58+
useEffect(() => {
59+
if (isAuthenticated === false) {
60+
setShowAuthModal(true);
61+
setIsExiting(false);
62+
} else if (isAuthenticated === true && showAuthModal) {
63+
// Start exit animation when user becomes authenticated
64+
setIsExiting(true);
65+
// Modal will be removed after animation completes via onExitComplete
66+
}
67+
}, [isAuthenticated, showAuthModal]);
68+
69+
// Handle exit animation completion
70+
const handleExitComplete = () => {
71+
setShowAuthModal(false);
72+
};
73+
5374
// Always render children; overlay AuthModal if not authenticated
5475
return (
5576
<>
5677
{children}
57-
{isAuthenticated === false && <AuthModal />}
78+
{showAuthModal && (
79+
<AuthModal
80+
isExiting={isExiting}
81+
onExitComplete={handleExitComplete}
82+
/>
83+
)}
5884
</>
5985
);
6086
}

src/frontend/src/styles/Modal.scss

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
display: flex;
88
align-items: center;
99
justify-content: center;
10-
animation: modalFadeIn 0.3s ease-out forwards;
10+
/* Animation is now applied via inline style */
1111
}
1212

1313
&__wrapper {
@@ -33,7 +33,7 @@
3333
border-radius: 12px;
3434
background-color: #232329;
3535
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3), 0 8px 10px -6px rgba(0, 0, 0, 0.2);
36-
animation: modalZoomIn 0.3s ease-out forwards;
36+
/* Animation is now applied via inline style */
3737
font-family: 'Roboto', sans-serif;
3838
}
3939

@@ -46,9 +46,8 @@
4646
z-index: 0; /* Behind modal */
4747
pointer-events: none;
4848
user-select: none;
49-
opacity: 0;
50-
animation: fadeInSlideUp 0.3s ease-out forwards;
51-
animation-delay: 1s; /* Delay appearance by 1 second */
49+
opacity: 0; /* Initial state, animation will control visibility */
50+
/* Animation is now applied via inline style */
5251
}
5352
}
5453

@@ -64,6 +63,18 @@
6463
}
6564
}
6665

66+
/* Animation for favicon to slide down and fade out */
67+
@keyframes fadeOutSlideDown {
68+
from {
69+
opacity: 1;
70+
transform: translateY(0);
71+
}
72+
to {
73+
opacity: 0;
74+
transform: translateY(5px);
75+
}
76+
}
77+
6778
/* Animation keyframes */
6879
@keyframes modalFadeIn {
6980
from {
@@ -74,6 +85,15 @@
7485
}
7586
}
7687

88+
@keyframes modalFadeOut {
89+
from {
90+
opacity: 1;
91+
}
92+
to {
93+
opacity: 0;
94+
}
95+
}
96+
7797
@keyframes modalZoomIn {
7898
from {
7999
transform: scale(0.95);
@@ -85,6 +105,17 @@
85105
}
86106
}
87107

108+
@keyframes modalZoomOut {
109+
from {
110+
transform: scale(1);
111+
opacity: 1;
112+
}
113+
to {
114+
transform: scale(0.95);
115+
opacity: 0;
116+
}
117+
}
118+
88119
/* Media query for screens under 700x700 */
89120
@media (max-width: 734px), (max-height: 700px) {
90121
.modal {

src/frontend/src/ui/AuthModal.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@ import "../styles/AuthModal.scss";
88
interface AuthModalProps {
99
description?: React.ReactNode;
1010
warningText?: string;
11+
onExitComplete?: () => void;
12+
isExiting?: boolean;
1113
}
1214

1315
const AuthModal: React.FC<AuthModalProps> = ({
1416
description = <>Welcome to your <strong className="highlight">whiteboard IDE</strong>. Open <strong className="highlight">terminals</strong> and start coding right away in your own <strong className="highlight">Ubuntu VM</strong>!</>,
1517
warningText = "🚧 This is a beta. Make backups! 🚧",
18+
onExitComplete,
19+
isExiting = false,
1620
}) => {
1721
const [isMounted, setIsMounted] = useState(false);
1822

@@ -45,6 +49,8 @@ const AuthModal: React.FC<AuthModalProps> = ({
4549
logoSrc="/assets/images/favicon.png"
4650
logoAlt="pad.ws logo"
4751
className="auth-modal"
52+
isExiting={isExiting}
53+
onExitComplete={onExitComplete}
4854
>
4955
<div className="auth-modal__content">
5056
<div id="modal-title" className="auth-modal__title-container">

src/frontend/src/ui/Modal.tsx

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from "react";
1+
import React, { useEffect } from "react";
22
import ReactDOM from "react-dom";
33
import "../styles/Modal.scss";
44

@@ -9,6 +9,8 @@ interface ModalProps {
99
logoAlt?: string;
1010
maxWidth?: string | number;
1111
className?: string;
12+
isExiting?: boolean;
13+
onExitComplete?: () => void;
1214
}
1315

1416
const Modal: React.FC<ModalProps> = ({
@@ -18,9 +20,41 @@ const Modal: React.FC<ModalProps> = ({
1820
logoAlt = "Logo",
1921
maxWidth = "500px",
2022
className = "",
23+
isExiting = false,
24+
onExitComplete,
2125
}) => {
26+
// For entrance: Modal appears first, then logo with delay
27+
// For exit: Logo disappears first, then modal with delay
28+
const overlayAnimation = isExiting ? "modalFadeOut" : "modalFadeIn";
29+
const containerAnimation = isExiting ? "modalZoomOut" : "modalZoomIn";
30+
const faviconAnimation = isExiting ? "fadeOutSlideDown" : "fadeInSlideUp";
31+
32+
// Animation delays
33+
const overlayDelay = isExiting ? "0.3s" : "0s"; // Delay modal exit until logo animation completes
34+
const containerDelay = isExiting ? "0.3s" : "0s"; // Delay container exit until logo animation completes
35+
const faviconDelay = isExiting ? "0s" : "0.3s"; // Logo appears with delay on entrance, but exits immediately
36+
37+
// Handle exit animation completion
38+
useEffect(() => {
39+
if (isExiting && onExitComplete) {
40+
// Wait for all animations to complete before calling onExitComplete
41+
// Logo animation (0.3s) + delay (0.3s) + modal animation (0.3s)
42+
const timer = setTimeout(() => {
43+
onExitComplete();
44+
}, 600); // Total animation duration with delay
45+
46+
return () => clearTimeout(timer);
47+
}
48+
}, [isExiting, onExitComplete]);
49+
2250
const modalContent = (
23-
<div className="modal__overlay">
51+
<div
52+
className="modal__overlay"
53+
style={{
54+
animation: `${overlayAnimation} 0.3s ease-out forwards`,
55+
animationDelay: overlayDelay
56+
}}
57+
>
2458
{/* Backdrop with blur effect */}
2559
<div className="modal__backdrop" aria-hidden="true" />
2660

@@ -33,12 +67,20 @@ const Modal: React.FC<ModalProps> = ({
3367
className="modal__favicon"
3468
alt={logoAlt}
3569
aria-hidden="true"
70+
style={{
71+
animation: `${faviconAnimation} 0.3s ease-out forwards`,
72+
animationDelay: faviconDelay
73+
}}
3674
/>
3775
)}
3876
{/* Modal container with animation */}
3977
<div
4078
className={`modal__container ${className}`}
41-
style={{ maxWidth }}
79+
style={{
80+
maxWidth,
81+
animation: `${containerAnimation} 0.3s ease-out forwards`,
82+
animationDelay: containerDelay
83+
}}
4284
role="dialog"
4385
aria-modal="true"
4486
>

0 commit comments

Comments
 (0)