Files
web_django_template/site/shipping/templates/shipping/detail.html
T
westfarn f3e34f5099 Add shopper accounts, reviews, tracking, and seed_demo (#10)
## Summary
- Closes #9. Port shop-gated buyer accounts (register/login/profile, password reset, order history), purchase-gated reviews, and Stripe customer ids. Cards stay on Stripe.
- Slim public contact form to email + interest + message. Shipping tracking status, scan events, paste-in numbers, and EasyPost webhook when FEATURE_SHIPPING is on.
- Add template-neutral `seed_demo` (generic SKUs, demo shoppers, guest orders, tracking stubs). Prod refused. `--reset` deletes tagged demo rows after invoices.

## Test plan
- [ ] `cd site && uv run python manage.py test` (173 tests passed locally)
- [ ] Shop on: `/account/register`, login, profile, password reset, claim guest orders by email
- [ ] Shop off: `/account/` URLs absent; Sign in / Account links hidden
- [ ] Checkout as logged-in buyer stores `Order.user` and Stripe customer id; product review only after paid purchase
- [ ] Shipping on: portal paste-in tracking + EasyPost webhook updates status; shipping off: orders still work without tracking UI
- [ ] `seed_demo` refused in prod; `--reset` rebuilds tagged catalog/shoppers without deleting staff invoices

Reviewed-on: #10
2026-09-21 13:29:36 -07:00

70 lines
2.7 KiB
HTML

{% extends "portal_base.html" %}
{% block title %}{{ shipment.order.number }} · Shipping{% endblock %}
{% block topbar_title %}{{ shipment.order.number }}{% endblock %}
{% block portal_content %}
<p>{{ shipment.order.email }} · {{ shipment.get_status_display }}</p>
{% if shipment.tracking_number %}
<p>
Tracking: {{ shipment.tracking_number }}
{% if shipment.tracking_status %} · {{ shipment.get_tracking_status_display }}{% endif %}
</p>
{% if shipment.tracking_url %}<p><a href="{{ shipment.tracking_url }}" target="_blank" rel="noopener">Track package</a></p>{% endif %}
<form method="post" action="{% url 'shipping:shipment_refresh_tracking' shipment.pk %}">
{% csrf_token %}
<button class="btn btn-ghost btn-sm" type="submit">Refresh tracking</button>
</form>
{% endif %}
{% if shipment.label_url %}<p><a href="{{ shipment.label_url }}">Download label</a></p>{% endif %}
<h3>Add tracking</h3>
<p class="hint-block">Paste a number from Pirate Ship or the carrier. EasyPost looks up scan events when an API key is set.</p>
<form method="post" action="{% url 'shipping:shipment_attach_tracking' shipment.pk %}">
{% csrf_token %}
<div class="form-grid">
<div class="field">
<label for="id_tracking_number">Tracking number</label>
<input id="id_tracking_number" name="tracking_number" value="{{ shipment.tracking_number }}" required>
</div>
<div class="field">
<label for="id_carrier">Carrier</label>
<input id="id_carrier" name="carrier" value="{{ shipment.carrier }}" placeholder="USPS, UPS, FedEx">
</div>
</div>
<button class="btn btn-primary" type="submit">Save tracking</button>
</form>
{% if shipment.tracking_events %}
<h3>Scan history</h3>
<table class="table">
<thead><tr><th>When</th><th>Status</th><th>Detail</th></tr></thead>
<tbody>
{% for event in shipment.tracking_events %}
<tr>
<td>{{ event.datetime }}</td>
<td>{{ event.status }}</td>
<td>{{ event.message }}{% if event.location %} · {{ event.location }}{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if shipment.status != 'labeled' %}
<form method="post" action="{% url 'shipping:shipment_buy' shipment.pk %}">
{% csrf_token %}
{% for rate in shipment.rates %}
<label>
<input type="radio" name="rate_id" value="{{ rate.id }}" {% if forloop.first %}checked{% endif %}>
{{ rate.carrier }} {{ rate.service }} — {{ rate.rate }} {{ rate.currency }}
</label><br>
{% empty %}
<p>No rates yet.</p>
{% endfor %}
{% if shipment.rates %}
<button class="btn btn-primary" type="submit">Buy label</button>
{% endif %}
</form>
{% endif %}
<p><a href="{% url 'shipping:shipment_list' %}">← All shipments</a></p>
{% endblock %}