216 lines
6.3 KiB
TypeScript
216 lines
6.3 KiB
TypeScript
import React, { useState } from "react";
|
||
import axios from "axios";
|
||
|
||
const API_URL = process.env.REACT_APP_API_URL || "http://localhost:5001";
|
||
|
||
const AlphaGate: React.FC = () => {
|
||
const [code, setCode] = useState("");
|
||
const [loading, setLoading] = useState(false);
|
||
const [error, setError] = useState("");
|
||
|
||
const handleCodeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||
const value = e.target.value.toUpperCase();
|
||
// Only allow alphanumeric, max 8 characters
|
||
if (value.length <= 8 && /^[A-Z0-9]*$/.test(value)) {
|
||
setCode(value);
|
||
}
|
||
setError("");
|
||
};
|
||
|
||
const handleSubmit = async (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
setError("");
|
||
|
||
if (code.length < 8) {
|
||
setError("Please enter a complete alpha access code");
|
||
return;
|
||
}
|
||
|
||
setLoading(true);
|
||
|
||
const fullCode = `ALPHA-${code}`;
|
||
|
||
try {
|
||
const response = await axios.post(
|
||
`${API_URL}/alpha/validate-code`,
|
||
{ code: fullCode },
|
||
{ withCredentials: true }
|
||
);
|
||
|
||
if (response.data.success) {
|
||
// Store alpha verification in localStorage as backup
|
||
localStorage.setItem("alphaVerified", "true");
|
||
// Reload the page to trigger access check
|
||
window.location.reload();
|
||
}
|
||
} catch (err: any) {
|
||
if (err.response?.status === 429) {
|
||
setError("Too many attempts. Please try again in a few minutes.");
|
||
} else if (err.response?.data?.error) {
|
||
setError(err.response.data.error);
|
||
} else {
|
||
setError("Failed to validate code. Please try again.");
|
||
}
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div
|
||
style={{
|
||
minHeight: "100vh",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
|
||
padding: "20px",
|
||
}}
|
||
>
|
||
<div
|
||
className="card shadow-lg"
|
||
style={{ maxWidth: "500px", width: "100%" }}
|
||
>
|
||
<div className="card-body p-5">
|
||
<div className="text-center mb-4">
|
||
<h1 className="h2 mb-3" style={{ color: "#667eea" }}>
|
||
Community Rentals
|
||
</h1>
|
||
</div>
|
||
|
||
<div className="mb-4">
|
||
<ul
|
||
className="text-start text-muted mb-3"
|
||
style={{ fontSize: "0.95rem" }}
|
||
>
|
||
<li className="mb-2">
|
||
<strong>Earn</strong> extra income with the stuff you already
|
||
have
|
||
</li>
|
||
<li className="mb-2">
|
||
<strong>Find</strong> items for special events, weekend
|
||
projects, family trips and more
|
||
</li>
|
||
<li className="mb-2">
|
||
<strong>Discover</strong> affordable options
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div className="mb-4">
|
||
<h6
|
||
className="fw-bold mb-2 text-center"
|
||
style={{ color: "#667eea" }}
|
||
>
|
||
Currently in Alpha Testing!
|
||
</h6>
|
||
<p className="text-muted small mb-0 text-center">
|
||
You're among the first to try Community Rentals! Help us create
|
||
something special by sharing your thoughts as we build this
|
||
together.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="mb-4">
|
||
<p className="text-center text-muted small mb-0">
|
||
Have an alpha code? Get started below! <br></br> Want to join?{" "}
|
||
<a
|
||
href="mailto:support@communityrentals.app?subject=Alpha Access Request"
|
||
className="text-decoration-none"
|
||
style={{ color: "#667eea" }}
|
||
>
|
||
Request access
|
||
</a>
|
||
</p>
|
||
</div>
|
||
|
||
<form onSubmit={handleSubmit}>
|
||
<div className="mb-3">
|
||
<div
|
||
style={{
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
gap: "0.5rem",
|
||
}}
|
||
>
|
||
{/* Static ALPHA- text */}
|
||
<span
|
||
style={{
|
||
fontFamily: "monospace",
|
||
fontSize: "1.1rem",
|
||
fontWeight: "400",
|
||
letterSpacing: "2px",
|
||
color: "#495057",
|
||
}}
|
||
>
|
||
ALPHA-
|
||
</span>
|
||
|
||
{/* Input for 8 characters */}
|
||
<input
|
||
type="text"
|
||
className={`form-control form-control-lg ${
|
||
error ? "border-danger" : ""
|
||
}`}
|
||
placeholder="XXXXXXXX"
|
||
value={code}
|
||
onChange={handleCodeChange}
|
||
disabled={loading}
|
||
style={{
|
||
fontFamily: "monospace",
|
||
letterSpacing: "2px",
|
||
fontSize: "1.1rem",
|
||
textAlign: "center",
|
||
width: "calc(8ch + 16px + 2rem)",
|
||
}}
|
||
maxLength={8}
|
||
autoFocus
|
||
/>
|
||
|
||
{/* Error icon outside the input */}
|
||
{error && (
|
||
<span
|
||
style={{
|
||
color: "#dc3545",
|
||
fontSize: "1.5rem",
|
||
lineHeight: 1,
|
||
}}
|
||
>
|
||
⚠️
|
||
</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<button
|
||
type="submit"
|
||
className="btn btn-primary btn-lg w-100"
|
||
disabled={loading || code.length < 8}
|
||
style={{
|
||
background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
|
||
border: "none",
|
||
}}
|
||
>
|
||
{loading ? (
|
||
<>
|
||
<span
|
||
className="spinner-border spinner-border-sm me-2"
|
||
role="status"
|
||
aria-hidden="true"
|
||
></span>
|
||
Validating...
|
||
</>
|
||
) : (
|
||
"Enter"
|
||
)}
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default AlphaGate;
|