cd /home/def/otb_billing || exit 1 set -e STAMP="$(date +%Y%m%d-%H%M%S)" NEW_VERSION="v0.6.0" ZIP_NAME="otb_billing-${NEW_VERSION}.zip" echo "===== backups =====" cp VERSION "VERSION.bak.${STAMP}" cp README.md "README.md.bak.${STAMP}" cp PROJECT_STATE.md "PROJECT_STATE.md.bak.${STAMP}" echo "===== version bump =====" printf '%s\n' "${NEW_VERSION}" > VERSION echo "===== update README + PROJECT_STATE =====" python3 <<'PY' from pathlib import Path from datetime import datetime, timezone root = Path("/home/def/otb_billing") version = "v0.6.0" stamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") utc_stamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC") readme = root / "README.md" project_state = root / "PROJECT_STATE.md" readme_text = readme.read_text() if readme.exists() else "" project_text = project_state.read_text() if project_state.exists() else "" readme_entry = f"""## {version} - {stamp} ### Highlights - Added authenticated `/portal/services` page as a new service hub inside the billing portal. - Added modular route file `backend/routes/portal_services.py` instead of expanding the main app with another large inline route block. - Added shared `templates/portal_base.html` layout using Jinja `{% extends %}` / block pattern. - Converted portal pages to the shared base-template structure for cleaner reuse and consistent branding. - Added branded service cards for: - Follow-me Tracker - Video Rendering / Streaming - Miner Rentals - Fixed portal services layout so it uses the same nav, footer, styling, and light/dark toggle behavior as the working portal/dashboard pages. - Corrected the Follow-me service link to use the real external service domain. - Updated service launch buttons so external services open in a new tab. - Improved portal services identity display to prepare for showing real logged-in client identity instead of a generic placeholder. ### Notes - This is the first successful rollout of the reusable `portal_base.html` structure and it should be used for future portal pages and future project builds. - This release establishes the billing portal as the launch point for OTB services. """ project_entry = f"""# Project State Update - {version} Updated: {utc_stamp} ## Current Version {version} ## Current Status OTB Billing has been advanced from a billing-only portal toward a unified service-launch platform. ## Completed This Session - Created `/portal/services` authenticated service hub. - Added modular route file: `backend/routes/portal_services.py` - Added shared Jinja portal base template: `templates/portal_base.html` - Converted: - `templates/portal_dashboard.html` - `templates/portal/services_here.html` to the new shared base-template architecture. - Restored consistent OTB branding, nav, footer, spacing, and dark/light toggle behavior on the services page. - Added service cards for: - Follow-me Tracker (active/beta) - Video Rendering / Streaming (coming soon) - Miner Rentals (coming soon) - Fixed service routing so Follow-me points to the real service domain. - Updated external service launch links to open in a new tab. - Confirmed the new shared template architecture is successful and should be reused in future pages/projects. ## Architectural Direction The portal now has a proven reusable pattern: - `templates/portal_base.html` - child templates using `{{% extends "portal_base.html" %}}` - focused page content blocks instead of duplicating full HTML shell on each page This is now the preferred portal/page structure going forward. ## Next Logical Steps - Unify logged-in client identity handling across portal routes. - Add real service-aware handoff / account linkage for Follow-me. - Add future service cards/pages without duplicating layout shell. - Move page-specific inline service-card CSS into shared stylesheet when ready. """ def prepend_once(existing: str, new_block: str, marker: str) -> str: if marker in existing: return existing if existing.startswith("# "): return existing.rstrip() + "\n\n" + new_block return new_block + existing readme.write_text(prepend_once(readme_text, readme_entry, f"## {version} - ")) project_state.write_text(prepend_once(project_text, project_entry, f"# Project State Update - {version}")) PY echo "===== build full zip snapshot =====" rm -f "releases/${ZIP_NAME}" mkdir -p releases python3 <<'PY' from pathlib import Path import zipfile root = Path("/home/def/otb_billing") zip_path = root / "releases" / "otb_billing-v0.6.0.zip" exclude_parts = { ".git", "__pycache__", ".pytest_cache", } exclude_suffixes = { ".pyc", ".pyo", } with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as zf: for path in root.rglob("*"): rel = path.relative_to(root) if any(part in exclude_parts for part in rel.parts): continue if path.suffix in exclude_suffixes: continue if path == zip_path: continue zf.write(path, arcname=str(Path("otb_billing-v0.6.0") / rel)) print(zip_path) PY echo "===== quick verify =====" echo echo "--- VERSION ---" cat VERSION echo echo "--- README.md top 40 ---" sed -n '1,40p' README.md echo echo "--- PROJECT_STATE.md top 60 ---" sed -n '1,60p' PROJECT_STATE.md echo echo "--- ZIP snapshot ---" ls -lh "releases/${ZIP_NAME}" echo echo "===== optional git review =====" git status --short