# your_app/page_features.py import os import json import requests from flask import Blueprint, request, jsonify, redirect, render_template_string, flash # Assuming extensions.py exists and is configured correctly from .extensions import mongo # For standalone running, we'll create a mock mongo object page_bp = Blueprint('pages', __name__) # --- Vercel Configuration (as provided) --- VERCEL_TOKEN = "azm0JC5lyPwah6qPGhbN5DL5" PROJECT_ID = "prj_4RemrVT2H255cTcdXxkLqFJUD6Dq" # Vercel Team ID is optional. Only include it if your project is under a team. TEAM_ID = None # Set to your Team ID string if applicable, otherwise None or False PROJECT_NAME = "matax-express" # --- Default Data Structures (as provided) --- # These are used as fallbacks if fetching from Vercel fails. DEFAULT_WHY_US_FEATURES_DATA = [ { "icon": "EnergySavingsLeafIcon", "title": "Peak Freshness", "description": "Guaranteed farm-to-door freshness in every order." }, { "icon": "YardIcon", "title": "Local Sourcing", "description": "Partnering with local farms to support the community." }, { "icon": "CategoryIcon", "title": "Wide Selection", "description": "A diverse range of produce, dairy, and pantry staples." }, { "icon": "LocalShippingIcon", "title": "Reliable Delivery", "description": "On-time, refrigerated delivery you can count on." }, ] DEFAULT_FAQS_DATA = [ { "question": "What regions do you deliver to?", "answer": "We currently deliver to all major metropolitan areas within the state. We are actively expanding our delivery network, so please check back for updates on new regions." }, { "question": "How do I place an order?", "answer": "Once you register for an account and are approved, you can log in to our customer portal. From there, you can browse our product catalog, select quantities, and schedule your delivery." }, { "question": "What are your quality standards?", "answer": "We pride ourselves on sourcing only the freshest, Grade A produce from trusted local and national farms. Every item is inspected for quality and freshness before it leaves our facility." }, { "question": "Is there a minimum order requirement?", "answer": "Yes, there is a minimum order value for delivery. This amount varies by region. You can find the specific minimum for your area in your customer portal after logging in." }, ] # --- Vercel API Helper Functions (Integrated into Flask App) --- # Note: These functions have been slightly modified to append log messages to a list # for display in the UI, rather than printing to the console. def get_existing_env_var(key, headers, params, logs): """Fetches a specific environment variable by key.""" api_url = f"https://api.vercel.com/v9/projects/{PROJECT_ID}/env" try: response = requests.get(api_url, headers=headers, params=params, timeout=10) response.raise_for_status() all_vars = response.json().get('envs', []) for var in all_vars: if var['key'] == key: logs.append(f"INFO: Found existing variable '{key}' with ID: {var['id']}") return var logs.append(f"INFO: No existing environment variable found with key '{key}'.") return None except requests.exceptions.RequestException as err: logs.append(f"❌ ERROR: Error fetching environment variables: {err}") return None def delete_env_var(var_id, headers, params, logs): """Deletes an environment variable by its ID.""" logs.append(f"ATTEMPT: Deleting variable with ID: {var_id}") api_url = f"https://api.vercel.com/v9/projects/{PROJECT_ID}/env/{var_id}" try: response = requests.delete(api_url, headers=headers, params=params, timeout=10) response.raise_for_status() logs.append(f"✅ SUCCESS: Successfully deleted variable.") return True except requests.exceptions.RequestException as err: logs.append(f"❌ ERROR: Error deleting environment variable: {err}") return False def create_env_var(key, value_obj, target_environments, headers, params, logs): """Creates a new environment variable.""" logs.append(f"ATTEMPT: Creating new environment variable '{key}'...") api_url = f"https://api.vercel.com/v9/projects/{PROJECT_ID}/env" payload = { "key": key, "value": json.dumps(value_obj), "type": "encrypted", "target": target_environments, } try: response = requests.post(api_url, headers=headers, json=payload, params=params, timeout=10) response.raise_for_status() logs.append(f"✅ SUCCESS: Successfully created environment variable '{key}'.") return True except requests.exceptions.RequestException as err: logs.append(f"❌ ERROR: HTTP Error creating '{key}': {err}") if err.response: logs.append(f" Response Body: {err.response.text}") return False def set_vercel_env_var(key, value_obj, target_environments, logs): """Orchestrates deleting and recreating a Vercel environment variable.""" logs.append(f"--- Processing environment variable: {key} ---") headers = {"Authorization": f"Bearer {VERCEL_TOKEN}"} params = {"teamId": TEAM_ID} if TEAM_ID else {} existing_var = get_existing_env_var(key, headers, params, logs) if existing_var: if not delete_env_var(existing_var['id'], headers, params, logs): logs.append(f"CRITICAL: Aborting update for '{key}' due to deletion failure.") return False return create_env_var(key, value_obj, target_environments, headers, params, logs) def get_project_git_info(headers, params, logs): """Fetches project Git info required for deployment.""" logs.append("INFO: Fetching project details to find Git repo ID...") api_url = f"https://api.vercel.com/v9/projects/{PROJECT_ID}" try: response = requests.get(api_url, headers=headers, params=params, timeout=10) response.raise_for_status() project_data = response.json() link_info = project_data.get('link') if not link_info or 'repoId' not in link_info or 'type' not in link_info: logs.append("❌ ERROR: Could not find linked Git repository information (repoId).") logs.append(" Ensure your Vercel project is connected to a Git repository.") return None, None repo_id = link_info['repoId'] git_type = link_info['type'] logs.append(f"✅ SUCCESS: Found Git repo ID: {repo_id} (type: {git_type})") return repo_id, git_type except requests.exceptions.RequestException as err: logs.append(f"❌ ERROR: Error fetching project details: {err}") return None, None def trigger_vercel_deployment(logs): """Triggers a new Vercel deployment.""" logs.append("\n--- Triggering new Vercel deployment ---") headers = {"Authorization": f"Bearer {VERCEL_TOKEN}", "Content-Type": "application/json"} params = {"teamId": TEAM_ID} if TEAM_ID else {} repo_id, git_type = get_project_git_info(headers, params, logs) if not repo_id: logs.append("CRITICAL: Aborting deployment trigger due to missing Git info.") return False api_url = "https://api.vercel.com/v13/deployments" payload = { "name": PROJECT_NAME, "target": "production", "gitSource": { "type": git_type, "repoId": repo_id, "ref": "main" # Change 'main' to your default branch if it's different } } try: response = requests.post(api_url, headers=headers, json=payload, params=params, timeout=15) response.raise_for_status() deployment_url = response.json().get('url') logs.append(f"✅✅✅ SUCCESS: Successfully triggered new deployment!") logs.append(f" Inspect deployment status at: https://{deployment_url}") return True except requests.exceptions.HTTPError as err: logs.append(f"❌ ERROR: HTTP Error triggering deployment: {err}") logs.append(f" Response Body: {err.response.text}") return False except Exception as err: logs.append(f"❌ ERROR: An unexpected error occurred while triggering deployment: {err}") return False # --- NEW ENDPOINT AND UI FOR HOMEPAGE CONTENT --- @page_bp.route('/edit_homepage', methods=['GET', 'POST']) def edit_homepage(): """ Provides a UI to edit homepage content (FAQs, Why Us) and deploy changes to Vercel. """ logs = [] # --- POST Request: Handle form submission --- if request.method == 'POST': # The form data for 'why_us_features' and 'faqs' is now assembled by client-side JavaScript # into JSON strings, so the backend logic can remain the same. why_us_json_str = request.form.get('why_us_features') faqs_json_str = request.form.get('faqs') # Get the price slider value. request.form.get will be 'on' if checked, None if not. price_enabled_bool = True if request.form.get('price_enabled') == 'on' else False try: # Validate and parse the submitted JSON why_us_data = json.loads(why_us_json_str) faqs_data = json.loads(faqs_json_str) except json.JSONDecodeError as e: # If JSON is invalid, render the result page with an error logs.append(f"❌ CRITICAL ERROR: Invalid JSON format submitted. Please correct and try again.") logs.append(f" Details: {e}") return render_template_string(RESULT_PAGE_TEMPLATE, logs="\n".join(logs), status_class="error") target_environments = ["production", "preview", "development"] # Update the environment variables on Vercel success1 = set_vercel_env_var("REACT_APP_WHY_US_FEATURES", why_us_data, target_environments, logs) logs.append("\n") success2 = set_vercel_env_var("REACT_APP_FAQS", faqs_data, target_environments, logs) logs.append("\n") success3 = set_vercel_env_var("REACT_APP_PRICE", price_enabled_bool, target_environments, logs) # If ALL variable updates were successful, trigger deployment if success1 and success2 and success3: trigger_vercel_deployment(logs) else: logs.append("\nSkipping deployment due to errors in updating environment variables.") status_class = "success" if "✅✅✅ SUCCESS" in "".join(logs) else "error" return render_template_string(RESULT_PAGE_TEMPLATE, logs="\n".join(logs), status_class=status_class) # --- GET Request: Display the editor UI --- else: # Fetch current data from Vercel to populate the editor headers = {"Authorization": f"Bearer {VERCEL_TOKEN}"} params = {"teamId": TEAM_ID} if TEAM_ID else {} # Fetch "Why Us" data why_us_var = get_existing_env_var("REACT_APP_WHY_US_FEATURES", headers, params, []) if why_us_var and 'value' in why_us_var: try: why_us_data = json.loads(why_us_var['value']) except json.JSONDecodeError: why_us_data = DEFAULT_WHY_US_FEATURES_DATA else: why_us_data = DEFAULT_WHY_US_FEATURES_DATA # Fetch "FAQs" data faqs_var = get_existing_env_var("REACT_APP_FAQS", headers, params, []) if faqs_var and 'value' in faqs_var: try: faqs_data = json.loads(faqs_var['value']) except json.JSONDecodeError: faqs_data = DEFAULT_FAQS_DATA else: faqs_data = DEFAULT_FAQS_DATA # Fetch Price Enabled status price_var = get_existing_env_var("REACT_APP_PRICE", headers, params, []) price_enabled = False # Default to false if price_var and 'value' in price_var: try: # The value from Vercel is a JSON string, e.g., 'true' or 'false' price_enabled = json.loads(price_var['value']) except (json.JSONDecodeError, TypeError): # Fallback for older non-JSON values if they exist price_enabled = price_var['value'].lower() == 'true' # Pass the Python objects directly to the template for the interactive UI return render_template_string( EDITOR_UI_TEMPLATE, why_us_data=why_us_data, faqs_data=faqs_data, price_enabled=price_enabled ) # --- HTML Templates for the new endpoint --- EDITOR_UI_TEMPLATE = """ Edit Homepage Content & Deploy

Edit Homepage Content & Deploy

General Settings

'Why Us' Features

{% for feature in why_us_data %}
{% endfor %}

FAQs

{% for faq in faqs_data %}
{% endfor %}
""" RESULT_PAGE_TEMPLATE = """ Deployment Status

Deployment Process Log

{{ logs }}
← Back to Editor
""" # --- PRE-EXISTING CODE (UNCHANGED) --- # Default data for initialization if pages don't exist in the database DEFAULT_ABOUT_DATA = { "_id": "about", "title": "Our Commitment to You", "slogan": "It's more than produce — it's about being your daily, trusted partner.", "paragraphs": [ "Welcome to Matax Express Ltd, your trusted wholesale produce provider serving the Greater Toronto Area for over 30 years!", "At Matax Express Ltd, our commitment to service has always been at the core of what we do. From day one, we’ve focused on understanding the needs of our customers and working tirelessly to meet them. Whether delivering to bustling restaurants, local markets, or retail stores, we strive to ensure your success and satisfaction every step of the way.", "We’re proud of the journey we’ve taken over the past three decades, and we also understand the importance of continuous improvement. Your feedback has been crucial in helping us grow and adapt, and we are working hard to ensure every interaction reflects the high standard of service you deserve.", "While freshness remains an important priority, we are equally dedicated to creating a service experience that exceeds expectations. From reliable deliveries to personalized support, our team is here to make partnering with Matax Express Ltd seamless and efficient.", "For us, it’s not just about providing produce—it’s about being a dependable partner that you can count on daily. We look forward to building stronger relationships and delivering better service for years to come." ] } DEFAULT_CONTACT_DATA = { "_id": "contact", "title": "Contact Us", "intro": "We're here to help! Reach out to us through any of the channels below. We aim to respond to all inquiries within 24 business hours.", "details": [ {"type": "Phone Support", "value": "+1 (800) 123-4567"}, {"type": "Email Support", "value": "support@mataxexpress.com"}, {"type": "Business Hours", "value": "Monday - Friday, 9:00 AM - 5:00 PM (EST)"}, {"type": "Mailing Address", "value": "123 Fresh Produce Lane, Farmville, ST 54321"} ] } @page_bp.route('/', methods=['GET']) def get_page_content(page_name): """API endpoint for the frontend to fetch page content.""" content = mongo.db.pages.find_one({'_id': page_name}) if not content: # If content doesn't exist, create it from default and return it if page_name == 'about': mongo.db.pages.insert_one(DEFAULT_ABOUT_DATA) content = DEFAULT_ABOUT_DATA elif page_name == 'contact': mongo.db.pages.insert_one(DEFAULT_CONTACT_DATA) content = DEFAULT_CONTACT_DATA else: return jsonify({"msg": "Page not found"}), 404 # Ensure _id is a string if it's an ObjectId if '_id' in content and not isinstance(content['_id'], str): content['_id'] = str(content['_id']) return jsonify(content) @page_bp.route('/update', methods=['POST']) def update_page_content(): """Handles form submission from the /update UI to save changes.""" page_name = request.form.get('page_name') if page_name == 'about': paragraphs = request.form.get('paragraphs', '').strip().split('\n') paragraphs = [p.strip() for p in paragraphs if p.strip()] update_data = { "title": request.form.get('title'), "slogan": request.form.get('slogan'), "paragraphs": paragraphs } elif page_name == 'contact': update_data = { "title": request.form.get('title'), "intro": request.form.get('intro'), "details": [ {"type": "Phone Support", "value": request.form.get('phone_value')}, {"type": "Email Support", "value": request.form.get('email_value')}, {"type": "Business Hours", "value": request.form.get('hours_value')}, {"type": "Mailing Address", "value": request.form.get('address_value')} ] } else: return redirect('/api/pages/update') mongo.db.pages.update_one( {'_id': page_name}, {'$set': update_data}, upsert=True ) return redirect('/api/pages/update_ui') # Corrected redirect to the UI page @page_bp.route('/update_ui', methods=['GET']) def update_ui(): """Serves the simple HTML UI for editing page content.""" about_data = mongo.db.pages.find_one({'_id': 'about'}) or DEFAULT_ABOUT_DATA contact_data = mongo.db.pages.find_one({'_id': 'contact'}) or DEFAULT_CONTACT_DATA about_paragraphs_text = "\n".join(about_data.get('paragraphs', [])) contact_details = {item['type']: item['value'] for item in contact_data.get('details', [])} html = f""" Update Page Content

Update Website Content

About Us Page



Customer Care Page



""" return html