Implement: User Authentication – Separate Login and Register Pages #12
@@ -0,0 +1,83 @@
|
|||||||
|
.container {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 2.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcomeText {
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signedInText {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emailStrong {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logoutButton {
|
||||||
|
padding: 0.5rem 1.25rem;
|
||||||
|
background-color: #b91c1c;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logoutButton:hover {
|
||||||
|
background-color: #991616;
|
||||||
|
}
|
||||||
|
|
||||||
|
.authLinks {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signInLink {
|
||||||
|
padding: 0.5rem 1.25rem;
|
||||||
|
background-color: #1a73e8;
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signInLink:hover {
|
||||||
|
background-color: #1557b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.registerLink {
|
||||||
|
padding: 0.5rem 1.25rem;
|
||||||
|
background-color: #fff;
|
||||||
|
color: #1a73e8;
|
||||||
|
text-decoration: none;
|
||||||
|
border: 1px solid #1a73e8;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.registerLink:hover {
|
||||||
|
background-color: #f0f5ff;
|
||||||
|
}
|
||||||
+14
-61
@@ -1,83 +1,36 @@
|
|||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { useAuth } from "../contexts/AuthContext";
|
import { useAuth } from "../contexts/AuthContext";
|
||||||
|
import styles from "./HomePage.module.css";
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
const { state, logout } = useAuth();
|
const { state, logout } = useAuth();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={styles.container}>
|
||||||
style={{
|
<div className={styles.card}>
|
||||||
minHeight: "100vh",
|
<h1 className={styles.heading}>Job Tracker</h1>
|
||||||
display: "flex",
|
<p className={styles.welcomeText}>
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
backgroundColor: "#f5f5f5",
|
|
||||||
fontFamily:
|
|
||||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
backgroundColor: "#fff",
|
|
||||||
padding: "2.5rem",
|
|
||||||
borderRadius: "8px",
|
|
||||||
boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
|
|
||||||
textAlign: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<h1 style={{ margin: "0 0 0.5rem" }}>Job Tracker</h1>
|
|
||||||
<p style={{ color: "#666", marginBottom: "1.5rem" }}>
|
|
||||||
Welcome to the Job Tracker application.
|
Welcome to the Job Tracker application.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{state.isAuthenticated && state.user ? (
|
{state.isAuthenticated && state.user ? (
|
||||||
<div>
|
<div>
|
||||||
<p style={{ marginBottom: "0.5rem" }}>
|
<p className={styles.signedInText}>
|
||||||
Signed in as <strong>{state.user.email}</strong>
|
Signed in as{" "}
|
||||||
|
<strong className={styles.emailStrong}>
|
||||||
|
{state.user.email}
|
||||||
|
</strong>
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button onClick={logout} className={styles.logoutButton}>
|
||||||
onClick={logout}
|
|
||||||
style={{
|
|
||||||
padding: "0.5rem 1.25rem",
|
|
||||||
backgroundColor: "#b91c1c",
|
|
||||||
color: "#fff",
|
|
||||||
border: "none",
|
|
||||||
borderRadius: "6px",
|
|
||||||
fontSize: "0.9rem",
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Sign Out
|
Sign Out
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ display: "flex", gap: "0.75rem", justifyContent: "center" }}>
|
<div className={styles.authLinks}>
|
||||||
<Link
|
<Link to="/login" className={styles.signInLink}>
|
||||||
to="/login"
|
|
||||||
style={{
|
|
||||||
padding: "0.5rem 1.25rem",
|
|
||||||
backgroundColor: "#1a73e8",
|
|
||||||
color: "#fff",
|
|
||||||
textDecoration: "none",
|
|
||||||
borderRadius: "6px",
|
|
||||||
fontSize: "0.9rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Sign In
|
Sign In
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link to="/register" className={styles.registerLink}>
|
||||||
to="/register"
|
|
||||||
style={{
|
|
||||||
padding: "0.5rem 1.25rem",
|
|
||||||
backgroundColor: "#fff",
|
|
||||||
color: "#1a73e8",
|
|
||||||
textDecoration: "none",
|
|
||||||
border: "1px solid #1a73e8",
|
|
||||||
borderRadius: "6px",
|
|
||||||
fontSize: "0.9rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Register
|
Register
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
.container {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 0 0 0.25rem;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
margin: 0 0 1.5rem;
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
padding: 0.6rem 0.75rem;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:focus {
|
||||||
|
border-color: #1a73e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
padding: 0.65rem;
|
||||||
|
background-color: #1a73e8;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
background-color: #fef2f2;
|
||||||
|
color: #b91c1c;
|
||||||
|
border: 1px solid #fecaca;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
background-color: #f0fdf4;
|
||||||
|
color: #166534;
|
||||||
|
border: 1px solid #bbf7d0;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
margin-top: 1.25rem;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: #1a73e8;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
+18
-117
@@ -1,6 +1,7 @@
|
|||||||
import { useState, type FormEvent, type ChangeEvent } from "react";
|
import { useState, type FormEvent, type ChangeEvent } from "react";
|
||||||
import { useNavigate, useLocation, Link } from "react-router-dom";
|
import { useNavigate, useLocation, Link } from "react-router-dom";
|
||||||
import { useAuth } from "../contexts/AuthContext";
|
import { useAuth } from "../contexts/AuthContext";
|
||||||
|
import styles from "./LoginPage.module.css";
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -33,17 +34,17 @@ export default function LoginPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={styles.container}>
|
<div className={styles.container}>
|
||||||
<div style={styles.card}>
|
<div className={styles.card}>
|
||||||
<h1 style={styles.title}>Sign In</h1>
|
<h1 className={styles.title}>Sign In</h1>
|
||||||
<p style={styles.subtitle}>Welcome back to Job Tracker</p>
|
<p className={styles.subtitle}>Welcome back to Job Tracker</p>
|
||||||
|
|
||||||
{state.error && <div style={styles.error}>{state.error}</div>}
|
{state.error && <div className={styles.error}>{state.error}</div>}
|
||||||
{successMessage && <div style={styles.success}>{successMessage}</div>}
|
{successMessage && <div className={styles.success}>{successMessage}</div>}
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} style={styles.form}>
|
<form onSubmit={handleSubmit} className={styles.form}>
|
||||||
<div style={styles.field}>
|
<div className={styles.field}>
|
||||||
<label htmlFor="email" style={styles.label}>
|
<label htmlFor="email" className={styles.label}>
|
||||||
Email
|
Email
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -51,14 +52,14 @@ export default function LoginPage() {
|
|||||||
type="email"
|
type="email"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={handleEmailChange}
|
onChange={handleEmailChange}
|
||||||
style={styles.input}
|
className={styles.input}
|
||||||
placeholder="you@example.com"
|
placeholder="you@example.com"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={styles.field}>
|
<div className={styles.field}>
|
||||||
<label htmlFor="password" style={styles.label}>
|
<label htmlFor="password" className={styles.label}>
|
||||||
Password
|
Password
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -66,8 +67,8 @@ export default function LoginPage() {
|
|||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={handlePasswordChange}
|
onChange={handlePasswordChange}
|
||||||
style={styles.input}
|
className={styles.input}
|
||||||
placeholder="········"
|
placeholder="\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -75,18 +76,15 @@ export default function LoginPage() {
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={state.isLoading}
|
disabled={state.isLoading}
|
||||||
style={{
|
className={styles.button}
|
||||||
...styles.button,
|
|
||||||
...(state.isLoading ? styles.buttonDisabled : {}),
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{state.isLoading ? "Signing in..." : "Sign In"}
|
{state.isLoading ? "Signing in..." : "Sign In"}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<p style={styles.footer}>
|
<p className={styles.footer}>
|
||||||
Don't have an account?{" "}
|
Don't have an account?{" "}
|
||||||
<Link to="/register" style={styles.link}>
|
<Link to="/register" className={styles.link}>
|
||||||
Create one
|
Create one
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
@@ -94,100 +92,3 @@ export default function LoginPage() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles: Record<string, React.CSSProperties> = {
|
|
||||||
container: {
|
|
||||||
minHeight: "100vh",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
backgroundColor: "#f5f5f5",
|
|
||||||
fontFamily:
|
|
||||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
||||||
},
|
|
||||||
card: {
|
|
||||||
backgroundColor: "#fff",
|
|
||||||
padding: "2rem",
|
|
||||||
borderRadius: "8px",
|
|
||||||
boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
|
|
||||||
width: "100%",
|
|
||||||
maxWidth: "400px",
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
margin: "0 0 0.25rem",
|
|
||||||
fontSize: "1.5rem",
|
|
||||||
fontWeight: 600,
|
|
||||||
},
|
|
||||||
subtitle: {
|
|
||||||
margin: "0 0 1.5rem",
|
|
||||||
color: "#666",
|
|
||||||
fontSize: "0.9rem",
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
gap: "1rem",
|
|
||||||
},
|
|
||||||
field: {
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
gap: "0.35rem",
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
fontSize: "0.85rem",
|
|
||||||
fontWeight: 500,
|
|
||||||
color: "#333",
|
|
||||||
},
|
|
||||||
input: {
|
|
||||||
padding: "0.6rem 0.75rem",
|
|
||||||
border: "1px solid #ccc",
|
|
||||||
borderRadius: "6px",
|
|
||||||
fontSize: "0.95rem",
|
|
||||||
outline: "none",
|
|
||||||
transition: "border-color 0.15s",
|
|
||||||
},
|
|
||||||
button: {
|
|
||||||
padding: "0.65rem",
|
|
||||||
backgroundColor: "#1a73e8",
|
|
||||||
color: "#fff",
|
|
||||||
border: "none",
|
|
||||||
borderRadius: "6px",
|
|
||||||
fontSize: "1rem",
|
|
||||||
fontWeight: 500,
|
|
||||||
cursor: "pointer",
|
|
||||||
marginTop: "0.5rem",
|
|
||||||
},
|
|
||||||
buttonDisabled: {
|
|
||||||
opacity: 0.6,
|
|
||||||
cursor: "not-allowed",
|
|
||||||
},
|
|
||||||
error: {
|
|
||||||
backgroundColor: "#fef2f2",
|
|
||||||
color: "#b91c1c",
|
|
||||||
border: "1px solid #fecaca",
|
|
||||||
borderRadius: "6px",
|
|
||||||
padding: "0.5rem 0.75rem",
|
|
||||||
fontSize: "0.85rem",
|
|
||||||
marginBottom: "0.5rem",
|
|
||||||
},
|
|
||||||
success: {
|
|
||||||
backgroundColor: "#f0fdf4",
|
|
||||||
color: "#166534",
|
|
||||||
border: "1px solid #bbf7d0",
|
|
||||||
borderRadius: "6px",
|
|
||||||
padding: "0.5rem 0.75rem",
|
|
||||||
fontSize: "0.85rem",
|
|
||||||
marginBottom: "0.5rem",
|
|
||||||
},
|
|
||||||
footer: {
|
|
||||||
marginTop: "1.25rem",
|
|
||||||
textAlign: "center",
|
|
||||||
fontSize: "0.85rem",
|
|
||||||
color: "#666",
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
color: "#1a73e8",
|
|
||||||
textDecoration: "none",
|
|
||||||
fontWeight: 500,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
.container {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 440px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin: 0 0 0.25rem;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
margin: 0 0 1.5rem;
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.halfField {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.35rem;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.required {
|
||||||
|
color: #b91c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
padding: 0.6rem 0.75rem;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:focus {
|
||||||
|
border-color: #1a73e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
padding: 0.65rem;
|
||||||
|
background-color: #1a73e8;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
background-color: #fef2f2;
|
||||||
|
color: #b91c1c;
|
||||||
|
border: 1px solid #fecaca;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
margin-top: 1.25rem;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: #1a73e8;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
+29
-132
@@ -1,6 +1,7 @@
|
|||||||
import { useState, type FormEvent, type ChangeEvent } from "react";
|
import { useState, type FormEvent, type ChangeEvent } from "react";
|
||||||
import { useNavigate, Link } from "react-router-dom";
|
import { useNavigate, Link } from "react-router-dom";
|
||||||
import { useAuth } from "../contexts/AuthContext";
|
import { useAuth } from "../contexts/AuthContext";
|
||||||
|
import styles from "./RegisterPage.module.css";
|
||||||
|
|
||||||
export default function RegisterPage() {
|
export default function RegisterPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -32,17 +33,17 @@ export default function RegisterPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={styles.container}>
|
<div className={styles.container}>
|
||||||
<div style={styles.card}>
|
<div className={styles.card}>
|
||||||
<h1 style={styles.title}>Create Account</h1>
|
<h1 className={styles.title}>Create Account</h1>
|
||||||
<p style={styles.subtitle}>Get started with Job Tracker</p>
|
<p className={styles.subtitle}>Get started with Job Tracker</p>
|
||||||
|
|
||||||
{state.error && <div style={styles.error}>{state.error}</div>}
|
{state.error && <div className={styles.error}>{state.error}</div>}
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} style={styles.form}>
|
<form onSubmit={handleSubmit} className={styles.form}>
|
||||||
<div style={styles.row}>
|
<div className={styles.row}>
|
||||||
<div style={styles.halfField}>
|
<div className={styles.halfField}>
|
||||||
<label htmlFor="firstName" style={styles.label}>
|
<label htmlFor="firstName" className={styles.label}>
|
||||||
First Name
|
First Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -52,11 +53,11 @@ export default function RegisterPage() {
|
|||||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
setFirstName(e.target.value)
|
setFirstName(e.target.value)
|
||||||
}
|
}
|
||||||
style={styles.input}
|
className={styles.input}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={styles.halfField}>
|
<div className={styles.halfField}>
|
||||||
<label htmlFor="lastName" style={styles.label}>
|
<label htmlFor="lastName" className={styles.label}>
|
||||||
Last Name
|
Last Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -66,14 +67,14 @@ export default function RegisterPage() {
|
|||||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
setLastName(e.target.value)
|
setLastName(e.target.value)
|
||||||
}
|
}
|
||||||
style={styles.input}
|
className={styles.input}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={styles.field}>
|
<div className={styles.field}>
|
||||||
<label htmlFor="email" style={styles.label}>
|
<label htmlFor="email" className={styles.label}>
|
||||||
Email <span style={styles.required}>*</span>
|
Email <span className={styles.required}>*</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="email"
|
id="email"
|
||||||
@@ -82,15 +83,15 @@ export default function RegisterPage() {
|
|||||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
setEmail(e.target.value)
|
setEmail(e.target.value)
|
||||||
}
|
}
|
||||||
style={styles.input}
|
className={styles.input}
|
||||||
placeholder="you@example.com"
|
placeholder="you@example.com"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={styles.field}>
|
<div className={styles.field}>
|
||||||
<label htmlFor="password" style={styles.label}>
|
<label htmlFor="password" className={styles.label}>
|
||||||
Password <span style={styles.required}>*</span>
|
Password <span className={styles.required}>*</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="password"
|
id="password"
|
||||||
@@ -99,16 +100,16 @@ export default function RegisterPage() {
|
|||||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
setPassword(e.target.value)
|
setPassword(e.target.value)
|
||||||
}
|
}
|
||||||
style={styles.input}
|
className={styles.input}
|
||||||
placeholder="Min. 8 characters"
|
placeholder="Min. 8 characters"
|
||||||
required
|
required
|
||||||
minLength={8}
|
minLength={8}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={styles.field}>
|
<div className={styles.field}>
|
||||||
<label htmlFor="passwordConfirm" style={styles.label}>
|
<label htmlFor="passwordConfirm" className={styles.label}>
|
||||||
Confirm Password <span style={styles.required}>*</span>
|
Confirm Password <span className={styles.required}>*</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
id="passwordConfirm"
|
id="passwordConfirm"
|
||||||
@@ -117,7 +118,7 @@ export default function RegisterPage() {
|
|||||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
setPasswordConfirm(e.target.value)
|
setPasswordConfirm(e.target.value)
|
||||||
}
|
}
|
||||||
style={styles.input}
|
className={styles.input}
|
||||||
placeholder="Repeat your password"
|
placeholder="Repeat your password"
|
||||||
required
|
required
|
||||||
minLength={8}
|
minLength={8}
|
||||||
@@ -127,18 +128,15 @@ export default function RegisterPage() {
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={state.isLoading}
|
disabled={state.isLoading}
|
||||||
style={{
|
className={styles.button}
|
||||||
...styles.button,
|
|
||||||
...(state.isLoading ? styles.buttonDisabled : {}),
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{state.isLoading ? "Creating Account..." : "Create Account"}
|
{state.isLoading ? "Creating Account..." : "Create Account"}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<p style={styles.footer}>
|
<p className={styles.footer}>
|
||||||
Already have an account?{" "}
|
Already have an account?{" "}
|
||||||
<Link to="/login" style={styles.link}>
|
<Link to="/login" className={styles.link}>
|
||||||
Sign in
|
Sign in
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
@@ -146,104 +144,3 @@ export default function RegisterPage() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles: Record<string, React.CSSProperties> = {
|
|
||||||
container: {
|
|
||||||
minHeight: "100vh",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
backgroundColor: "#f5f5f5",
|
|
||||||
fontFamily:
|
|
||||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
||||||
},
|
|
||||||
card: {
|
|
||||||
backgroundColor: "#fff",
|
|
||||||
padding: "2rem",
|
|
||||||
borderRadius: "8px",
|
|
||||||
boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
|
|
||||||
width: "100%",
|
|
||||||
maxWidth: "440px",
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
margin: "0 0 0.25rem",
|
|
||||||
fontSize: "1.5rem",
|
|
||||||
fontWeight: 600,
|
|
||||||
},
|
|
||||||
subtitle: {
|
|
||||||
margin: "0 0 1.5rem",
|
|
||||||
color: "#666",
|
|
||||||
fontSize: "0.9rem",
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
gap: "1rem",
|
|
||||||
},
|
|
||||||
row: {
|
|
||||||
display: "flex",
|
|
||||||
gap: "0.75rem",
|
|
||||||
},
|
|
||||||
field: {
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
gap: "0.35rem",
|
|
||||||
},
|
|
||||||
halfField: {
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
gap: "0.35rem",
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
fontSize: "0.85rem",
|
|
||||||
fontWeight: 500,
|
|
||||||
color: "#333",
|
|
||||||
},
|
|
||||||
required: {
|
|
||||||
color: "#b91c1c",
|
|
||||||
},
|
|
||||||
input: {
|
|
||||||
padding: "0.6rem 0.75rem",
|
|
||||||
border: "1px solid #ccc",
|
|
||||||
borderRadius: "6px",
|
|
||||||
fontSize: "0.95rem",
|
|
||||||
outline: "none",
|
|
||||||
transition: "border-color 0.15s",
|
|
||||||
},
|
|
||||||
button: {
|
|
||||||
padding: "0.65rem",
|
|
||||||
backgroundColor: "#1a73e8",
|
|
||||||
color: "#fff",
|
|
||||||
border: "none",
|
|
||||||
borderRadius: "6px",
|
|
||||||
fontSize: "1rem",
|
|
||||||
fontWeight: 500,
|
|
||||||
cursor: "pointer",
|
|
||||||
marginTop: "0.5rem",
|
|
||||||
},
|
|
||||||
buttonDisabled: {
|
|
||||||
opacity: 0.6,
|
|
||||||
cursor: "not-allowed",
|
|
||||||
},
|
|
||||||
error: {
|
|
||||||
backgroundColor: "#fef2f2",
|
|
||||||
color: "#b91c1c",
|
|
||||||
border: "1px solid #fecaca",
|
|
||||||
borderRadius: "6px",
|
|
||||||
padding: "0.5rem 0.75rem",
|
|
||||||
fontSize: "0.85rem",
|
|
||||||
marginBottom: "0.5rem",
|
|
||||||
},
|
|
||||||
footer: {
|
|
||||||
marginTop: "1.25rem",
|
|
||||||
textAlign: "center",
|
|
||||||
fontSize: "0.85rem",
|
|
||||||
color: "#666",
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
color: "#1a73e8",
|
|
||||||
textDecoration: "none",
|
|
||||||
fontWeight: 500,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user