This commit is contained in:
jackiettran
2025-10-30 15:38:57 -04:00
parent d1cb857aa7
commit ee3a6fd8e1
13 changed files with 1400 additions and 12 deletions

View File

@@ -1,9 +1,10 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { AuthProvider, useAuth } from './contexts/AuthContext';
import Navbar from './components/Navbar';
import Footer from './components/Footer';
import AuthModal from './components/AuthModal';
import AlphaGate from './components/AlphaGate';
import Home from './pages/Home';
import GoogleCallback from './pages/GoogleCallback';
import VerifyEmail from './pages/VerifyEmail';
@@ -25,10 +26,49 @@ import CreateItemRequest from './pages/CreateItemRequest';
import MyRequests from './pages/MyRequests';
import EarningsDashboard from './pages/EarningsDashboard';
import PrivateRoute from './components/PrivateRoute';
import axios from 'axios';
import './App.css';
const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:5001';
const AppContent: React.FC = () => {
const { showAuthModal, authModalMode, closeAuthModal } = useAuth();
const [hasAlphaAccess, setHasAlphaAccess] = useState<boolean | null>(null);
const [checkingAccess, setCheckingAccess] = useState(true);
useEffect(() => {
const checkAlphaAccess = async () => {
try {
const response = await axios.get(`${API_URL}/alpha/verify-session`, {
withCredentials: true,
});
setHasAlphaAccess(response.data.hasAccess);
} catch (error) {
console.error('Error checking alpha access:', error);
setHasAlphaAccess(false);
} finally {
setCheckingAccess(false);
}
};
checkAlphaAccess();
}, []);
// Show loading state while checking
if (checkingAccess) {
return (
<div className="d-flex align-items-center justify-content-center min-vh-100">
<div className="spinner-border text-primary" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
);
}
// Show alpha gate if no access
if (!hasAlphaAccess) {
return <AlphaGate />;
}
return (
<>

View File

@@ -0,0 +1,215 @@
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;