Initial commit: Open sourcing all of the Maple Open Technologies code.

This commit is contained in:
Bartlomiej Mika 2025-12-02 14:33:08 -05:00
commit 755d54a99d
2010 changed files with 448675 additions and 0 deletions

View file

@ -0,0 +1,424 @@
// File: monorepo/web/maplefile-frontend/src/pages/Anonymous/Register/VerifySuccess.jsx
// UIX version - Registration Success (100% UIX Components)
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
import { useNavigate } from "react-router";
import {
Card,
Button,
Alert,
GDPRFooter,
Navigation,
PageContainer,
} from "../../../components/UIX";
import { useUIXTheme } from "../../../components/UIX/themes/useUIXTheme";
import {
ArrowRightIcon,
CheckCircleIcon,
LockClosedIcon,
ShieldCheckIcon,
UserIcon,
DocumentCheckIcon,
KeyIcon,
CloudArrowUpIcon,
SparklesIcon,
InformationCircleIcon,
} from "@heroicons/react/24/outline";
const VerifySuccess = () => {
const navigate = useNavigate();
const { getThemeClasses } = useUIXTheme();
// Refs for cleanup
const isMountedRef = useRef(true);
const timerRef = useRef(null);
const [email, setEmail] = useState("");
const [userRole, setUserRole] = useState(null);
const [countdown, setCountdown] = useState(10);
useEffect(() => {
isMountedRef.current = true;
// Get data from sessionStorage
const registeredEmail = sessionStorage.getItem("registeredEmail");
const storedUserRole = sessionStorage.getItem("userRole");
if (!registeredEmail || !storedUserRole) {
// Redirect back to registration if no data found
if (import.meta.env.DEV) {
console.log("[VerifySuccess] No registration data found, redirecting to register");
}
navigate("/register");
return;
}
// Validate email format to prevent XSS/tampering
const emailRegex = /^[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
if (!emailRegex.test(registeredEmail)) {
if (import.meta.env.DEV) {
console.error("[VerifySuccess] Invalid email format in sessionStorage");
}
sessionStorage.removeItem("registeredEmail");
sessionStorage.removeItem("userRole");
navigate("/register");
return;
}
// Validate user role (must be 1, 2, or 3)
const roleNumber = parseInt(storedUserRole, 10);
if (isNaN(roleNumber) || roleNumber < 1 || roleNumber > 3) {
if (import.meta.env.DEV) {
console.error("[VerifySuccess] Invalid user role in sessionStorage");
}
sessionStorage.removeItem("registeredEmail");
sessionStorage.removeItem("userRole");
navigate("/register");
return;
}
if (isMountedRef.current) {
setEmail(registeredEmail);
setUserRole(roleNumber);
}
return () => {
isMountedRef.current = false;
if (timerRef.current) {
clearInterval(timerRef.current);
}
};
}, [navigate]);
// Auto-redirect countdown
useEffect(() => {
timerRef.current = setInterval(() => {
setCountdown((prev) => {
if (prev <= 1) {
navigate("/login");
return 0;
}
return prev - 1;
});
}, 1000);
return () => {
if (timerRef.current) {
clearInterval(timerRef.current);
}
};
}, []); // eslint-disable-line react-hooks/exhaustive-deps
// navigate is stable but we only want this to run once on mount
const getUserRoleText = useCallback((role) => {
switch (role) {
case 1:
return {
text: "Root User",
className: "bg-purple-100 text-purple-700",
};
case 2:
return {
text: "Company User",
className: "bg-blue-100 text-blue-700",
};
case 3:
return {
text: "Individual User",
className: "bg-green-100 text-green-700",
};
default:
return {
text: "Unknown",
className: "bg-gray-100 text-gray-700",
};
}
}, []);
const handleRegisterAnother = useCallback(() => {
// Clear session storage
sessionStorage.removeItem("registrationResult");
sessionStorage.removeItem("registeredEmail");
sessionStorage.removeItem("userRole");
navigate("/register");
}, [navigate]);
const handleGoToLogin = useCallback(() => {
// Clear session storage
sessionStorage.removeItem("registrationResult");
sessionStorage.removeItem("registeredEmail");
sessionStorage.removeItem("userRole");
// Navigate to login page
navigate("/login");
}, [navigate]);
// Memoize role info
const roleInfo = useMemo(() => getUserRoleText(userRole), [userRole, getUserRoleText]);
// Memoize navigation links
const navLinks = useMemo(() => [], []); // No links on success page
if (!email || userRole === null) {
return (
<PageContainer>
<Card className="text-center p-8 max-w-md mx-auto mt-20">
<h2 className={`text-2xl font-bold ${getThemeClasses("text-primary")} mb-4`}>
Loading...
</h2>
<p className={getThemeClasses("text-secondary")}>
Loading success page...
</p>
</Card>
</PageContainer>
);
}
return (
<PageContainer showBlobs>
{/* Navigation */}
<Navigation icon={LockClosedIcon} logoText="MapleFile" links={navLinks}>
<div className={`flex items-center space-x-2 text-sm ${getThemeClasses("text-success")}`}>
<CheckCircleIcon className="h-5 w-5" />
<span className="font-semibold">Registration Complete</span>
</div>
</Navigation>
{/* Main Content */}
<div className="flex-1 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8 relative z-10">
<div className="max-w-2xl w-full space-y-8">
{/* Success Animation */}
<div className="text-center animate-fade-in-up">
<div className="flex justify-center mb-6">
<div className="relative">
<div className="flex items-center justify-center h-20 w-20 bg-gradient-to-br from-green-500 to-green-600 rounded-full shadow-lg animate-bounce-once">
<CheckCircleIcon className="h-12 w-12 text-white" />
</div>
<div className="absolute -inset-1 bg-gradient-to-r from-green-500 to-green-600 rounded-full blur opacity-30 animate-pulse"></div>
<div className="absolute -top-1 -right-1">
<SparklesIcon className="h-6 w-6 text-yellow-500 animate-pulse" />
</div>
</div>
</div>
<h2 className={`text-4xl font-black ${getThemeClasses("text-primary")} mb-2`}>
Welcome to MapleFile!
</h2>
<p className={`text-xl ${getThemeClasses("text-secondary")}`}>
Your account has been successfully created
</p>
</div>
{/* GDPR Notice */}
<Alert type="info">
<div className="text-xs space-y-1">
<p className="font-semibold">Data Processing Notice</p>
<p>
Your account data is stored securely and processed in compliance with GDPR.
You have the right to access, rectify, or delete your data at any time.
(Legal basis: Contract - necessary for service provision)
</p>
</div>
</Alert>
{/* Account Details Card */}
<Card className="shadow-2xl animate-fade-in-up-delay">
<div className="flex items-center mb-6">
<div className={`flex items-center justify-center h-12 w-12 ${getThemeClasses("bg-gradient-secondary")} rounded-xl mr-4`}>
<UserIcon className="h-6 w-6 text-white" />
</div>
<h3 className={`text-xl font-semibold ${getThemeClasses("text-primary")}`}>
Account Details
</h3>
</div>
<div className="space-y-4">
<div className={`flex items-center justify-between p-3 rounded-lg ${getThemeClasses("bg-card")}`}>
<span className={`text-sm font-medium ${getThemeClasses("text-secondary")}`}>
Email Address
</span>
<span className={`text-sm font-mono ${getThemeClasses("text-primary")}`}>{email}</span>
</div>
<div className={`flex items-center justify-between p-3 rounded-lg ${getThemeClasses("bg-card")}`}>
<span className={`text-sm font-medium ${getThemeClasses("text-secondary")}`}>
User Role
</span>
<span className={`text-sm font-semibold px-3 py-1 rounded-full ${roleInfo.className}`}>
{roleInfo.text}
</span>
</div>
<div className={`flex items-center justify-between p-3 rounded-lg ${getThemeClasses("bg-card")}`}>
<span className={`text-sm font-medium ${getThemeClasses("text-secondary")}`}>
Account Status
</span>
<span className={`text-sm font-semibold ${getThemeClasses("text-success")} flex items-center`}>
<CheckCircleIcon className="h-4 w-4 mr-1" />
Active
</span>
</div>
</div>
</Card>
{/* What's Next Section */}
<Alert type="info" className="animate-fade-in-up-delay-2">
<div>
<h3 className="text-lg font-semibold mb-4">
What's Next?
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="flex items-start">
<div className={`flex items-center justify-center h-8 w-8 ${getThemeClasses("bg-card")} rounded-lg mr-3 flex-shrink-0`}>
<KeyIcon className="h-4 w-4" />
</div>
<div>
<h4 className={`text-sm font-semibold ${getThemeClasses("text-primary")}`}>
Secure Your Recovery Phrase
</h4>
<p className={`text-xs ${getThemeClasses("text-secondary")} mt-1`}>
Keep your 12-word recovery phrase in a safe place
</p>
</div>
</div>
<div className="flex items-start">
<div className={`flex items-center justify-center h-8 w-8 ${getThemeClasses("bg-card")} rounded-lg mr-3 flex-shrink-0`}>
<CloudArrowUpIcon className="h-4 w-4" />
</div>
<div>
<h4 className={`text-sm font-semibold ${getThemeClasses("text-primary")}`}>
Start Uploading Files
</h4>
<p className={`text-xs ${getThemeClasses("text-secondary")} mt-1`}>
Your files are encrypted before leaving your device
</p>
</div>
</div>
<div className="flex items-start">
<div className={`flex items-center justify-center h-8 w-8 ${getThemeClasses("bg-card")} rounded-lg mr-3 flex-shrink-0`}>
<DocumentCheckIcon className="h-4 w-4" />
</div>
<div>
<h4 className={`text-sm font-semibold ${getThemeClasses("text-primary")}`}>
Organize Your Documents
</h4>
<p className={`text-xs ${getThemeClasses("text-secondary")} mt-1`}>
Create collections to organize your files
</p>
</div>
</div>
<div className="flex items-start">
<div className={`flex items-center justify-center h-8 w-8 ${getThemeClasses("bg-card")} rounded-lg mr-3 flex-shrink-0`}>
<ShieldCheckIcon className="h-4 w-4" />
</div>
<div>
<h4 className={`text-sm font-semibold ${getThemeClasses("text-primary")}`}>
Share Securely
</h4>
<p className={`text-xs ${getThemeClasses("text-secondary")} mt-1`}>
Share files with end-to-end encryption
</p>
</div>
</div>
</div>
</div>
</Alert>
{/* Security Reminder */}
<Alert type="warning" className="animate-fade-in-up-delay-3">
<div className="flex items-start">
<ShieldCheckIcon className="h-6 w-6 mr-3 flex-shrink-0 mt-1" />
<div className="flex-1">
<h3 className="text-sm font-semibold mb-2">
Security Reminder
</h3>
<p className="text-sm">
Your account uses end-to-end encryption. Your recovery phrase
is the <strong>only way</strong> to recover your data if you
forget your password. Make sure it's stored securely in a
physical location!
</p>
</div>
</div>
</Alert>
{/* Auto-redirect Notice */}
<div className={`text-center p-4 rounded-lg ${getThemeClasses("bg-card")} animate-fade-in-up-delay-3`}>
<p className={`text-sm ${getThemeClasses("text-secondary")}`}>
Redirecting to login page in{" "}
<span className={`font-semibold ${getThemeClasses("text-primary")}`}>{countdown}</span>{" "}
seconds...
</p>
</div>
{/* Action Buttons */}
<div className="flex flex-col sm:flex-row gap-4 animate-fade-in-up-delay-3">
<Button
type="button"
onClick={handleRegisterAnother}
variant="secondary"
fullWidth
>
Register Another Account
</Button>
<Button
type="button"
onClick={handleGoToLogin}
variant="primary"
fullWidth
>
<LockClosedIcon className="mr-2 h-5 w-5" />
Sign In Now
<ArrowRightIcon className="ml-2 h-4 w-4" />
</Button>
</div>
</div>
</div>
{/* Footer */}
<GDPRFooter />
{/* CSS Animations */}
<style>{`
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes bounce-once {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-20px);
}
}
.animate-fade-in-up {
animation: fade-in-up 0.6s ease-out;
}
.animate-fade-in-up-delay {
animation: fade-in-up 0.6s ease-out 0.2s both;
}
.animate-fade-in-up-delay-2 {
animation: fade-in-up 0.6s ease-out 0.4s both;
}
.animate-fade-in-up-delay-3 {
animation: fade-in-up 0.6s ease-out 0.6s both;
}
.animate-bounce-once {
animation: bounce-once 0.8s ease-out;
}
`}</style>
</PageContainer>
);
};
export default VerifySuccess;