06/15 Phase 4 + 5 codes
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
{# Reusable ad slot partial. Usage: {% include "ads/_slot.html" with slot="sidebar" %} #}
|
||||
{% if show_ads and ads.get(slot) %}
|
||||
{% set ad = ads[slot] %}
|
||||
<div class="ad-slot ad-{{ slot }}">
|
||||
<span class="ad-label">Ad</span>
|
||||
<a href="{{ url_for('ads.click', ad_id=ad.id) }}" target="_blank" rel="noopener sponsored">
|
||||
{% if ad.creative_path %}
|
||||
<img src="{{ url_for('listings.media', rel='ads/' + ad.creative_path) }}"
|
||||
alt="{{ ad.alt_text or ad.advertiser_name }}" class="ad-img">
|
||||
{% else %}
|
||||
<div class="ad-text-creative">{{ ad.advertiser_name }}</div>
|
||||
{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -4,15 +4,17 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block title %}Classifieds{% endblock %}</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.v2.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header">
|
||||
{% set slot = "header" %}{% include "ads/_slot.html" %}
|
||||
<div class="wrap">
|
||||
<a class="brand" href="{{ url_for('main.index') }}">Classifieds</a>
|
||||
<nav class="nav">
|
||||
<a href="{{ url_for('listings.browse') }}">{{ _('Browse') }}</a>
|
||||
<a href="{{ url_for('payments.pricing') }}">{{ _('Pricing') }}</a>
|
||||
{% if current_user.is_authenticated %}
|
||||
<a href="{{ url_for('listings.create') }}">{{ _('Post') }}</a>
|
||||
<a href="{{ url_for('listings.mine') }}">{{ _('My listings') }}</a>
|
||||
@@ -21,11 +23,9 @@
|
||||
{{ _('Messages') }}
|
||||
{% if unread_count %}<span class="badge-count">{{ unread_count }}</span>{% endif %}
|
||||
</a>
|
||||
<a href="{{ url_for('payments.my_billing') }}">{{ _('Billing') }}</a>
|
||||
<span class="hi">{{ current_user.display_name }}</span>
|
||||
<form method="post" action="{{ url_for('auth.logout') }}" class="logout-form">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<button type="submit" class="link-btn">{{ _('Sign out') }}</button>
|
||||
</form>
|
||||
<a href="{{ url_for('auth.logout') }}">{{ _('Sign out') }}</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('auth.login') }}">{{ _('Sign in') }}</a>
|
||||
<a class="btn" href="{{ url_for('auth.register') }}">{{ _('Register') }}</a>
|
||||
@@ -54,7 +54,8 @@
|
||||
</main>
|
||||
|
||||
<footer class="site-footer">
|
||||
<div class="wrap">© {{ 2025 }} Classifieds</div>
|
||||
{% set slot = "footer" %}{% include "ads/_slot.html" %}
|
||||
<div class="wrap">© {{ 2025 }} Classifieds · <a href="{{ url_for('ads.sponsor_directory') }}">{{ _('Sponsors') }}</a></div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
</div>
|
||||
<button class="btn" type="submit">{{ _('Apply') }}</button>
|
||||
</form>
|
||||
{% set slot = "sidebar" %}{% include "ads/_slot.html" %}
|
||||
</aside>
|
||||
|
||||
<section class="results">
|
||||
@@ -41,6 +42,9 @@
|
||||
{% if not results %}<p class="muted">{{ _('No listings found.') }}</p>{% endif %}
|
||||
<div class="grid">
|
||||
{% for l, dist in results %}
|
||||
{% if loop.index % 6 == 0 %}
|
||||
{% set slot = "inline" %}{% include "ads/_slot.html" %}
|
||||
{% endif %}
|
||||
<a class="tile card" href="{{ url_for('listings.detail', listing_id=l.id) }}">
|
||||
{% if l.cover %}<img class="thumb" src="{{ url_for('listings.media', rel=l.cover.thumb_path) }}" alt="">
|
||||
{% else %}<div class="thumb noimg">{{ _('No photo') }}</div>{% endif %}
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
</div>
|
||||
{% if is_owner %}
|
||||
<a class="btn" href="{{ url_for('listings.edit', listing_id=listing.id) }}">{{ _('Edit') }}</a>
|
||||
<a class="btn ghost" href="{{ url_for('payments.boost_listing', listing_id=listing.id) }}">{{ _('⚡ Boost') }}</a>
|
||||
{% if listing.status.value == 'active' %}
|
||||
<form method="post" action="{{ url_for('listings.mark_sold', listing_id=listing.id) }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><button class="btn ghost" type="submit">{{ _('Mark sold') }}</button>
|
||||
|
||||
@@ -66,11 +66,7 @@
|
||||
var groups = document.querySelectorAll('.attr-group');
|
||||
function sync(){
|
||||
groups.forEach(function(g){
|
||||
var visible = g.dataset.cat === sel.value;
|
||||
g.style.display = visible ? 'block' : 'none';
|
||||
g.querySelectorAll('input,select,textarea').forEach(function(el){
|
||||
el.disabled = !visible;
|
||||
});
|
||||
g.style.display = (g.dataset.cat === sel.value) ? 'block' : 'none';
|
||||
});
|
||||
}
|
||||
if (sel){ sel.addEventListener('change', sync); sync(); }
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
<span class="muted">{{ _('Active: %(a)s', a=active) }}{% if cap is not none %} / {{ cap }}{% else %} / ∞{% endif %}</span>
|
||||
<a class="btn" href="{{ url_for('listings.create') }}">{{ _('Post new') }}</a>
|
||||
</div>
|
||||
{% if cap is not none and active >= cap %}
|
||||
<div class="upgrade-prompt">
|
||||
{{ _("You've reached your plan's listing limit.") }}
|
||||
<a href="{{ url_for('payments.pricing') }}">{{ _('Upgrade for more') }}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if not items %}<p class="muted">{{ _('No listings yet.') }}</p>{% endif %}
|
||||
<table class="list">
|
||||
{% for l in items %}
|
||||
@@ -16,7 +22,10 @@
|
||||
<td>{{ l.price_display or '—' }}</td>
|
||||
<td><span class="badge {{ 'ok' if l.is_live else 'warn' }}">{{ l.status.value }}</span></td>
|
||||
<td>{{ l.view_count }} {{ _('views') }}</td>
|
||||
<td><a href="{{ url_for('listings.edit', listing_id=l.id) }}">{{ _('Edit') }}</a></td>
|
||||
<td>
|
||||
<a href="{{ url_for('listings.edit', listing_id=l.id) }}">{{ _('Edit') }}</a>
|
||||
· <a href="{{ url_for('payments.boost_listing', listing_id=l.id) }}">{{ _('Boost') }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('My billing') }}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="card billing-wrap">
|
||||
<h2>{{ _('Billing') }}</h2>
|
||||
|
||||
<section class="billing-section">
|
||||
<h3>{{ _('Current plan') }}</h3>
|
||||
<div class="billing-plan">
|
||||
<strong>{{ plan.name if plan else _('Free') }}</strong>
|
||||
{% if sub and sub.is_active %}
|
||||
<span class="badge ok">{{ sub.status }}</span>
|
||||
{% if sub.current_period_end %}
|
||||
<span class="muted small">
|
||||
{{ _('Renews') if not sub.cancel_at_period_end else _('Ends') }}
|
||||
{{ sub.current_period_end.strftime('%b %d, %Y') }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if sub.cancel_at_period_end %}
|
||||
<span class="badge warn">{{ _('Canceling') }}</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="billing-actions">
|
||||
{% if stripe_enabled %}
|
||||
{% if sub and sub.stripe_customer_id %}
|
||||
<a class="btn" href="{{ url_for('payments.portal') }}">{{ _('Manage subscription') }}</a>
|
||||
{% else %}
|
||||
<a class="btn" href="{{ url_for('payments.pricing') }}">{{ _('Upgrade plan') }}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% if active_boosts %}
|
||||
<section class="billing-section">
|
||||
<h3>{{ _('Active boosts') }}</h3>
|
||||
<table class="list">
|
||||
{% for b in active_boosts %}
|
||||
<tr>
|
||||
<td>{{ b.listing.title[:50] }}</td>
|
||||
<td><span class="badge cat">{{ b.type }}</span></td>
|
||||
<td class="muted small">{{ _('Expires') }} {{ b.expires_at.strftime('%b %d, %Y') }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<section class="billing-section">
|
||||
<h3>{{ _('Transaction history') }}</h3>
|
||||
{% if not txns %}
|
||||
<p class="muted">{{ _('No transactions yet.') }}</p>
|
||||
{% else %}
|
||||
<table class="list">
|
||||
<tr><th>{{ _('Date') }}</th><th>{{ _('Type') }}</th><th>{{ _('Amount') }}</th><th>{{ _('Status') }}</th></tr>
|
||||
{% for t in txns %}
|
||||
<tr>
|
||||
<td class="muted small">{{ t.created_at.strftime('%b %d, %Y') }}</td>
|
||||
<td>{{ t.type }}</td>
|
||||
<td>${{ "%.2f"|format(t.amount_cents / 100) }}</td>
|
||||
<td><span class="badge {{ 'ok' if t.status == 'succeeded' else 'warn' }}">{{ t.status }}</span></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
</section>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,34 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Boost listing') }}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="card narrow">
|
||||
<h2>{{ _('Boost listing') }}</h2>
|
||||
<p class="muted">{{ listing.title[:60] }}</p>
|
||||
<form method="post" novalidate>
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
<div class="boost-options">
|
||||
{% for btype, info in boost_types.items() %}
|
||||
<label class="boost-option {{ 'active-boost' if btype in active_boosts }}">
|
||||
<input type="radio" name="boost_type" value="{{ btype }}"
|
||||
{{ 'checked' if loop.first }} {{ 'disabled' if btype in active_boosts }}>
|
||||
<div class="boost-info">
|
||||
<div class="boost-label">{{ info.label }}
|
||||
{% if btype in active_boosts %}<span class="badge ok">{{ _('Active') }}</span>{% endif %}
|
||||
</div>
|
||||
<div class="muted small">{{ info.days }} {{ _('days') }}</div>
|
||||
</div>
|
||||
<div class="boost-price">${{ "%.2f"|format(info.cents / 100) }}</div>
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if stripe_enabled %}
|
||||
<button class="btn" type="submit">{{ _('Continue to payment') }}</button>
|
||||
{% else %}
|
||||
<p class="muted">{{ _('Payments not configured yet.') }}</p>
|
||||
{% endif %}
|
||||
</form>
|
||||
<p class="muted small" style="margin-top:12px">
|
||||
<a href="{{ url_for('listings.detail', listing_id=listing.id) }}">← {{ _('Back to listing') }}</a>
|
||||
</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,52 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Pricing') }}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="pricing-wrap">
|
||||
<div class="pricing-head">
|
||||
<h1>{{ _('Simple, transparent pricing') }}</h1>
|
||||
<p class="muted">{{ _('Upgrade anytime. Cancel anytime.') }}</p>
|
||||
</div>
|
||||
<div class="plan-grid">
|
||||
{% for plan in plans %}
|
||||
<div class="plan-card card {{ 'current' if current_plan and current_plan.id == plan.id }}">
|
||||
{% if current_plan and current_plan.id == plan.id %}
|
||||
<div class="plan-badge">{{ _('Current plan') }}</div>
|
||||
{% endif %}
|
||||
<div class="plan-name">{{ plan.name }}</div>
|
||||
<div class="plan-price">
|
||||
{% if plan.price_monthly_cents == 0 %}
|
||||
<span class="price-amount">{{ _('Free') }}</span>
|
||||
{% else %}
|
||||
<span class="price-amount">${{ "%.0f"|format(plan.price_monthly_cents / 100) }}</span>
|
||||
<span class="price-period">{{ _('/mo') }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<ul class="plan-features">
|
||||
<li>{{ _('%(n)s active listings', n=plan.limit('active_listings') or '∞') }}</li>
|
||||
<li>{{ _('%(n)s-day listing life', n=plan.limit('listing_life_days')) }}</li>
|
||||
<li>{{ _('%(n)s photos/listing', n=plan.limit('images_per_listing')) }}</li>
|
||||
{% if plan.limit('ad_free') %}<li class="feat-yes">{{ _('Ad-free browsing') }}</li>{% endif %}
|
||||
{% if plan.limit('verified_badge') %}<li class="feat-yes">{{ _('Verified badge') }}</li>{% endif %}
|
||||
{% if plan.limit('storefront') %}<li class="feat-yes">{{ _('Storefront page') }}</li>{% endif %}
|
||||
{% if plan.limit('scheduled_posting') %}<li class="feat-yes">{{ _('Scheduled posting') }}</li>{% endif %}
|
||||
{% if plan.limit('bulk_csv') %}<li class="feat-yes">{{ _('Bulk CSV upload') }}</li>{% endif %}
|
||||
{% if plan.limit('priority_support') %}<li class="feat-yes">{{ _('Priority support') }}</li>{% endif %}
|
||||
</ul>
|
||||
{% if plan.slug == 'free' %}
|
||||
{% if not current_user.is_authenticated %}
|
||||
<a class="btn plan-btn" href="{{ url_for('auth.register') }}">{{ _('Get started') }}</a>
|
||||
{% else %}
|
||||
<span class="btn plan-btn ghost disabled">{{ _('Your plan') }}</span>
|
||||
{% endif %}
|
||||
{% elif current_plan and current_plan.id == plan.id %}
|
||||
<a class="btn plan-btn ghost" href="{{ url_for('payments.portal') }}">{{ _('Manage') }}</a>
|
||||
{% elif stripe_enabled %}
|
||||
<a class="btn plan-btn" href="{{ url_for('payments.subscribe', slug=plan.slug) }}">{{ _('Upgrade') }}</a>
|
||||
{% else %}
|
||||
<span class="btn plan-btn ghost disabled">{{ _('Coming soon') }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,17 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Payment successful') }}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="card narrow" style="text-align:center;padding:48px 24px">
|
||||
<div style="font-size:48px">✅</div>
|
||||
<h2>{{ _('Payment successful!') }}</h2>
|
||||
{% if mode == 'subscription' %}
|
||||
<p>{{ _('Your subscription is now active. Enjoy your upgraded plan!') }}</p>
|
||||
<a class="btn" href="{{ url_for('payments.my_billing') }}">{{ _('View billing') }}</a>
|
||||
{% else %}
|
||||
<p>{{ _('Your boost is now live on your listing.') }}</p>
|
||||
<a class="btn" href="{{ url_for('listings.detail', listing_id=listing_id) }}">{{ _('View listing') }}</a>
|
||||
{% endif %}
|
||||
<br><br>
|
||||
<a href="{{ url_for('listings.mine') }}" class="muted small">{{ _('My listings') }}</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,24 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{ _('Sponsors') }}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<h2>{{ _('Our Sponsors') }}</h2>
|
||||
<p class="muted">{{ _('These businesses support our community classifieds.') }}</p>
|
||||
{% if not sponsors %}
|
||||
<p class="muted">{{ _('No sponsors yet.') }}</p>
|
||||
{% endif %}
|
||||
<div class="sponsor-grid">
|
||||
{% for s in sponsors %}
|
||||
<a class="sponsor-card card" href="{{ s.url }}" target="_blank" rel="noopener sponsored">
|
||||
{% if s.logo_path %}
|
||||
<img src="{{ url_for('listings.media', rel='sponsors/' + s.logo_path) }}"
|
||||
alt="{{ s.name }}" class="sponsor-logo">
|
||||
{% else %}
|
||||
<div class="sponsor-name-only">{{ s.name }}</div>
|
||||
{% endif %}
|
||||
{% if s.tagline %}<p class="muted small">{{ s.tagline }}</p>{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user