# 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 = """