Commit Graph

29 Commits

Author SHA1 Message Date
jamey
2f4cd81f98 add abandoned cart recovery
When a Stripe checkout session expires without payment, if the customer
entered their email, we record an AbandonedCart and schedule a single
plain-text recovery email (1h delay via Oban).

Privacy design:
- feature is off by default; shop owner opts in via admin settings
- only contacts customers who entered their email at Stripe checkout
- single email, never more (emailed_at timestamp gate)
- suppression list blocks repeat contact; one-click unsubscribe via
  signed token (/unsubscribe/:token)
- records pruned after 30 days (nightly Oban cron)
- no tracking pixels, no redirected links, no HTML

Legal notes:
- custom_text added to Stripe session footer when recovery is on
- UK PECR soft opt-in; EU legitimate interests both satisfied by this design

Files:
- migration: abandoned_carts + email_suppressions tables
- schemas: AbandonedCart, EmailSuppression
- context: Orders.create_abandoned_cart, check_suppression, add_suppression,
  has_recent_paid_order?, get_abandoned_cart_by_session, mark_abandoned_cart_emailed
- workers: AbandonedCartEmailWorker (checkout queue), AbandonedCartPruneWorker (cron)
- notifier: OrderNotifier.deliver_cart_recovery/3
- webhook: extended checkout.session.expired handler
- controller: UnsubscribeController, admin settings toggle
- tests: 28 new tests across context, workers, and controller

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:02:37 +00:00
jamey
e26a02a0fb fix setup flow stale state and Stripe URL issues
All checks were successful
deploy / deploy (push) Successful in 1m9s
Onboarding: re-fetch setup_status() after provider/Stripe connect instead
of manually patching the local assigns, which could miss admin_created and
leave users stuck on the setup page with no way forward.

Dev config: respect PHX_HOST for endpoint URL so Stripe checkout redirects
to the correct host instead of always using localhost.

Stripe setup: detect private/LAN IPs (10.x, 172.16-31.x, 192.168.x) as
unreachable, not just localhost — prevents creating webhook endpoints that
Stripe can never reach.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 16:51:44 +00:00
jamey
2bd2e613c7 add privacy-first analytics with progressive event collection
All checks were successful
deploy / deploy (push) Successful in 3m20s
Three-layer pipeline: Plug for all HTTP requests (no JS needed), LiveView
hook for SPA navigations, JS hook for screen width. ETS-backed buffer
batches writes to SQLite every 10s. Daily-rotating salt for visitor hashing.
Includes admin dashboard with date ranges, visitor trends, top pages,
sources, devices, and e-commerce conversion funnel. Oban cron for 12-month
data retention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 12:50:55 +00:00
jamey
b0aed4c1d6 add Printify client test coverage with Req.Test stubs
All checks were successful
deploy / deploy (push) Successful in 1m15s
Same pattern as the Printful work: wire up base_options/0 so tests can
inject a Req.Test plug, fix unreachable 204 clause in delete, add
HTTP-level client tests and provider integration tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 10:35:24 +00:00
jamey
a45e85ef4c add Printful client test coverage with Req.Test stubs
All checks were successful
deploy / deploy (push) Successful in 1m10s
Wire up Req.Test plug for the Printful HTTP client so tests can stub
responses. Adds HTTP-level tests for the client, provider integration
tests, and mockup enricher tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 10:20:49 +00:00
jamey
19d8c7d0fd tune SQLite PRAGMAs for production
Some checks failed
deploy / deploy (push) Has been cancelled
Add journal_size_limit (64MB), default_transaction_mode: :immediate,
and mmap_size (128MB) across dev, test, and prod configs. Benchmarks
showed IMMEDIATE mode eliminates transaction upgrade BUSY errors and
mmap improves read throughput.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 09:23:41 +00:00
jamey
9d9bd09059 auto-confirm admin during setup, skip email verification
Some checks failed
deploy / deploy (push) Has been cancelled
Setup wizard no longer requires email delivery. Admin account is
auto-confirmed and auto-logged-in via token redirect. Adds setup
secret gate for prod (logged on boot), SMTP env var config in
runtime.exs, email_configured? helper, and admin warning banner
when email isn't set up. Includes plan files for this task and
the follow-up email settings UI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 10:24:26 +00:00
jamey
eb65b11e4d fix flaky sqlite test contention
All checks were successful
deploy / deploy (push) Successful in 41s
Increase DBConnection queue tolerance to stop requests being dropped
under load, and flip 3 write-heavy test files to async: false since
SQLite's single-writer model doesn't play well with concurrent writes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 22:54:12 +00:00
jamey
9528700862 rename project from SimpleshopTheme to Berrypod
All modules, configs, paths, and references updated.
836 tests pass, zero warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 21:23:15 +00:00
jamey
82583822f5 remove Tailwind + DaisyUI theme + heroicons plugin, admin fully custom CSS (Phase 7)
replace Tailwind CLI with esbuild for admin CSS bundling. admin now uses
hand-written utility classes (admin/utilities.css), static heroicon CSS
generated by mix generate_admin_icons, plain CSS colour themes extracted
from DaisyUI plugin config, and minimal resets. rename app.css to admin.css
for clarity alongside shop.css. delete vendor/daisyui-theme.js and
vendor/heroicons.js. no Tailwind dependency remains in the project.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 01:15:28 +00:00
jamey
f5f6374f7b replace Tailwind in content + collection, remove shop Tailwind entirely (Phase 5c)
- Replace all Tailwind utilities in content.ex and collection.ex with
  semantic CSS classes (content body, contact form, cards, reviews, etc.)
- Delete app-shop.css (Tailwind shop entry point)
- Remove shop Tailwind config from config.exs, dev.exs, mix.exs
- Remove shop Tailwind stylesheet link from shop_root.html.heex
- Add collection filter bar, empty state, and select dropdown styles
- Fix filter pill sizing (use theme font vars instead of hardcoded rem)
- Fix active pill contrast (tinted accent background + dark accent text)
- Fix --t-text-on-accent fallback for pill legibility
- Add padding/font-size to .themed-select

Shop pages now use zero Tailwind. Admin Tailwind remains for Phase 6.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 19:07:15 +00:00
jamey
fcd1b1ce80 wire shop.css alongside Tailwind for CSS migration (Phase 1)
- esbuild profile for shop.css bundling (dev watcher + build aliases)
- shop.css loaded as second stylesheet in shop_root layout
- LiveView display:contents rule in reset layer
- updated Lighthouse + Screenshots tasks for new esbuild target

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 23:45:13 +00:00
jamey
bb358f890b consolidate image serving and clean up pipeline
Move all image URL logic into ProductImage.url/2 and thumbnail_url/1,
remove dead on-demand generation code from Optimizer, strip controller
routes down to SVG recolor only, fix mockup startup check to verify all
variant formats, and isolate test image cache directory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 17:47:41 +00:00
jamey
5c2f70ce44 add shipping costs with live exchange rates and country detection
Shipping rates fetched from Printify during product sync, converted to
GBP at sync time using frankfurter.app ECB exchange rates with 5%
buffer. Cached in shipping_rates table per blueprint/provider/country.

Cart page shows shipping estimate with country selector (detected from
Accept-Language header, persisted in cookie). Stripe Checkout includes
shipping_options for UK domestic and international delivery. Order
shipping_cost extracted from Stripe on payment.

ScheduledSyncWorker runs every 6 hours via Oban cron to keep rates
and exchange rates fresh. REST_OF_THE_WORLD fallback covers unlisted
countries. 780 tests, 0 failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 10:48:00 +00:00
jamey
1a69736734 add mobile swipe for product card images and fix dev asset caching
Product cards now use CSS scroll-snap on touch devices (mobile) for
swiping between images, with dot indicators and a JS hook for active
state. Desktop keeps the existing hover crossfade via @media (hover:
hover). Dots use size differentiation (WCAG 2.2 AA compliant) with
outline rings for contrast on any background.

Also fixes: no-image placeholder (SVG icon instead of broken img),
unnecessary wrapper div for single-image cards, and dev static asset
caching (was immutable for all envs, now only prod).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 12:24:52 +00:00
jamey
88291f276b add observability: LiveDashboard in prod, error tracking, JSON logging
- Move LiveDashboard to /admin/dashboard behind session auth (all envs)
- Add ErrorTracker at /admin/errors for auto-captured exceptions
- Add Oban job and LiveView metrics to telemetry module
- Add logger_json for structured JSON logs in production
- Enable os_mon for CPU/disk/memory in LiveDashboard OS Data tab
- Extend logger metadata with oban_worker and oban_queue fields

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 17:02:21 +00:00
jamey
1ee37c853d add Docker deployment with Alpine image, release config and health check
- Alpine multi-stage Dockerfile (131 MB image)
- Release overlays (bin/server, bin/migrate), env.sh, Release module
- Health check endpoint at GET /health
- Fly.io config with SQLite volume mount
- Fix hardcoded paths in optimizer.ex and variant_cache.ex to use
  Application.app_dir/2 (breaks in releases where Plug.Static serves
  from a different directory than CWD)
- strip_beams: true in release config
- Optimised .dockerignore and .gitignore for mockup variants

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 16:21:05 +00:00
jamey
3e19887499 feat: add Printify order submission and fulfilment tracking
Submit paid orders to Printify via provider API with idempotent
guards, Stripe address mapping, and error handling. Track fulfilment
status through submitted → processing → shipped → delivered via
webhook-driven updates (primary) and Oban Cron polling fallback.

- 9 fulfilment fields on orders (status, provider IDs, tracking, timestamps)
- OrderSubmissionWorker with retry logic, auto-enqueued after Stripe payment
- FulfilmentStatusWorker polls every 30 mins for missed webhook events
- Printify order webhook handlers (sent-to-production, shipment, delivered)
- Admin UI: fulfilment column in table, fulfilment card with tracking info,
  submit/retry and refresh buttons on order detail
- Mox provider mocking for test isolation (Provider.for_type configurable)
- 33 new tests (555 total), verified against real Printify API

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 09:51:51 +00:00
jamey
eede9bb517 feat: add encrypted settings, guided Stripe setup, and admin credentials page
Store API keys and secrets encrypted in the SQLite database via the
existing Vault module (AES-256-GCM). The only external dependency is
SECRET_KEY_BASE — everything else lives in the portable DB file.

- Add encrypted_value column to settings table with new "encrypted" type
- Add put_secret/get_secret/delete_setting/secret_hint to Settings context
- Add Secrets module to load encrypted config into Application env at startup
- Add Stripe.Setup module with connect/disconnect/verify_api_key flow
  - Auto-creates webhook endpoints via Stripe API in production
  - Detects localhost and shows Stripe CLI instructions for dev
- Add admin credentials page at /admin/settings with guided setup:
  - Not configured: single Secret key input with dashboard link
  - Connected (production): status display, webhook info, disconnect
  - Connected (dev): Stripe CLI instructions, manual signing secret input
- Remove Stripe env vars from dev.exs and runtime.exs
- Fix CSSCache test startup crash (handle_continue instead of init)
- Add nav link for Credentials page

507 tests, 0 failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 17:12:53 +00:00
jamey
ff1bc483b9 feat: add Stripe checkout, order persistence, and webhook handling
Stripe-hosted Checkout integration with full order lifecycle:
- stripity_stripe ~> 3.2 with sandbox/prod config via env vars
- Order and OrderItem schemas with price snapshots at purchase time
- CheckoutController creates pending order then redirects to Stripe
- StripeWebhookController verifies signatures and confirms payment
- Success page with real-time PubSub updates from webhook
- Shop flash messages for checkout error feedback
- Cart cleared after successful payment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 08:30:17 +00:00
jamey
bbd748f123 chore: enable sqlite wal mode for dev and prod
better concurrency and crash recovery for web workloads

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 14:30:27 +00:00
jamey
a44790362a fix: resolve sqlite database busy errors in tests
- enable WAL journal mode for better concurrent access
- increase busy_timeout to 10s
- reduce pool_size to 1 to prevent write conflicts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 14:29:24 +00:00
jamey
c1e19889d4 fix: add Oban Lifeline plugin to rescue orphaned jobs
Jobs stuck in "executing" state after server restarts will now be
automatically rescued after 5 minutes. This prevents jobs from
being permanently orphaned when the server restarts mid-execution.

Also updates tidewave 0.5.3 -> 0.5.4 and related dependencies.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 13:48:04 +00:00
jamey
ee1da08941 fix: enable Tidewave remote access properly
Pass allow_remote_access option directly to the Tidewave plug
instead of using application config (which was not being read).
Remove the ineffective config line from dev.exs.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 13:47:58 +00:00
c5c06d9979 feat: add Products context with provider integration (Phase 1)
Implement the schema foundation for syncing products from POD providers
like Printify. This includes encrypted credential storage, product/variant
schemas, and an Oban worker for background sync.

New modules:
- Vault: AES-256-GCM encryption for API keys
- Products context: CRUD and sync operations for products
- Provider behaviour: abstraction for POD provider implementations
- ProductSyncWorker: Oban job for async product sync

Schemas: ProviderConnection, Product, ProductImage, ProductVariant

Also reorganizes Printify client to lib/simpleshop_theme/clients/ and
mockup generator to lib/simpleshop_theme/mockups/ for better structure.

134 tests added covering all new functionality.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 20:32:20 +00:00
1b12dc3e7f perf: split CSS bundles for shop and admin pages
Create separate CSS bundles to reduce shop page load times:
- app-shop.css (45KB/7.8KB gzip): Shop pages only, no daisyUI
- app.css (139KB): Admin pages with daisyUI and theme editor

Key changes:
- Add app-shop.css with targeted @source paths for shop files only
- Move .preview-frame rules from theme-layer2-attributes.css to app.css
- Delete fonts.css (fonts now generated inline by CSSGenerator)
- Add inline all-fonts generation in theme editor for typography switching
- Configure separate Tailwind profiles and watchers for both bundles

Shop pages now load 54% less CSS by excluding:
- daisyUI components (admin only)
- .preview-frame theme switching rules (editor only)
- Admin-specific Tailwind utilities

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 11:36:20 +00:00
dbadd2a376 feat: add oban dependency for background jobs
Add Oban ~> 2.18 with SQLite support (Oban.Engines.Lite) for durable
background job processing. Configure aggressive pruning (60s max_age)
to keep database lean, with a dedicated images queue.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 22:01:08 +00:00
9b73fcdf7a mix phx.gen.auth Accounts User users 2025-12-30 12:26:46 +00:00
4f2ed90044 mix phx.new simpleshop_theme --database sqlite3 --adapter bandit --binary-id 2025-12-30 12:26:26 +00:00