Protocol Raw Customer Portal Documentation¶
Version: 2.6
Created: November 30, 2025
Last Updated: January 21, 2026
Status: โ
Production Ready
Portal URL: https://my.protocolraw.co.uk
What's New in v2.6¶
Transition Guide Feature:
- โ
New TransitionView with personalised 10-day feeding schedule
- โ
First-visit detection and automatic redirect for new customers
- โ
Deep linking support via ?view=transition URL parameter
- โ
Mobile-responsive design with browser back button support
- โ
"Getting Started" card on HomeView (shown for first 3 orders)
- โ
Multi-dog support with per-dog calculated portions
Previous versions: - v2.5: Payment method update via Seal magic link - v2.4: Credit balance display - v2.3: Documentation consolidation - v2.2: Self-serve address editing (with Shopify + Seal sync) - v2.1: Tracking card with shipment status - v2.0: Initial elevated design release
Table of Contents¶
- Overview
- Architecture
- Authentication
- Landing Page
- Portal Features
- Transition Guide
- Credit Balance Display
- Payment Method Update
- Shipment Tracking
- Address Editing
- 48-Hour Delivery Lock System
- Email Notifications
- Database Schema
- API Reference
- Edge Functions
- Make.com Integration
- Deployment
- Troubleshooting
- Version History
Overview¶
Purpose¶
The Protocol Raw Customer Portal is a self-service interface that empowers customers to manage their subscription, view order history, track deliveries, and verify batch safetyโall without contacting support. Built as part of the AI-native operations strategy, the portal reduces contact volume by enabling 90%+ self-serve resolution.
Design Philosophy¶
"Self-Serve First" - Every common customer need addressable without support contact - Proactive information reduces need to ask questions - Friction-free subscription management increases retention
Visual Identity - Protocol Raw elevated brand aesthetic - Warm, premium color palette (Old Lace, Forest Green, Terracotta) - Clean typography (Montserrat headings, Inter body) - Consistent with main website and Proof Portal
Key Features¶
| Feature | Status | Version |
|---|---|---|
| Magic link authentication | โ Built | v2.0 |
| View subscription status | โ Built | v2.0 |
| View next delivery date | โ Built | v2.0 |
| Skip next delivery | โ Built | v2.0 |
| Reschedule delivery | โ Built | v2.0 |
| Pause subscription | โ Built | v2.0 |
| Resume subscription | โ Built | v2.0 |
| Change box size | โ Built | v2.0 |
| Change frequency | โ Built | v2.0 |
| Update feeding plan | โ Built | v2.0 |
| View order history | โ Built | v2.0 |
| View batch proof | โ Built | v2.0 |
| Cancel subscription | โ Built | v2.0 |
| 48-hour delivery lock | โ Built | v2.0 |
| Multi-dog support | โ Built | v2.0 |
| Request portal access | โ Built | v2.0 |
| Track current delivery | โ Built | v2.1 |
| Update shipping address | โ Built | v2.2 |
| Credit balance display | โ Built | v2.4 |
| Update payment method | โ Built | v2.5 |
| Transition Guide | โ Built | v2.6 |
| First-visit redirect | โ Built | v2.6 |
| Deep linking (?view=) | โ Built | v2.6 |
Architecture¶
System Components¶
Customer โ Landing Page โ Magic Link Email โ Authenticated Portal
โ
Supabase (Database)
โ
โโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโรขโย
โ โ โ
Customer Data Seal API Customer.io
(Orders, Dogs) (Subscription) (Events)
Technology Stack¶
| Layer | Technology | Purpose |
|---|---|---|
| Frontend | React (Vite) | Single-page application |
| Hosting | Cloudflare Pages | Static hosting + edge caching |
| Database | Supabase PostgreSQL | Customer data, orders, shipments |
| Auth | Magic Link (Custom) | Passwordless authentication |
| Subscriptions | Seal Subscriptions | Shopify subscription management |
| Customer.io | Transactional emails | |
| Styling | Inline + CSS Variables | Protocol Raw brand system |
| Edge Functions | Supabase Edge Functions | Server-side API calls (e.g., Seal) |
File Structure¶
customer-portal/
โโโ index.html
โโโ src/
โ โโโ App.jsx # Main application
โ โโโ Portal.jsx # Authenticated portal view
โ โโโ api.js # Supabase client + API functions
โ โโโ components/
โ โ โโโ Card.jsx
โ โ โโโ Button.jsx
โ โ โโโ Modal.jsx
โ โ โโโ ...
โ โโโ views/
โ โโโ HomeView.jsx
โ โโโ SubscriptionView.jsx
โ โโโ OrderHistoryView.jsx
โ โโโ FeedingPlanView.jsx
โ โโโ TransitionView.jsx # NEW in v2.6
โโโ package.json
โโโ vite.config.js
URLs¶
Production:
- Landing: https://my.protocolraw.co.uk
- Portal: https://my.protocolraw.co.uk/?token=<magic_link_token>
- Transition Guide (deep link): https://my.protocolraw.co.uk/?token=<token>&view=transition
Authentication¶
Magic Link Flow¶
- Customer requests access (landing page or email link)
- System generates secure token (UUID)
- Token stored in
raw_ops.portal_tokenswith 7-day expiry - Customer.io sends email with magic link
- Customer clicks link โ token validated โ session created
- Token marked as used (single-use)
Token Schema¶
CREATE TABLE raw_ops.portal_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
customer_id UUID REFERENCES raw_ops.customers(id),
token UUID UNIQUE NOT NULL DEFAULT gen_random_uuid(),
expires_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() + INTERVAL '7 days'),
used_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
Session Management¶
- Token validated on each API call
- Session persists via localStorage
- Auto-logout after token expiry
- Re-authentication via "Send new link" flow
Security¶
- Tokens are single-use (marked used after first validation)
- 7-day expiry window
- No password storage
- Rate limiting on token requests (5 per hour per email)
Landing Page¶
Purpose¶
Entry point for customers without active session. Provides portal access request form and explains available features.
Components¶
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย
โ [Protocol Raw Logo] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ Manage Your Subscription โ
โ โ
โ Enter your email to receive a secure login link. โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย โ
โ โ Email address โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ [Send Login Link] โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ What you can do in your portal: โ
โ โข View and manage your subscription โ
โ โข Track your current delivery โ
โ โข Update your feeding plan โ
โ โข View your order history โ
โ โข Access batch verification reports โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
States¶
- Initial: Email input form
- Loading: Submitting request
- Success: "Check your email" confirmation
- Error: Email not found / rate limited
Portal Features¶
Home View¶
The main dashboard showing subscription status, quick actions, and recent activity.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย
โ Welcome back, [Name] [Log Out] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย โ
โ โ โ รฏยธย PAYMENT WARNING (if payment_status !== 'healthy') โ โ
โ โ We had trouble processing your last payment. โ โ
โ โ [Update payment method] โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย โ
โ โ GETTING STARTED (if total_orders <= 3) รขโ ย v2.6 โ โ
โ โ โ โ
โ โ Your 10-Day Transition Plan โ โ
โ โ A personalised feeding schedule for a smooth switch. โ โ
โ โ โ โ
โ โ View transition guide โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย โ
โ โ NEXT DELIVERY โ โ
โ โ โ โ
โ โ Tuesday, 28 January [12 days] โ โ
โ โ 12kg Protocol Raw Complete โ โ
โ โ โ โ
โ โ [Skip This Delivery] [Reschedule] โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโรขโย โโโโโโโโโโโโโโโโโโโโโโรขโย โ
โ โ Quick Actions โ โ Your Subscription โ โ
โ โ โ โ โ โ
โ โ โข Change box size โ โ Status: Active โ โ
โ โ โข Change frequency โ โ Box: 12kg โ โ
โ โ โข Update address โ โ Every: 4 weeks โ โ
โ โ โข Pause sub โ โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย โ
โ โ Credit Balance ยฃ15 โ โ
โ โ โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย โ โ
โ โ โ โ Credit expiring soon. Active subscriptions use โ โ โ
โ โ โ it automatically. โ โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ โ
โ โ Applied automatically to your next renewal. โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย โ
โ โ VERIFICATION STATS โ โ
โ โ โ โ
โ โ [5] Boxes Received [5] Batches Verified โ โ
โ โ โ โ
โ โ Your last batch: PR-E769561D [View Report โ] โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Subscription Management¶
Change Box Size - Options: 8kg (ยฃ89), 12kg (ยฃ109), 16kg (ยฃ129) - Confirmation modal with price change - Effective from next billing
Change Frequency - Options: Every 2, 3, 4, 5, 6 weeks - Feeding plan recalculation shown - Effective from next billing
Skip Delivery - One-click skip with confirmation - Shows next scheduled date after skip - Cannot skip if within 48-hour lock
Pause Subscription - Confirmation with counter-offer (discount) - Pause duration options: 2, 4, 6 weeks - Auto-resume after pause period
Resume Subscription - One-click resume - Next delivery date shown
Cancel Subscription - Multi-step flow with retention offers - Reason selection - Final confirmation - Post-cancellation survey
Order History View¶
Timeline of all orders with batch links.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย
โ Order History โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ รขโย 15 January 2026 โ
โ Order #1234 ยท 12kg ยท Delivered โ
โ Batch: PR-E769561D [View Proof โ] โ
โ โ
โ รขโย 18 December 2025 โ
โ Order #1189 ยท 12kg ยท Delivered โ
โ Batch: PR-A3B7C912 [View Proof โ] โ
โ โ
โ รขโย 20 November 2025 โ
โ Order #1156 ยท 8kg ยท Delivered โ
โ Batch: PR-F4D2E891 [View Proof โ] โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Feeding Plan View¶
Update dog details and see recommended feeding amounts.
Editable Fields: - Dog name - Weight (kg) - Life stage (Puppy/Adult/Senior) - Activity level (Low/Moderate/High/Very High) - Body condition (Underweight/Ideal/Overweight)
Calculated Display: - Daily feeding amount (grams) - Weekly consumption - Box duration at current consumption
Multi-Dog Support: - Add/remove dogs - Household total consumption - Combined feeding plan
Transition Guide¶
Overview¶
The Transition Guide replaces printed transition instructions with a personalised, interactive portal experience. New customers are automatically directed to this view on their first portal visit.
Purpose: Provides new customers with a personalised transition guide showing exact gram amounts for their dog(s), calculated based on weight, life stage, and activity level.
First-Visit Detection¶
Database Field:
Behaviour:
1. Customer authenticates via magic link
2. System checks if portal_first_visited_at is NULL
3. If NULL (first visit):
- Set portal_first_visited_at to current timestamp
- Redirect to TransitionView
4. If NOT NULL (returning visit):
- Show HomeView as normal
Edge Function Update (portal-session):
// After successful authentication, check first visit
if (!customer.portal_first_visited_at) {
// Update the timestamp
await supabase
.from('customers')
.update({ portal_first_visited_at: new Date().toISOString() })
.eq('id', customer.id);
// Include redirect flag in response
return { ...sessionData, isFirstVisit: true };
}
Deep Linking¶
The Transition Guide can be accessed directly via URL parameter:
Use Cases: - Insert card QR code links directly to transition guide - Customer.io emails can deep link to this view - Support agents can share direct link
Implementation:
// In Portal.jsx useEffect
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const viewParam = params.get('view');
if (viewParam === 'transition') {
setView('transition');
}
}, []);
TransitionView Components¶
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย
โ รขโ ย Back โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ GETTING STARTED โ
โ โ
โ Your 10-Day Transition Plan โ
โ A measured approach to switching your dog to raw feeding, โ
โ based on established veterinary best practice. โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย โ
โ โ "You'll find different opinions online about how quickly โ โ
โ โ to transition. Our approach gives your dog's digestive โ โ
โ โ system time to adapt..." โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ A measured transition. A confident start. โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ [โผ Who this guide is for] โ
โ [โผ When to consult your vet first] โ
โ [โผ Safe handling] โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ THE SCHEDULE (Warm Linen background) โ
โ โ
โ [Dog Name]'s Schedule โ
โ Calculated based on weight, life stage, and activity level. โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย โ
โ โ [Avatar] Luna โโโโโโโโโโโโโโรขโย โ โ
โ โ Adult ยท 25kg ยท Moderate โ Target dailyโ โ โ
โ โ โ 400g โ โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ โ
โ โ โโโโโโโโโโโรขโย โ โ
โ โ โ Day 1โ2 โ 100g Protocol Raw Complete โ โ
โ โ โโโโโโโโโโโโ + 75% current food โโโโโ โ
โ โ โ โ
โ โ โโโโโโโโโโโรขโย โ โ
โ โ โ Day 3โ4 โ 200g Protocol Raw Complete โ โ
โ โ โโโโโโโโโโโโ + 50% current food โโโโโโโ โ
โ โ โ โ
โ โ โโโโโโโโโโโรขโย โ โ
โ โ โ Day 5โ6 โ 300g Protocol Raw Complete โ โ
โ โ โโโโโโโโโโโโ + 25% current food โโโโโโโโโ โ
โ โ โ โ
โ โ โโโโโโโโโโโรขโย โ โ
โ โ โ Day 7โ8 โ 350g Protocol Raw Complete โ โ
โ โ โโโโโโโโโโโโ + 10% current food โโโโโโโโโโโ โ
โ โ โ โ
โ โ โโโโโโโโโโโรขโย โ โ โ
โ โ โ Day 9โ10 โ 400g Protocol Raw Complete โโโโโโโโโ โ
โ โ โโโโโโโโโโโโ (100% transitioned) โ โ
โ โ โ โ
โ โ ๐ก Puppy tip (if life_stage === 'puppy') โ โ
โ โ Split daily portions into 3โ4 smaller meals... โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ WHAT TO EXPECT โ
โ โ
โ Normal Changes During Transition โ
โ These are signs the transition is working, not problems to solve. โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโรขโย โโโโโโโโโโโโโโโโโโโโโโโโรขโย โ
โ โ โ Stool changes โ โ ๐ง Drinking less โ โ
โ โ โ โ โ โ
โ โ Stools may become โ โ Raw food is naturallyโ โ
โ โ softer initially... โ โ high in moisture... โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโรขโย โโโโโโโโโโโโโโโโโโโโโโโโรขโย โ
โ โ ๐ต Digestive adjust โ โ โจ Coat improvement โ โ
โ โ โ โ โ โ
โ โ Some dogs experience โ โ Temporary dandruff โ โ
โ โ occasional bile... โ โ as body adjusts... โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ [โผ Extra support for sensitive stomachs] โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย โ
โ โ โ รฏยธย When to pause and call your vet โ โ
โ โ โ โ
โ โ Trust your instincts. Pause the transition if you notice: โ โ
โ โ โข Diarrhoea lasting more than 48 hours โ โ
โ โ โข Persistent vomiting โ โ
โ โ โข Refusal to eat for more than 24 hours โ โ
โ โ โข Lethargy or signs of discomfort โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย โ
โ โ [Forest Green background] โ โ
โ โ โ โ
โ โ โ VERIFIED โ โ
โ โ โ โ
โ โ Verify your batch โ โ
โ โ โ โ
โ โ Every pouch has a QR code linking to independent lab โ โ
โ โ results for your specific batch. Scan any pouch to see โ โ
โ โ the safety certificate. โ โ
โ โ โ โ
โ โ Welcome to Protocol Raw. โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Transition Schedule Calculation¶
Constants:
const TRANSITION_SCHEDULE = [
{ days: '1โ2', percent: 25, currentPercent: 75 },
{ days: '3โ4', percent: 50, currentPercent: 50 },
{ days: '5โ6', percent: 75, currentPercent: 25 },
{ days: '7โ8', percent: 90, currentPercent: 10 },
{ days: '9โ10', percent: 100, currentPercent: 0 },
];
Gram Calculation:
function roundTo25(grams) {
return Math.round(grams / 25) * 25;
}
function getScheduleForDog(dog) {
const dailyGrams = dog.daily_grams || 400;
return TRANSITION_SCHEDULE.map(phase => ({
...phase,
grams: roundTo25(dailyGrams * (phase.percent / 100)),
}));
}
Example Output (25kg adult dog, 400g daily):
| Days | Protocol Raw Complete | Current Food |
|---|---|---|
| 1โ2 | 100g | 75% |
| 3โ4 | 200g | 50% |
| 5โ6 | 300g | 25% |
| 7โ8 | 350g | 10% |
| 9โ10 | 400g | 0% โ |
Multi-Dog Support¶
If customer has multiple dogs, the TransitionView displays a separate schedule card for each dog:
Each card shows: - Dog name and avatar - Life stage, weight, activity level - Target daily grams (calculated via RER/MER methodology) - Personalised transition schedule - Puppy tip (if applicable)
HomeView Integration¶
"Getting Started" Card:
Shown on HomeView when total_orders <= 3 (first 3 orders):
Card Design:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย
โ GETTING STARTED โ
โ โ
โ Your 10-Day Transition Plan โ
โ A personalised feeding schedule for a smooth switch to raw. โ
โ โ
โ View transition guide โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Styling follows Visual Identity Guide v2.2: - Warm Linen background (#EBE8E3) - Section label in Burnt Sienna (#B85C3A) - Green accent bar on hover - Arrow gap animation on hover
Mobile Responsiveness¶
Back Button Handling:
Browser/phone back button is intercepted to prevent leaving the portal:
// Handle browser/phone back button
useEffect(() => {
if (view !== 'home') {
window.history.pushState({ view }, '', window.location.pathname);
}
const handlePopState = (event) => {
if (view !== 'home') {
goHome();
}
};
window.addEventListener('popstate', handlePopState);
return () => window.removeEventListener('popstate', handlePopState);
}, [view]);
// Set initial history state on mount
useEffect(() => {
window.history.replaceState({ view: 'home' }, '', window.location.pathname);
}, []);
Responsive Typography:
/* Hero title */
fontSize: clamp(28px, 7vw, 36px)
/* Section headings */
fontSize: clamp(22px, 6vw, 28px)
/* Body text */
fontSize: clamp(15px, 4vw, 16px)
Responsive Grid:
/* What to Expect cards - auto single column on mobile */
gridTemplateColumns: repeat(auto-fit, minmax(280px, 1fr))
Responsive Spacing:
/* Card padding */
padding: clamp(24px, 5vw, 36px)
/* Pullquote padding */
padding: clamp(20px, 5vw, 32px)
Insert Card Integration¶
The physical insert card included in every box links to the Transition Guide:
Front of card (QR code):
- Links to: https://my.protocolraw.co.uk?view=transition
- Text: "Scan for your personalised feeding plan"
Note: The insert card QR links to the portal transition guide, NOT to the batch proof page. Batch verification is handled by the QR codes printed directly on the pouches.
See: SOP Update Brief - Insert Card Simplification for full details.
Customer.io Integration¶
Template: transition_guide
Trigger: First order, +24 hours
Purpose: Remind customer about transition guide in portal
CTA Link:
Note: Email content should be brief and drive to portal, not duplicate the transition content inline.
Credit Balance Display¶
Overview¶
The customer portal displays available credit balance when the customer has credits > ยฃ0. Credits are earned through referrals and are automatically applied at subscription renewal.
User Interface¶
Location: HomeView, between Quick Actions and Verification Stats
Display Conditions:
- Only shown when credits.has_credits === true
- Hidden when balance is ยฃ0
Components:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย
โ Credit Balance ยฃ15 โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย โ
โ โ โ Credit expiring soon. Active โ โ รขโ ย Only if has_expiring_soon
โ โ subscriptions use it automatically. โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Applied automatically to your next renewal. โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Styling: - Card: White background, 24px margin bottom - Header: Montserrat Bold 18px, Espresso (#2B2523) - Balance: Montserrat Bold 28px, Forest Green (#2D5144) - Warning banner: Warm amber background (#FEF3E7), left border (#D4A574) - Info text: Inter 14px, Warm Gray (#6B6360)
API Integration¶
File: src/api.js
export async function getCustomerCredits(customerId) {
const response = await fetch(`${SUPABASE_URL}/rest/v1/rpc/get_customer_credits`, {
method: 'POST',
headers: {
'apikey': SUPABASE_ANON_KEY,
'Authorization': `Bearer ${SUPABASE_ANON_KEY}`,
'Content-Type': 'application/json',
'Content-Profile': 'raw_ops',
},
body: JSON.stringify({ p_customer_id: customerId }),
});
if (!response.ok) {
console.error('Failed to fetch credits');
return null;
}
return response.json();
}
Note: Uses Content-Profile: raw_ops header because the function is in the raw_ops schema.
Data Loading¶
Credits are fetched in loadDashboard() alongside shipment data:
// In Portal.jsx loadDashboard()
if (data.customer?.id) {
const shipmentData = await api.getActiveShipment(data.customer.id);
setShipment(shipmentData);
const creditData = await api.getCustomerCredits(data.customer.id);
setCredits(creditData);
}
Payment Method Update¶
Overview¶
Customers can update their payment method directly from the portal when their subscription has payment issues or proactively.
User Interface¶
Payment Warning Banner:
Displayed when subscription.payment_status !== 'healthy':
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย
โ โ รฏยธย We had trouble processing your last payment. โ
โ โ
โ [Update payment method] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Update Button Behaviour: 1. Customer clicks "Update payment method" 2. Button shows loading state 3. Portal calls Edge Function to get Seal magic link 4. Customer redirected to Seal's payment update page 5. On completion, customer returns to portal
Edge Function: get-payment-update-url¶
// supabase/functions/get-payment-update-url/index.ts
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
serve(async (req) => {
const { customer_id } = await req.json()
// Get subscription from database
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
)
const { data: subscription } = await supabase
.from('subscriptions')
.select('seal_subscription_id')
.eq('customer_id', customer_id)
.single()
if (!subscription?.seal_subscription_id) {
return new Response(JSON.stringify({ error: 'No subscription found' }), { status: 404 })
}
// Get payment update URL from Seal API
const sealResponse = await fetch(
`https://api.sealsubscriptions.com/v1/subscriptions/${subscription.seal_subscription_id}/payment-update-url`,
{
headers: {
'Authorization': `Bearer ${Deno.env.get('SEAL_API_TOKEN')}`,
}
}
)
const { url } = await sealResponse.json()
return new Response(JSON.stringify({ url }), {
headers: { 'Content-Type': 'application/json' }
})
})
API Function¶
File: src/api.js
export async function getPaymentUpdateUrl(customerId) {
const response = await fetch(`${SUPABASE_URL}/functions/v1/get-payment-update-url`, {
method: 'POST',
headers: {
'apikey': SUPABASE_ANON_KEY,
'Authorization': `Bearer ${SUPABASE_ANON_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ customer_id: customerId }),
});
if (!response.ok) {
throw new Error('Failed to get payment update URL');
}
return response.json();
}
Dunning Email Fallback¶
When dunning emails are sent, they include a link to the customer portal where the payment warning banner will be displayed:
Shipment Tracking¶
Overview¶
Real-time tracking information for in-transit deliveries.
Tracking Card¶
Displayed on HomeView when there's an active shipment:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย
โ ๐ฆ Your order is on its way โ
โ โ
โ Status: Out for delivery โ
โ Estimated: Today by 6pm โ
โ โ
โ [Track with DPD โ] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
API Function¶
export async function getActiveShipment(customerId) {
const { data, error } = await supabase
.from('shipments')
.select('*')
.eq('customer_id', customerId)
.in('status', ['shipped', 'in_transit', 'out_for_delivery'])
.order('created_at', { ascending: false })
.limit(1)
.single();
if (error || !data) return null;
return data;
}
Address Editing¶
Overview¶
Self-serve address updates with automatic sync to Shopify and Seal.
User Interface¶
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย
โ Delivery Address [Edit] โ
โ โ
โ John Smith โ
โ 123 Main Street โ
โ London, E1 4AB โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Edit Modal¶
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย
โ Edit Delivery Address [X] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ Address Line 1 * โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย โ
โ โ 123 Main Street โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Address Line 2 โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย โ
โ โ Flat 4 โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ City * โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย โ
โ โ London โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Postcode * โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย โ
โ โ E1 4AB โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ [Cancel] [Save Address] โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Sync Flow¶
- Customer submits address change
- Portal updates
raw_ops.customerstable - Creates record in
raw_ops.address_changesfor audit - Make.com scenario triggered via webhook
- Scenario updates Shopify customer address
- Scenario updates Seal subscription address
- Sync status updated in
address_changestable
48-Hour Delivery Lock System¶
Purpose¶
Prevents subscription changes when a delivery is imminent to avoid fulfillment confusion.
Lock Window¶
- Applies to orders within 48 hours of scheduled ship date
- Blocks: Skip, Reschedule, Pause, Box Size Change, Frequency Change
- Does NOT block: View-only actions, Address changes, Payment updates
User Interface¶
When locked:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโรขโย
โ ๐ Changes temporarily locked โ
โ โ
โ Your next order ships within 48 hours. Contact support if โ
โ you need to make urgent changes. โ
โ โ
โ Unlocks: Tuesday, 28 Jan at 10:00 AM โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
API Check¶
function isWithinLockWindow(nextDeliveryDate) {
const lockWindowMs = 48 * 60 * 60 * 1000; // 48 hours
const now = new Date();
const deliveryDate = new Date(nextDeliveryDate);
return (deliveryDate - now) < lockWindowMs;
}
Email Notifications¶
Portal Access Request¶
Template: portal_access
Trigger: Customer requests portal access
Subject: "Your Protocol Raw Portal Login"
Subscription Changes¶
All subscription changes trigger confirmation emails via Customer.io:
- subscription_paused
- subscription_resumed
- subscription_cancelled
- delivery_skipped
- delivery_rescheduled
- box_size_changed
- frequency_changed
Transition Guide Reminder¶
Template: transition_guide
Trigger: First order, +24 hours
Subject: "Your personalised transition guide is ready"
CTA Link:
Database Schema¶
portal_tokens¶
CREATE TABLE raw_ops.portal_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
customer_id UUID REFERENCES raw_ops.customers(id),
token UUID UNIQUE NOT NULL DEFAULT gen_random_uuid(),
expires_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() + INTERVAL '7 days'),
used_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
customer_credits¶
CREATE TABLE raw_ops.customer_credits (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
customer_id UUID REFERENCES raw_ops.customers(id),
amount_pence INTEGER NOT NULL,
remaining_pence INTEGER NOT NULL,
source TEXT NOT NULL, -- 'referral', 'goodwill', 'promotion'
status TEXT NOT NULL DEFAULT 'available',
expires_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
subscriptions (payment status fields)¶
-- Relevant fields for payment status
ALTER TABLE raw_ops.subscriptions ADD COLUMN IF NOT EXISTS payment_status TEXT DEFAULT 'healthy';
-- Values: 'healthy', 'failing', 'failed'
address_changes¶
CREATE TABLE raw_ops.address_changes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
customer_id UUID REFERENCES raw_ops.customers(id),
old_address JSONB,
new_address JSONB,
source TEXT NOT NULL, -- 'customer_portal' or 'ops_portal'
agent_email TEXT, -- Only for ops_portal changes
reason TEXT,
support_ticket_id UUID,
shopify_synced BOOLEAN DEFAULT FALSE,
seal_synced BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
customers (v2.6 additions)¶
-- Added in v2.6 for first-visit detection
ALTER TABLE raw_ops.customers
ADD COLUMN portal_first_visited_at TIMESTAMPTZ DEFAULT NULL;
COMMENT ON COLUMN raw_ops.customers.portal_first_visited_at IS
'Timestamp of first portal visit. Used for first-visit redirect to Transition Guide.';
-- Index for analytics (optional)
CREATE INDEX idx_customers_first_visit
ON raw_ops.customers(portal_first_visited_at)
WHERE portal_first_visited_at IS NOT NULL;
Key Views¶
v_customer_portal_data
CREATE VIEW raw_ops.v_customer_portal_data AS
SELECT
c.id,
c.email,
c.first_name,
c.last_name,
c.address1,
c.address2,
c.city,
c.province,
c.zip,
c.seal_subscription_id,
c.portal_first_visited_at, -- Added in v2.6
json_agg(DISTINCT d.*) AS dogs,
(SELECT COUNT(*) FROM raw_ops.orders WHERE customer_id = c.id) AS order_count
FROM raw_ops.customers c
LEFT JOIN raw_ops.dogs d ON d.customer_id = c.id
GROUP BY c.id;
API Reference¶
Authentication¶
Validate Token
POST /rest/v1/rpc/validate_portal_token
Body: { "p_token": "uuid" }
Returns: { "valid": true, "customer_id": "uuid" }
Request Portal Access
POST /rest/v1/rpc/request_portal_access
Body: { "p_email": "customer@example.com" }
Returns: { "success": true }
Session Response (v2.6)¶
The portal session endpoint now includes isFirstVisit flag:
{
"success": true,
"customer": {
"id": "uuid",
"first_name": "John",
"email": "john@example.com",
"portal_first_visited_at": null
},
"dogs": [
{
"id": "uuid",
"dog_name": "Luna",
"dog_weight_kg": 25,
"life_stage": "adult",
"activity_level": "moderate",
"daily_grams": 400
}
],
"subscription": {...},
"isFirstVisit": true
}
Customer Data¶
Get Portal Data
Get Credits
POST /rest/v1/rpc/get_customer_credits
Headers: Content-Profile: raw_ops
Body: { "p_customer_id": "uuid" }
Subscription Actions¶
All subscription actions route through Make.com scenarios for Seal API interaction.
Edge Functions¶
portal-session¶
Validates token, creates session, handles first-visit detection.
get-payment-update-url¶
Returns Seal magic link for payment method update.
Deployment¶
# Deploy the payment update edge function
supabase functions deploy get-payment-update-url
# Set required secrets
supabase secrets set SEAL_API_TOKEN=your_seal_token_here
Make.com Integration¶
Portal Webhook Scenarios¶
| Scenario | Trigger | Action |
|---|---|---|
| PORTAL-01 | Address change | Sync to Shopify + Seal |
| PORTAL-02 | Skip delivery | Call Seal API |
| PORTAL-03 | Pause subscription | Call Seal API |
| PORTAL-04 | Resume subscription | Call Seal API |
| PORTAL-05 | Box size change | Call Seal API |
| PORTAL-06 | Frequency change | Call Seal API |
| PORTAL-07 | Cancel subscription | Call Seal API + send email |
Deployment¶
Cloudflare Pages¶
# Build
npm run build
# Deploy
wrangler pages deploy dist --project-name=protocol-raw-customer-portal
Environment Variables¶
Set in Cloudflare Pages dashboard:
- VITE_SUPABASE_URL
- VITE_SUPABASE_ANON_KEY
Troubleshooting¶
Magic Link Not Arriving¶
Symptoms: Customer requests access but no email arrives
Investigation: 1. Check Customer.io for event delivery 2. Verify email exists in raw_ops.customers 3. Check spam/junk folder 4. Verify token created in portal_tokens
Common causes: - Email not in customers table (not a subscriber) - Customer.io event failed - Email in spam
Portal Shows "Session Expired"¶
Symptoms: Customer clicks link but sees expired message
Investigation:
SELECT token, expires_at, used_at
FROM raw_ops.portal_tokens
WHERE customer_id = '[customer_id]'
ORDER BY created_at DESC
LIMIT 5;
Common causes: - Token older than 7 days - Token already used (single-use) - Customer clicked old link
Subscription Actions Failing¶
Symptoms: Customer clicks action but nothing happens
Investigation: 1. Check browser console for errors 2. Verify Make.com scenario history 3. Check Seal API response
Common causes: - Seal subscription ID mismatch - Make.com scenario paused - Within 48-hour lock window
Credits Not Appearing¶
Symptoms: Customer has credit but portal shows no credit card
Investigation:
-
Check browser console for API errors:
-
Verify credit exists in database:
-
Test the function directly:
-
Check API headers - must use
Content-Profile: raw_opsnotAccept-Profile
Common causes:
- Credit expired (check expires_at)
- Credit fully applied (check remaining_pence)
- Credit status not 'available' (check status)
- Wrong schema header in API call
Credits Showing Wrong Balance¶
Investigation:
-- Get all available credits
SELECT id, amount_pence, remaining_pence, status, expires_at
FROM raw_ops.customer_credits
WHERE customer_id = '[customer_id]'
ORDER BY created_at DESC;
-- Check the view used by function
SELECT * FROM raw_ops.v_customer_credit_balance
WHERE customer_id = '[customer_id]';
Payment Update Button Not Working¶
Symptoms: Customer clicks "Update payment method" but nothing happens or error shown
Investigation:
- Check browser console for errors
-
Check Supabase Edge Function logs:
-
Verify subscription exists:
-
Test Edge Function directly:
-
Verify Seal API credentials:
Common causes: - No active subscription found - Seal API token expired or invalid - Seal subscription ID not stored in our database - Edge function not deployed - CORS issues (check browser network tab)
Payment Warning Not Showing¶
Symptoms: Customer is in dunning but no warning banner appears
Investigation:
-
Check subscription payment_status:
-
Verify payment_status is not 'healthy':
- Should be 'failing' or 'failed' to show warning
Common causes: - payment_status still 'healthy' (webhook not processed) - Subscription state out of sync with Seal
Address Changes Not Syncing¶
Symptoms: Address updated in portal but old address in Shopify/Seal
Investigation:
Check sync status: - shopify_synced = false โ Shopify update failed - seal_synced = false โ Seal update failed
Common causes: - Make.com scenario error - Shopify API rate limited - Seal subscription ID missing
Transition Guide Not Showing for New Customers¶
Symptoms: New customer goes straight to HomeView instead of TransitionView
Investigation:
-
Check if first visit was already recorded:
-
If
portal_first_visited_atis already set, the redirect won't happen -
Check browser console for
isFirstVisitflag in session response
Common causes:
- Customer visited portal before v2.6 deployment
- Edge function not updated to include isFirstVisit flag
- Frontend not handling the redirect
Transition Guide Deep Link Not Working¶
Symptoms: ?view=transition URL parameter ignored
Investigation:
- Check browser console for URL parsing errors
- Verify the URL includes both
tokenandviewparameters - Check if authentication succeeds before view is set
Common causes: - Token validation failing (view param processed but user logged out) - Frontend useEffect not running (React render issue)
Version History¶
| Version | Date | Changes |
|---|---|---|
| 2.6 | 2026-01-21 | Transition Guide: personalised 10-day feeding schedule, first-visit detection, deep linking, mobile back button handling |
| 2.5 | 2026-01-17 | Payment method update via Seal magic link |
| 2.4 | 2026-01-10 | Credit balance display |
| 2.3 | 2026-01-05 | Documentation consolidation |
| 2.2 | 2025-12-20 | Self-serve address editing |
| 2.1 | 2025-12-10 | Shipment tracking card |
| 2.0 | 2025-11-30 | Initial elevated design release |
Related Documentation¶
- SOP-SUB-00: Subscription State Management (dunning flow, payment webhooks)
- SOP CS-00: Customer Operations System (includes portal specifications)
- SOP-REF-01 v2.1: Referral System (credit ledger integration)
- Protocol_Raw_Email_Design_System_v1_0.md: Email templates
- Protocol_Raw_Proof_Portal_Documentation_v1_0.md: Batch verification portal
- Protocol_Raw_Operations_Portal_Documentation_v3_6.md: Internal operations portal
- Visual-Identity-Guide-v2_2-Component-Patterns.md: Component styling guidelines
Document Owner: Protocol Raw Operations Team
Last Reviewed: January 21, 2026
Next Review: April 21, 2026
Version: 2.6
Status: Production Ready โ
End of Protocol Raw Customer Portal Documentation v2.6