544 lines
22 KiB
TypeScript
544 lines
22 KiB
TypeScript
import { useEffect, useState } from "preact/hooks";
|
|
import FloatingTileButtons from "./tiles/FloatingTileButtons.tsx";
|
|
import type { OnboardingStatus } from "../services/onboardingService.ts";
|
|
|
|
interface MemberHomeProps {
|
|
onboardingStatus?: OnboardingStatus | null;
|
|
}
|
|
|
|
export default function MemberHome({ onboardingStatus }: MemberHomeProps) {
|
|
const [spinDirection, setSpinDirection] = useState<"cw" | "ccw">("cw");
|
|
|
|
const handleLogoMouseEnter = () => {
|
|
setSpinDirection(Math.random() < 0.5 ? "cw" : "ccw");
|
|
};
|
|
|
|
useEffect(() => {
|
|
const lockRoute = localStorage.getItem("lock-route");
|
|
if (lockRoute) {
|
|
localStorage.removeItem("lock-route");
|
|
globalThis.location.href = lockRoute;
|
|
return;
|
|
}
|
|
const loginPurpose = localStorage.getItem("login-purpose");
|
|
if (loginPurpose) {
|
|
localStorage.removeItem("login-purpose");
|
|
const destination = loginPurpose === "landlord"
|
|
? "/landlord/dashboard"
|
|
: loginPurpose === "tenant"
|
|
? "/tenant/dashboard"
|
|
: "";
|
|
if (destination) {
|
|
globalThis.location.href = destination;
|
|
}
|
|
}
|
|
}, []);
|
|
|
|
// Sample tile data for FloatingTileButtons
|
|
const tileIds = [
|
|
{ id: "landlord-hub", label: "Landlord" },
|
|
{ id: "tenant-hub", label: "Tenant" },
|
|
{ id: "admin-center", label: "Admin" },
|
|
{ id: "user-profile", label: "Profile" },
|
|
{ id: "dashboard", label: "Dashboard" },
|
|
{ id: "financial-calendar", label: "Financial Calendar" },
|
|
];
|
|
|
|
const tenantOnboarded = Boolean(onboardingStatus?.onboarded_tenant);
|
|
const landlordStep = onboardingStatus?.onboarding_step ?? 0;
|
|
const landlordStarted = Boolean(onboardingStatus?.onboarded_landlord) ||
|
|
landlordStep > 0;
|
|
const landlordReady = landlordStarted && landlordStep === 0;
|
|
const landlordInProgress = landlordStep > 0;
|
|
const landlordProgressHref = landlordStep >= 2
|
|
? "/landlord/onboarding/property-setup"
|
|
: "/landlord/onboarding/profile";
|
|
|
|
const landlordPrimary = landlordReady
|
|
? { label: "Landlord Dashboard", href: "/landlord/dashboard" }
|
|
: landlordInProgress
|
|
? { label: "Continue Landlord Setup", href: landlordProgressHref }
|
|
: { label: "Start Landlord Setup", href: "/onboarding#landlord" };
|
|
|
|
const tenantPrimary = tenantOnboarded
|
|
? { label: "Tenant Dashboard", href: "/tenant/dashboard" }
|
|
: { label: "Start Tenant Setup", href: "/onboarding#tenant" };
|
|
|
|
return (
|
|
<div class="min-h-screen bg-black flex items-start justify-center relative overflow-hidden w-full pt-16">
|
|
{/* Animated background grid */}
|
|
<div class="absolute inset-0 opacity-20">
|
|
<div
|
|
class="absolute inset-0"
|
|
style={{
|
|
backgroundImage:
|
|
`linear-gradient(rgba(107, 206, 220, 0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(107, 206, 220, 0.1) 1px, transparent 1px)`,
|
|
backgroundSize: "50px 50px",
|
|
}}
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Teal colored orbs */}
|
|
<div class="absolute w-48 h-48 bg-teal-400 rounded-full filter blur-3xl opacity-20 animate-float-teal-1">
|
|
</div>
|
|
<div class="absolute w-56 h-56 bg-cyan-400 rounded-full filter blur-3xl opacity-20 animate-float-teal-2">
|
|
</div>
|
|
<div class="absolute w-52 h-52 bg-emerald-400 rounded-full filter blur-3xl opacity-20 animate-float-teal-3">
|
|
</div>
|
|
|
|
<div class="relative z-10 flex flex-col items-center text-center px-6 w-full max-w-none mt-4">
|
|
{/* Large Logo */}
|
|
<div class="mb-4 transform hover:scale-105 transition-transform duration-300">
|
|
<img
|
|
src="/logo.svg"
|
|
alt="Cyber Hybrid Hub Logo"
|
|
width="150"
|
|
height="150"
|
|
class={`drop-shadow-2xl filter drop-shadow-[0_0_20px_rgba(107,206,220,0.5)] spin-on-hover ${
|
|
spinDirection === "cw"
|
|
? "spin-direction-cw"
|
|
: "spin-direction-ccw"
|
|
}`}
|
|
onMouseEnter={handleLogoMouseEnter}
|
|
/>
|
|
</div>
|
|
|
|
{/* Main message */}
|
|
<div class="max-w-none w-full px-4">
|
|
<h1 class="text-3xl md:text-5xl text-transparent bg-clip-text bg-linear-to-r from-teal-400 via-cyan-400 to-emerald-400 mb-6 animate-pulse">
|
|
Apps available to you!
|
|
</h1>
|
|
|
|
{/* Real Estate Hub */}
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 w-full max-w-6xl mx-auto mb-10">
|
|
<div
|
|
id="landlord-hub"
|
|
class="group relative overflow-hidden bg-linear-to-br from-emerald-900/20 via-teal-900/25 to-cyan-900/20 border border-emerald-500/30 rounded-2xl p-6 backdrop-blur-sm hover:border-emerald-400/60 transition-all duration-300 hover:shadow-[0_0_40px_rgba(52,211,153,0.25)]"
|
|
>
|
|
<div class="absolute inset-0 bg-emerald-400/5 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
|
</div>
|
|
<div class="relative z-10">
|
|
<div class="flex items-start justify-between gap-4">
|
|
<div>
|
|
<h2 class="text-2xl font-bold text-emerald-200">
|
|
Landlord HQ
|
|
</h2>
|
|
<p class="text-gray-400 text-sm">
|
|
Launch new listings, manage leases, and track revenue.
|
|
</p>
|
|
</div>
|
|
<span
|
|
class={`text-xs uppercase tracking-widest px-3 py-1 rounded-full border ${
|
|
landlordReady
|
|
? "border-emerald-400/40 text-emerald-200 bg-emerald-500/15"
|
|
: landlordInProgress
|
|
? "border-amber-400/40 text-amber-200 bg-amber-500/10"
|
|
: "border-slate-400/40 text-slate-200 bg-slate-500/10"
|
|
}`}
|
|
>
|
|
{landlordReady
|
|
? "Onboarded"
|
|
: landlordInProgress
|
|
? "In Progress"
|
|
: "Not Set Up"}
|
|
</span>
|
|
</div>
|
|
<div class="mt-5 grid grid-cols-2 gap-4 text-left text-sm text-gray-300">
|
|
<div>
|
|
<p class="text-emerald-200 font-semibold">
|
|
Ownership Tools
|
|
</p>
|
|
<p class="text-xs text-gray-400">
|
|
Properties, applications, rent schedules.
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-emerald-200 font-semibold">
|
|
Tenant Pipeline
|
|
</p>
|
|
<p class="text-xs text-gray-400">
|
|
Review applications and coordinate repairs.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="mt-6 flex flex-col sm:flex-row gap-3">
|
|
<a
|
|
href={landlordPrimary.href}
|
|
class="inline-flex items-center justify-center rounded-full px-5 py-2 text-sm font-semibold text-white bg-emerald-500/80 hover:bg-emerald-400/90 transition"
|
|
>
|
|
{landlordPrimary.label}
|
|
</a>
|
|
<a
|
|
href={landlordReady
|
|
? "/landlord/properties/add"
|
|
: "/onboarding#landlord"}
|
|
class="inline-flex items-center justify-center rounded-full px-5 py-2 text-sm font-semibold text-emerald-100 border border-emerald-400/40 hover:border-emerald-300/80 hover:text-white transition"
|
|
>
|
|
Add New Property
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
id="tenant-hub"
|
|
class="group relative overflow-hidden bg-linear-to-br from-cyan-900/20 via-teal-900/15 to-sky-900/20 border border-cyan-500/30 rounded-2xl p-6 backdrop-blur-sm hover:border-cyan-400/60 transition-all duration-300 hover:shadow-[0_0_40px_rgba(56,189,248,0.25)]"
|
|
>
|
|
<div class="absolute inset-0 bg-cyan-400/5 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
|
</div>
|
|
<div class="relative z-10">
|
|
<div class="flex items-start justify-between gap-4">
|
|
<div>
|
|
<h2 class="text-2xl font-bold text-cyan-200">
|
|
Tenant Command
|
|
</h2>
|
|
<p class="text-gray-400 text-sm">
|
|
Pay rent, request maintenance, and stay in sync.
|
|
</p>
|
|
</div>
|
|
<span
|
|
class={`text-xs uppercase tracking-widest px-3 py-1 rounded-full border ${
|
|
tenantOnboarded
|
|
? "border-cyan-400/40 text-cyan-200 bg-cyan-500/15"
|
|
: "border-slate-400/40 text-slate-200 bg-slate-500/10"
|
|
}`}
|
|
>
|
|
{tenantOnboarded ? "Active" : "Not Set Up"}
|
|
</span>
|
|
</div>
|
|
<div class="mt-5 grid grid-cols-2 gap-4 text-left text-sm text-gray-300">
|
|
<div>
|
|
<p class="text-cyan-200 font-semibold">Payment Hub</p>
|
|
<p class="text-xs text-gray-400">
|
|
Auto-pay options and transaction history.
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-cyan-200 font-semibold">Service Requests</p>
|
|
<p class="text-xs text-gray-400">
|
|
Track repairs and chat with your landlord.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="mt-6 flex flex-col sm:flex-row gap-3">
|
|
<a
|
|
href={tenantPrimary.href}
|
|
class="inline-flex items-center justify-center rounded-full px-5 py-2 text-sm font-semibold text-white bg-cyan-500/80 hover:bg-cyan-400/90 transition"
|
|
>
|
|
{tenantPrimary.label}
|
|
</a>
|
|
<a
|
|
href="/onboarding#tenant"
|
|
class="inline-flex items-center justify-center rounded-full px-5 py-2 text-sm font-semibold text-cyan-100 border border-cyan-400/40 hover:border-cyan-300/80 hover:text-white transition"
|
|
>
|
|
Apply to Property
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* App tiles container */}
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 w-full max-w-6xl mx-auto">
|
|
{/* Admin Center Tile */}
|
|
<div
|
|
id="admin-center"
|
|
class="group relative overflow-hidden bg-linear-to-br from-teal-900/20 to-cyan-900/20 border border-teal-500/30 rounded-xl p-6 backdrop-blur-sm hover:border-teal-400/60 transition-all duration-300 hover:shadow-[0_0_30px_rgba(107,206,220,0.3)]"
|
|
>
|
|
<div class="absolute inset-0 bg-teal-400/5 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
|
</div>
|
|
<div class="relative z-10">
|
|
<div class="w-12 h-12 bg-teal-400/20 rounded-lg flex items-center justify-center mb-4 group-hover:bg-teal-400/30 transition-colors">
|
|
<svg
|
|
class="w-6 h-6 text-teal-400"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
|
|
>
|
|
</path>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
|
>
|
|
</path>
|
|
</svg>
|
|
</div>
|
|
<h3 class="text-xl font-bold text-teal-400 mb-2">
|
|
Admin Center
|
|
</h3>
|
|
<p class="text-gray-400 text-sm">
|
|
Manage system settings and user permissions
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* User Profile Tile */}
|
|
<div
|
|
id="user-profile"
|
|
class="group relative overflow-hidden bg-linear-to-br from-teal-900/20 to-cyan-900/20 border border-teal-500/30 rounded-xl p-6 backdrop-blur-sm hover:border-teal-400/60 transition-all duration-300 hover:shadow-[0_0_30px_rgba(107,206,220,0.3)]"
|
|
>
|
|
<div class="absolute inset-0 bg-teal-400/5 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
|
</div>
|
|
<div class="relative z-10">
|
|
<div class="w-12 h-12 bg-teal-400/20 rounded-lg flex items-center justify-center mb-4 group-hover:bg-teal-400/30 transition-colors">
|
|
<svg
|
|
class="w-6 h-6 text-teal-400"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
|
>
|
|
</path>
|
|
</svg>
|
|
</div>
|
|
<h3 class="text-xl font-bold text-teal-400 mb-2">
|
|
User Profile
|
|
</h3>
|
|
<p class="text-gray-400 text-sm">
|
|
View and edit your personal information
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Dashboard Tile */}
|
|
<button
|
|
onClick={() => {
|
|
localStorage.setItem("lock-route", "/language/dashboard");
|
|
globalThis.location.href = "/language/dashboard";
|
|
}}
|
|
type="button"
|
|
id="dashboard"
|
|
class="group relative overflow-hidden bg-gradient-to-br from-teal-900/20 to-cyan-900/20 border border-teal-500/30 rounded-xl p-6 backdrop-blur-sm hover:border-teal-400/60 transition-all duration-300 hover:shadow-[0_0_30px_rgba(107,206,220,0.3)]"
|
|
>
|
|
<div class="absolute inset-0 bg-teal-400/5 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
|
</div>
|
|
<div class="relative z-10">
|
|
<div class="w-12 h-12 bg-teal-400/20 rounded-lg flex items-center justify-center mb-4 group-hover:bg-teal-400/30 transition-colors">
|
|
<svg
|
|
class="w-6 h-6 text-teal-400"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
|
|
>
|
|
</path>
|
|
</svg>
|
|
</div>
|
|
<h3 class="text-xl font-bold text-teal-400 mb-2">Dashboard</h3>
|
|
<p class="text-gray-400 text-sm">
|
|
View analytics and system overview
|
|
</p>
|
|
</div>
|
|
</button>
|
|
{/* Financial Calendar Tile */}
|
|
<a
|
|
href={`/calendar/financial-calendar/${new Date().getFullYear()}/${
|
|
(new Date().getMonth() + 1).toString().padStart(2, "0")
|
|
}?`}
|
|
id="financial-calendar"
|
|
class="group relative overflow-hidden bg-linear-to-br from-teal-900/20 to-cyan-900/20 border border-teal-500/30 rounded-xl p-6 backdrop-blur-sm hover:border-teal-400/60 transition-all duration-300 hover:shadow-[0_0_30px_rgba(107,206,220,0.3)]"
|
|
>
|
|
<div class="absolute inset-0 bg-teal-400/5 opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
|
</div>
|
|
<div class="relative z-10">
|
|
<div class="w-12 h-12 bg-teal-400/20 rounded-lg flex items-center justify-center mb-4 group-hover:bg-teal-400/30 transition-colors">
|
|
<svg
|
|
class="w-6 h-6 text-teal-400"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M8 7V3m8 4V3M5 11h14M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5A2 2 0 003 7v12a2 2 0 002 2z"
|
|
>
|
|
</path>
|
|
</svg>
|
|
</div>
|
|
<h3 class="text-xl font-bold text-teal-400 mb-2">
|
|
Financial Calendar
|
|
</h3>
|
|
<p class="text-gray-400 text-sm">
|
|
See your incomes and obligations by day
|
|
</p>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Glitch effect container */}
|
|
<div class="relative inline-block mb-8">
|
|
<div class="absolute inset-0 bg-teal-400 opacity-20 blur-xl animate-pulse">
|
|
</div>
|
|
<div class="relative bg-linear-to-r from-teal-500/20 to-cyan-500/20 border border-teal-500/50 rounded-lg px-8 py-4 backdrop-blur-sm">
|
|
<p class="text-teal-300 text-sm font-mono">
|
|
> Loading applications...
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Animated scanlines */}
|
|
<div class="absolute inset-0 pointer-events-none">
|
|
<div
|
|
class="absolute inset-0 bg-linear-to-b from-transparent via-teal-500/5 to-transparent animate-pulse"
|
|
style={{
|
|
animation: "scan 3s linear infinite",
|
|
backgroundSize: "100% 3px",
|
|
}}
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Floating Tile Buttons */}
|
|
<FloatingTileButtons ids={tileIds} />
|
|
|
|
<style jsx>
|
|
{`
|
|
@keyframes scan {
|
|
0% { transform: translateY(-100%); }
|
|
100% { transform: translateY(100vh); }
|
|
}
|
|
|
|
@keyframes float-teal-1 {
|
|
0% {
|
|
transform: translate(-300px, -300px) rotate(0deg) scale(0.4);
|
|
opacity: 0;
|
|
}
|
|
25% {
|
|
transform: translate(-150px, -150px) rotate(90deg) scale(0.7);
|
|
opacity: 0.3;
|
|
}
|
|
50% {
|
|
transform: translate(0px, 0px) rotate(180deg) scale(1.1);
|
|
opacity: 0.5;
|
|
}
|
|
75% {
|
|
transform: translate(150px, 150px) rotate(270deg) scale(0.7);
|
|
opacity: 0.3;
|
|
}
|
|
100% {
|
|
transform: translate(300px, 300px) rotate(360deg) scale(0.4);
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
@keyframes float-teal-2 {
|
|
0% {
|
|
transform: translate(300px, -300px) rotate(0deg) scale(0.4);
|
|
opacity: 0;
|
|
}
|
|
25% {
|
|
transform: translate(150px, -150px) rotate(-90deg) scale(0.7);
|
|
opacity: 0.3;
|
|
}
|
|
50% {
|
|
transform: translate(0px, 0px) rotate(-180deg) scale(1.1);
|
|
opacity: 0.5;
|
|
}
|
|
75% {
|
|
transform: translate(-150px, 150px) rotate(-270deg) scale(0.7);
|
|
opacity: 0.3;
|
|
}
|
|
100% {
|
|
transform: translate(-300px, 300px) rotate(-360deg) scale(0.4);
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
@keyframes float-teal-3 {
|
|
0% {
|
|
transform: translate(0px, 300px) rotate(0deg) scale(0.4);
|
|
opacity: 0;
|
|
}
|
|
25% {
|
|
transform: translate(0px, 150px) rotate(180deg) scale(0.7);
|
|
opacity: 0.3;
|
|
}
|
|
50% {
|
|
transform: translate(0px, 0px) rotate(360deg) scale(1.1);
|
|
opacity: 0.5;
|
|
}
|
|
75% {
|
|
transform: translate(0px, -150px) rotate(540deg) scale(0.7);
|
|
opacity: 0.3;
|
|
}
|
|
100% {
|
|
transform: translate(0px, -300px) rotate(720deg) scale(0.4);
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
.animate-float-teal-1 {
|
|
animation: float-teal-1 12s ease-in-out infinite;
|
|
top: 50%;
|
|
left: 50%;
|
|
margin-top: -24px;
|
|
margin-left: -24px;
|
|
}
|
|
|
|
.animate-float-teal-2 {
|
|
animation: float-teal-2 15s ease-in-out infinite;
|
|
top: 50%;
|
|
left: 50%;
|
|
margin-top: -28px;
|
|
margin-left: -28px;
|
|
}
|
|
|
|
.animate-float-teal-3 {
|
|
animation: float-teal-3 18s ease-in-out infinite;
|
|
top: 50%;
|
|
left: 50%;
|
|
margin-top: -26px;
|
|
margin-left: -26px;
|
|
}
|
|
|
|
.spin-on-hover {
|
|
transition: transform 0.3s ease;
|
|
animation-play-state: paused;
|
|
}
|
|
|
|
.spin-direction-cw:hover {
|
|
animation: spin-cw 4s linear infinite;
|
|
}
|
|
|
|
.spin-direction-ccw:hover {
|
|
animation: spin-ccw 4s linear infinite;
|
|
}
|
|
|
|
@keyframes spin-cw {
|
|
from { transform: rotate(0deg); }
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
@keyframes spin-ccw {
|
|
from { transform: rotate(0deg); }
|
|
to { transform: rotate(-360deg); }
|
|
}
|
|
`}
|
|
</style>
|
|
</div>
|
|
);
|
|
}
|