You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

17 KiB

Component Migration Template

Overview

This template provides step-by-step instructions for migrating Vue components from legacy patterns to PlatformServiceMixin.

Before Migration Checklist

  • Component uses import * as databaseUtil
  • Component uses import { logConsoleAndDb }
  • Component has direct PlatformServiceFactory.getInstance() calls
  • Component has manual error handling for database operations
  • Component has verbose SQL result processing

Step-by-Step Migration

Step 1: Update Imports

// ❌ BEFORE - Legacy imports
import * as databaseUtil from "../db/databaseUtil";
import { logConsoleAndDb } from "../db/databaseUtil";
import { PlatformServiceFactory } from "../services/PlatformServiceFactory";

// ✅ AFTER - Clean imports
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { Contact } from "@/db/tables/contacts";
import { Settings } from "@/db/tables/settings";

Step 2: Add Mixin to Component

// ❌ BEFORE - No mixin
@Component({
  components: { /* ... */ }
})
export default class MyComponent extends Vue {
  // ...
}

// ✅ AFTER - With mixin
@Component({
  components: { /* ... */ }
})
export default class MyComponent extends Vue {
  mixins: [PlatformServiceMixin],
  // ...
}

Step 3: Replace Database Operations

// ❌ BEFORE - Legacy database access
async loadContacts() {
  try {
    const platformService = PlatformServiceFactory.getInstance();
    const result = await platformService.dbQuery("SELECT * FROM contacts");
    const contacts = databaseUtil.mapQueryResultToValues(result);
    await logConsoleAndDb("Contacts loaded successfully");
    return contacts;
  } catch (error) {
    await logConsoleAndDb("Error loading contacts: " + error, true);
    throw error;
  }
}

// ✅ AFTER - Mixin methods
async loadContacts() {
  try {
    const contacts = await this.$getAllContacts();
    await this.$log("Contacts loaded successfully");
    return contacts;
  } catch (error) {
    await this.$logError(`[${this.$options.name}] Error loading contacts: ${error}`);
    throw error;
  }
}

Step 4: Replace Settings Operations

// ❌ BEFORE - Legacy settings access
async loadSettings() {
  const settingsRow = await databaseUtil.retrieveSettingsForActiveAccount();
  const settings = settingsRow || {};
  return settings;
}

async saveSettings(changes: Partial<Settings>) {
  await databaseUtil.updateDefaultSettings(changes);
  await logConsoleAndDb("Settings saved");
}

// ✅ AFTER - Mixin methods
async loadSettings() {
  return await this.$settings();
}

async saveSettings(changes: Partial<Settings>) {
  await this.$saveSettings(changes);
  await this.$log("Settings saved");
}

Step 5: Replace Logging Operations

// ❌ BEFORE - Legacy logging
try {
  // operation
} catch (error) {
  console.error("Error occurred:", error);
  await logConsoleAndDb("Error: " + error, true);
}

// ✅ AFTER - Mixin logging
try {
  // operation
} catch (error) {
  await this.$logError(`[${this.$options.name}] Error: ${error}`);
}

Common Migration Patterns

Pattern 1: Database Query + Result Processing

// ❌ BEFORE
const platformService = PlatformServiceFactory.getInstance();
const result = await platformService.dbQuery(sql, params);
const processed = databaseUtil.mapQueryResultToValues(result);

// ✅ AFTER
const processed = await this.$query(sql, params);

Pattern 2: Settings Retrieval

// ❌ BEFORE
const settingsRow = await databaseUtil.retrieveSettingsForActiveAccount();
const value = settingsRow?.[field] || defaultValue;

// ✅ AFTER
const settings = await this.$settings();
const value = settings[field] || defaultValue;

Pattern 3: Contact Operations

// ❌ BEFORE
const platformService = PlatformServiceFactory.getInstance();
const contacts = await platformService.dbQuery("SELECT * FROM contacts");
const mappedContacts = databaseUtil.mapQueryResultToValues(contacts);

// ✅ AFTER
const contacts = await this.$getAllContacts();

Pattern 4: Error Handling

// ❌ BEFORE
try {
  // operation
} catch (error) {
  console.error("[MyComponent] Error:", error);
  await databaseUtil.logToDb("Error: " + error, "error");
}

// ✅ AFTER
try {
  // operation
} catch (error) {
  await this.$logError(`[${this.$options.name}] Error: ${error}`);
}

Notification Migration (Additional Step)

If component uses this.$notify() calls, also migrate to notification helpers:

Import and Setup

// Add imports
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
import {
  NOTIFY_CONTACT_LOADING_ISSUE,
  NOTIFY_FEED_LOADING_ISSUE,
  // Add other constants as needed
} from "@/constants/notifications";

// Add property
notify!: ReturnType<typeof createNotifyHelpers>;

// Initialize in created()
created() {
  this.notify = createNotifyHelpers(this.$notify);
}

Replace Notification Calls

// ❌ BEFORE
this.$notify({
  group: "alert",
  type: "warning",
  title: "Warning", 
  text: "Something went wrong"
}, 5000);

// ✅ AFTER - Use constants for reusable messages
this.notify.warning(NOTIFY_CONTACT_LOADING_ISSUE.message, TIMEOUTS.LONG);

// ✅ AFTER - Literal strings for dynamic content
this.notify.error(userMessage || "Fallback error message", TIMEOUTS.LONG);

Common Notification Patterns

  • Warning: this.notify.warning(NOTIFY_CONSTANT.message, TIMEOUTS.LONG)
  • Error: this.notify.error(NOTIFY_CONSTANT.message, TIMEOUTS.LONG)
  • Success: this.notify.success(NOTIFY_CONSTANT.message, TIMEOUTS.STANDARD)
  • Toast: this.notify.toast(title, message, TIMEOUTS.SHORT)
  • Confirm: this.notify.confirm(message, onYes)
  • Standard patterns: this.notify.confirmationSubmitted(), this.notify.sent(), etc.

Notification Constants Guidelines

  • Use constants for static, reusable messages (defined in src/constants/notifications.ts)
  • Use literal strings for dynamic messages with variables
  • Add new constants to notifications.ts for new reusable messages

Extract Literals from Complex Modals

IMPORTANT: Even when complex modals must remain as raw $notify calls due to advanced features (custom buttons, nested callbacks, promptToStopAsking, etc.), always extract literal strings to constants:

// ❌ BAD - Literals in complex modal
this.$notify({
  group: "modal",
  type: "confirm", 
  title: "Are you nearby with cameras?",
  text: "If so, we'll use those with QR codes to share.",
  yesText: "we are nearby with cameras",
  noText: "we will share another way",
  onNo: () => { /* complex callback */ }
});

// ✅ GOOD - Constants used even in complex modal
export const NOTIFY_CAMERA_SHARE_METHOD = {
  title: "Are you nearby with cameras?",
  text: "If so, we'll use those with QR codes to share.", 
  yesText: "we are nearby with cameras",
  noText: "we will share another way",
};

this.$notify({
  group: "modal",
  type: "confirm",
  title: NOTIFY_CAMERA_SHARE_METHOD.title,
  text: NOTIFY_CAMERA_SHARE_METHOD.text,
  yesText: NOTIFY_CAMERA_SHARE_METHOD.yesText,
  noText: NOTIFY_CAMERA_SHARE_METHOD.noText,
  onNo: () => { /* complex callback preserved */ }
});

This approach provides:

  • Consistency: All user-facing text centralized
  • Maintainability: Easy to update messages
  • Localization: Ready for future i18n support
  • Testability: Constants can be imported in tests

Template Logic Streamlining

Move Complex Template Logic to Class

When migrating components, look for opportunities to simplify template expressions by moving logic into computed properties or methods:

Pattern 1: Repeated Function Calls

// ❌ BEFORE - Template with repeated function calls
<template>
  <div>{{ formatName(user.firstName, user.lastName, user.title) }}</div>
  <div>{{ formatName(contact.firstName, contact.lastName, contact.title) }}</div>
</template>

// ✅ AFTER - Computed properties for repeated logic
<template>
  <div>{{ userDisplayName }}</div>
  <div>{{ contactDisplayName }}</div>
</template>

// Class methods
get userDisplayName() {
  return this.formatName(this.user?.firstName, this.user?.lastName, this.user?.title);
}

get contactDisplayName() {
  return this.formatName(this.contact?.firstName, this.contact?.lastName, this.contact?.title);
}

Pattern 2: Complex Conditional Logic

// ❌ BEFORE - Complex template conditions
<template>
  <div v-if="profile?.locLat && profile?.locLon && profile?.showLocation">
    <l-map :center="[profile.locLat, profile.locLon]" :zoom="12">
      <!-- map content -->
    </l-map>
  </div>
</template>

// ✅ AFTER - Computed properties for clarity
<template>
  <div v-if="shouldShowMap">
    <l-map :center="mapCenter" :zoom="mapZoom">
      <!-- map content -->
    </l-map>
  </div>
</template>

// Class methods
get shouldShowMap() {
  return this.profile?.locLat && this.profile?.locLon && this.profile?.showLocation;
}

get mapCenter() {
  return [this.profile?.locLat, this.profile?.locLon];
}

get mapZoom() {
  return 12;
}

Pattern 3: Repeated Configuration Objects

// ❌ BEFORE - Repeated inline objects
<template>
  <l-tile-layer
    url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
    layer-type="base"
    name="OpenStreetMap"
  />
  <l-tile-layer
    url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
    layer-type="base"
    name="OpenStreetMap"
  />
</template>

// ✅ AFTER - Computed property for configuration
<template>
  <l-tile-layer
    :url="tileLayerUrl"
    layer-type="base"
    name="OpenStreetMap"
  />
  <l-tile-layer
    :url="tileLayerUrl"
    layer-type="base"
    name="OpenStreetMap"
  />
</template>

// Class methods
get tileLayerUrl() {
  return "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
}

Pattern 4: Array/Object Construction in Template

// ❌ BEFORE - Complex array construction in template
<template>
  <component :coords="[item.lat || 0, item.lng || 0]" />
</template>

// ✅ AFTER - Computed property for complex data
<template>
  <component :coords="itemCoordinates" />
</template>

// Class methods
get itemCoordinates() {
  return [this.item?.lat || 0, this.item?.lng || 0];
}

Benefits of Logic Streamlining

  1. Improved Readability: Template becomes cleaner and easier to understand
  2. Better Performance: Vue caches computed properties, avoiding recalculation
  3. Easier Testing: Logic can be unit tested independently
  4. Reduced Duplication: Common expressions defined once
  5. Type Safety: TypeScript can better validate computed property return types

Guidelines for Logic Streamlining

  • Move to computed properties: Expressions used multiple times or complex calculations
  • Keep in template: Simple property access (user.name) or single-use expressions
  • Document computed properties: Add JSDoc comments explaining purpose and return types
  • Use descriptive names: userDisplayName instead of getName()

After Migration Checklist

⚠️ CRITICAL: Use docs/migration-templates/COMPLETE_MIGRATION_CHECKLIST.md for comprehensive validation

Phase 1: Database Migration

  • All databaseUtil imports removed
  • All logConsoleAndDb imports removed
  • All direct PlatformServiceFactory.getInstance() calls removed
  • Component includes PlatformServiceMixin in mixins array
  • Database operations use mixin methods ($db, $query, $getAllContacts, etc.)
  • Settings operations use mixin methods ($settings, $saveSettings)
  • Logging uses mixin methods ($log, $logError, $logAndConsole)

Phase 2: SQL Abstraction (if applicable)

  • All raw SQL queries replaced with service methods
  • Contact operations use $getContact(), $deleteContact(), $updateContact()
  • Settings operations use $accountSettings(), $saveSettings()
  • NO raw SQL queries remain (SELECT, INSERT, UPDATE, DELETE)

Phase 3: Notification Migration (if applicable)

  • createNotifyHelpers imported and initialized
  • notify! property declared and created in created()
  • All this.$notify() calls replaced with helper methods
  • Hardcoded timeouts replaced with TIMEOUTS constants
  • Static messages use notification constants from @/constants/notifications
  • Dynamic messages use literal strings appropriately

Final Validation

  • Error handling includes component name context
  • Component compiles without TypeScript errors
  • Component functionality works as expected
  • scripts/validate-migration.sh shows "Technically Compliant"
  • scripts/validate-notification-completeness.sh shows as complete

Validation Commands

# Check overall migration status
scripts/validate-migration.sh

# Check notification migration completeness  
scripts/validate-notification-completeness.sh

# Check for compilation errors
npm run lint-fix

Testing Migration

  1. Compile Check: npm run build should complete without errors
  2. Runtime Check: Component should load and function normally
  3. Logging Check: Verify logs appear in console and database
  4. Error Handling Check: Verify errors are properly logged and handled

Troubleshooting

Common Issues

  1. Missing Mixin Methods: Ensure component properly extends PlatformServiceMixin
  2. TypeScript Errors: Check that all types are properly imported
  3. Runtime Errors: Verify all async operations are properly awaited
  4. Missing Context: Add component name to error messages for better debugging

Performance Considerations

  • Mixin methods include caching for frequently accessed data
  • Database operations are queued and optimized
  • Error logging includes proper context and formatting

Phase 4: Testing and Validation

4.1 Multi-Platform Testing Requirements

ALL MIGRATIONS MUST BE TESTED ON ALL SUPPORTED PLATFORMS:

Web Platform Testing (Required)

  • Test in Chrome/Chromium (primary browser)
  • Test in Firefox (secondary browser)
  • Test in Safari (if applicable)
  • Verify PWA functionality works correctly
  • Test responsive design on different screen sizes

Desktop Platform Testing (Required)

  • Test Electron app functionality
  • Verify desktop-specific features work
  • Test file system access (if applicable)
  • Verify native desktop integrations

Mobile Platform Testing (Required)

  • Test iOS app via Capacitor
  • Test Android app via Capacitor
  • Verify mobile-specific features (camera, contacts, etc.)
  • Test deep linking functionality
  • Verify push notifications work

4.2 Functional Testing Per Platform

For each platform, test these core scenarios:

Database Operations

  • Create/Read/Update/Delete operations work
  • Data persistence across app restarts
  • Database migration handling (if applicable)

Logging and Error Handling

  • Errors are logged correctly to console
  • Errors are stored in database logs
  • Error messages display appropriately to users
  • Network errors are handled gracefully

User Interface

  • All buttons and interactions work
  • Loading states display correctly
  • Error states display appropriately
  • Responsive design works on platform

4.3 Platform-Specific Testing Notes

Web Platform

  • Test offline/online scenarios
  • Verify IndexedDB storage works
  • Test service worker functionality
  • Check browser developer tools for errors

Desktop Platform

  • Test native menu integrations
  • Verify file system permissions
  • Test auto-updater functionality
  • Check Electron developer tools

Mobile Platform

  • Test device permissions (camera, storage, etc.)
  • Verify app store compliance
  • Test background/foreground transitions
  • Check native debugging tools

4.4 Sign-Off Requirements

MIGRATION IS NOT COMPLETE UNTIL ALL PLATFORMS ARE TESTED AND SIGNED OFF:

## Testing Sign-Off Checklist

### Web Platform ✅/❌
- [ ] Chrome: Tested by [Name] on [Date]
- [ ] Firefox: Tested by [Name] on [Date]  
- [ ] Safari: Tested by [Name] on [Date]
- [ ] Notes: [Any platform-specific issues or observations]

### Desktop Platform ✅/❌
- [ ] Windows: Tested by [Name] on [Date]
- [ ] macOS: Tested by [Name] on [Date]
- [ ] Linux: Tested by [Name] on [Date]
- [ ] Notes: [Any platform-specific issues or observations]

### Mobile Platform ✅/❌
- [ ] iOS: Tested by [Name] on [Date]
- [ ] Android: Tested by [Name] on [Date]
- [ ] Notes: [Any platform-specific issues or observations]

### Final Sign-Off
- [ ] All platforms tested and working
- [ ] No regressions identified
- [ ] Performance is acceptable
- [ ] Migration completed by: [Name] on [Date]