The Manage Account page (manage.html) provides a secure self-service portal for users to:
How it works:
/api/validate/api/account/statusSecurity:
Visual Design:
Free Tier Card:
Pro Tier Card:
How it works:
Security:
For Free Users:
signup.html with email pre-filledFor Pro Users:
/api/account/statusRequest:
POST https://transparency-flask.onrender.com/api/account/status
Content-Type: application/json
{
"email": "user@example.com"
}
Response (Account Found):
{
"found": true,
"email": "user@example.com",
"license_key": "ABC1-DEF2-GHI3-JKL4",
"tier": "pro",
"active": true,
"expiry": null,
"created_at": "2025-11-23T10:30:00"
}
Response (Not Found):
{
"found": false,
"message": "No active license found for this email"
}
Implementation: Added to server/app.py lines 889-921
/api/recover-licenseRequest:
POST https://transparency-flask.onrender.com/api/recover-license
Content-Type: application/json
{
"email": "user@example.com"
}
Response (Success):
{
"success": true,
"message": "License key sent to your email"
}
Response (Not Found):
{
"success": false,
"error": "No active license found for this email"
}
Implementation: Added to server/app.py lines 977-1015
User → Enter email → Click "Check Account"
↓
Backend → Find license (tier='free')
↓
Page → Show account details
↓
Display: "FREE" badge
Display: License key
Display: Two tier cards (Free = current, Pro = available)
↓
User clicks "Upgrade to Pro"
↓
Redirect to signup.html?email=user@example.com
↓
User completes payment
↓
Backend upgrades existing license to Pro (same license key!)
User → Enter email → Click "Check Account"
↓
Backend → Find license (tier='pro')
↓
Page → Show account details
↓
Display: "PRO" badge (gradient with glow)
Display: License key
Display: Subscription renewal date
Display: Two tier cards (Pro = current, Free = available)
↓
User clicks "Downgrade to Free"
↓
Confirmation: "Are you sure? You'll lose Pro features"
↓
If confirmed → Redirect to Stripe customer portal to cancel
↓
User cancels subscription in Stripe
↓
Webhook → Backend downgrades to free automatically
User → Enter email → Click "Check Account"
↓
Backend → No license found
↓
Page → Show "No account found" message
Page → Show tier selection cards (both available)
↓
Option A: User clicks "Get Free License"
↓
Backend → Creates free license
Backend → Sends email
Page → Shows success message
Page → Auto-refreshes account status
Page → Now shows Free tier as current
Option B: User clicks "Subscribe to Pro"
↓
Redirect to signup.html
User completes Stripe checkout
Backend → Creates Pro license
Desktop View:
Mobile View:
Color Scheme:
Animations:
Added “Manage Account” link to main navigation:
<li><a href="manage.html">Manage Account</a></li>
Should be added to:
The signup page can link to manage account for existing users:
<p>Already have an account? <a href="manage.html">Manage Account</a></p>
Extension can direct users to manage account page:
// In extension popup or settings
chrome.tabs.create({ url: 'https://transparencyai.io/manage.html' });
For Pro users who want to downgrade, you need to set up Stripe’s customer portal:
You can either:
Option A: Direct Link (Simple)
// In manage.html
function switchToFree() {
if (currentAccount && currentAccount.tier === 'pro') {
// Open Stripe billing portal
window.location.href = 'https://billing.stripe.com/p/login/YOUR_SESSION_LINK';
}
}
Option B: Backend Session Generation (Better)
Add endpoint to server/app.py:
@app.route('/api/create-portal-session', methods=['POST'])
def create_portal_session():
"""Create Stripe customer portal session"""
try:
data = request.get_json()
email = data.get('email')
# Find license
license_record = License.query.filter_by(email=email, active=True).first()
if not license_record or not license_record.customer_id:
return jsonify({'error': 'No subscription found'}), 404
# Create portal session
session = stripe.billing_portal.Session.create(
customer=license_record.customer_id,
return_url='https://transparencyai.io/manage.html',
)
return jsonify({'url': session.url}), 200
except Exception as e:
return jsonify({'error': str(e)}), 500
Then update manage.html:
async function switchToFree() {
if (currentAccount && currentAccount.tier === 'pro') {
if (confirm('Downgrade to Free? You\'ll lose Pro features.')) {
const response = await fetch(`${API_URL}/api/create-portal-session`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: currentAccount.email })
});
const data = await response.json();
if (response.ok) {
window.location.href = data.url;
}
}
}
}
# Test with existing account
curl -X POST https://transparency-flask.onrender.com/api/account/status \
-H "Content-Type: application/json" \
-d '{"email": "test@example.com"}'
# Test with non-existent account
curl -X POST https://transparency-flask.onrender.com/api/account/status \
-H "Content-Type: application/json" \
-d '{"email": "nonexistent@example.com"}'
Open page: https://transparencyai.io/manage.html
The /api/account/status endpoint returns:
Does NOT return:
Consider adding rate limiting to prevent abuse:
from flask_limiter import Limiter
limiter = Limiter(app, key_func=lambda: request.remote_addr)
@app.route('/api/account/status', methods=['POST'])
@limiter.limit("10 per minute")
def account_status():
# ...
/api/account/status endpoint (lines 889-921)Set up Stripe customer portal (for downgrades)
Status: ✅ Complete and ready for deployment Last Updated: November 23, 2025