-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add error handling and loading animation
- Loading branch information
Showing
3 changed files
with
352 additions
and
78 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,15 @@ | ||
import { ReactElement, useState } from "react"; | ||
import { Autocomplete, Box, Button, TextField, IconButton, InputAdornment, Typography } from "@mui/material"; | ||
import { | ||
Autocomplete, | ||
Box, | ||
Button, | ||
TextField, | ||
IconButton, | ||
InputAdornment, | ||
Typography, | ||
Alert, | ||
CircularProgress, | ||
} from "@mui/material"; | ||
import Navbar from "../../components/Navbar/Navbar"; | ||
import Footer from "../../components/Footer/Footer"; | ||
import Visibility from "@mui/icons-material/Visibility"; | ||
|
@@ -9,9 +19,18 @@ import "./AccountSettings.scss"; | |
const AccountSettings = (): ReactElement => { | ||
const [showPassword, setShowPassword] = useState(false); | ||
const [showEmail, setShowEmail] = useState(false); | ||
const [loading, setLoading] = useState(false); | ||
const [error, setError] = useState<string | null>(null); | ||
const [confirmationMessage, setConfirmationMessage] = useState<string | null>(null); | ||
const [displayedName, setDisplayedName] = useState("Echomo"); | ||
const [profilePhotoUrl, setProfilePhotoUrl] = useState(""); | ||
const [preferredLanguage, setPreferredLanguage] = useState(""); | ||
const [email, setEmail] = useState("[email protected]"); | ||
const [password, setPassword] = useState("password"); | ||
|
||
const handleClickShowPassword = () => setShowPassword(!showPassword); | ||
const handleClickShowEmail = () => setShowEmail(!showEmail); | ||
|
||
const programmingLanguages = [ | ||
"JavaScript", | ||
"Python2", | ||
|
@@ -36,34 +55,69 @@ const AccountSettings = (): ReactElement => { | |
"MATLAB", | ||
]; | ||
|
||
const handleSaveChanges = async () => { | ||
setLoading(true); | ||
setError(null); | ||
setConfirmationMessage(null); | ||
|
||
try { | ||
if (!displayedName || !email || !password) { | ||
throw new Error("Displayed name, email, and password are required."); | ||
} | ||
// Simulate API call | ||
await new Promise((resolve, reject) => { | ||
setTimeout(() => { | ||
if (email === "[email protected]") { | ||
reject(new Error("Email already in use.")); | ||
} else { | ||
resolve("Account details updated successfully!"); | ||
} | ||
}, 3000); | ||
}); | ||
|
||
setConfirmationMessage("Account details saved successfully!"); | ||
} catch (err: any) { | ||
setError(err.message); | ||
} finally { | ||
setLoading(false); | ||
} | ||
}; | ||
|
||
return ( | ||
<Box className="AccountSettings"> | ||
<Navbar /> | ||
<Box className="AccountSettings-container"> | ||
<Typography variant="body2" className="Home-welcome-title"> | ||
Login | ||
Account Settings | ||
</Typography> | ||
<Typography className="AccountSettings-subtitle" sx={{ mb: 3 }}> | ||
Customize your information and experience here!{" "} | ||
Customize your information and experience here! | ||
</Typography> | ||
|
||
<form className="AccountSettings-form"> | ||
<form | ||
className="AccountSettings-form" | ||
onSubmit={(e) => { | ||
e.preventDefault(); | ||
handleSaveChanges(); | ||
}} | ||
> | ||
<Box className="AccountSettings-row"> | ||
<Typography variant="body1">Displayed Name (to others)</Typography> | ||
<TextField | ||
variant="outlined" | ||
placeholder="Enter your name" | ||
className="AccountSettings-input" | ||
fullWidth | ||
defaultValue="Echomo" | ||
value={displayedName} | ||
onChange={(e) => setDisplayedName(e.target.value)} | ||
/> | ||
</Box> | ||
|
||
<Box className="AccountSettings-row"> | ||
<Typography variant="body1">Profile Photo</Typography> | ||
<Box className="AccountSettings-photo-section"> | ||
<img | ||
src="https://randomuser.me/api/portraits/men/75.jpg" | ||
src={profilePhotoUrl || "https://randomuser.me/api/portraits/men/75.jpg"} | ||
alt="Profile" | ||
className="AccountSettings-profile-photo" | ||
/> | ||
|
@@ -72,6 +126,8 @@ const AccountSettings = (): ReactElement => { | |
placeholder="Enter new URL to change" | ||
className="AccountSettings-url-input" | ||
fullWidth | ||
value={profilePhotoUrl} | ||
onChange={(e) => setProfilePhotoUrl(e.target.value)} | ||
/> | ||
</Box> | ||
</Box> | ||
|
@@ -88,6 +144,8 @@ const AccountSettings = (): ReactElement => { | |
placeholder="Enter your preferred programming language" | ||
className="AccountSettings-input" | ||
fullWidth | ||
value={preferredLanguage} | ||
onChange={(e) => setPreferredLanguage(e.target.value)} | ||
/> | ||
)} | ||
filterOptions={(options, { inputValue }) => | ||
|
@@ -103,7 +161,8 @@ const AccountSettings = (): ReactElement => { | |
variant="outlined" | ||
className="AccountSettings-input" | ||
fullWidth | ||
defaultValue="[email protected]" | ||
value={email} | ||
onChange={(e) => setEmail(e.target.value)} | ||
type={showEmail ? "text" : "email"} | ||
/> | ||
</Box> | ||
|
@@ -114,8 +173,9 @@ const AccountSettings = (): ReactElement => { | |
variant="outlined" | ||
className="AccountSettings-input" | ||
fullWidth | ||
value={password} | ||
onChange={(e) => setPassword(e.target.value)} | ||
type={showPassword ? "text" : "password"} | ||
defaultValue="password" | ||
InputProps={{ | ||
endAdornment: ( | ||
<InputAdornment position="end"> | ||
|
@@ -128,8 +188,26 @@ const AccountSettings = (): ReactElement => { | |
/> | ||
</Box> | ||
|
||
<Button color="primary" variant="contained" className="AccountSettings-save-button"> | ||
Save Changes | ||
{error && ( | ||
<Alert severity="error" sx={{ mb: 2 }}> | ||
{error} | ||
</Alert> | ||
)} | ||
|
||
{confirmationMessage && ( | ||
<Alert severity="success" sx={{ mb: 2 }}> | ||
{confirmationMessage} | ||
</Alert> | ||
)} | ||
|
||
<Button | ||
color="primary" | ||
variant="contained" | ||
className="AccountSettings-save-button" | ||
onClick={handleSaveChanges} | ||
disabled={loading} | ||
> | ||
{loading ? <CircularProgress size={24} /> : "Save Changes"} | ||
</Button> | ||
</form> | ||
</Box> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
import { ReactElement, useState } from "react"; | ||
import { Box, Button, Typography, TextField, IconButton, InputAdornment } from "@mui/material"; | ||
import { useNavigate } from "react-router-dom"; | ||
import { Box, Button, Typography, TextField, IconButton, InputAdornment, Alert, CircularProgress } from "@mui/material"; | ||
import Navbar from "../../components/Navbar/Navbar"; | ||
import Footer from "../../components/Footer/Footer"; | ||
import Visibility from "@mui/icons-material/Visibility"; | ||
|
@@ -8,11 +9,62 @@ import "./Login.scss"; | |
|
||
const Login = (): ReactElement => { | ||
const [showPassword, setShowPassword] = useState(false); | ||
const [email, setEmail] = useState(""); | ||
const [password, setPassword] = useState(""); | ||
const [errors, setErrors] = useState<{ email?: string; password?: string }>({}); | ||
const [loginError, setLoginError] = useState<string | null>(null); | ||
const [loading, setLoading] = useState(false); | ||
const navigate = useNavigate(); | ||
|
||
const togglePasswordVisibility = () => { | ||
setShowPassword((prev) => !prev); | ||
}; | ||
|
||
const validateEmail = (email: string) => { | ||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; | ||
return emailRegex.test(email); | ||
}; | ||
|
||
const handleLogin = () => { | ||
setErrors({}); | ||
setLoginError(null); | ||
|
||
let formValid = true; | ||
const newErrors: { email?: string; password?: string } = {}; | ||
|
||
if (!email) { | ||
formValid = false; | ||
newErrors.email = "Email is required."; | ||
} else if (!validateEmail(email)) { | ||
formValid = false; | ||
newErrors.email = "Please enter a valid email."; | ||
} | ||
|
||
if (!password) { | ||
formValid = false; | ||
newErrors.password = "Password is required."; | ||
} | ||
|
||
if (!formValid) { | ||
setErrors(newErrors); | ||
return; | ||
} | ||
|
||
// Simulate login process | ||
try { | ||
if (email === "[email protected]" && password === "password") { | ||
setLoading(true); | ||
setTimeout(() => { | ||
navigate("/"); | ||
}, 3000); | ||
} else { | ||
setLoginError("Invalid email or password."); | ||
} | ||
} catch (error) { | ||
setLoginError("An unexpected error occurred. Please try again."); | ||
} | ||
}; | ||
|
||
return ( | ||
<Box className="Login"> | ||
<Navbar /> | ||
|
@@ -26,42 +78,66 @@ const Login = (): ReactElement => { | |
</Typography> | ||
|
||
<Box className="Login-form" sx={{ display: "flex", flexDirection: "column", alignItems: "center" }}> | ||
<Box sx={{ display: "flex", width: "100%", justifyContent: "space-between" }}> | ||
<TextField label="Enter your Email" variant="outlined" fullWidth sx={{ mr: 1 }} /> | ||
<TextField | ||
label="Enter your Password" | ||
type={showPassword ? "text" : "password"} | ||
variant="outlined" | ||
fullWidth | ||
InputProps={{ | ||
endAdornment: ( | ||
<InputAdornment position="end"> | ||
<IconButton onClick={togglePasswordVisibility} className="password-eye-icon" edge="end"> | ||
{showPassword ? <VisibilityOff /> : <Visibility />} | ||
</IconButton> | ||
</InputAdornment> | ||
), | ||
}} | ||
/> | ||
</Box> | ||
<Typography | ||
component="a" | ||
href="/forgot-password" | ||
className="forgot-password" | ||
sx={{ textDecoration: "underline", textAlign: "center" }} | ||
> | ||
Forgot password? | ||
</Typography> | ||
|
||
<Button color="primary" variant="contained" className="LoginUp-button"> | ||
Login | ||
</Button> | ||
|
||
<Typography className="Login-signup"> | ||
Not registered yet? <a href="/signup">Sign Up</a> | ||
</Typography> | ||
|
||
{/* <Typography className="SignUp-or">Or Continue with</Typography> */} | ||
{loading ? ( | ||
<CircularProgress sx={{ mb: 3 }} /> | ||
) : ( | ||
<> | ||
<Box sx={{ display: "flex", width: "100%", justifyContent: "space-between" }}> | ||
<TextField | ||
label="Enter your Email" | ||
variant="outlined" | ||
fullWidth | ||
sx={{ mr: 1 }} | ||
value={email} | ||
onChange={(e) => setEmail(e.target.value)} | ||
error={!!errors.email} | ||
helperText={errors.email} | ||
/> | ||
<TextField | ||
label="Enter your Password" | ||
type={showPassword ? "text" : "password"} | ||
variant="outlined" | ||
fullWidth | ||
value={password} | ||
onChange={(e) => setPassword(e.target.value)} | ||
error={!!errors.password} | ||
helperText={errors.password} | ||
InputProps={{ | ||
endAdornment: ( | ||
<InputAdornment position="end"> | ||
<IconButton onClick={togglePasswordVisibility} className="password-eye-icon" edge="end"> | ||
{showPassword ? <VisibilityOff /> : <Visibility />} | ||
</IconButton> | ||
</InputAdornment> | ||
), | ||
}} | ||
/> | ||
</Box> | ||
|
||
<Typography | ||
component="a" | ||
href="/forgot-password" | ||
className="forgot-password" | ||
sx={{ textDecoration: "underline", textAlign: "center" }} | ||
> | ||
Forgot password? | ||
</Typography> | ||
|
||
{loginError && ( | ||
<Alert severity="error" sx={{ mt: 2 }}> | ||
{loginError} | ||
</Alert> | ||
)} | ||
|
||
<Button color="primary" variant="contained" className="LoginUp-button" onClick={handleLogin}> | ||
Login | ||
</Button> | ||
|
||
<Typography className="Login-signup"> | ||
Not registered yet? <a href="/signup">Sign Up</a> | ||
</Typography> | ||
</> | ||
)} | ||
</Box> | ||
</Box> | ||
|
||
|
Oops, something went wrong.