Skip to content

Admin Settings - Module Specification

1. Overview

1.1 Purpose

Admin Settings manages store locations, brand configuration, and system-wide settings. Provides centralized configuration for multi-store operations including business hours, contact information, social media links, and brand customization.

1.2 Scope

In Scope: - Store management (locations, hours, contact, social media, financing offers) - Brand configuration (logos, social links, disclaimers, notification banner) - System-wide display settings (average rating, payment icons)

Out of Scope: - Virtual/SEO locations (→ CMS module) - User management (→ Auth module) - Media file storage (→ Media module) - Content pages and blog (→ CMS module)


2. Dependencies

2.1 Upstream (What This Module Needs)

Module/Service What We Need Interface
Media Store images, logos, payment icons Server Actions

2.2 Downstream (What Needs This Module)

Module/Service What They Need Interface
Customer Website Store locations, brand config, hours Server Actions
Inventory Store assignment for puppies Server Actions
CRM Store info for leads Server Actions
CMS Brand settings for content display Server Actions

3. Data Ownership

3.1 Entities This Module Owns

Entity Description Key Fields
Store Physical store location id, name, address, hours, phone, coordinates, socialLinks
Brand Brand-wide configuration id, name, logos, socialLinks, disclaimers, notificationBanner
FinancingOffer Financing provider per store id, storeId, providerName, requirements, applicationLink

3.2 Entities This Module Uses (Read-Only)

Entity Owner How We Use It
Media Media Logos, store images, payment icons

3.3 Data Schema

import type { LexicalEditorState } from '@/modules/rich-text-editor';
import type { Media } from '@/modules/media';

interface Store {
  id: string;
  slug: string;
  name: string;

  // Address
  addressStreet: string;
  addressCity: string;
  addressState: string;
  addressZip: string;
  countyPermit?: string;

  // Coordinates
  latitude: number;
  longitude: number;

  // Maps
  locationIframeUrl?: string;       // Store-specific map embed
  googleMapsPlaceId?: string;       // For reviews integration
  googleMapsLink?: string;
  appleMapsUrl?: string;

  // Hours (flexible per day)
  hours: StoreHours;

  // Contact
  phoneNumber: string;

  // Display
  order: number;                    // Display order in listings
  exteriorImageId?: Media['id'];
  interiorImageId?: Media['id'];

  // Social media
  socialLinks: SocialLinks;

  isActive: boolean;
  createdAt: Date;
  updatedAt: Date;
}

interface StoreHours {
  monday: DayHours | null;          // null = closed
  tuesday: DayHours | null;
  wednesday: DayHours | null;
  thursday: DayHours | null;
  friday: DayHours | null;
  saturday: DayHours | null;
  sunday: DayHours | null;
}

interface DayHours {
  open: string;                     // "09:00" format
  close: string;                    // "21:00" format
}

interface SocialLinks {
  facebook?: string;
  instagram?: string;
  pinterest?: string;
  twitter?: string;
  tiktok?: string;
}

interface Brand {
  id: string;
  name: string;

  // Logos
  logoDarkId: Media['id'];
  footerLogoId: Media['id'];

  // Images
  subscribeImageId?: Media['id'];        // Newsletter section
  missingPhotoFillerId?: Media['id'];    // Placeholder image
  allCardsAcceptedIconId?: Media['id'];  // Payment methods

  // Maps
  allLocationsEmbedUrl: string;          // Google Maps embed for all stores

  // Display
  averageReviewRating: string;           // e.g., "4.9"
  notificationBanner?: LexicalEditorState;  // Top banner announcement

  // Legal
  footerDisclaimer: LexicalEditorState;
  footerSecondDisclaimer?: LexicalEditorState;

  // Social media (brand-level)
  socialLinks: SocialLinks;

  createdAt: Date;
  updatedAt: Date;
}

interface FinancingOffer {
  id: string;
  storeId: Store['id'];
  providerName: string;
  requirements: string[];           // List of requirements
  applicationLink: string;
  bannerImageId?: Media['id'];
  order: number;                    // Display order
  isActive: boolean;
  createdAt: Date;
  updatedAt: Date;
}

4. Service Level Objectives

Objective Target Measurement
Settings Read Latency < 50ms Application monitoring
Settings Update Latency < 200ms Application monitoring
Availability 99.9% Health checks
Cache Hit Rate > 95% Application monitoring

Note: Brand and Store settings are heavily cached as they rarely change. Cache invalidation on update.


5. Interface Contract

5.1 Internal Service Interface

interface AdminSettingsService {
  // Store operations
  getStore(id: string, include?: StoreInclude): Promise<Store>;
  getStoreBySlug(slug: string, include?: StoreInclude): Promise<Store>;
  listStores(filters?: StoreFilters, include?: StoreInclude): Promise<Store[]>;
  createStore(data: CreateStoreDTO): Promise<Store>;
  updateStore(id: string, data: UpdateStoreDTO): Promise<Store>;
  deleteStore(id: string): Promise<void>;

  // Brand operations (single brand per instance)
  getBrand(include?: BrandInclude): Promise<Brand>;
  updateBrand(data: UpdateBrandDTO): Promise<Brand>;

  // Financing offer operations
  getFinancingOffer(id: string): Promise<FinancingOffer>;
  listFinancingOffers(storeId: string): Promise<FinancingOffer[]>;
  createFinancingOffer(data: CreateFinancingOfferDTO): Promise<FinancingOffer>;
  updateFinancingOffer(id: string, data: UpdateFinancingOfferDTO): Promise<FinancingOffer>;
  deleteFinancingOffer(id: string): Promise<void>;
}

// Include patterns
interface StoreInclude {
  financingOffers?: boolean;
  media?: boolean;                  // Hydrate exteriorImage, interiorImage
}

interface BrandInclude {
  media?: boolean;                  // Hydrate all logo/image fields
}

// Filters
interface StoreFilters {
  isActive?: boolean;
  search?: string;                  // Search by name, city
}

// DTOs
interface CreateStoreDTO {
  slug: string;
  name: string;
  addressStreet: string;
  addressCity: string;
  addressState: string;
  addressZip: string;
  countyPermit?: string;
  latitude: number;
  longitude: number;
  locationIframeUrl?: string;
  googleMapsPlaceId?: string;
  googleMapsLink?: string;
  appleMapsUrl?: string;
  hours: StoreHours;
  phoneNumber: string;
  order?: number;
  exteriorImageId?: Media['id'];
  interiorImageId?: Media['id'];
  socialLinks?: SocialLinks;
  isActive?: boolean;
}

interface UpdateStoreDTO extends Partial<CreateStoreDTO> {}

interface UpdateBrandDTO {
  name?: string;
  logoDarkId?: Media['id'];
  footerLogoId?: Media['id'];
  subscribeImageId?: Media['id'];
  missingPhotoFillerId?: Media['id'];
  allCardsAcceptedIconId?: Media['id'];
  allLocationsEmbedUrl?: string;
  averageReviewRating?: string;
  notificationBanner?: LexicalEditorState;
  footerDisclaimer?: LexicalEditorState;
  footerSecondDisclaimer?: LexicalEditorState;
  socialLinks?: SocialLinks;
}

interface CreateFinancingOfferDTO {
  storeId: Store['id'];
  providerName: string;
  requirements: string[];
  applicationLink: string;
  bannerImageId?: Media['id'];
  order?: number;
  isActive?: boolean;
}

interface UpdateFinancingOfferDTO extends Partial<Omit<CreateFinancingOfferDTO, 'storeId'>> {}

5.2 Remote Procedure Interface

Communication Pattern: Next.js Server Actions

Procedure Input Output Auth Description
getStore { id, include? } Store Public Get store details
getStoreBySlug { slug, include? } Store Public Get store by slug
listStores { filters?, include? } Store[] Public List all stores
createStore CreateStoreDTO Store Admin Create new store
updateStore { id, data } Store Admin Update store
deleteStore { id } void Admin Delete store
getBrand { include? } Brand Public Get brand settings
updateBrand UpdateBrandDTO Brand Admin Update brand settings
listFinancingOffers { storeId } FinancingOffer[] Public Get financing for store
createFinancingOffer CreateFinancingOfferDTO FinancingOffer Admin Add financing offer
updateFinancingOffer { id, data } FinancingOffer Admin Update financing offer
deleteFinancingOffer { id } void Admin Delete financing offer

6. Error Handling Strategy

Error Code Condition User Message
STORE_NOT_FOUND Store doesn't exist "Store not found"
STORE_SLUG_EXISTS Duplicate store slug "A store with this URL already exists"
STORE_INVALID_HOURS Invalid hours format "Invalid store hours format"
STORE_INVALID_COORDINATES Invalid lat/lng "Invalid coordinates provided"
BRAND_NOT_FOUND Brand not configured "Brand settings not found"
FINANCING_NOT_FOUND Financing offer doesn't exist "Financing offer not found"
FINANCING_INVALID_STORE Store doesn't exist for offer "Invalid store for financing offer"
SETTINGS_UNAUTHORIZED Insufficient permissions "You don't have permission to modify settings"

Error Response Format

interface ErrorResponse {
  code: string;
  message: string;
  field?: string;           // For validation errors
  details?: Record<string, unknown>;
}

7. Observability

7.1 Logging

Event Level Fields to Include
Store created INFO store_id, store_name, user_id
Store updated INFO store_id, changed_fields, user_id
Store deleted INFO store_id, user_id
Brand updated INFO changed_fields, user_id
Financing offer created INFO offer_id, store_id, provider_name, user_id
Financing offer deleted INFO offer_id, store_id, user_id
Settings cache invalidated DEBUG cache_key, reason

7.2 Metrics

Metric Type Description
settings_reads_total Counter Total reads by entity type
settings_updates_total Counter Total updates by entity type
settings_cache_hits Counter Cache hit count
settings_cache_misses Counter Cache miss count
settings_read_duration_ms Histogram Read latency

8. Testing Strategy

Test Type Coverage Target Tools Focus Areas
Unit 80%+ Vitest Hours validation, coordinate parsing, DTO validation
Integration Critical paths Vitest Store CRUD, Brand updates, cache invalidation
E2E Happy paths Playwright Admin settings UI, store management

9. Feature Inventory

Feature ID Name Status Priority
FEATURE-047 Store CRUD Draft Must Have
FEATURE-048 Store Hours Management Draft Must Have
FEATURE-049 Brand Settings Management Draft Must Have
FEATURE-050 Financing Offers Management Draft Should Have
FEATURE-051 Store Locator Map Embed Draft Should Have

10. Access Control

Operation Permissions

Operation Public Sales Kennel Staff Photographer Admin
View stores Yes Yes Yes Yes Yes
View brand settings Yes Yes Yes Yes Yes
View financing offers Yes Yes Yes Yes Yes
Create store No No No No Yes
Update store No No No No Yes
Delete store No No No No Yes
Update brand settings No No No No Yes
Manage financing offers No No No No Yes

Note: All write operations are Admin-only. Read operations are public for customer website display.


11. Decisions

D1: Single Brand Per Instance (2026-01-20)

Status: Accepted Context: Need brand configuration but each deployment is a separate brand Decision: One Brand entity per instance, no multi-brand support Consequences: Simpler schema, getBrand() returns single record, no brand selection needed

D2: Flexible Day-Based Hours (2026-01-20)

Status: Accepted Context: Stores have varying hours per day of week Decision: Use StoreHours object with each day as separate field, null indicates closed Consequences: Flexible per-day configuration, easy to display, supports holiday closures

D3: Financing Offers as Separate Entity (2026-01-20)

Status: Accepted Context: Stores can have multiple financing providers with different requirements Decision: FinancingOffer as separate entity linked to Store, not embedded array Consequences: Easier CRUD, can query across stores, supports ordering and activation

Status: Accepted Context: Footer disclaimers are long legal text that may need formatting Decision: Use LexicalEditorState for disclaimer fields instead of plain strings Consequences: Supports bold, links, paragraphs in legal text; consistent with other rich text in system


12. References


Change Log

Version Date Author Changes
1.0 2026-01-20 Claude Initial module spec - Draft