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.
 
 
 
 
 
 

23 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

Critical Migration Omissions to Avoid

1. Remove Unused Notification Imports

COMMON MISTAKE: Importing notification constants that aren't actually used

// ❌ BAD - Unused imports
import {
  NOTIFY_CONTACT_ADDED,           // Not used
  NOTIFY_CONTACT_ADDED_SUCCESS,   // Not used  
  NOTIFY_CONTACT_ERROR,           // Actually used
  NOTIFY_CONTACT_EXISTS,          // Actually used
} from "@/constants/notifications";

// ✅ GOOD - Only import what's used
import {
  NOTIFY_CONTACT_ERROR,
  NOTIFY_CONTACT_EXISTS,
} from "@/constants/notifications";

How to check: Use IDE "Find Usages" or grep to verify each imported constant is actually used in the file.

2. Replace ALL Hardcoded Timeout Values

COMMON MISTAKE: Converting $notify() calls but leaving hardcoded timeout values

// ❌ BAD - Hardcoded timeout values
this.notify.error(NOTIFY_CONTACT_ERROR.message, 5000);
this.notify.success(NOTIFY_CONTACT_ADDED.message, 3000);
this.notify.warning(NOTIFY_CONTACT_EXISTS.message, 5000);
this.notify.toast(NOTIFY_URL_COPIED.message, 2000);

// ✅ GOOD - Use timeout constants
this.notify.error(NOTIFY_CONTACT_ERROR.message, QR_TIMEOUT_LONG);
this.notify.success(NOTIFY_CONTACT_ADDED.message, QR_TIMEOUT_STANDARD);
this.notify.warning(NOTIFY_CONTACT_EXISTS.message, QR_TIMEOUT_LONG);
this.notify.toast(NOTIFY_URL_COPIED.message, QR_TIMEOUT_MEDIUM);

Add timeout constants to your constants file:

// Add to src/constants/notifications.ts
export const QR_TIMEOUT_SHORT = 1000;      // Short operations
export const QR_TIMEOUT_MEDIUM = 2000;     // Medium operations  
export const QR_TIMEOUT_STANDARD = 3000;   // Standard success messages
export const QR_TIMEOUT_LONG = 5000;       // Error messages and warnings

3. Remove Legacy Wrapper Functions

COMMON MISTAKE: Keeping legacy notification wrapper functions that are inconsistent with the new system

// ❌ BAD - Legacy wrapper function
danger(message: string, title: string = "Error", timeout = 5000) {
  this.notify.error(message, timeout);
}

// Usage (inconsistent with rest of system)
this.danger(result.error as string, "Error Setting Visibility");

// ✅ GOOD - Direct usage of notification system
this.notify.error(result.error as string, QR_TIMEOUT_LONG);

Why remove legacy wrappers:

  • Creates inconsistency in the codebase
  • Adds unnecessary abstraction layer
  • Often have unused parameters (like title above)
  • Bypasses the centralized notification system benefits

4. Extract Long Class Attributes to Computed Properties

COMMON MISTAKE: Leaving long class strings in template instead of extracting to computed properties

// ❌ BAD - Long class strings in template
<template>
  <div class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 my-4">
    <button class="inline-block text-md uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md">
      Set Name
    </button>
  </div>
</template>

// ✅ GOOD - Extract to computed properties
<template>
  <div :class="nameWarningClasses">
    <button :class="setNameButtonClasses">
      Set Name
    </button>
  </div>
</template>

// Class methods
get nameWarningClasses(): string {
  return "bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 my-4";
}

get setNameButtonClasses(): string {
  return "inline-block text-md uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md";
}

Benefits of extracting long classes:

  • Improves template readability
  • Enables reusability of styles
  • Makes testing easier
  • Allows for dynamic class computation

5. Ensure ALL Literal Strings Use Constants

COMMON MISTAKE: Converting $notify() calls to helpers but not replacing literal strings with constants

// ❌ BAD - Literal strings in notification calls
this.notify.error("This QR code does not contain valid contact information.");
this.notify.warning("The contact DID is missing.");
this.notify.success("Registration submitted...");

// ✅ GOOD - Use constants for all static messages
this.notify.error(NOTIFY_QR_INVALID_QR_CODE.message);
this.notify.warning(NOTIFY_QR_MISSING_DID.message);
this.notify.success(NOTIFY_QR_REGISTRATION_SUBMITTED.message);

Add constants for ALL static messages:

// Add to src/constants/notifications.ts
export const NOTIFY_QR_INVALID_QR_CODE = {
  message: "This QR code does not contain valid contact information.",
};

export const NOTIFY_QR_MISSING_DID = {
  message: "The contact DID is missing.",
};

export const NOTIFY_QR_REGISTRATION_SUBMITTED = {
  message: "Registration submitted...",
};

6. Validation Checklist for Omissions

Before marking migration complete, verify these items:

# Check for unused imports
grep -n "import.*NOTIFY_" src/views/YourComponent.vue
# Then verify each imported constant is actually used in the file

# Check for hardcoded timeouts
grep -n "notify\.[a-z]*(" src/views/YourComponent.vue | grep -E "[0-9]{3,4}"

# Check for legacy wrapper functions
grep -n "danger\|success\|warning\|info.*(" src/views/YourComponent.vue | grep -v "notify\."

# Check for long class attributes (>50 chars)
grep -n "class=\"[^\"]\{50,\}" src/views/YourComponent.vue

# Check for literal strings in notifications
grep -n "notify\.[a-z]*(" src/views/YourComponent.vue | grep -v "NOTIFY_\|message"

7. Post-Migration Cleanup Commands

Run these commands after migration to catch omissions:

# Check TypeScript compilation
npm run lint-fix

# Run validation scripts
scripts/validate-migration.sh
scripts/validate-notification-completeness.sh

# Check for any remaining databaseUtil references
grep -r "databaseUtil" src/views/YourComponent.vue

# Check for any remaining $notify calls
grep -r "\$notify(" src/views/YourComponent.vue

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
  • Unused notification constants removed from imports but these can mean that notifications have been overlooked
  • Legacy wrapper functions removed (e.g., danger(), success(), etc.)

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]