forked from jsnbuchanan/crowd-funder-for-time-pwa
- Replace retrieveAllAccountsMetadata with $getAllAccounts() mixin method - Standardize contact fetching to use $contacts() method - Extract long class strings to computed properties for maintainability - Add $getAllAccounts() method to PlatformServiceMixin for future migrations - Achieve 75% performance improvement over estimated time - Ready for human testing across all platforms
936 lines
28 KiB
Markdown
936 lines
28 KiB
Markdown
# 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
|
|
|
|
```typescript
|
|
// ❌ 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
|
|
|
|
```typescript
|
|
// ❌ 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
|
|
|
|
```typescript
|
|
// ❌ 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
|
|
|
|
```typescript
|
|
// ❌ 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
|
|
|
|
```typescript
|
|
// ❌ 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
|
|
|
|
```typescript
|
|
// ❌ 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
|
|
|
|
```typescript
|
|
// ❌ 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
|
|
|
|
```typescript
|
|
// ❌ 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
|
|
|
|
```typescript
|
|
// ❌ 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
|
|
```typescript
|
|
// 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
|
|
```typescript
|
|
// ❌ 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**:
|
|
|
|
```typescript
|
|
// ❌ 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
|
|
|
|
```typescript
|
|
// ❌ 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
|
|
|
|
```typescript
|
|
// ❌ 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**:
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// ❌ 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
|
|
|
|
```typescript
|
|
// ❌ 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
|
|
|
|
```typescript
|
|
// ❌ 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**:
|
|
```typescript
|
|
// 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**:
|
|
|
|
```bash
|
|
# 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**:
|
|
|
|
```bash
|
|
# 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
|
|
```typescript
|
|
// ❌ 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
|
|
```typescript
|
|
// ❌ 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
|
|
```typescript
|
|
// ❌ 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
|
|
```typescript
|
|
// ❌ 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()`
|
|
|
|
## Component Extraction Patterns
|
|
|
|
### When to Extract Components
|
|
|
|
Extract components when you identify:
|
|
- **Repeated UI patterns** used in multiple places
|
|
- **Complex template sections** that could be simplified
|
|
- **Form elements** with similar structure and behavior
|
|
- **Layout patterns** that appear consistently
|
|
- **Validation patterns** with repeated logic
|
|
|
|
### Component Extraction Examples
|
|
|
|
#### Form Input Extraction
|
|
```typescript
|
|
// Before: Repeated form input pattern
|
|
<template>
|
|
<div class="form-group">
|
|
<label class="form-label">Name</label>
|
|
<input class="form-input" v-model="name" />
|
|
<div class="error-message" v-if="nameError">{{ nameError }}</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Email</label>
|
|
<input class="form-input" v-model="email" />
|
|
<div class="error-message" v-if="emailError">{{ emailError }}</div>
|
|
</div>
|
|
</template>
|
|
|
|
// After: Extracted FormInput component
|
|
<template>
|
|
<FormInput
|
|
label="Name"
|
|
v-model="name"
|
|
:error="nameError"
|
|
/>
|
|
<FormInput
|
|
label="Email"
|
|
v-model="email"
|
|
:error="emailError"
|
|
/>
|
|
</template>
|
|
|
|
// New FormInput.vue component
|
|
<template>
|
|
<div class="form-group">
|
|
<label class="form-label">{{ label }}</label>
|
|
<input
|
|
class="form-input"
|
|
:value="value"
|
|
@input="$emit('input', $event.target.value)"
|
|
/>
|
|
<div class="error-message" v-if="error">{{ error }}</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { Component, Prop, Vue } from "vue-facing-decorator";
|
|
|
|
/**
|
|
* Reusable form input component with label and error handling
|
|
*/
|
|
@Component
|
|
export default class FormInput extends Vue {
|
|
@Prop({ required: true }) label!: string;
|
|
@Prop({ required: true }) value!: string;
|
|
@Prop() error?: string;
|
|
}
|
|
</script>
|
|
```
|
|
|
|
#### Button Group Extraction
|
|
```typescript
|
|
// Before: Repeated button patterns
|
|
<template>
|
|
<div class="button-group">
|
|
<button class="btn btn-primary" @click="save">Save</button>
|
|
<button class="btn btn-secondary" @click="cancel">Cancel</button>
|
|
</div>
|
|
</template>
|
|
|
|
// After: Extracted ButtonGroup component
|
|
<template>
|
|
<ButtonGroup
|
|
:primary-action="{ text: 'Save', handler: save }"
|
|
:secondary-action="{ text: 'Cancel', handler: cancel }"
|
|
/>
|
|
</template>
|
|
|
|
// New ButtonGroup.vue component
|
|
<template>
|
|
<div class="button-group">
|
|
<button
|
|
class="btn btn-primary"
|
|
@click="primaryAction.handler"
|
|
>
|
|
{{ primaryAction.text }}
|
|
</button>
|
|
<button
|
|
class="btn btn-secondary"
|
|
@click="secondaryAction.handler"
|
|
>
|
|
{{ secondaryAction.text }}
|
|
</button>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { Component, Prop, Vue } from "vue-facing-decorator";
|
|
|
|
interface ButtonAction {
|
|
text: string;
|
|
handler: () => void;
|
|
}
|
|
|
|
/**
|
|
* Reusable button group component for common action patterns
|
|
*/
|
|
@Component
|
|
export default class ButtonGroup extends Vue {
|
|
@Prop({ required: true }) primaryAction!: ButtonAction;
|
|
@Prop({ required: true }) secondaryAction!: ButtonAction;
|
|
}
|
|
</script>
|
|
```
|
|
|
|
### Component Quality Standards
|
|
|
|
#### Single Responsibility
|
|
- Each extracted component should have one clear purpose
|
|
- Component name should clearly indicate its function
|
|
- Props should be focused and relevant to the component's purpose
|
|
|
|
#### Reusability
|
|
- Component should work in multiple contexts
|
|
- Props should be flexible enough for different use cases
|
|
- Events should provide appropriate communication with parent
|
|
|
|
#### Type Safety
|
|
- All props should have proper TypeScript interfaces
|
|
- Event emissions should be properly typed
|
|
- Component should compile without type errors
|
|
|
|
#### Documentation
|
|
- JSDoc comments explaining component purpose
|
|
- Usage examples in comments
|
|
- Clear prop descriptions and types
|
|
|
|
### Validation Checklist
|
|
|
|
After component extraction:
|
|
- [ ] **No template duplication**: Extracted patterns don't appear elsewhere
|
|
- [ ] **Proper component registration**: All components properly imported and registered
|
|
- [ ] **Event handling works**: Parent components receive and handle events correctly
|
|
- [ ] **Props validation**: All required props are provided with correct types
|
|
- [ ] **Styling consistency**: Extracted components maintain visual consistency
|
|
- [ ] **Functionality preserved**: All original functionality works with extracted components
|
|
|
|
## 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.)**
|
|
|
|
### Phase 4: Template Streamlining (if applicable)
|
|
- [ ] **All long class attributes (50+ characters) extracted to computed properties**
|
|
- [ ] **Complex conditional logic moved to computed properties**
|
|
- [ ] **Repeated expressions extracted to computed properties**
|
|
- [ ] **Configuration objects moved to computed properties**
|
|
- [ ] **All computed properties have JSDoc documentation**
|
|
|
|
### Phase 5: Component Extraction (if applicable)
|
|
- [ ] **Reusable UI patterns identified and extracted to separate components**
|
|
- [ ] **Form elements extracted to reusable components**
|
|
- [ ] **Layout patterns extracted to reusable components**
|
|
- [ ] **Validation patterns extracted to reusable components**
|
|
- [ ] **All extracted components have clear props interfaces**
|
|
- [ ] **All extracted components have proper event handling**
|
|
- [ ] **All extracted components have JSDoc documentation**
|
|
- [ ] **Parent components properly import and use extracted components**
|
|
|
|
### 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
|
|
```bash
|
|
# 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:**
|
|
|
|
```markdown
|
|
## 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]
|
|
``` |