Component: Card — Chevron variant (no title) · Pilot Card use case
Type: Domain control — composes the generic CardViewModel via PilotCardFactory (maps domain data → scaffold; it does not subclass the scaffold — see Decisions D11)
Last Updated: 2026-06-05
Renders the Card Chevron variant (heading + link + right chevron, no eyebrow/title/image, on a
#dfedf9surface). See the Chevron Variant section incard/card-component.html.
Shows how CardViewModel properties map to the rendered control using the pilot card as a concrete example.
HeaderText
│
▼
MEC ◄── UserInfo.MEC ?? "ALPA"
┌──────────────────────────────────────┐
│ │
ContentText ───────► │ Welcome Johnson │ ◄── $"{FirstName} {LastName}" (no rank — see note)
│ │
LinkText ──────────► │ View Contract › │ ◄── contract?.Title ?? "View Contract"
Link ──────────────► │ (tap navigates to destination) │ ◄── contract?.FileID (DataManager)
│ │
└──────────────────────────────────────┘
IsLinkVisible ──────► Controls visibility of link row ◄── contract is not null
IsLoading ──────────► Default strategy — no skeleton/overlay
Note: This document serves as a reference example for future presentation-layer phases.
When implementing specific use cases, follow this pattern to map domain data onto the genericCardViewModelvia a factory — compose it, don't subclass it.
| Property | Status | Source | Notes |
|---|---|---|---|
| HeaderText | ✅ Available | IAuthentication.GetUserInfo().MEC |
Falls back to "ALPA" |
| ContentText | ✅ Available | UserInfo.FirstName + UserInfo.LastName |
Composed greeting — rank not available (see note below) |
| LinkText | ✅ Resolved | DocumentDatabase via DataManager |
First contract doc title for pilot's MEC scope |
| Link | ✅ Resolved | DocumentDatabase via DataManager |
First contract doc FileID/URL for pilot's MEC scope |
| IsLinkVisible | ✅ Available | ViewModel state | Always visible for pilot card |
| IsLoading | ✅ Available | ViewModel state | Default strategy (no skeleton) |
UserInfo.UserType is populated by the auth API but contains membership tier only (STAFF or MEMBER). It does not contain pilot rank (Captain, First Officer, etc.).
No backend source for pilot rank currently exists. Options:
1. Omit rank from the greeting — display "Welcome {FirstName} {LastName}"
2. Request a new API field from the backend team to surface pilot rank
3. Derive from member data if rank is available elsewhere (e.g., contract or profile endpoint — needs investigation)
Current recommendation: Omit rank until a confirmed data source is identified.
The domain control is split three ways: the scaffold CardViewModel stays domain-free; a pure factory maps domain data onto it (data in → VM out, no services, no async); a domain service owns the DI-injected async gathering, then hands the data to the factory. This keeps the library reusable and follows the RawRepresentationFactory<T> rule. (The factory must not hold an IServiceProvider — that would be a service-locator anti-pattern; see D11.)
// Scaffold — generic, domain-free (unchanged)
public class CardViewModel : SurfaceViewModel
{
public string HeaderText { get; set; }
public string ContentText { get; set; }
public string LinkText { get; set; }
public string Link { get; set; }
public bool IsLinkVisible { get; set; }
}
// Domain control — PURE MAPPER: domain data in, scaffold VM out
public sealed class PilotCardFactory : RawRepresentationFactory<CardViewModel>
{
public CardViewModel Create(UserInfo user, DocumentItem? contract) => new()
{
HeaderText = user?.MEC ?? "ALPA",
// rank omitted — UserInfo.UserType is STAFF/MEMBER, not pilot rank
ContentText = $"Welcome {user?.FirstName} {user?.LastName}",
LinkText = contract?.Title ?? "View Contract",
Link = contract?.FileID ?? string.Empty, // resolved by SharedActionsService
IsLinkVisible = contract is not null,
};
}
// Domain service — owns the async gathering (DI-injected), then maps
public sealed class PilotCardService(
IAuthentication authentication,
DataManager dataManager,
PilotCardFactory factory)
{
public async Task<CardViewModel> BuildAsync()
{
var user = authentication.GetUserInfo();
// Resolve the contract via existing DocumentDatabase infrastructure:
// categories for the pilot's MEC scope → "Contract" category → first doc
var categories = await dataManager.GetDocumentCategoriesForScopeAsync(user.MEC);
var contractCategory = categories?.FirstOrDefault(c =>
c.Category.Contains("Contract", StringComparison.OrdinalIgnoreCase));
DocumentItem? contract = null;
if (contractCategory != null)
{
var docs = await dataManager.GetDocumentsForScopeCategoryAsync(
user.MEC, contractCategory.Category);
contract = docs?.FirstOrDefault();
}
return factory.Create(user, contract); // pure projection
}
}
IAuthentication.GetUserInfo().MEC"ALPA"UserInfo.FirstName + UserInfo.LastName"Welcome Jose Johnson"UserInfo.UserType returns STAFF or MEMBER (membership tier), not rankDataManager.GetDocumentsForScopeCategoryAsync(MEC, "Contract")"View Contract" if doc found but has no titleIsLinkVisible = falseDocumentItem.FileID from the contract document — resolved/opened via SharedActionsServicetrue only when a contract document is found for the pilot's MEC scope| Service | Interface | Notes |
|---|---|---|
| Authentication | IAuthentication |
Provides UserInfo (MEC, name, UserType=STAFF/MEMBER) |
| DataManager | DataManager |
GetDocumentCategoriesForScopeAsync + GetDocumentsForScopeCategoryAsync for contract lookup |
docs/component-specifications/card/card-component.htmlALPAMobile/Services/IAuthentication.csDocument Owner: ALPA Mobile Team
Purpose: Reference pattern for presentation-layer phase — do not use as active implementation spec