gui fixes

This commit is contained in:
Rodolfo Martinez 2025-12-09 22:02:14 -05:00
parent 3bf89fe2fa
commit b3e87772ec
4 changed files with 81 additions and 338 deletions

View file

@ -7,9 +7,9 @@
<!-- Security Headers (Defense in Depth - Backend should also set HTTP headers) --> <!-- Security Headers (Defense in Depth - Backend should also set HTTP headers) -->
<!-- Note: connect-src includes localhost:8000 (API), localhost:8334 (MinIO/S3 dev), and production S3 endpoints --> <!-- Note: connect-src includes localhost:8000 (API), localhost:8334 (MinIO/S3 dev), and production S3 endpoints -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' http://localhost:8000 http://localhost:8334 https://*.digitaloceanspaces.com https://*.amazonaws.com ws://localhost:*; frame-ancestors 'none'; base-uri 'self'; form-action 'self';"> <!-- Note: frame-ancestors and X-Frame-Options must be set via HTTP headers, not meta tags -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' http://localhost:8000 http://localhost:8334 https://*.digitaloceanspaces.com https://*.amazonaws.com ws://localhost:*; base-uri 'self'; form-action 'self';">
<meta http-equiv="X-Content-Type-Options" content="nosniff"> <meta http-equiv="X-Content-Type-Options" content="nosniff">
<meta http-equiv="X-Frame-Options" content="DENY">
<meta name="referrer" content="strict-origin-when-cross-origin"> <meta name="referrer" content="strict-origin-when-cross-origin">
<meta http-equiv="Permissions-Policy" content="geolocation=(), microphone=(), camera=()"> <meta http-equiv="Permissions-Policy" content="geolocation=(), microphone=(), camera=()">

View file

@ -1,7 +1,7 @@
// File Path: web/frontend/src/components/Layout/Layout.jsx // File Path: web/frontend/src/components/Layout/Layout.jsx
// Fixed Layout Component - Mobile Menu Now Works Properly // Fixed Layout Component - Mobile Menu Now Works Properly
import React, { useState, useEffect, useCallback } from "react"; import React, { useState, useEffect, useCallback, useRef } from "react";
import TopNavbar from "./TopNavbar"; import TopNavbar from "./TopNavbar";
import Sidebar from "./Sidebar"; import Sidebar from "./Sidebar";
import { useInactivityTimeout } from "../../hooks/useInactivityTimeout"; import { useInactivityTimeout } from "../../hooks/useInactivityTimeout";
@ -292,7 +292,16 @@ function Layout({ children }) {
return () => window.removeEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize);
}, [isMobile]); // Include isMobile but use functional state updates to avoid loops }, [isMobile]); // Include isMobile but use functional state updates to avoid loops
// Track last toggle time to prevent double-clicks
const lastToggleRef = useRef(0);
const handleMenuToggle = useCallback(() => { const handleMenuToggle = useCallback(() => {
const now = Date.now();
// Prevent double-clicks only (100ms window)
if (now - lastToggleRef.current < 100) {
return;
}
lastToggleRef.current = now;
setIsSidebarOpen(current => !current); setIsSidebarOpen(current => !current);
}, []); }, []);
@ -300,8 +309,17 @@ function Layout({ children }) {
setIsSidebarOpen(false); setIsSidebarOpen(false);
}, []); }, []);
// Track last collapse toggle time to prevent double-clicks
const lastCollapseToggleRef = useRef(0);
// Handle collapse toggle for desktop view // Handle collapse toggle for desktop view
const handleCollapseToggle = useCallback(() => { const handleCollapseToggle = useCallback(() => {
const now = Date.now();
// Prevent double-clicks only (100ms window)
if (now - lastCollapseToggleRef.current < 100) {
return;
}
lastCollapseToggleRef.current = now;
setSidebarCollapsed(current => { setSidebarCollapsed(current => {
const newCollapsedState = !current; const newCollapsedState = !current;
localStorage.setItem("sidebarCollapsed", newCollapsedState.toString()); localStorage.setItem("sidebarCollapsed", newCollapsedState.toString());

View file

@ -11,6 +11,7 @@ import {
UserCircleIcon, UserCircleIcon,
ArrowRightOnRectangleIcon, ArrowRightOnRectangleIcon,
ChevronDownIcon, ChevronDownIcon,
LockClosedIcon,
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
import { Modal, Button, useUIXTheme } from "../UIX"; import { Modal, Button, useUIXTheme } from "../UIX";
@ -248,12 +249,20 @@ function TopNavbar({
${isMobile ? "justify-center" : "justify-start ml-4"} ${isMobile ? "justify-center" : "justify-start ml-4"}
`} `}
> >
<img <Link
src="/img/compressed-logo.png" to="/dashboard"
alt="MapleFile Logo" className="flex items-center gap-2 hover:opacity-90 transition-opacity"
className="h-8 w-auto sm:h-9 md:h-10" aria-label="Go to Dashboard"
draggable="false" >
/> {/* Lock icon with theme-aware background */}
<div className={`${getThemeClasses("bg-gradient-secondary")} p-1.5 sm:p-2 rounded-lg`}>
<LockClosedIcon className="h-5 w-5 sm:h-6 sm:w-6 text-white" aria-hidden="true" />
</div>
{/* MapleFile text - hidden on mobile */}
<span className="hidden sm:inline text-white font-semibold text-base md:text-lg tracking-tight">
MapleFile
</span>
</Link>
</div> </div>
{/* Spacer for mobile to balance the layout */} {/* Spacer for mobile to balance the layout */}

View file

@ -6,13 +6,10 @@ import Layout from "../../../components/Layout/Layout";
import { DetailLiteView, Button, useUIXTheme } from "../../../components/UIX"; import { DetailLiteView, Button, useUIXTheme } from "../../../components/UIX";
import { import {
ArrowDownTrayIcon, ArrowDownTrayIcon,
InformationCircleIcon,
ComputerDesktopIcon, ComputerDesktopIcon,
ShieldCheckIcon,
DocumentTextIcon,
CodeBracketIcon,
HomeIcon, HomeIcon,
UserIcon, UserIcon,
ArrowTopRightOnSquareIcon,
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
// Constant empty object to prevent re-renders (reused across renders) // Constant empty object to prevent re-renders (reused across renders)
@ -21,34 +18,6 @@ const EMPTY_ENTITY_DATA = {};
const ExportData = () => { const ExportData = () => {
const { getThemeClasses } = useUIXTheme(); const { getThemeClasses } = useUIXTheme();
// Theme-aware section styles
const sectionStyles = useMemo(() => ({
success: {
container: getThemeClasses("export-section-success-bg"),
border: getThemeClasses("export-section-success-border"),
icon: getThemeClasses("export-section-success-icon"),
title: getThemeClasses("export-section-success-title"),
text: getThemeClasses("export-section-success-text"),
muted: getThemeClasses("export-section-success-muted"),
},
info: {
container: getThemeClasses("export-section-info-bg"),
border: getThemeClasses("export-section-info-border"),
icon: getThemeClasses("export-section-info-icon"),
title: getThemeClasses("export-section-info-title"),
text: getThemeClasses("export-section-info-text"),
},
warning: {
container: getThemeClasses("export-section-warning-bg"),
border: getThemeClasses("export-section-warning-border"),
icon: getThemeClasses("export-section-warning-icon"),
title: getThemeClasses("export-section-warning-title"),
text: getThemeClasses("export-section-warning-text"),
muted: getThemeClasses("export-section-warning-muted"),
code: getThemeClasses("export-section-warning-code"),
},
}), [getThemeClasses]);
// Breadcrumb configuration // Breadcrumb configuration
const breadcrumbItems = useMemo( const breadcrumbItems = useMemo(
() => [ () => [
@ -74,7 +43,7 @@ const ExportData = () => {
const headerConfig = useMemo( const headerConfig = useMemo(
() => ({ () => ({
title: "Export Your Data", title: "Export Your Data",
subtitle: "Download a complete copy of your account data in accordance with GDPR Article 20 (Right to Data Portability)", subtitle: "Download a complete copy of your account data (GDPR Article 20)",
icon: ArrowDownTrayIcon, icon: ArrowDownTrayIcon,
}), }),
[], [],
@ -83,203 +52,36 @@ const ExportData = () => {
// Content sections // Content sections
const contentSections = useMemo(() => { const contentSections = useMemo(() => {
return ( return (
<div className="space-y-6" role="main" aria-label="Export data options"> <div className="space-y-6" role="main" aria-label="Export data information">
{/* Quick Export Option - GDPR Compliance */} {/* Desktop App Required Notice */}
<section <section
aria-labelledby="quick-export-heading" aria-labelledby="desktop-required-heading"
className={`${sectionStyles.success.container} border-2 ${sectionStyles.success.border} rounded-xl p-6`} className={`${getThemeClasses("bg-accent-light")} border-2 ${getThemeClasses("border-accent")} rounded-xl p-6`}
> >
<div className="flex items-start"> <div className="flex items-start">
<ArrowDownTrayIcon className={`h-6 w-6 ${sectionStyles.success.icon} mr-3 flex-shrink-0 mt-0.5`} aria-hidden="true" /> <ComputerDesktopIcon className={`h-8 w-8 ${getThemeClasses("text-accent")} mr-4 flex-shrink-0 mt-0.5`} aria-hidden="true" />
<div className="flex-1"> <div className="flex-1">
<h3 id="quick-export-heading" className={`text-lg font-semibold ${sectionStyles.success.title} mb-2`}> <h3 id="desktop-required-heading" className={`text-xl font-semibold ${getThemeClasses("text-primary")} mb-3`}>
Quick Export (Web Browser) Desktop App Required
</h3> </h3>
<p className={`${sectionStyles.success.text} mb-3`}> <p className={`${getThemeClasses("text-secondary")} mb-4 text-base`}>
In compliance with GDPR Article 20, you can export your data directly from your web browser. This option is suitable for accounts with moderate amounts of data. Data export is only available through the <strong>MapleFile Desktop Application</strong>. Due to end-to-end encryption, your files can only be decrypted and exported using the native app.
</p>
<p className={`${sectionStyles.success.text} mb-4`}>
<strong>What gets exported:</strong> Profile information, collection metadata, and file lists (metadata only - actual file downloads require desktop app due to E2EE).
</p>
<div className="flex space-x-3">
<Button
variant="primary"
icon={ArrowDownTrayIcon}
onClick={() => alert('This feature will be implemented by the backend team. It should generate a JSON export of user profile, collections, and file metadata.')}
>
Export Metadata (JSON)
</Button>
</div>
<p className={`text-xs ${sectionStyles.success.muted} mt-3`}>
<strong>Timeline:</strong> Your export will be available for download within 24 hours. Large exports may take up to 72 hours. You'll receive an email when ready.
</p>
</div>
</div>
</section>
{/* Information Notice */}
<section
aria-labelledby="desktop-info-heading"
className={`${sectionStyles.info.container} border-2 ${sectionStyles.info.border} rounded-xl p-6`}
>
<div className="flex items-start">
<InformationCircleIcon className={`h-6 w-6 ${sectionStyles.info.icon} mr-3 flex-shrink-0 mt-0.5`} aria-hidden="true" />
<div className="flex-1">
<h3 id="desktop-info-heading" className={`text-lg font-semibold ${sectionStyles.info.title} mb-2`}>
Desktop Application for Complete Export
</h3>
<p className={`${sectionStyles.info.text} mb-3`}>
For exporting <strong>complete data including decrypted files</strong>, we recommend using the <strong>MapleFile Desktop Application</strong>. Due to end-to-end encryption, only the desktop app can decrypt and export your actual file contents.
</p>
<p className={sectionStyles.info.text}>
The desktop application efficiently manages the decryption and download of your complete data archive with all files in their original format.
</p>
</div>
</div>
</section>
{/* E2EE Explanation */}
<section
aria-labelledby="e2ee-heading"
className={`${getThemeClasses("bg-card")} rounded-xl shadow-lg border ${getThemeClasses("border-secondary")} p-6`}
>
<div className="flex items-start mb-4">
<ShieldCheckIcon className={`h-6 w-6 ${getThemeClasses("icon-success")} mr-3 flex-shrink-0 mt-0.5`} aria-hidden="true" />
<div>
<h3 id="e2ee-heading" className={`text-lg font-semibold mb-2 ${getThemeClasses("text-primary")}`}>
Why End-to-End Encryption Matters
</h3>
<p className={`${getThemeClasses("text-secondary")} mb-3`}>
Your files are encrypted on your device before they're uploaded to our servers. This means:
</p>
<ul className={`space-y-2 ${getThemeClasses("text-secondary")}`} role="list" aria-label="Encryption benefits">
<li className="flex items-start">
<span className={`${getThemeClasses("icon-success")} mr-2`} aria-hidden="true"></span>
<span>Only you can decrypt and read your files</span>
</li>
<li className="flex items-start">
<span className={`${getThemeClasses("icon-success")} mr-2`} aria-hidden="true"></span>
<span>MapleFile servers cannot access your file contents</span>
</li>
<li className="flex items-start">
<span className={`${getThemeClasses("icon-success")} mr-2`} aria-hidden="true"></span>
<span>Your privacy is protected even if servers are compromised</span>
</li>
<li className="flex items-start">
<span className={`${getThemeClasses("icon-success")} mr-2`} aria-hidden="true"></span>
<span>Decryption requires your local encryption keys</span>
</li>
</ul>
</div>
</div>
</section>
{/* Desktop Application Installation */}
<section
aria-labelledby="install-heading"
className={`${getThemeClasses("bg-card")} rounded-xl shadow-lg border ${getThemeClasses("border-secondary")} p-6`}
>
<div className="flex items-start mb-4">
<ComputerDesktopIcon className={`h-6 w-6 mr-3 flex-shrink-0 mt-0.5 ${getThemeClasses("link-primary")}`} aria-hidden="true" />
<div className="flex-1">
<h3 id="install-heading" className={`text-lg font-semibold mb-2 ${getThemeClasses("text-primary")}`}>
Install MapleFile Desktop Application
</h3>
<p className={`${getThemeClasses("text-secondary")} mb-4`}>
The MapleFile Desktop Application is a native application that can export your data with full decryption support and an intuitive user interface.
</p>
{/* Repository Link */}
<div className={`${getThemeClasses("bg-muted")} border ${getThemeClasses("border-secondary")} rounded-lg p-4 mb-4`}>
<div className="flex items-start">
<CodeBracketIcon className={`h-5 w-5 ${getThemeClasses("text-muted")} mr-3 mt-0.5`} aria-hidden="true" />
<div className="flex-1">
<p className={`text-sm font-medium mb-2 ${getThemeClasses("text-primary")}`}>
Source Code & Installation Instructions
</p> </p>
<a <a
href="https://codeberg.org/mapleopentech/monorepo/src/branch/main/native/desktop/maplefile" href="https://maplefile.com"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className={`${getThemeClasses("link-primary")} hover:underline text-sm font-mono break-all`} aria-label="Download MapleFile Desktop App (opens in new tab)"
> >
https://codeberg.org/mapleopentech/monorepo/src/branch/main/native/desktop/maplefile <Button
variant="primary"
icon={ArrowTopRightOnSquareIcon}
>
Download at maplefile.com
</Button>
</a> </a>
</div> </div>
</div> </div>
</div>
{/* Installation Steps */}
<div className="space-y-3">
<h4 className={`font-medium ${getThemeClasses("text-primary")}`}>Installation Steps:</h4>
<ol className={`space-y-3 ${getThemeClasses("text-secondary")}`} role="list" aria-label="Installation steps">
<li className="flex items-start">
<span className={`font-semibold mr-3 min-w-[24px] ${getThemeClasses("link-primary")}`} aria-hidden="true">1.</span>
<span>Visit the repository link above</span>
</li>
<li className="flex items-start">
<span className={`font-semibold mr-3 min-w-[24px] ${getThemeClasses("link-primary")}`} aria-hidden="true">2.</span>
<span>Follow the installation instructions in the README.md file</span>
</li>
<li className="flex items-start">
<span className={`font-semibold mr-3 min-w-[24px] ${getThemeClasses("link-primary")}`} aria-hidden="true">3.</span>
<span>Install the desktop application for your operating system (Windows, macOS, or Linux)</span>
</li>
<li className="flex items-start">
<span className={`font-semibold mr-3 min-w-[24px] ${getThemeClasses("link-primary")}`} aria-hidden="true">4.</span>
<span>Log in to your MapleFile account using the desktop application</span>
</li>
<li className="flex items-start">
<span className={`font-semibold mr-3 min-w-[24px] ${getThemeClasses("link-primary")}`} aria-hidden="true">5.</span>
<span>Use the export feature to download and decrypt your data</span>
</li>
</ol>
</div>
</div>
</div>
</section>
{/* What Gets Exported */}
<section
aria-labelledby="exported-data-heading"
className={`${getThemeClasses("bg-card")} rounded-xl shadow-lg border ${getThemeClasses("border-secondary")} p-6`}
>
<div className="flex items-start">
<DocumentTextIcon className={`h-6 w-6 ${getThemeClasses("text-muted")} mr-3 flex-shrink-0 mt-0.5`} aria-hidden="true" />
<div className="flex-1">
<h3 id="exported-data-heading" className={`text-lg font-semibold mb-3 ${getThemeClasses("text-primary")}`}>
What Data Will Be Exported
</h3>
<p className={`${getThemeClasses("text-secondary")} mb-3`}>
The desktop application will export a complete archive of your MapleFile account, including:
</p>
<ul className={`space-y-2 ${getThemeClasses("text-secondary")}`} role="list" aria-label="Exported data types">
<li className="flex items-start">
<span className={`mr-2 ${getThemeClasses("link-primary")}`} aria-hidden="true"></span>
<span><strong>Profile Information:</strong> Your account details, settings, and preferences</span>
</li>
<li className="flex items-start">
<span className={`mr-2 ${getThemeClasses("link-primary")}`} aria-hidden="true"></span>
<span><strong>Collections (Folders):</strong> All your collection metadata and structure</span>
</li>
<li className="flex items-start">
<span className={`mr-2 ${getThemeClasses("link-primary")}`} aria-hidden="true"></span>
<span><strong>Files:</strong> All uploaded files, fully decrypted and in their original format</span>
</li>
<li className="flex items-start">
<span className={`mr-2 ${getThemeClasses("link-primary")}`} aria-hidden="true"></span>
<span><strong>File Metadata:</strong> Creation dates, modification dates, file sizes, and descriptions</span>
</li>
<li className="flex items-start">
<span className={`mr-2 ${getThemeClasses("link-primary")}`} aria-hidden="true"></span>
<span><strong>Sharing Information:</strong> Details about collections shared with you or by you</span>
</li>
</ul>
<p className={`${getThemeClasses("text-muted")} text-sm mt-4`}>
The export will be provided as a ZIP archive with a structured folder hierarchy matching your collections.
</p>
</div>
</div>
</section> </section>
{/* GDPR Information */} {/* GDPR Information */}
@ -290,99 +92,13 @@ const ExportData = () => {
<h3 id="gdpr-heading" className={`text-sm font-semibold mb-2 ${getThemeClasses("text-primary")}`}> <h3 id="gdpr-heading" className={`text-sm font-semibold mb-2 ${getThemeClasses("text-primary")}`}>
GDPR Article 20 - Right to Data Portability GDPR Article 20 - Right to Data Portability
</h3> </h3>
<p className={`text-sm ${getThemeClasses("text-secondary")} mb-2`}> <p className={`text-sm ${getThemeClasses("text-secondary")}`}>
You have the right to receive the personal data concerning you, which you have provided to us, in a structured, commonly used and machine-readable format. You also have the right to transmit those data to another controller. You have the right to receive your personal data in a structured, commonly used, and machine-readable format. The MapleFile Desktop Application provides your complete data archive in standard formats.
</p>
<p className={`text-sm ${getThemeClasses("text-muted")}`}>
The MapleFile Desktop Application ensures compliance with this right by providing your complete data archive in standard formats (JSON metadata, original file formats).
</p> </p>
</section> </section>
{/* Security Notice */}
<section
aria-labelledby="security-heading"
className={`${sectionStyles.warning.container} border-2 ${sectionStyles.warning.border} rounded-xl p-6`}
>
<div className="flex items-start">
<ShieldCheckIcon className={`h-6 w-6 ${sectionStyles.warning.icon} mr-3 flex-shrink-0 mt-0.5`} aria-hidden="true" />
<div className="flex-1">
<h3 id="security-heading" className={`text-lg font-semibold ${sectionStyles.warning.title} mb-2`}>
Security: Verify Your Download
</h3>
<p className={`${sectionStyles.warning.text} mb-3`}>
For your security, always verify the authenticity of downloaded software:
</p>
<ul className={`space-y-2 ${sectionStyles.warning.text}`} role="list" aria-label="Security verification steps">
<li className="flex items-start">
<span className={`${sectionStyles.warning.icon} mr-2`} aria-hidden="true"></span>
<span>Only download from the official Codeberg repository linked above</span>
</li>
<li className="flex items-start">
<span className={`${sectionStyles.warning.icon} mr-2`} aria-hidden="true"></span>
<span>Verify the repository URL matches: <code className={`${sectionStyles.warning.code} px-1 rounded`}>codeberg.org/mapleopentech</code></span>
</li>
<li className="flex items-start">
<span className={`${sectionStyles.warning.icon} mr-2`} aria-hidden="true"></span>
<span>Check that your browser shows a secure HTTPS connection (padlock icon)</span>
</li>
<li className="flex items-start">
<span className={`${sectionStyles.warning.icon} mr-2`} aria-hidden="true"></span>
<span>Review the release notes and commit history before installation</span>
</li>
</ul>
<p className={`text-sm ${sectionStyles.warning.muted} mt-3`}>
<strong>Warning:</strong> Never download MapleFile software from unofficial sources or third-party websites.
</p>
</div>
</div>
</section>
{/* Support Section */}
<section
aria-labelledby="support-heading"
className={`${getThemeClasses("bg-card")} rounded-xl shadow-lg border ${getThemeClasses("border-secondary")} p-6`}
>
<h3 id="support-heading" className={`text-lg font-semibold mb-3 ${getThemeClasses("text-primary")}`}>
Need Help?
</h3>
<p className={`${getThemeClasses("text-secondary")} mb-3`}>
If you encounter any issues installing or using the MapleFile Desktop Application, please:
</p>
<ul className={`space-y-2 ${getThemeClasses("text-secondary")}`} role="list" aria-label="Support options">
<li className="flex items-start">
<span className={`mr-2 ${getThemeClasses("link-primary")}`} aria-hidden="true"></span>
<span>Check the README.md file in the repository for detailed documentation</span>
</li>
<li className="flex items-start">
<span className={`mr-2 ${getThemeClasses("link-primary")}`} aria-hidden="true"></span>
<span>Open an issue on the Codeberg repository for technical support</span>
</li>
<li className="flex items-start">
<span className={`mr-2 ${getThemeClasses("link-primary")}`} aria-hidden="true"></span>
<span>Review the application's help documentation and user guide</span>
</li>
</ul>
</section>
{/* Action Button */}
<div className="flex justify-center py-6">
<a
href="https://codeberg.org/mapleopentech/monorepo/src/branch/main/native/desktop/maplefile"
target="_blank"
rel="noopener noreferrer"
aria-label="Go to MapleFile Desktop Application Repository (opens in new tab)"
>
<Button
variant="primary"
icon={CodeBracketIcon}
>
Go to MapleFile Desktop Application Repository
</Button>
</a>
</div>
</div> </div>
); );
}, [getThemeClasses, sectionStyles]); }, [getThemeClasses]);
// Field sections for DetailLiteView // Field sections for DetailLiteView
const fieldSections = useMemo(() => { const fieldSections = useMemo(() => {