Files
core/docs/scratch/customer-accounts-feature-survey.html
T

477 lines
20 KiB
HTML
Raw Normal View History

<title>Customer Accounts Feature Survey</title>
<style>
:root {
--paper: #FAFAF7;
--ink: #1C1C1A;
--muted: #6B6B63;
--accent: #2F5D50;
--accent-soft: #E4EDE9;
--good: #3F7A5C;
--good-soft: #E6F0EA;
--warn: #B8863B;
--warn-soft: #F5ECDC;
--miss: #A14B3B;
--miss-soft: #F5E5E0;
--hairline: #E4E2DB;
--card: #FFFFFF;
}
:root:not([data-theme="light"]) {
@media (prefers-color-scheme: dark) {
--paper: #17181A;
--ink: #EDEBE4;
--muted: #9B9A90;
--accent: #7FBFA8;
--accent-soft: #1E2C27;
--good: #6FBF97;
--good-soft: #1B2A22;
--warn: #D9A85C;
--warn-soft: #2C2418;
--miss: #D97C68;
--miss-soft: #2E1E1A;
--hairline: #2C2D2E;
--card: #1E1F21;
}
}
:root[data-theme="dark"] {
--paper: #17181A;
--ink: #EDEBE4;
--muted: #9B9A90;
--accent: #7FBFA8;
--accent-soft: #1E2C27;
--good: #6FBF97;
--good-soft: #1B2A22;
--warn: #D9A85C;
--warn-soft: #2C2418;
--miss: #D97C68;
--miss-soft: #2E1E1A;
--hairline: #2C2D2E;
--card: #1E1F21;
}
* { box-sizing: border-box; }
body {
background: var(--paper);
color: var(--ink);
font-family: "IBM Plex Sans", ui-sans-serif, system-ui, sans-serif;
font-size: 15.5px;
line-height: 1.55;
margin: 0;
padding: 4.5rem 1.5rem 6rem;
}
.wrap {
max-width: 780px;
margin: 0 auto;
}
header.page {
margin-bottom: 3.25rem;
}
.eyebrow {
font-family: "IBM Plex Mono", ui-monospace, monospace;
font-size: 0.72rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--accent);
margin-bottom: 0.9rem;
}
h1 {
font-family: "Fraunces", Georgia, serif;
font-weight: 560;
font-size: clamp(2.1rem, 4.5vw, 2.65rem);
line-height: 1.08;
letter-spacing: -0.01em;
margin: 0 0 0.9rem;
text-wrap: balance;
}
.dek {
color: var(--muted);
max-width: 60ch;
font-size: 1.02rem;
}
.dek strong {
color: var(--ink);
font-weight: 600;
}
.legend {
display: flex;
flex-wrap: wrap;
gap: 0.6rem;
margin-top: 1.6rem;
}
.chip {
display: inline-flex;
align-items: center;
gap: 0.4rem;
font-family: "IBM Plex Mono", ui-monospace, monospace;
font-size: 0.72rem;
letter-spacing: 0.04em;
padding: 0.28rem 0.6rem;
border-radius: 3px;
}
.chip.have { background: var(--good-soft); color: var(--good); }
.chip.partial { background: var(--warn-soft); color: var(--warn); }
.chip.missing { background: var(--miss-soft); color: var(--miss); }
section.category {
margin-top: 3rem;
}
.cat-head {
display: flex;
align-items: baseline;
gap: 0.85rem;
border-bottom: 1px solid var(--hairline);
padding-bottom: 0.7rem;
margin-bottom: 1.1rem;
}
.cat-num {
font-family: "Fraunces", Georgia, serif;
font-size: 1.05rem;
color: var(--accent);
font-variant-numeric: tabular-nums;
min-width: 1.6rem;
}
.cat-head h2 {
font-family: "Fraunces", Georgia, serif;
font-weight: 500;
font-size: 1.28rem;
margin: 0;
letter-spacing: -0.005em;
}
.cat-note {
color: var(--muted);
font-size: 0.86rem;
margin: 0 0 1.2rem;
max-width: 62ch;
}
.feature {
display: grid;
grid-template-columns: 1fr auto;
gap: 0.3rem 1rem;
padding: 1.05rem 0;
border-bottom: 1px solid var(--hairline);
align-items: start;
}
.feature:last-child { border-bottom: none; }
.f-name {
font-weight: 600;
font-size: 0.98rem;
}
.f-status {
font-family: "IBM Plex Mono", ui-monospace, monospace;
font-size: 0.68rem;
letter-spacing: 0.06em;
text-transform: uppercase;
padding: 0.22rem 0.55rem;
border-radius: 3px;
white-space: nowrap;
height: fit-content;
}
.f-status.have { background: var(--good-soft); color: var(--good); }
.f-status.partial { background: var(--warn-soft); color: var(--warn); }
.f-status.missing { background: var(--miss-soft); color: var(--miss); }
.f-note {
grid-column: 1 / -1;
color: var(--muted);
font-size: 0.87rem;
margin-top: 0.15rem;
max-width: 66ch;
}
.f-note code {
font-family: "IBM Plex Mono", ui-monospace, monospace;
font-size: 0.82em;
background: var(--accent-soft);
color: var(--accent);
padding: 0.08em 0.35em;
border-radius: 3px;
}
footer.page {
margin-top: 4rem;
padding-top: 1.5rem;
border-top: 1px solid var(--hairline);
color: var(--muted);
font-size: 0.82rem;
display: flex;
justify-content: space-between;
gap: 1rem;
flex-wrap: wrap;
}
footer.page a { color: var(--accent); }
@media (max-width: 560px) {
body { padding: 3rem 1.1rem 4rem; }
.feature { grid-template-columns: 1fr; }
.f-status { justify-self: start; }
}
</style>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400..600&family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap">
<div class="wrap">
<header class="page">
<div class="eyebrow">boboko / customer accounts · competitive survey</div>
<h1>What customer accounts elsewhere can do that boboko can't yet</h1>
<p class="dek">
A feature-by-feature pass across Shopify, WooCommerce, and PrestaShop's
account layer — sourced, not recalled from memory — checked against what
<strong>Lunar's <code>Customer</code>/<code>Address</code>/<code>CustomerGroup</code></strong>
models actually support today and what exists (or doesn't) in boboko-core
and 3dealer right now. For deciding what to design next, not a build order.
</p>
<div class="legend">
<span class="chip have">● have</span>
<span class="chip partial">◐ partial</span>
<span class="chip missing">○ missing</span>
</div>
</header>
<section class="category">
<div class="cat-head">
<span class="cat-num">01</span>
<h2>Whether an account exists at all</h2>
</div>
<p class="cat-note">The storefront-facing account experience, as distinct from staff/admin auth in <code>Modules\Core\Auth</code>.</p>
<div class="feature">
<div class="f-name">Customer↔User linking (data model)</div>
<span class="f-status have">have</span>
<div class="f-note">Fully modeled by Lunar core — <code>Customer::users()</code> / <code>User::customers()</code> via <code>customer_user</code> pivot (<code>LunarUser</code> trait), plus <code>User::latestCustomer()</code>.</div>
</div>
<div class="feature">
<div class="f-name">Customer record auto-created on signup</div>
<span class="f-status have">have</span>
<div class="f-note"><code>Modules\Core\Customer\Listeners\CreateCustomerForUser</code> attaches a new <code>Customer</code> to every <code>User</code> on <code>UserCreated</code>, gated by <code>config('core.auto_create_customer_for_user')</code>.</div>
</div>
<div class="feature">
<div class="f-name">Storefront login / registration UI</div>
<span class="f-status missing">missing</span>
<div class="f-note">3dealer has no auth scaffolding at all — no Breeze/Fortify/Sanctum in <code>composer.json</code>, no <code>login</code>/<code>register</code> views, nothing in <code>routes/web.php</code>. Only <code>Modules\Core\Auth</code>'s Filament staff panel login exists.</div>
</div>
<div class="feature">
<div class="f-name">Account/profile page (name, addresses, orders)</div>
<span class="f-status missing">missing</span>
<div class="f-note">No <code>AccountController</code>, no <code>account</code>/<code>profile</code> route, no matching Blade views anywhere in 3dealer's <code>app/</code> or <code>resources/views</code> — confirmed by exhaustive grep.</div>
</div>
<div class="feature">
<div class="f-name">Account nav link in header</div>
<span class="f-status missing">missing</span>
<div class="f-note"><code>resources/views/components/header.blade.php</code> has a cart icon and a search button but no account/login link at all — not even a dead one. The cart icon itself links to <code>/cart</code>, which also has no matching route, matching this codebase's known stubbed-UI pattern.</div>
</div>
</section>
<section class="category">
<div class="cat-head">
<span class="cat-num">02</span>
<h2>Order history &amp; tracking</h2>
</div>
<p class="cat-note">Letting a customer see and follow their own orders without contacting support.</p>
<div class="feature">
<div class="f-name">Order history data (per customer)</div>
<span class="f-status have">have</span>
<div class="f-note">Fully modeled — <code>Customer::orders()</code> and <code>User::orders()</code> both exist (<code>Lunar\Models\Order</code>), with <code>status</code>, line items, addresses, and transactions already relational.</div>
</div>
<div class="feature">
<div class="f-name">Self-service order history / status page</div>
<span class="f-status missing">missing</span>
<div class="f-note">No storefront route or controller reads <code>Order</code> for a logged-in customer — the data exists, nothing surfaces it. Shopify's rebuilt (2026) customer-accounts UI and PrestaShop's order-detail tracking page are both native; WooCommerce ships this in My Account by default.</div>
</div>
<div class="feature">
<div class="f-name">Shipment tracking numbers surfaced to customer</div>
<span class="f-status missing">missing</span>
<div class="f-note">No tracking-number field found on <code>Order</code>/<code>OrderLine</code>/shipping models in <code>vendor/lunarphp/core</code>; PrestaShop's tracking module patches this same gap with a third-party add-on, so it isn't a "native everywhere" bar either.</div>
</div>
<div class="feature">
<div class="f-name">Reorder / buy-again from order history</div>
<span class="f-status missing">missing</span>
<div class="f-note">Needs an order-history UI to exist first (see above) plus a "re-add these lines to cart" action — Lunar's <code>Cart::add()</code> already supports the mechanics, nothing wires an <code>Order</code> line back into a new cart.</div>
</div>
</section>
<section class="category">
<div class="cat-head">
<span class="cat-num">03</span>
<h2>Saved addresses</h2>
</div>
<p class="cat-note">What a returning customer doesn't have to retype.</p>
<div class="feature">
<div class="f-name">Multiple saved addresses per customer</div>
<span class="f-status have">have</span>
<div class="f-note"><code>Customer::addresses()</code> (<code>HasMany</code>) — <code>Lunar\Models\Address</code> has no cap on count.</div>
</div>
<div class="feature">
<div class="f-name">Separate default shipping / billing address</div>
<span class="f-status have">have</span>
<div class="f-note"><code>Address::shipping_default</code> and <code>billing_default</code> booleans; <code>AddressObserver</code> auto-unsets the previous default when a new one is flagged, so only one of each can be true at a time.</div>
</div>
<div class="feature">
<div class="f-name">Self-service address book (add/edit/delete UI)</div>
<span class="f-status missing">missing</span>
<div class="f-note">Only Filament's staff-facing <code>AddressRelationManager</code> (<code>src/Customer/RelationManagers/AddressRelationManager.php</code>) touches addresses today — that's an admin back-office view, not a storefront one. No customer-facing CRUD exists.</div>
</div>
<div class="feature">
<div class="f-name">Address autocomplete / validation at entry</div>
<span class="f-status missing">missing</span>
<div class="f-note">Nothing in <code>lunarphp/core</code> or boboko-core wires a geocoding/validation service — this is a storefront-only concern layered on top of the plain <code>line_one</code>…<code>postcode</code> fields.</div>
</div>
</section>
<section class="category">
<div class="cat-head">
<span class="cat-num">04</span>
<h2>Login &amp; identity</h2>
</div>
<p class="cat-note">How a customer gets in, and how forgiving that path is.</p>
<div class="feature">
<div class="f-name">Email + password login</div>
<span class="f-status missing">missing</span>
<div class="f-note">No storefront auth guard/routes configured — see 01. <code>Modules\Core\Auth\Services\OtpService</code>/<code>UserOtpService</code> exist but are wired to staff/Filament login, not a customer-facing flow.</div>
</div>
<div class="feature">
<div class="f-name">Passwordless / magic-link / OTP login</div>
<span class="f-status partial">partial</span>
<div class="f-note"><code>UserOtpService</code> and <code>UserOtpMail</code> already implement an OTP-by-email mechanism for the staff panel — the building block for a customer-facing passwordless flow exists, just not exposed to a storefront route. Shopify ships this as sign-in links (6-digit email code) by default in its 2026 customer accounts.</div>
</div>
<div class="feature">
<div class="f-name">Social login (Google / Apple / Facebook)</div>
<span class="f-status missing">missing</span>
<div class="f-note">No <code>laravel/socialite</code> in either <code>composer.json</code>. Shopify offers Google/Facebook sign-in and "Sign in with Shop" natively; this would be a from-scratch integration here.</div>
</div>
<div class="feature">
<div class="f-name">Guest checkout → account conversion</div>
<span class="f-status missing">missing</span>
<div class="f-note">No storefront checkout flow exists yet in 3dealer to convert from — this depends on checkout being built before it's meaningful. Lunar's <code>Cart::user_id</code>/<code>customer_id</code> nullable-until-claimed design would support it once a checkout and account UI exist.</div>
</div>
</section>
<section class="category">
<div class="cat-head">
<span class="cat-num">05</span>
<h2>Payments &amp; saved methods</h2>
</div>
<p class="cat-note">Whether a returning customer can skip re-entering card details.</p>
<div class="feature">
<div class="f-name">Saved payment methods on account</div>
<span class="f-status missing">missing</span>
<div class="f-note">No tokenized-card storage model found in <code>lunarphp/core</code> or boboko-core's payment integration. Even Shopify gates this behind Enterprise; WooCommerce's version depends entirely on gateway-level tokenization (e.g. Stripe), not a core feature.</div>
</div>
</section>
<section class="category">
<div class="cat-head">
<span class="cat-num">06</span>
<h2>Wishlist &amp; saved items</h2>
</div>
<p class="cat-note">Keeping track of products outside the cart.</p>
<div class="feature">
<div class="f-name">Wishlist / saved-for-later products</div>
<span class="f-status missing">missing</span>
<div class="f-note">No <code>wishlist</code> model, table, or reference anywhere in <code>src/</code> or <code>vendor/lunarphp</code> — grep confirms zero hits. Shopify also has no native wishlist (third-party apps like Flits fill the gap); WooCommerce/PrestaShop are the same story via plugins, so this is a genuinely common gap, not a boboko-specific one.</div>
</div>
</section>
<section class="category">
<div class="cat-head">
<span class="cat-num">07</span>
<h2>Groups, pricing &amp; B2B</h2>
</div>
<p class="cat-note">Where boboko is already ahead of a typical single-tenant storefront — Lunar's <code>CustomerGroup</code> does real work here.</p>
<div class="feature">
<div class="f-name">Customer groups for differentiated pricing/visibility</div>
<span class="f-status have">have</span>
<div class="f-note"><code>CustomerGroup</code> model plus <code>HasCustomerGroups</code> trait — <code>Product::customerGroup()</code> scope and <code>Price</code>'s polymorphic customer-group awareness are both real, shipped behavior, not scaffolding.</div>
</div>
<div class="feature">
<div class="f-name">Scheduled group availability (time-boxed access)</div>
<span class="f-status have">have</span>
<div class="f-note"><code>HasCustomerGroups::scheduleCustomerGroup()</code> / <code>unscheduleCustomerGroup()</code>, backed by <code>CanScheduleAvailability</code> — supports a <code>starts_at</code>/<code>ends_at</code> window per group, e.g. early access for wholesale.</div>
</div>
<div class="feature">
<div class="f-name">Multi-user company / B2B accounts</div>
<span class="f-status partial">partial</span>
<div class="f-note"><code>Customer::users()->sync([...])</code> already supports attaching several <code>User</code>s to one <code>Customer</code> record — the data model allows a shared company account today, but nothing (invite flow, role/permission split between company users, storefront switch-account UI) is built on top of it. PrestaShop's "Multi-User Customer Account" add-on is the closest native comparison, and it's a paid third-party module there too.</div>
</div>
<div class="feature">
<div class="f-name">Self-service customer-group selection at registration</div>
<span class="f-status missing">missing</span>
<div class="f-note">Groups exist and are assignable (<code>HasCustomerGroups::bootHasCustomerGroups()</code> auto-syncs default groups on creation), but nothing lets a customer request/select a group like "wholesale" at signup — that's currently a staff-only Filament action via <code>CustomerResourceExtension</code>.</div>
</div>
</section>
<section class="category">
<div class="cat-head">
<span class="cat-num">08</span>
<h2>Loyalty, retention &amp; data rights</h2>
</div>
<p class="cat-note">Longer-tail account features — noted for completeness, not depth (data rights specifically overlaps a separate Privacy survey).</p>
<div class="feature">
<div class="f-name">Loyalty / rewards points program</div>
<span class="f-status missing">missing</span>
<div class="f-note">No points/loyalty model anywhere in <code>lunarphp/core</code> or boboko-core — <code>Discount</code>'s <code>BuyXGetY</code> type is the closest primitive, but it's a promo mechanic, not an accruing balance. PrestaShop and WooCommerce both rely on third-party modules for this too (Knowband, Webkul, Yith).</div>
</div>
<div class="feature">
<div class="f-name">Self-service data export / account deletion</div>
<span class="f-status missing">missing</span>
<div class="f-note">The only related tool is <code>boboko:anonymize</code> — a local-environment-only dev command that scrubs <code>users</code>/<code>lunar_customers</code> for testing, not a customer-facing GDPR flow. WooCommerce's closest native equivalent is also a paid add-on (Data Privacy Manager); flagged briefly here, full treatment belongs to the separate Privacy survey.</div>
</div>
<div class="feature">
<div class="f-name">Subscription / recurring-order management</div>
<span class="f-status missing">missing</span>
<div class="f-note">No subscription model, billing-cycle field, or recurring-cart concept found in <code>lunarphp/core</code>. This is WooCommerce Subscriptions/Shopify-app territory on the platforms researched too — not a core-package feature anywhere.</div>
</div>
</section>
<footer class="page">
<span>Compiled 2026-08-28 — chips backed by web research (Shopify/WooCommerce/PrestaShop feature claims) are noted inline by platform name; all other claims are direct reads of <code>vendor/lunarphp/core/src</code>, boboko-core's <code>src/</code>, and 3dealer's <code>app/</code>/<code>resources/views</code>/<code>routes</code>.</span>
<span>boboko-core / docs</span>
</footer>
</div>