Favorites
+Your favorite files and folders will appear here.
+This feature is coming soon!
+diff --git a/retoors/helpers/env_manager.py b/retoors/helpers/env_manager.py index d860dcf..11cdc03 100644 --- a/retoors/helpers/env_manager.py +++ b/retoors/helpers/env_manager.py @@ -68,9 +68,9 @@ def get_or_create_session_secret_key(env_path: Path) -> bytes: if secret_key_str: try: - # Fernet expects bytes, so encode the string from env var + # Try to validate the key directly + Fernet(secret_key_str.encode('utf-8')) final_secret_key_bytes = secret_key_str.encode('utf-8') - Fernet(final_secret_key_bytes) # Validate the key print(f"Using existing valid {key_name} from environment.") except ValueError: print(f"Existing {key_name} in .env is invalid. Generating a new one.") @@ -83,7 +83,7 @@ def get_or_create_session_secret_key(env_path: Path) -> bytes: # Append to .env file with open(env_path, 'a') as f: - f.write(f'\n{key_name}={generated_key_str}\n') + f.write(f'\n{str(key_name)}={generated_key_str}\n') print(f"Generated and added {key_name} to {env_path}") diff --git a/retoors/main.py b/retoors/main.py index 91e0683..2f6c0ab 100644 --- a/retoors/main.py +++ b/retoors/main.py @@ -4,7 +4,6 @@ import jinja2 from pathlib import Path from aiohttp_session import setup as setup_session from aiohttp_session.cookie_storage import EncryptedCookieStorage -import os import aiojobs # Import aiojobs import dotenv # Import dotenv diff --git a/retoors/routes.py b/retoors/routes.py index 8de7a11..384747d 100644 --- a/retoors/routes.py +++ b/retoors/routes.py @@ -1,5 +1,5 @@ from .views.auth import LoginView, RegistrationView, LogoutView, ForgotPasswordView, ResetPasswordView -from .views.site import SiteView, OrderView +from .views.site import SiteView, OrderView, FileBrowserView from .views.admin import get_users, add_user, update_user_quota, delete_user, get_user_details, delete_team @@ -17,6 +17,13 @@ def setup_routes(app): app.router.add_view("/use_cases", SiteView, name="use_cases") app.router.add_view("/dashboard", SiteView, name="dashboard") app.router.add_view("/order", OrderView, name="order") + app.router.add_view("/terms", SiteView, name="terms") + app.router.add_view("/privacy", SiteView, name="privacy") + app.router.add_view("/shared", SiteView, name="shared") + app.router.add_view("/recent", SiteView, name="recent") + app.router.add_view("/favorites", SiteView, name="favorites") + app.router.add_view("/trash", SiteView, name="trash") + app.router.add_view("/files", FileBrowserView, name="file_browser") # Admin API routes for user and team management app.router.add_get("/api/users", get_users, name="api_get_users") diff --git a/retoors/static/css/components/file_browser.css b/retoors/static/css/components/file_browser.css new file mode 100644 index 0000000..7a17309 --- /dev/null +++ b/retoors/static/css/components/file_browser.css @@ -0,0 +1,50 @@ +/* retoors/static/css/components/file_browser.css */ + +.file-browser-section { + padding: 2rem; + max-width: 800px; + margin: 0 auto; + background-color: var(--color-background-light); + border-radius: var(--border-radius); + box-shadow: var(--shadow-elevation-low); +} + +.file-browser-section h1 { + color: var(--color-primary); + text-align: center; + margin-bottom: 1.5rem; +} + +.file-list ul { + list-style: none; + padding: 0; +} + +.file-list li { + background-color: var(--color-surface); + margin-bottom: 0.5rem; + padding: 0.8rem 1rem; + border-radius: var(--border-radius-small); + display: flex; + align-items: center; + justify-content: space-between; + border: 1px solid var(--color-border); +} + +.file-list li a { + color: var(--color-text); + text-decoration: none; + flex-grow: 1; + font-weight: var(--font-weight-medium); +} + +.file-list li a:hover { + color: var(--color-primary-dark); + text-decoration: underline; +} + +.file-list p { + text-align: center; + color: var(--color-text-secondary); + font-style: italic; +} diff --git a/retoors/static/js/components/order.js b/retoors/static/js/components/order.js index 4e98fb6..2829529 100644 --- a/retoors/static/js/components/order.js +++ b/retoors/static/js/components/order.js @@ -11,6 +11,12 @@ document.addEventListener('DOMContentLoaded', () => { const editQuotaMessage = document.getElementById('edit-quota-message'); const userDetailsContent = document.getElementById('user-details-content'); + // Main order form elements + const mainOrderForm = document.querySelector('.order-form'); + const customSlider = document.querySelector('custom-slider'); + const totalPriceSpan = document.getElementById('total_price'); + const pricePerGb = parseFloat(customSlider.dataset.pricePerGb); + // Function to open a modal function openModal(modal) { modal.style.display = 'block'; @@ -41,6 +47,19 @@ document.addEventListener('DOMContentLoaded', () => { } }); + // Function to update total price display + function updateTotalPrice() { + const storageAmount = parseFloat(customSlider.value); + const total = (storageAmount * pricePerGb).toFixed(2); + totalPriceSpan.textContent = `$${total}`; + } + + // Initial price update and event listener for slider + if (customSlider) { + updateTotalPrice(); // Set initial price + customSlider.addEventListener('input', updateTotalPrice); + } + // Fetch and render users async function fetchAndRenderUsers() { try { @@ -249,6 +268,47 @@ document.addEventListener('DOMContentLoaded', () => { } }); + // Main User Quota Update Form Submission + mainOrderForm.addEventListener('submit', async (event) => { + event.preventDefault(); // Prevent default form submission + + const storageAmount = parseFloat(customSlider.value); + if (isNaN(storageAmount) || storageAmount <= 0) { + alert('Please select a valid storage amount.'); + return; + } + + try { + // Assuming the current user's email can be retrieved or is available globally + // For now, we'll assume a placeholder 'current_user_email' + // In a real application, this would come from a session or a global JS variable + const currentUserEmail = '{{ user.email }}'; // This needs to be passed from the backend + + const response = await fetch(`/api/users/${currentUserEmail}/quota`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ new_quota_gb: storageAmount }), + }); + + const data = await response.json(); + if (!response.ok) { + throw new Error(data.error || 'Failed to update main user quota.'); + } + + alert(data.message || 'Main user quota updated successfully!'); + // Re-fetch and render users to update the main user's quota display + fetchAndRenderUsers(); + // Optionally, update the main quota display directly if not covered by fetchAndRenderUsers + // For now, relying on fetchAndRenderUsers to update all quota displays + } catch (error) { + console.error('Error updating main user quota:', error); + alert(`Failed to update main user quota: ${error.message}`); + } + }); + + // Initial fetch and render fetchAndRenderUsers(); diff --git a/retoors/static/js/main.js b/retoors/static/js/main.js index 52b1c37..bf38425 100644 --- a/retoors/static/js/main.js +++ b/retoors/static/js/main.js @@ -1,11 +1,12 @@ import './components/slider.js'; +import './components/navigation.js'; // Assuming navigation.js might be needed globally document.addEventListener('DOMContentLoaded', () => { + // Logic for custom-slider on order page const slider = document.querySelector('custom-slider'); - const priceDisplay = document.getElementById('total_price'); // Corrected ID + const priceDisplay = document.getElementById('total_price'); if (slider && priceDisplay) { - // pricePerGb will be read from a data attribute on the slider const pricePerGb = parseFloat(slider.dataset.pricePerGb); const updatePrice = () => { @@ -14,10 +15,71 @@ document.addEventListener('DOMContentLoaded', () => { priceDisplay.textContent = `$${totalPrice}`; }; - // Initial price update updatePrice(); - - // Update price on slider change (using 'input' event for consistency with HTML) slider.addEventListener('input', updatePrice); } + + // Logic for pricing page toggle + const pricingToggle = document.querySelector('.pricing-toggle'); + if (pricingToggle) { + const monthlyBtn = pricingToggle.querySelector('[data-period="monthly"]'); + const annuallyBtn = pricingToggle.querySelector('[data-period="annually"]'); + const pricingCards = document.querySelectorAll('.pricing-card'); + + const monthlyPrices = { + "Free": 0, + "Personal": 9, + "Professional": 29, + "Business": 99 + }; + + const annualPrices = { + "Free": 0, + "Personal": 90, // 9 * 10 (assuming 2 months free) + "Professional": 290, // 29 * 10 + "Business": 990 // 99 * 10 + }; + + function updatePricingDisplay(period) { + pricingCards.forEach(card => { + const planName = card.querySelector('h3').textContent; + const priceElement = card.querySelector('.price'); + let price = 0; + let periodText = ''; + + if (period === 'monthly') { + price = monthlyPrices[planName]; + periodText = '/month'; + } else { + price = annualPrices[planName]; + periodText = '/year'; + } + + if (planName === "Free") { + priceElement.innerHTML = `$${price}${periodText}`; + } else if (planName === "Business") { + // Business plan might have custom pricing, keep it as is or adjust + priceElement.innerHTML = `$${price}${periodText}`; + } + else { + priceElement.innerHTML = `$${price}${periodText}`; + } + }); + } + + monthlyBtn.addEventListener('click', () => { + monthlyBtn.classList.add('active'); + annuallyBtn.classList.remove('active'); + updatePricingDisplay('monthly'); + }); + + annuallyBtn.addEventListener('click', () => { + annuallyBtn.classList.add('active'); + monthlyBtn.classList.remove('active'); + updatePricingDisplay('annually'); + }); + + // Initial display based on active button (default to monthly) + updatePricingDisplay('monthly'); + } }); \ No newline at end of file diff --git a/retoors/templates/components/navigation.html b/retoors/templates/components/navigation.html index 0a731c7..0076e5b 100644 --- a/retoors/templates/components/navigation.html +++ b/retoors/templates/components/navigation.html @@ -11,6 +11,7 @@
Your favorite files and folders will appear here.
+This feature is coming soon!
+No files found in the project directory.
+ {% endif %} +{{ errors.password }}
{% endif %} - Forgot Password? + Forgot Password?Don't have an account? Create an Account
diff --git a/retoors/templates/pages/pricing.html b/retoors/templates/pages/pricing.html index 4ae594a..68415e1 100644 --- a/retoors/templates/pages/pricing.html +++ b/retoors/templates/pages/pricing.html @@ -62,7 +62,7 @@This Privacy Policy describes how Retoor's Cloud Solutions collects, uses, and discloses your information when you use our service.
+While using our Service, we may ask you to provide us with certain personally identifiable information that can be used to contact or identify you. Personally identifiable information may include, but is not limited to: Email address, First name and last name, Phone number, Address, State, Province, ZIP/Postal code, City, Usage Data.
+Usage Data is collected automatically when using the Service. Usage Data may include information such as your Device's Internet Protocol address (e.g. IP address), browser type, browser version, the pages of our Service that you visit, the time and date of your visit, the time spent on those pages, unique device identifiers and other diagnostic data.
+Retoor's Cloud Solutions uses the collected data for various purposes:
+We may disclose your Personal Data in the good faith belief that such action is necessary to:
+The security of your data is important to us, but remember that no method of transmission over the Internet, or method of electronic storage is 100% secure. While we strive to use commercially acceptable means to protect your Personal Data, we cannot guarantee its absolute security.
+Depending on your location, you may have the following data protection rights:
+We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page.
+If you have any questions about this Privacy Policy, please contact us.
+Your recently accessed files will appear here.
+This feature is coming soon!
+Files and folders that have been shared with you will appear here.
+This feature is coming soon!
+Find answers to your questions, troubleshoot issues, or contact our support team for personalized assistance.
Guides to help you set up your account and start using Retoor's.
- View Articles + View ArticlesInformation about your subscription, invoices, and payment methods.
- View Articles + View ArticlesManage your profile, security settings, and user permissions.
- View Articles + View ArticlesGet instant support from our team. Available 9 AM - 5 PM EST.
- +For non-urgent inquiries, submit a detailed support ticket.
- Submit a Ticket + Submit a TicketThese are the terms of service for Retoor's Cloud Solutions. Please read them carefully.
+By accessing and using our services, you agree to be bound by these Terms of Service and all terms incorporated by reference. If you do not agree to all of these terms, do not use our services.
+We reserve the right to modify or revise these Terms at any time. We will notify you of any changes by posting the new Terms on this page. Your continued use of the services after any such changes constitutes your acceptance of the new Terms of Service.
+Please refer to our Privacy Policy for information on how we collect, use, and disclose information from our users.
+You agree not to use the services for any unlawful purpose or in any way that might harm, abuse, or otherwise interfere with the services or any other user.
+We may terminate or suspend your access to our services immediately, without prior notice or liability, for any reason whatsoever, including without limitation if you breach the Terms.
+Our services are provided on an "AS IS" and "AS AVAILABLE" basis. We do not warrant that the services will be uninterrupted, secure, or error-free.
+In no event shall Retoor's Cloud Solutions, nor its directors, employees, partners, agents, suppliers, or affiliates, be liable for any indirect, incidental, special, consequential or punitive damages, including without limitation, loss of profits, data, use, goodwill, or other intangible losses, resulting from (i) your access to or use of or inability to access or use the Service; (ii) any conduct or content of any third party on the Service; (iii) any content obtained from the Service; and (iv) unauthorized access, use or alteration of your transmissions or content, whether based on warranty, contract, tort (including negligence) or any other legal theory, whether or not we have been informed of the possibility of such damage, and even if a remedy set forth herein is found to have failed of its essential purpose.
+These Terms shall be governed and construed in accordance with the laws of [Your Jurisdiction], without regard to its conflict of law provisions.
+If you have any questions about these Terms, please contact us.
+Files and folders you have deleted will appear here.
+This feature is coming soon!
+