From c281f1e9ea16b99abd55df76f9640cd23b880061 Mon Sep 17 00:00:00 2001 From: retoor Date: Sun, 9 Nov 2025 19:09:51 +0100 Subject: [PATCH] Update. --- retoors/routes.py | 19 +++ retoors/services/file_service.py | 101 +++++++----- retoors/static/css/base.css | 96 +++++++++-- retoors/static/css/components/dashboard.css | 45 +++++- retoors/static/css/components/form.css | 105 ++++++++++++ retoors/static/css/components/index.css | 159 ++++++++++++++++--- retoors/static/css/components/login.css | 51 ++++-- retoors/static/css/components/order.css | 55 +++++++ retoors/static/css/components/register.css | 51 ++++-- retoors/static/css/components/support.css | 46 ++++++ retoors/static/css/components/use_cases.css | 61 +++++-- retoors/static/js/main.js | 103 +++++++----- retoors/templates/components/navigation.html | 4 +- retoors/templates/layouts/dashboard.html | 1 + retoors/templates/pages/file_browser.html | 40 ++++- 15 files changed, 774 insertions(+), 163 deletions(-) diff --git a/retoors/routes.py b/retoors/routes.py index 1a45ca5..233346d 100644 --- a/retoors/routes.py +++ b/retoors/routes.py @@ -5,6 +5,16 @@ from .views.migrate import MigrateView from .views.admin import get_users, add_user, update_user_quota, delete_user, get_user_details, delete_team from .views.editor import FileEditorView, FileContentView from .views.viewer import ViewerView +from .views.sharing import ( + create_share_page, + create_share_handler, + view_share, + download_shared_file, + manage_shares, + get_share_details, + update_share, + get_item_shares +) def setup_routes(app): app.router.add_view("/login", LoginView, name="login") @@ -56,3 +66,12 @@ def setup_routes(app): app.router.add_delete("/api/users/{email}", delete_user, name="api_delete_user") app.router.add_get("/api/users/{email}", get_user_details, name="api_get_user_details") app.router.add_delete("/api/teams/{parent_email}", delete_team, name="api_delete_team") + + app.router.add_get("/sharing/create", create_share_page, name="create_share_page") + app.router.add_post("/api/sharing/create", create_share_handler, name="create_share") + app.router.add_get("/share/{share_id}", view_share, name="view_share") + app.router.add_get("/share/{share_id}/download", download_shared_file, name="download_share") + app.router.add_get("/sharing/manage", manage_shares, name="manage_shares") + app.router.add_get("/api/sharing/{share_id}", get_share_details, name="get_share_details") + app.router.add_put("/api/sharing/{share_id}", update_share, name="update_share") + app.router.add_get("/api/sharing/item/shares", get_item_shares, name="get_item_shares") diff --git a/retoors/services/file_service.py b/retoors/services/file_service.py index cbb2158..6f0a59e 100644 --- a/retoors/services/file_service.py +++ b/retoors/services/file_service.py @@ -8,6 +8,7 @@ import logging import hashlib import os from .storage_service import StorageService +from .sharing_service import SharingService logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) @@ -21,6 +22,7 @@ class FileService: self.base_dir = base_dir self.user_service = user_service self.storage = StorageService() + self.sharing_service = SharingService() self.base_dir.mkdir(parents=True, exist_ok=True) self.drives_dir = self.base_dir / "drives" self.drives_dir.mkdir(exist_ok=True) @@ -225,77 +227,92 @@ class FileService: logger.info(f"delete_item: Item deleted: {item_path}") return True - async def generate_share_link(self, user_email: str, item_path: str) -> str | None: - """Generates a shareable link for a file or folder.""" + async def generate_share_link( + self, + user_email: str, + item_path: str, + permission: str = "view", + scope: str = "public", + password: str = None, + expiration_days: int = None, + disable_download: bool = False, + recipient_emails: list = None + ) -> str | None: + logger.debug(f"generate_share_link: Generating link for user '{user_email}', item '{item_path}'") metadata = await self._load_metadata(user_email) if item_path not in metadata: logger.warning(f"generate_share_link: Item does not exist: {item_path}") return None - user = await self.user_service.get_user_by_email(user_email) - if not user: - logger.warning(f"generate_share_link: User not found: {user_email}") - return None - share_id = str(uuid.uuid4()) - if "shared_items" not in user: - user["shared_items"] = {} - user["shared_items"][share_id] = { - "user_email": user_email, - "item_path": item_path, - "created_at": datetime.datetime.now(datetime.timezone.utc).isoformat(), - "expires_at": (datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=7)).isoformat(), # 7-day expiry - } - await self.user_service.update_user(user_email, shared_items=user["shared_items"]) + share_id = await self.sharing_service.create_share( + owner_email=user_email, + item_path=item_path, + permission=permission, + scope=scope, + password=password, + expiration_days=expiration_days, + disable_download=disable_download, + recipient_emails=recipient_emails + ) + logger.info(f"generate_share_link: Share link generated with ID: {share_id} for item: {item_path}") return share_id - async def get_shared_item(self, share_id: str) -> dict | None: - """Retrieves information about a shared item.""" - logger.debug(f"get_shared_item: Retrieving shared item with ID: {share_id}") - all_users = await self.user_service.get_all_users() - for user in all_users: - if "shared_items" in user and share_id in user["shared_items"]: - shared_item = user["shared_items"][share_id] - expiry_time = datetime.datetime.fromisoformat(shared_item["expires_at"]) - if expiry_time > datetime.datetime.now(datetime.timezone.utc): - logger.info(f"get_shared_item: Found valid shared item for ID: {share_id}") - return shared_item - else: - logger.warning(f"get_shared_item: Shared item {share_id} has expired.") - logger.warning(f"get_shared_item: No valid shared item found for ID: {share_id}") - return None + async def get_shared_item(self, share_id: str, password: str = None, accessor_email: str = None) -> dict | None: + + logger.debug(f"get_shared_item: Retrieving shared item with ID: {share_id}") + + share = await self.sharing_service.get_share(share_id) + if not share: + logger.warning(f"get_shared_item: No valid shared item found for ID: {share_id}") + return None + + if not await self.sharing_service.verify_share_access(share_id, password, accessor_email): + logger.warning(f"get_shared_item: Access denied for share {share_id}") + return None + + await self.sharing_service.record_share_access(share_id, accessor_email) + + logger.info(f"get_shared_item: Found valid shared item for ID: {share_id}") + return share + + async def get_shared_file_content(self, share_id: str, password: str = None, accessor_email: str = None, requested_file_path: str = None) -> tuple[bytes, str] | None: - async def get_shared_file_content(self, share_id: str, requested_file_path: str | None = None) -> tuple[bytes, str] | None: - """Retrieves the content of a shared file.""" logger.debug(f"get_shared_file_content: Retrieving content for shared file with ID: {share_id}, requested_file_path: {requested_file_path}") - shared_item = await self.get_shared_item(share_id) + + shared_item = await self.get_shared_item(share_id, password, accessor_email) if not shared_item: return None - user_email = shared_item["user_email"] - item_path = shared_item["item_path"] # This is the path of the originally shared item (file or folder) + if shared_item.get("disable_download", False): + logger.warning(f"get_shared_file_content: Download disabled for share {share_id}") + return None + + user_email = shared_item["owner_email"] + item_path = shared_item["item_path"] target_path = item_path if requested_file_path: target_path = requested_file_path - # Security check: Ensure the requested file is actually within the shared item's directory - if not target_path.startswith(item_path + '/'): + if not target_path.startswith(item_path + '/') and target_path != item_path: logger.warning(f"get_shared_file_content: Requested file path '{requested_file_path}' is not within shared item path '{item_path}' for share_id: {share_id}") return None return await self.download_file(user_email, target_path) - async def get_shared_folder_content(self, share_id: str) -> list | None: - """Retrieves the content of a shared folder.""" + async def get_shared_folder_content(self, share_id: str, password: str = None, accessor_email: str = None) -> list | None: + logger.debug(f"get_shared_folder_content: Retrieving content for shared folder with ID: {share_id}") - shared_item = await self.get_shared_item(share_id) + + shared_item = await self.get_shared_item(share_id, password, accessor_email) if not shared_item: return None - user_email = shared_item["user_email"] + user_email = shared_item["owner_email"] item_path = shared_item["item_path"] metadata = await self._load_metadata(user_email) + if item_path not in metadata or metadata[item_path]["type"] != "dir": logger.warning(f"get_shared_folder_content: Shared item is not a directory: {item_path}") return None diff --git a/retoors/static/css/base.css b/retoors/static/css/base.css index dc98158..17a0042 100644 --- a/retoors/static/css/base.css +++ b/retoors/static/css/base.css @@ -45,7 +45,7 @@ html, body { /* General typography */ h1 { - font-size: 3.5rem; + font-size: clamp(1.75rem, 5vw, 3.5rem); font-weight: 700; margin-bottom: 1.5rem; color: var(--text-color); @@ -54,7 +54,7 @@ h1 { } h2 { - font-size: 2.5rem; + font-size: clamp(1.5rem, 4vw, 2.5rem); font-weight: 700; margin-bottom: 1rem; color: var(--text-color); @@ -63,7 +63,7 @@ h2 { } h3 { - font-size: 1.5rem; + font-size: clamp(1.125rem, 3vw, 1.5rem); font-weight: 600; margin-bottom: 0.75rem; color: var(--text-color); @@ -73,30 +73,30 @@ h3 { p { margin-bottom: 1rem; color: var(--light-text-color); - font-size: 1.125rem; + font-size: clamp(0.875rem, 2vw, 1.125rem); line-height: 1.7; } /* Card-like styling for sections */ .card { background-color: var(--card-background); - border-radius: 12px; /* Slightly more rounded corners */ - box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); /* Softer, more prominent shadow */ - padding: 3rem; /* Increased padding */ - margin-bottom: 35px; /* Increased margin */ + border-radius: 12px; + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); + padding: clamp(1rem, 4vw, 3rem); + margin-bottom: clamp(20px, 3vw, 35px); } .container { flex: 1; display: flex; - flex-direction: column; /* Allow content to stack vertically */ - justify-content: flex-start; /* Align content to the top */ + flex-direction: column; + justify-content: flex-start; align-items: center; - padding: 20px; + padding: clamp(10px, 2vw, 20px); box-sizing: border-box; - width: 100%; /* Ensure container takes full width */ - max-width: 1200px; /* Max width for overall content */ - margin: 0 auto; /* Center the container */ + width: 100%; + max-width: 1200px; + margin: 0 auto; } .retoors-container { @@ -468,15 +468,15 @@ main { @media (max-width: 480px) { .brand-text { - font-size: 1.25rem; + font-size: 1.125rem; } .nav-menu { - gap: 0.75rem; + gap: 0.5rem; } .nav-menu a { - font-size: 0.85rem; + font-size: 0.8rem; } .nav-actions { @@ -490,5 +490,67 @@ main { width: 100%; max-width: 280px; text-align: center; + padding: 0.5rem 1rem; + font-size: 0.85rem; + } + + .form-container { + padding: 1.5rem; + margin: 20px auto; + } + + .form-container h2 { + font-size: 1.5rem; + } + + .form-group input[type="email"], + .form-group input[type="password"], + .form-group input[type="text"], + .form-group input[type="number"] { + padding: 12px 14px; + } + + .btn-primary, + .btn-outline { + padding: 0.5rem 1rem; + font-size: 0.85rem; + } +} + +@media (max-width: 360px) { + html, body { + font-size: 14px; + } + + .site-nav { + padding: 8px 10px; + } + + .brand-text { + font-size: 1rem; + } + + .nav-menu a { + font-size: 0.75rem; + } + + .card { + padding: 1rem; + margin-bottom: 15px; + } + + .container { + padding: 8px; + } + + .form-container { + padding: 1rem; + margin: 15px auto; + } + + .btn-primary, + .btn-outline { + padding: 0.45rem 0.85rem; + font-size: 0.8rem; } } \ No newline at end of file diff --git a/retoors/static/css/components/dashboard.css b/retoors/static/css/components/dashboard.css index 915c112..e59c081 100644 --- a/retoors/static/css/components/dashboard.css +++ b/retoors/static/css/components/dashboard.css @@ -300,11 +300,54 @@ .file-list-table th, .file-list-table td { - padding: 10px 12px; + padding: 8px 10px; font-size: 0.85rem; } .file-list-table td { max-width: 150px; } +} + +@media (max-width: 360px) { + .dashboard-layout { + padding: 8px; + } + + .dashboard-sidebar { + padding: 12px; + } + + .dashboard-content { + padding: 12px; + } + + .dashboard-content-header h2 { + font-size: 1.25rem; + } + + .dashboard-actions .btn-primary, + .dashboard-actions .btn-outline { + padding: 0.5rem 0.8rem; + font-size: 0.8rem; + } + + .file-list-table { + font-size: 0.75rem; + } + + .file-list-table th, + .file-list-table td { + padding: 6px 8px; + font-size: 0.75rem; + } + + .file-list-table td { + max-width: 120px; + } + + .file-search-bar { + padding: 8px 12px; + font-size: 0.9rem; + } } \ No newline at end of file diff --git a/retoors/static/css/components/form.css b/retoors/static/css/components/form.css index 8b13789..33cc448 100644 --- a/retoors/static/css/components/form.css +++ b/retoors/static/css/components/form.css @@ -1 +1,106 @@ +/* Styles for Form Pages */ +.form-page-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + padding: clamp(30px, 5vh, 60px) clamp(15px, 3vw, 20px) clamp(40px, 6vh, 80px); + box-sizing: border-box; + width: 100%; + max-width: 100%; +} + +.form-page-container h1 { + font-size: clamp(1.75rem, 4vw, 2.5rem); + color: var(--text-color); + margin-bottom: 10px; + text-align: center; +} + +.form-page-container .subtitle { + font-size: clamp(0.95rem, 2vw, 1.1rem); + color: var(--light-text-color); + margin-bottom: clamp(20px, 4vh, 30px); + text-align: center; + max-width: 600px; +} + +.form-page-container .form-container { + max-width: 450px; + width: 100%; + padding: clamp(1.5rem, 4vw, 2.5rem); + box-sizing: border-box; + margin-bottom: clamp(20px, 4vh, 30px); +} + +.login-link { + display: block; + text-align: center; + margin-top: 25px; + font-size: 0.95rem; + color: var(--light-text-color); +} + +.login-link a { + color: var(--primary-color); + font-weight: 500; +} + +.login-link a:hover { + color: var(--dutch-red); + text-decoration: underline; +} + +.message { + color: #2E7D32; + background-color: #E8F5E9; + border: 1px solid #81C784; + padding: 1rem 1.5rem; + margin-bottom: 1.5rem; + border-radius: 8px; + font-size: 0.95rem; + font-weight: 500; + text-align: center; +} + +/* Responsive adjustments */ +@media (max-width: 600px) { + .form-page-container { + padding: 30px 15px 40px; + } + .form-page-container h1 { + font-size: 1.75rem; + } + .form-page-container .subtitle { + font-size: 0.95rem; + margin-bottom: 20px; + } + .form-page-container .form-container { + padding: 1.5rem; + } +} + +@media (max-width: 360px) { + .form-page-container { + padding: 20px 10px 30px; + } + .form-page-container h1 { + font-size: 1.5rem; + margin-bottom: 8px; + } + .form-page-container .subtitle { + font-size: 0.875rem; + margin-bottom: 15px; + } + .form-page-container .form-container { + padding: 1rem; + } + .login-link { + font-size: 0.85rem; + } + .message { + font-size: 0.85rem; + padding: 0.75rem 1rem; + } +} diff --git a/retoors/static/css/components/index.css b/retoors/static/css/components/index.css index 61e6eac..9e8aa3e 100644 --- a/retoors/static/css/components/index.css +++ b/retoors/static/css/components/index.css @@ -1,7 +1,7 @@ /* Hero Section */ .hero-section { text-align: center; - padding: 80px 20px 60px; + padding: clamp(30px, 8vw, 80px) clamp(10px, 3vw, 20px) clamp(20px, 6vw, 60px); background: linear-gradient(135deg, rgba(33, 70, 139, 0.03) 0%, var(--dutch-white) 100%); margin-bottom: 0; border-top: 4px solid transparent; @@ -55,7 +55,7 @@ /* Features Section */ .features-section { - padding: 80px 20px; + padding: clamp(30px, 8vw, 80px) clamp(10px, 3vw, 20px); background-color: #FFFFFF; } @@ -77,8 +77,8 @@ .features-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); - gap: 2.5rem; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); + gap: clamp(1.5rem, 3vw, 2.5rem); max-width: 1200px; margin: 0 auto; } @@ -109,7 +109,7 @@ /* Pricing Calculator Section */ .pricing-calculator { - padding: 80px 20px; + padding: clamp(30px, 8vw, 80px) clamp(10px, 3vw, 20px); background: #FFFFFF; text-align: center; } @@ -136,13 +136,13 @@ } .storage-value { - font-size: 4rem; + font-size: clamp(2rem, 8vw, 4rem); font-weight: 700; color: var(--dutch-blue); } .storage-unit { - font-size: 2rem; + font-size: clamp(1.25rem, 4vw, 2rem); color: var(--light-text-color); margin-left: 0.5rem; } @@ -194,7 +194,7 @@ .price-display { margin-bottom: 2.5rem; - padding: 2rem; + padding: clamp(1rem, 3vw, 2rem); background: linear-gradient(135deg, rgba(33, 70, 139, 0.05) 0%, rgba(174, 28, 40, 0.05) 100%); border-radius: 12px; border: 2px solid var(--dutch-blue); @@ -205,41 +205,42 @@ align-items: baseline; justify-content: center; margin-bottom: 0.5rem; + flex-wrap: wrap; } .currency { - font-size: 2rem; + font-size: clamp(1.25rem, 4vw, 2rem); color: var(--text-color); margin-right: 0.25rem; } .price-value { - font-size: 4rem; + font-size: clamp(2rem, 8vw, 4rem); font-weight: 700; color: var(--dutch-red); } .price-period { - font-size: 1.25rem; + font-size: clamp(0.875rem, 2.5vw, 1.25rem); color: var(--light-text-color); margin-left: 0.5rem; } .price-description { - font-size: 1.125rem; + font-size: clamp(0.875rem, 2.5vw, 1.125rem); color: var(--dutch-blue); font-weight: 600; margin: 0; } .pricing-calculator .cta-btn { - padding: 1rem 3rem; - font-size: 1.125rem; + padding: clamp(0.75rem, 2vw, 1rem) clamp(1.5rem, 5vw, 3rem); + font-size: clamp(0.875rem, 2.5vw, 1.125rem); } /* Use Cases Section */ .use-cases-section { - padding: 80px 20px; + padding: clamp(30px, 8vw, 80px) clamp(10px, 3vw, 20px); background: linear-gradient(135deg, rgba(174, 28, 40, 0.02) 0%, rgba(33, 70, 139, 0.02) 100%); text-align: center; } @@ -252,8 +253,8 @@ .use-cases-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 2rem; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); + gap: clamp(1.5rem, 3vw, 2rem); max-width: 1200px; margin: 0 auto; } @@ -294,7 +295,7 @@ /* CTA Section */ .cta-section { - padding: 100px 20px; + padding: clamp(40px, 10vw, 100px) clamp(10px, 3vw, 20px); background: linear-gradient(135deg, var(--dutch-blue) 0%, var(--dutch-blue-dark) 100%); text-align: center; color: var(--dutch-white); @@ -398,17 +399,135 @@ } @media (max-width: 480px) { + .hero-section { + padding: 40px 15px 30px; + } + .hero-section h1 { - font-size: 2rem; + font-size: 1.75rem; } .hero-subtitle { - font-size: 1rem; + font-size: 0.95rem; } .features-header h2, .use-cases-section h2, + .cta-section h2 { + font-size: 1.5rem; + } + + .pricing-calculator { + padding: 40px 15px; + } + + .pricing-calculator h2 { + font-size: 1.5rem; + } + + .calculator-subtitle { + font-size: 0.9rem; + margin-bottom: 2rem; + } + + .storage-display { + margin-bottom: 1.5rem; + } + + .slider-container { + margin-bottom: 2rem; + } + + .features-section, + .use-cases-section { + padding: 40px 15px; + } + + .cta-section { + padding: 40px 15px; + } + .cta-section h2 { font-size: 1.75rem; } + + .cta-section p { + font-size: 1rem; + } +} + +@media (max-width: 360px) { + .hero-section { + padding: 30px 10px 20px; + } + + .hero-section h1 { + font-size: 1.5rem; + margin-bottom: 0.75rem; + } + + .hero-subtitle { + font-size: 0.875rem; + } + + .pricing-calculator { + padding: 30px 10px; + } + + .pricing-calculator h2 { + font-size: 1.25rem; + margin-bottom: 0.5rem; + } + + .calculator-subtitle { + font-size: 0.85rem; + margin-bottom: 1.5rem; + } + + .storage-display { + margin-bottom: 1rem; + } + + .slider-container { + margin-bottom: 1.5rem; + } + + .price-display { + margin-bottom: 1.5rem; + padding: 0.75rem; + } + + .features-section, + .use-cases-section { + padding: 30px 10px; + } + + .features-grid { + gap: 1.5rem; + } + + .feature-card { + padding: 1.5rem; + } + + .use-cases-grid { + gap: 1.5rem; + } + + .use-case-card { + padding: 1.5rem; + } + + .cta-section { + padding: 30px 10px; + } + + .cta-section h2 { + font-size: 1.5rem; + } + + .cta-section p { + font-size: 0.9rem; + margin-bottom: 1.5rem; + } } \ No newline at end of file diff --git a/retoors/static/css/components/login.css b/retoors/static/css/components/login.css index 60f0bc1..6642be5 100644 --- a/retoors/static/css/components/login.css +++ b/retoors/static/css/components/login.css @@ -4,31 +4,33 @@ display: flex; flex-direction: column; align-items: center; - justify-content: center; - min-height: calc(100vh - 120px); /* Adjust based on header/footer height */ - padding: 20px; + justify-content: flex-start; + padding: clamp(30px, 5vh, 60px) clamp(15px, 3vw, 20px) clamp(40px, 6vh, 80px); box-sizing: border-box; + width: 100%; + max-width: 100%; } .login-page-container h1 { - font-size: 2.5rem; + font-size: clamp(1.75rem, 4vw, 2.5rem); color: var(--text-color); margin-bottom: 10px; text-align: center; } .login-page-container .subtitle { - font-size: 1.1rem; + font-size: clamp(0.95rem, 2vw, 1.1rem); color: var(--light-text-color); - margin-bottom: 30px; + margin-bottom: clamp(20px, 4vh, 30px); text-align: center; } .form-container { - max-width: 450px; /* Adjust as needed */ + max-width: 450px; width: 100%; - padding: 2.5rem; + padding: clamp(1.5rem, 4vw, 2.5rem); box-sizing: border-box; + margin-bottom: clamp(20px, 4vh, 30px); } .forgot-password-link { @@ -66,13 +68,40 @@ /* Responsive adjustments */ @media (max-width: 600px) { + .login-page-container { + padding: 30px 15px 40px; + } .login-page-container h1 { - font-size: 2rem; + font-size: 1.75rem; } .login-page-container .subtitle { - font-size: 1rem; + font-size: 0.95rem; + margin-bottom: 20px; } .form-container { - padding: 2rem; + padding: 1.5rem; + } +} + +@media (max-width: 360px) { + .login-page-container { + padding: 20px 10px 30px; + } + .login-page-container h1 { + font-size: 1.5rem; + margin-bottom: 8px; + } + .login-page-container .subtitle { + font-size: 0.875rem; + margin-bottom: 15px; + } + .form-container { + padding: 1rem; + } + .forgot-password-link { + font-size: 0.85rem; + } + .create-account-link { + font-size: 0.85rem; } } diff --git a/retoors/static/css/components/order.css b/retoors/static/css/components/order.css index fa1dae1..44b95a6 100644 --- a/retoors/static/css/components/order.css +++ b/retoors/static/css/components/order.css @@ -373,4 +373,59 @@ flex-grow: unset; width: 100%; } +} + +@media (max-width: 360px) { + .overview-grid { + grid-template-columns: 1fr; + gap: 20px; + } + .quota-overview-card { + padding: 10px; + } + .quota-overview-card h2 { + font-size: 1rem; + } + .quota-overview-card .subtitle { + font-size: 0.8rem; + } + .donut-chart-container { + width: 120px; + height: 120px; + } + .donut-chart-container::before { + width: 90px; + height: 90px; + } + .donut-chart-text { + font-size: 0.85rem; + } + .order-form-card { + padding: 10px; + } + .order-form-card h3 { + font-size: 1rem; + } + .user-quotas-section { + padding: 10px; + } + .user-quotas-header h2 { + font-size: 1rem; + } + .user-quota-list { + grid-template-columns: 1fr; + gap: 15px; + } + .user-quota-item { + padding: 15px; + } + .user-quota-item .user-info h4 { + font-size: 1rem; + } + .modal-content { + padding: 15px; + } + .modal-content h3 { + font-size: 1.1rem; + } } \ No newline at end of file diff --git a/retoors/static/css/components/register.css b/retoors/static/css/components/register.css index b57d2a9..7f02d8f 100644 --- a/retoors/static/css/components/register.css +++ b/retoors/static/css/components/register.css @@ -4,31 +4,33 @@ display: flex; flex-direction: column; align-items: center; - justify-content: center; - min-height: calc(100vh - 120px); /* Adjust based on header/footer height */ - padding: 20px; + justify-content: flex-start; + padding: clamp(30px, 5vh, 60px) clamp(15px, 3vw, 20px) clamp(40px, 6vh, 80px); box-sizing: border-box; + width: 100%; + max-width: 100%; } .register-page-container h1 { - font-size: 2.5rem; + font-size: clamp(1.75rem, 4vw, 2.5rem); color: var(--text-color); margin-bottom: 10px; text-align: center; } .register-page-container .subtitle { - font-size: 1.1rem; + font-size: clamp(0.95rem, 2vw, 1.1rem); color: var(--light-text-color); - margin-bottom: 30px; + margin-bottom: clamp(20px, 4vh, 30px); text-align: center; } .form-container { - max-width: 450px; /* Adjust as needed */ + max-width: 450px; width: 100%; - padding: 2.5rem; + padding: clamp(1.5rem, 4vw, 2.5rem); box-sizing: border-box; + margin-bottom: clamp(20px, 4vh, 30px); } .terms-checkbox { @@ -77,13 +79,40 @@ /* Responsive adjustments */ @media (max-width: 600px) { + .register-page-container { + padding: 30px 15px 40px; + } .register-page-container h1 { - font-size: 2rem; + font-size: 1.75rem; } .register-page-container .subtitle { - font-size: 1rem; + font-size: 0.95rem; + margin-bottom: 20px; } .form-container { - padding: 2rem; + padding: 1.5rem; + } +} + +@media (max-width: 360px) { + .register-page-container { + padding: 20px 10px 30px; + } + .register-page-container h1 { + font-size: 1.5rem; + margin-bottom: 8px; + } + .register-page-container .subtitle { + font-size: 0.875rem; + margin-bottom: 15px; + } + .form-container { + padding: 1rem; + } + .terms-checkbox { + font-size: 0.85rem; + } + .login-link { + font-size: 0.85rem; } } diff --git a/retoors/static/css/components/support.css b/retoors/static/css/components/support.css index 26fa28b..c56b7fb 100644 --- a/retoors/static/css/components/support.css +++ b/retoors/static/css/components/support.css @@ -169,7 +169,53 @@ .support-hero h1, .support-categories h2, .contact-options h2 { font-size: 1.2rem; } + .support-hero p { + font-size: 0.85rem; + } .category-card, .contact-card { padding: 15px; } + .search-support .search-input { + font-size: 0.85rem; + } +} + +@media (max-width: 360px) { + .support-hero { + padding: 0; + margin-bottom: 20px; + } + .support-hero h1, .support-categories h2, .contact-options h2 { + font-size: 1.1rem; + } + .support-hero p { + font-size: 0.8rem; + } + .search-support { + max-width: 100%; + margin-bottom: 20px; + } + .search-support .search-input { + padding: 8px 12px; + font-size: 0.8rem; + } + .support-categories { + margin-bottom: 30px; + } + .category-grid, .contact-grid { + grid-template-columns: 1fr; + gap: 15px; + } + .category-card, .contact-card { + padding: 12px; + } + .category-card h3, .contact-card h3 { + font-size: 1rem; + } + .category-card p, .contact-card p { + font-size: 0.85rem; + } + .contact-options { + margin-bottom: 30px; + } } \ No newline at end of file diff --git a/retoors/static/css/components/use_cases.css b/retoors/static/css/components/use_cases.css index 4cd6d9e..6efc001 100644 --- a/retoors/static/css/components/use_cases.css +++ b/retoors/static/css/components/use_cases.css @@ -2,18 +2,18 @@ .use-cases-hero { text-align: center; - padding: 40px 20px; - margin-bottom: 60px; + padding: clamp(20px, 5vw, 40px) clamp(10px, 3vw, 20px); + margin-bottom: clamp(30px, 6vw, 60px); } .use-cases-hero h1 { - font-size: 3rem; + font-size: clamp(1.75rem, 5vw, 3rem); color: var(--text-color); margin-bottom: 1rem; } .use-cases-hero p { - font-size: 1.2rem; + font-size: clamp(0.95rem, 2.5vw, 1.2rem); color: var(--light-text-color); max-width: 800px; margin: 0 auto; @@ -21,11 +21,11 @@ .use-case-scenarios { display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 30px; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); + gap: clamp(20px, 3vw, 30px); max-width: 1200px; - margin: 0 auto 60px auto; - padding: 0 20px; + margin: 0 auto clamp(30px, 6vw, 60px) auto; + padding: 0 clamp(10px, 3vw, 20px); } .scenario-card { @@ -127,9 +127,52 @@ @media (max-width: 480px) { .use-cases-hero h1 { - font-size: 2rem; + font-size: 1.75rem; + } + .use-cases-hero p { + font-size: 0.9rem; } .scenario-card { padding: 20px; } + .scenario-card h2 { + font-size: 1.5rem; + } + .use-cases-cta h2 { + font-size: 1.5rem; + } +} + +@media (max-width: 360px) { + .use-cases-hero { + padding: 15px 10px; + margin-bottom: 20px; + } + .use-cases-hero h1 { + font-size: 1.5rem; + } + .use-cases-hero p { + font-size: 0.85rem; + } + .use-case-scenarios { + gap: 20px; + padding: 0 10px; + grid-template-columns: 1fr; + min-width: 0; + } + .scenario-card { + padding: 15px; + } + .scenario-card h2 { + font-size: 1.25rem; + } + .scenario-card p { + font-size: 0.9rem; + } + .use-cases-cta { + padding: 20px 10px; + } + .use-cases-cta h2 { + font-size: 1.25rem; + } } \ No newline at end of file diff --git a/retoors/static/js/main.js b/retoors/static/js/main.js index 37469d8..d92dbd2 100644 --- a/retoors/static/js/main.js +++ b/retoors/static/js/main.js @@ -115,62 +115,65 @@ document.addEventListener('DOMContentLoaded', () => { async function shareFile(paths, names) { const modal = document.getElementById('share-modal'); - const linkContainer = document.getElementById('share-link-container'); - const loading = document.getElementById('share-loading'); - const shareLinkInput = document.getElementById('share-link-input'); const shareFileName = document.getElementById('share-file-name'); - const shareLinksList = document.getElementById('share-links-list'); + const quickShareResult = document.getElementById('quick-share-result'); + const quickShareLinkInput = document.getElementById('quick-share-link-input'); + const generateQuickShareBtn = document.getElementById('generate-quick-share-btn'); + const advancedShareBtn = document.getElementById('advanced-share-btn'); - shareLinkInput.value = ''; - if (shareLinksList) shareLinksList.innerHTML = ''; - linkContainer.style.display = 'none'; - loading.style.display = 'block'; + quickShareResult.style.display = 'none'; + quickShareLinkInput.value = ''; modal.classList.add('show'); + const currentPath = paths[0]; + const currentName = names[0]; + if (paths.length === 1) { - shareFileName.textContent = `Sharing: ${names[0]}`; + shareFileName.textContent = `Sharing: ${currentName}`; } else { shareFileName.textContent = `Sharing ${paths.length} items`; } - try { - const response = await fetch(`/files/share_multiple`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ paths: paths }) - }); - const data = await response.json(); + generateQuickShareBtn.onclick = async function() { + generateQuickShareBtn.disabled = true; + generateQuickShareBtn.textContent = 'Generating...'; - if (data.share_links && data.share_links.length > 0) { - if (data.share_links.length === 1) { - shareLinkInput.value = data.share_links[0]; - linkContainer.style.display = 'block'; + try { + const response = await fetch('/api/sharing/create', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + item_path: currentPath, + permission: 'view', + scope: 'public', + expiration_days: 7 + }) + }); + + const data = await response.json(); + + if (data.success) { + quickShareLinkInput.value = data.share_url; + quickShareResult.style.display = 'block'; + generateQuickShareBtn.textContent = 'Generate Another Link'; } else { - // Display multiple links - if (!shareLinksList) { - // Create the list if it doesn't exist - const ul = document.createElement('ul'); - ul.id = 'share-links-list'; - linkContainer.appendChild(ul); - shareLinksList = ul; - } - data.share_links.forEach(item => { - const li = document.createElement('li'); - li.innerHTML = `${item.name}: `; - shareLinksList.appendChild(li); - }); - linkContainer.style.display = 'block'; + alert('Error generating share link: ' + data.error); + generateQuickShareBtn.textContent = 'Generate Quick Share Link'; } - loading.style.display = 'none'; - } else { - loading.textContent = 'Error generating share link(s)'; + } catch (error) { + console.error('Error generating share link:', error); + alert('Error generating share link'); + generateQuickShareBtn.textContent = 'Generate Quick Share Link'; + } finally { + generateQuickShareBtn.disabled = false; } - } catch (error) { - console.error('Error sharing files:', error); - loading.textContent = 'Error generating share link(s)'; - } + }; + + advancedShareBtn.onclick = function() { + window.location.href = `/sharing/create?item_path=${encodeURIComponent(currentPath)}`; + }; } function copyShareLink() { @@ -295,6 +298,20 @@ document.addEventListener('DOMContentLoaded', () => { copyShareLinkBtn.addEventListener('click', copyShareLink); } + const copyQuickShareBtn = document.getElementById('copy-quick-share-btn'); + if (copyQuickShareBtn) { + copyQuickShareBtn.addEventListener('click', function() { + const input = document.getElementById('quick-share-link-input'); + input.select(); + navigator.clipboard.writeText(input.value).then(() => { + alert('Share link copied to clipboard'); + }).catch(err => { + document.execCommand('copy'); + alert('Share link copied to clipboard'); + }); + }); + } + document.getElementById('select-all')?.addEventListener('change', function(e) { const checkboxes = document.querySelectorAll('.file-checkbox'); checkboxes.forEach(cb => cb.checked = e.target.checked); diff --git a/retoors/templates/components/navigation.html b/retoors/templates/components/navigation.html index 8fa11e2..fdb1183 100644 --- a/retoors/templates/components/navigation.html +++ b/retoors/templates/components/navigation.html @@ -5,12 +5,12 @@ Retoor's