forked from trent_larson/crowd-funder-for-time-pwa
Mark UserProfileView.vue as human tested, update migration tracker
- Human testing confirmed UserProfileView.vue works correctly - Updated testing tracker with 21 complete migrations (88% success) - Added ImportAccountView.vue to ready-for-testing list - Migration progress: 4 components human tested, 17 ready for testing
This commit is contained in:
@@ -15,6 +15,11 @@ This checklist ensures NO migration steps are forgotten. **Every component migra
|
|||||||
|
|
||||||
## Pre-Migration Assessment
|
## Pre-Migration Assessment
|
||||||
|
|
||||||
|
### Date Time Context
|
||||||
|
- [ ] Always use system date command to establish accurate time context
|
||||||
|
- [ ] Use time log to track project progress
|
||||||
|
- [ ] Use historical time durations to improve estimates
|
||||||
|
|
||||||
### [ ] 1. Identify Legacy Patterns
|
### [ ] 1. Identify Legacy Patterns
|
||||||
- [ ] Count `databaseUtil` imports and calls
|
- [ ] Count `databaseUtil` imports and calls
|
||||||
- [ ] Count raw SQL queries (`SELECT`, `INSERT`, `UPDATE`, `DELETE`)
|
- [ ] Count raw SQL queries (`SELECT`, `INSERT`, `UPDATE`, `DELETE`)
|
||||||
@@ -78,19 +83,27 @@ This checklist ensures NO migration steps are forgotten. **Every component migra
|
|||||||
- [ ] **Use literal strings** for dynamic messages with variables
|
- [ ] **Use literal strings** for dynamic messages with variables
|
||||||
- [ ] **Document decision** for each notification call
|
- [ ] **Document decision** for each notification call
|
||||||
|
|
||||||
|
### [ ] 11. Template Logic Streamlining
|
||||||
|
- [ ] **Review template** for repeated expressions or complex logic
|
||||||
|
- [ ] **Move repeated function calls** to computed properties
|
||||||
|
- [ ] **Simplify complex conditional logic** with computed properties
|
||||||
|
- [ ] **Extract configuration objects** to computed properties
|
||||||
|
- [ ] **Document computed properties** with JSDoc comments
|
||||||
|
- [ ] **Use descriptive names** for computed properties
|
||||||
|
|
||||||
## Validation Phase
|
## Validation Phase
|
||||||
|
|
||||||
### [ ] 11. Run Validation Script
|
### [ ] 12. Run Validation Script
|
||||||
- [ ] Execute: `scripts/validate-migration.sh`
|
- [ ] Execute: `scripts/validate-migration.sh`
|
||||||
- [ ] **MUST show**: "Technically Compliant" (not "Mixed Pattern")
|
- [ ] **MUST show**: "Technically Compliant" (not "Mixed Pattern")
|
||||||
- [ ] **Zero** legacy patterns detected
|
- [ ] **Zero** legacy patterns detected
|
||||||
|
|
||||||
### [ ] 12. Run Linting
|
### [ ] 13. Run Linting
|
||||||
- [ ] Execute: `npm run lint-fix`
|
- [ ] Execute: `npm run lint-fix`
|
||||||
- [ ] **Zero errors** introduced
|
- [ ] **Zero errors** introduced
|
||||||
- [ ] **TypeScript compiles** without errors
|
- [ ] **TypeScript compiles** without errors
|
||||||
|
|
||||||
### [ ] 13. Manual Code Review
|
### [ ] 14. Manual Code Review
|
||||||
- [ ] **NO** `databaseUtil` imports or calls
|
- [ ] **NO** `databaseUtil` imports or calls
|
||||||
- [ ] **NO** raw SQL queries (`SELECT`, `INSERT`, `UPDATE`, `DELETE`)
|
- [ ] **NO** raw SQL queries (`SELECT`, `INSERT`, `UPDATE`, `DELETE`)
|
||||||
- [ ] **NO** `$notify()` calls with object syntax
|
- [ ] **NO** `$notify()` calls with object syntax
|
||||||
@@ -100,40 +113,43 @@ This checklist ensures NO migration steps are forgotten. **Every component migra
|
|||||||
|
|
||||||
## Documentation Phase
|
## Documentation Phase
|
||||||
|
|
||||||
### [ ] 14. Update Migration Documentation
|
### [ ] 15. Update Migration Documentation
|
||||||
- [ ] Create `docs/migration-testing/[COMPONENT]_MIGRATION.md`
|
- [ ] Create `docs/migration-testing/[COMPONENT]_MIGRATION.md`
|
||||||
- [ ] Document all changes made
|
- [ ] Document all changes made
|
||||||
- [ ] Include before/after examples
|
- [ ] Include before/after examples
|
||||||
- [ ] Note validation results
|
- [ ] Note validation results
|
||||||
|
- [ ] Provide a guide to finding the components in the user interface
|
||||||
|
|
||||||
### [ ] 15. Update Testing Tracker
|
### [ ] 16. Update Testing Tracker
|
||||||
- [ ] Update `docs/migration-testing/HUMAN_TESTING_TRACKER.md`
|
- [ ] Update `docs/migration-testing/HUMAN_TESTING_TRACKER.md`
|
||||||
- [ ] Mark component as "Ready for Testing"
|
- [ ] Mark component as "Ready for Testing"
|
||||||
- [ ] Include notes about migration completed
|
- [ ] Include notes about migration completed
|
||||||
|
|
||||||
## Human Testing Phase
|
## Human Testing Phase
|
||||||
|
|
||||||
### [ ] 16. Test All Functionality
|
### [ ] 17. Test All Functionality
|
||||||
- [ ] **Core functionality** works correctly
|
- [ ] **Core functionality** works correctly
|
||||||
- [ ] **Database operations** function properly
|
- [ ] **Database operations** function properly
|
||||||
- [ ] **Notifications** display correctly with proper timing
|
- [ ] **Notifications** display correctly with proper timing
|
||||||
- [ ] **Error scenarios** handled gracefully
|
- [ ] **Error scenarios** handled gracefully
|
||||||
- [ ] **Cross-platform** compatibility (web/mobile)
|
- [ ] **Cross-platform** compatibility (web/mobile)
|
||||||
|
|
||||||
### [ ] 17. Confirm Testing Complete
|
### [ ] 18. Confirm Testing Complete
|
||||||
- [ ] User confirms component works correctly
|
- [ ] User confirms component works correctly
|
||||||
- [ ] Update testing tracker with results
|
- [ ] Update testing tracker with results
|
||||||
- [ ] Mark as "Human Tested" in validation script
|
- [ ] Mark as "Human Tested" in validation script
|
||||||
|
|
||||||
## Final Validation
|
## Final Validation
|
||||||
|
|
||||||
### [ ] 18. Comprehensive Check
|
### [ ] 19. Comprehensive Check
|
||||||
- [ ] Component shows as "Technically Compliant" in validation
|
- [ ] Component shows as "Technically Compliant" in validation
|
||||||
- [ ] All manual testing passed
|
- [ ] All manual testing passed
|
||||||
- [ ] Zero legacy patterns remain
|
- [ ] Zero legacy patterns remain
|
||||||
- [ ] Documentation complete
|
- [ ] Documentation complete
|
||||||
- [ ] Ready for production
|
- [ ] Ready for production
|
||||||
|
|
||||||
|
## Wait for human confirmationb before proceeding to next file unless directly overidden.
|
||||||
|
|
||||||
## 🚨 FAILURE CONDITIONS
|
## 🚨 FAILURE CONDITIONS
|
||||||
|
|
||||||
**❌ INCOMPLETE MIGRATION** if ANY of these remain:
|
**❌ INCOMPLETE MIGRATION** if ANY of these remain:
|
||||||
@@ -167,5 +183,5 @@ This checklist ensures NO migration steps are forgotten. **Every component migra
|
|||||||
**⚠️ WARNING**: This checklist exists because steps were previously forgotten. DO NOT skip any items. The triple migration pattern (Database + SQL + Notifications) is MANDATORY for all component migrations.
|
**⚠️ WARNING**: This checklist exists because steps were previously forgotten. DO NOT skip any items. The triple migration pattern (Database + SQL + Notifications) is MANDATORY for all component migrations.
|
||||||
|
|
||||||
**Author**: Matthew Raymer
|
**Author**: Matthew Raymer
|
||||||
**Date**: 2024-01-XX
|
**Date**: 2024-07-07
|
||||||
**Purpose**: Prevent migration oversight by cementing ALL requirements
|
**Purpose**: Prevent migration oversight by cementing ALL requirements
|
||||||
@@ -233,6 +233,139 @@ this.notify.error(userMessage || "Fallback error message", TIMEOUTS.LONG);
|
|||||||
- **Use literal strings** for dynamic messages with variables
|
- **Use literal strings** for dynamic messages with variables
|
||||||
- **Add new constants** to `notifications.ts` for new reusable messages
|
- **Add new constants** to `notifications.ts` for new reusable messages
|
||||||
|
|
||||||
|
## 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()`
|
||||||
|
|
||||||
## After Migration Checklist
|
## After Migration Checklist
|
||||||
|
|
||||||
⚠️ **CRITICAL**: Use `docs/migration-templates/COMPLETE_MIGRATION_CHECKLIST.md` for comprehensive validation
|
⚠️ **CRITICAL**: Use `docs/migration-templates/COMPLETE_MIGRATION_CHECKLIST.md` for comprehensive validation
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
# Human Testing Tracker for PlatformServiceMixin Migration
|
# Human Testing Tracker for PlatformServiceMixin Migration
|
||||||
|
|
||||||
**Last Updated**: 2025-07-07
|
**Last Updated**: 2025-07-07 07:39 UTC
|
||||||
**Migration Phase**: Notification Migration Complete (86% success rate)
|
**Migration Phase**: Notification Migration Complete (91% success rate)
|
||||||
|
|
||||||
## Testing Status Summary
|
## Testing Status Summary
|
||||||
|
|
||||||
### 📊 **Current Status**
|
### 📊 **Current Status**
|
||||||
- **✅ Complete Migrations**: 19 components (86%)
|
- **✅ Complete Migrations**: 21 components (88%)
|
||||||
- **⚠️ Appropriately Incomplete**: 3 components (14%)
|
- **⚠️ Appropriately Incomplete**: 3 components (12%)
|
||||||
- **🧪 Human Testing**: 3 confirmed tested, 16 ready for testing
|
- **🧪 Human Testing**: 4 confirmed tested, 17 ready for testing
|
||||||
|
|
||||||
## ✅ Completed Testing
|
## ✅ Completed Testing
|
||||||
| Component | Migration Status | Human Testing | Notes |
|
| Component | Migration Status | Human Testing | Notes |
|
||||||
@@ -16,11 +16,12 @@
|
|||||||
| **ClaimAddRawView.vue** | ✅ Complete | ✅ Tested | Initial reference implementation |
|
| **ClaimAddRawView.vue** | ✅ Complete | ✅ Tested | Initial reference implementation |
|
||||||
| **LogView.vue** | ✅ Complete | ✅ Tested | Database migration validated |
|
| **LogView.vue** | ✅ Complete | ✅ Tested | Database migration validated |
|
||||||
| **HomeView.vue** | ✅ Complete | ✅ Tested | Database + Notifications migrated |
|
| **HomeView.vue** | ✅ Complete | ✅ Tested | Database + Notifications migrated |
|
||||||
|
| **UserProfileView.vue** | ✅ Complete | ✅ Tested 2025-07-07 | Triple migration + template streamlining |
|
||||||
|
|
||||||
## 🔄 Ready for Testing (16 Components)
|
## 🔄 Ready for Testing (17 Components)
|
||||||
All these components have completed the triple migration pattern and are ready for human validation:
|
All these components have completed the triple migration pattern and are ready for human validation:
|
||||||
|
|
||||||
### **Views (11 components)**
|
### **Views (12 components)**
|
||||||
| Component | Database | SQL Abstraction | Notifications | Ready |
|
| Component | Database | SQL Abstraction | Notifications | Ready |
|
||||||
|-----------|----------|----------------|---------------|--------|
|
|-----------|----------|----------------|---------------|--------|
|
||||||
| **AccountViewView.vue** | ✅ | ✅ | ✅ | ✅ |
|
| **AccountViewView.vue** | ✅ | ✅ | ✅ | ✅ |
|
||||||
@@ -33,6 +34,7 @@ All these components have completed the triple migration pattern and are ready f
|
|||||||
| **ContactGiftingView.vue** | ✅ | ✅ | ✅ | ✅ |
|
| **ContactGiftingView.vue** | ✅ | ✅ | ✅ | ✅ |
|
||||||
| **RecentOffersToUserView.vue** | ✅ | ✅ | ✅ | ✅ |
|
| **RecentOffersToUserView.vue** | ✅ | ✅ | ✅ | ✅ |
|
||||||
| **RecentOffersToUserProjectsView.vue** | ✅ | ✅ | ✅ | ✅ |
|
| **RecentOffersToUserProjectsView.vue** | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| **ImportAccountView.vue** | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
|
||||||
### **Components (5 components)**
|
### **Components (5 components)**
|
||||||
| Component | Database | SQL Abstraction | Notifications | Ready |
|
| Component | Database | SQL Abstraction | Notifications | Ready |
|
||||||
@@ -108,7 +110,7 @@ When testing components, record results as:
|
|||||||
## Migration Completion Status
|
## Migration Completion Status
|
||||||
|
|
||||||
### 🏆 **Achievement Summary**
|
### 🏆 **Achievement Summary**
|
||||||
- **86% Migration Success Rate**: 19 out of 22 components fully migrated
|
- **88% Migration Success Rate**: 21 out of 24 components fully migrated
|
||||||
- **All Security Objectives Met**: No mixed patterns, proper abstractions
|
- **All Security Objectives Met**: No mixed patterns, proper abstractions
|
||||||
- **Code Quality Improved**: Standardized patterns, eliminated linting issues
|
- **Code Quality Improved**: Standardized patterns, eliminated linting issues
|
||||||
- **Documentation Complete**: Comprehensive guides and checklists
|
- **Documentation Complete**: Comprehensive guides and checklists
|
||||||
|
|||||||
@@ -156,3 +156,9 @@ export const NOTIFY_CONFIRM_CLAIM = {
|
|||||||
title: "Confirm",
|
title: "Confirm",
|
||||||
text: "Do you personally confirm that this is true?",
|
text: "Do you personally confirm that this is true?",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// UserProfileView.vue constants
|
||||||
|
export const NOTIFY_PROFILE_LOAD_ERROR = {
|
||||||
|
title: "Profile Load Error",
|
||||||
|
message: "There was a problem loading the profile.",
|
||||||
|
};
|
||||||
|
|||||||
@@ -87,13 +87,38 @@ import { Component, Vue } from "vue-facing-decorator";
|
|||||||
import { Router } from "vue-router";
|
import { Router } from "vue-router";
|
||||||
|
|
||||||
import { AppString, NotificationIface } from "../constants/app";
|
import { AppString, NotificationIface } from "../constants/app";
|
||||||
import * as databaseUtil from "../db/databaseUtil";
|
|
||||||
import { DEFAULT_ROOT_DERIVATION_PATH } from "../libs/crypto";
|
import { DEFAULT_ROOT_DERIVATION_PATH } from "../libs/crypto";
|
||||||
import { retrieveAccountCount, importFromMnemonic } from "../libs/util";
|
import { retrieveAccountCount, importFromMnemonic } from "../libs/util";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
|
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||||
|
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import Account View Component
|
||||||
|
*
|
||||||
|
* Allows users to import existing identifiers using seed phrases:
|
||||||
|
* - Secure mnemonic phrase input with validation
|
||||||
|
* - Advanced options for custom derivation paths
|
||||||
|
* - Legacy uPort compatibility support
|
||||||
|
* - Test environment utilities for development
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Secure seed phrase import functionality
|
||||||
|
* - Custom derivation path configuration
|
||||||
|
* - Account erasure options for fresh imports
|
||||||
|
* - Development mode test utilities
|
||||||
|
* - Comprehensive error handling and validation
|
||||||
|
*
|
||||||
|
* Security Considerations:
|
||||||
|
* - Seed phrases are handled securely and not logged
|
||||||
|
* - Import process includes validation and error recovery
|
||||||
|
* - Advanced options are hidden by default
|
||||||
|
*
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
components: {},
|
components: {},
|
||||||
|
mixins: [PlatformServiceMixin],
|
||||||
})
|
})
|
||||||
export default class ImportAccountView extends Vue {
|
export default class ImportAccountView extends Vue {
|
||||||
TEST_USER_0_MNEMONIC =
|
TEST_USER_0_MNEMONIC =
|
||||||
@@ -105,6 +130,8 @@ export default class ImportAccountView extends Vue {
|
|||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
$router!: Router;
|
$router!: Router;
|
||||||
|
|
||||||
|
notify!: ReturnType<typeof createNotifyHelpers>;
|
||||||
|
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
derivationPath = DEFAULT_ROOT_DERIVATION_PATH;
|
derivationPath = DEFAULT_ROOT_DERIVATION_PATH;
|
||||||
mnemonic = "";
|
mnemonic = "";
|
||||||
@@ -112,21 +139,62 @@ export default class ImportAccountView extends Vue {
|
|||||||
showAdvanced = false;
|
showAdvanced = false;
|
||||||
shouldErase = false;
|
shouldErase = false;
|
||||||
|
|
||||||
async created() {
|
/**
|
||||||
|
* Initializes notification helpers
|
||||||
|
*/
|
||||||
|
created() {
|
||||||
|
this.notify = createNotifyHelpers(this.$notify);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component initialization
|
||||||
|
*
|
||||||
|
* Loads account count and server settings for import configuration
|
||||||
|
* Uses PlatformServiceMixin for secure database access
|
||||||
|
*/
|
||||||
|
async mounted() {
|
||||||
|
await this.initializeSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes component settings and account information
|
||||||
|
*/
|
||||||
|
private async initializeSettings() {
|
||||||
this.numAccounts = await retrieveAccountCount();
|
this.numAccounts = await retrieveAccountCount();
|
||||||
// get the server, to help with import on the test server
|
const settings = await this.$accountSettings();
|
||||||
const settings = await databaseUtil.retrieveSettingsForActiveAccount();
|
|
||||||
this.apiServer = settings.apiServer || "";
|
this.apiServer = settings.apiServer || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles cancel button click
|
||||||
|
*
|
||||||
|
* Navigates back to previous view
|
||||||
|
*/
|
||||||
public onCancelClick() {
|
public onCancelClick() {
|
||||||
this.$router.back();
|
this.$router.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if running on production server
|
||||||
|
*
|
||||||
|
* @returns True if not on production server (enables test utilities)
|
||||||
|
*/
|
||||||
public isNotProdServer() {
|
public isNotProdServer() {
|
||||||
return this.apiServer !== AppString.PROD_ENDORSER_API_SERVER;
|
return this.apiServer !== AppString.PROD_ENDORSER_API_SERVER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports identifier from mnemonic phrase
|
||||||
|
*
|
||||||
|
* Processes the mnemonic phrase with optional custom derivation path
|
||||||
|
* and account erasure options. Handles validation and error scenarios
|
||||||
|
* with appropriate user feedback.
|
||||||
|
*
|
||||||
|
* Error Handling:
|
||||||
|
* - Invalid mnemonic format validation
|
||||||
|
* - Import process failure recovery
|
||||||
|
* - User-friendly error messaging
|
||||||
|
*/
|
||||||
public async fromMnemonic() {
|
public async fromMnemonic() {
|
||||||
try {
|
try {
|
||||||
await importFromMnemonic(
|
await importFromMnemonic(
|
||||||
@@ -139,24 +207,14 @@ export default class ImportAccountView extends Vue {
|
|||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
logger.error("Error importing from mnemonic:", err);
|
logger.error("Error importing from mnemonic:", err);
|
||||||
if (err == "Error: invalid mnemonic") {
|
if (err == "Error: invalid mnemonic") {
|
||||||
this.$notify(
|
this.notify.error(
|
||||||
{
|
"Please check your mnemonic and try again.",
|
||||||
group: "alert",
|
TIMEOUTS.LONG
|
||||||
type: "danger",
|
|
||||||
title: "Invalid Mnemonic",
|
|
||||||
text: "Please check your mnemonic and try again.",
|
|
||||||
},
|
|
||||||
5000,
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.$notify(
|
this.notify.error(
|
||||||
{
|
"Got an error creating that identifier.",
|
||||||
group: "alert",
|
TIMEOUTS.LONG
|
||||||
type: "danger",
|
|
||||||
title: "Error",
|
|
||||||
text: "Got an error creating that identifier.",
|
|
||||||
},
|
|
||||||
5000,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<div class="mt-8">
|
<div class="mt-8">
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<font-awesome icon="user" class="fa-fw text-slate-400"></font-awesome>
|
<font-awesome icon="user" class="fa-fw text-slate-400"></font-awesome>
|
||||||
{{ didInfo(profile.issuerDid, activeDid, allMyDids, allContacts) }}
|
{{ profileDisplayName }}
|
||||||
<button title="Copy Link to Profile" @click="onCopyLinkClick()">
|
<button title="Copy Link to Profile" @click="onCopyLinkClick()">
|
||||||
<font-awesome
|
<font-awesome
|
||||||
icon="link"
|
icon="link"
|
||||||
@@ -46,46 +46,42 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Map for first coordinates -->
|
<!-- Map for first coordinates -->
|
||||||
<div v-if="profile?.locLat && profile?.locLon" class="mt-4">
|
<div v-if="hasFirstLocation" class="mt-4">
|
||||||
<h2 class="text-lg font-semibold">Location</h2>
|
<h2 class="text-lg font-semibold">Location</h2>
|
||||||
<div class="h-96 mt-2 w-full">
|
<div class="h-96 mt-2 w-full">
|
||||||
<l-map
|
<l-map
|
||||||
ref="profileMap"
|
ref="profileMap"
|
||||||
:center="[profile.locLat, profile.locLon]"
|
:center="firstLocationCoords"
|
||||||
:zoom="12"
|
:zoom="mapZoom"
|
||||||
>
|
>
|
||||||
<l-tile-layer
|
<l-tile-layer
|
||||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
:url="tileLayerUrl"
|
||||||
layer-type="base"
|
layer-type="base"
|
||||||
name="OpenStreetMap"
|
name="OpenStreetMap"
|
||||||
/>
|
/>
|
||||||
<l-marker :lat-lng="[profile.locLat, profile.locLon]">
|
<l-marker :lat-lng="firstLocationCoords">
|
||||||
<l-popup>{{
|
<l-popup>{{ profileDisplayName }}</l-popup>
|
||||||
didInfo(profile.issuerDid, activeDid, allMyDids, allContacts)
|
|
||||||
}}</l-popup>
|
|
||||||
</l-marker>
|
</l-marker>
|
||||||
</l-map>
|
</l-map>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Map for second coordinates -->
|
<!-- Map for second coordinates -->
|
||||||
<div v-if="profile?.locLat2 && profile?.locLon2" class="mt-4">
|
<div v-if="hasSecondLocation" class="mt-4">
|
||||||
<h2 class="text-lg font-semibold">Second Location</h2>
|
<h2 class="text-lg font-semibold">Second Location</h2>
|
||||||
<div class="h-96 mt-2 w-full">
|
<div class="h-96 mt-2 w-full">
|
||||||
<l-map
|
<l-map
|
||||||
ref="profileMap"
|
ref="profileMap"
|
||||||
:center="[profile.locLat2, profile.locLon2]"
|
:center="secondLocationCoords"
|
||||||
:zoom="12"
|
:zoom="mapZoom"
|
||||||
>
|
>
|
||||||
<l-tile-layer
|
<l-tile-layer
|
||||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
:url="tileLayerUrl"
|
||||||
layer-type="base"
|
layer-type="base"
|
||||||
name="OpenStreetMap"
|
name="OpenStreetMap"
|
||||||
/>
|
/>
|
||||||
<l-marker :lat-lng="[profile.locLat2, profile.locLon2]">
|
<l-marker :lat-lng="secondLocationCoords">
|
||||||
<l-popup>{{
|
<l-popup>{{ profileDisplayName }}</l-popup>
|
||||||
didInfo(profile.issuerDid, activeDid, allMyDids, allContacts)
|
|
||||||
}}</l-popup>
|
|
||||||
</l-marker>
|
</l-marker>
|
||||||
</l-map>
|
</l-map>
|
||||||
</div>
|
</div>
|
||||||
@@ -111,15 +107,32 @@ import {
|
|||||||
DEFAULT_PARTNER_API_SERVER,
|
DEFAULT_PARTNER_API_SERVER,
|
||||||
NotificationIface,
|
NotificationIface,
|
||||||
} from "../constants/app";
|
} from "../constants/app";
|
||||||
import * as databaseUtil from "../db/databaseUtil";
|
|
||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact } from "../db/tables/contacts";
|
||||||
import { didInfo, getHeaders } from "../libs/endorserServer";
|
import { didInfo, getHeaders } from "../libs/endorserServer";
|
||||||
import { UserProfile } from "../libs/partnerServer";
|
import { UserProfile } from "../libs/partnerServer";
|
||||||
import { retrieveAccountDids } from "../libs/util";
|
import { retrieveAccountDids } from "../libs/util";
|
||||||
import { logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
|
||||||
import { Settings } from "@/db/tables/settings";
|
|
||||||
import { useClipboard } from "@vueuse/core";
|
import { useClipboard } from "@vueuse/core";
|
||||||
|
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||||
|
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
||||||
|
import { NOTIFY_PROFILE_LOAD_ERROR } from "@/constants/notifications";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User Profile View Component
|
||||||
|
*
|
||||||
|
* Displays individual user profile information including:
|
||||||
|
* - Basic profile data and description
|
||||||
|
* - Location information with interactive maps
|
||||||
|
* - Profile link sharing functionality
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Profile data loading from partner API
|
||||||
|
* - Interactive maps for location visualization
|
||||||
|
* - Copy-to-clipboard functionality for profile links
|
||||||
|
* - Responsive design with loading states
|
||||||
|
*
|
||||||
|
* @author Matthew Raymer
|
||||||
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
LMap,
|
LMap,
|
||||||
@@ -129,12 +142,15 @@ import { useClipboard } from "@vueuse/core";
|
|||||||
QuickNav,
|
QuickNav,
|
||||||
TopMessage,
|
TopMessage,
|
||||||
},
|
},
|
||||||
|
mixins: [PlatformServiceMixin],
|
||||||
})
|
})
|
||||||
export default class UserProfileView extends Vue {
|
export default class UserProfileView extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
$router!: Router;
|
$router!: Router;
|
||||||
$route!: RouteLocationNormalizedLoaded;
|
$route!: RouteLocationNormalizedLoaded;
|
||||||
|
|
||||||
|
notify!: ReturnType<typeof createNotifyHelpers>;
|
||||||
|
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
allContacts: Array<Contact> = [];
|
allContacts: Array<Contact> = [];
|
||||||
allMyDids: Array<string> = [];
|
allMyDids: Array<string> = [];
|
||||||
@@ -145,29 +161,47 @@ export default class UserProfileView extends Vue {
|
|||||||
// make this function available to the Vue template
|
// make this function available to the Vue template
|
||||||
didInfo = didInfo;
|
didInfo = didInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes notification helpers
|
||||||
|
*/
|
||||||
|
created() {
|
||||||
|
this.notify = createNotifyHelpers(this.$notify);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component initialization
|
||||||
|
*
|
||||||
|
* Loads account settings, contacts, and profile data
|
||||||
|
* Uses PlatformServiceMixin for database operations
|
||||||
|
*/
|
||||||
async mounted() {
|
async mounted() {
|
||||||
const platformService = PlatformServiceFactory.getInstance();
|
await this.initializeSettings();
|
||||||
const settingsQuery = await platformService.dbQuery(
|
await this.loadContacts();
|
||||||
"SELECT * FROM settings",
|
|
||||||
);
|
|
||||||
const settings = databaseUtil.mapQueryResultToValues(
|
|
||||||
settingsQuery,
|
|
||||||
) as Settings[];
|
|
||||||
this.activeDid = settings[0]?.activeDid || "";
|
|
||||||
this.partnerApiServer =
|
|
||||||
settings[0]?.partnerApiServer || this.partnerApiServer;
|
|
||||||
|
|
||||||
const contactQuery = await platformService.dbQuery(
|
|
||||||
"SELECT * FROM contacts",
|
|
||||||
);
|
|
||||||
this.allContacts = databaseUtil.mapQueryResultToValues(
|
|
||||||
contactQuery,
|
|
||||||
) as unknown as Contact[];
|
|
||||||
this.allMyDids = await retrieveAccountDids();
|
|
||||||
|
|
||||||
await this.loadProfile();
|
await this.loadProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes account settings from database
|
||||||
|
*/
|
||||||
|
private async initializeSettings() {
|
||||||
|
const settings = await this.$accountSettings();
|
||||||
|
this.activeDid = settings.activeDid || "";
|
||||||
|
this.partnerApiServer = settings.partnerApiServer || this.partnerApiServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all contacts from database
|
||||||
|
*/
|
||||||
|
private async loadContacts() {
|
||||||
|
this.allContacts = await this.$getAllContacts();
|
||||||
|
this.allMyDids = await retrieveAccountDids();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads user profile data from partner API
|
||||||
|
*
|
||||||
|
* Handles profile loading with error handling and loading states
|
||||||
|
*/
|
||||||
async loadProfile() {
|
async loadProfile() {
|
||||||
const profileId: string = this.$route.params.id as string;
|
const profileId: string = this.$route.params.id as string;
|
||||||
if (!profileId) {
|
if (!profileId) {
|
||||||
@@ -196,35 +230,85 @@ export default class UserProfileView extends Vue {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Error loading profile:", error);
|
logger.error("Error loading profile:", error);
|
||||||
this.$notify(
|
this.notify.error(NOTIFY_PROFILE_LOAD_ERROR.message, TIMEOUTS.LONG);
|
||||||
{
|
|
||||||
group: "alert",
|
|
||||||
type: "danger",
|
|
||||||
title: "Error",
|
|
||||||
text: "There was a problem loading the profile.",
|
|
||||||
},
|
|
||||||
5000,
|
|
||||||
);
|
|
||||||
} finally {
|
} finally {
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies profile link to clipboard
|
||||||
|
*
|
||||||
|
* Creates a deep link to the profile and copies it to the clipboard
|
||||||
|
* Shows success notification when completed
|
||||||
|
*/
|
||||||
onCopyLinkClick() {
|
onCopyLinkClick() {
|
||||||
const deepLink = `${APP_SERVER}/deep-link/user-profile/${this.profile?.rowId}`;
|
const deepLink = `${APP_SERVER}/deep-link/user-profile/${this.profile?.rowId}`;
|
||||||
useClipboard()
|
useClipboard()
|
||||||
.copy(deepLink)
|
.copy(deepLink)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.$notify(
|
this.notify.copied("profile link", TIMEOUTS.STANDARD);
|
||||||
{
|
});
|
||||||
group: "alert",
|
}
|
||||||
type: "toast",
|
|
||||||
title: "Copied",
|
/**
|
||||||
text: "A link to this profile was copied to the clipboard.",
|
* Computed properties for template logic streamlining
|
||||||
},
|
*/
|
||||||
2000,
|
|
||||||
);
|
/**
|
||||||
});
|
* Gets the display name for the profile using didInfo utility
|
||||||
|
* @returns Formatted display name for the profile owner
|
||||||
|
*/
|
||||||
|
get profileDisplayName() {
|
||||||
|
return this.didInfo(this.profile?.issuerDid, this.activeDid, this.allMyDids, this.allContacts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the profile has first location coordinates
|
||||||
|
* @returns True if both latitude and longitude are available
|
||||||
|
*/
|
||||||
|
get hasFirstLocation() {
|
||||||
|
return this.profile?.locLat && this.profile?.locLon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the coordinate array for the first location
|
||||||
|
* @returns Array of [latitude, longitude] for map center
|
||||||
|
*/
|
||||||
|
get firstLocationCoords() {
|
||||||
|
return [this.profile?.locLat, this.profile?.locLon];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the profile has second location coordinates
|
||||||
|
* @returns True if both latitude and longitude are available
|
||||||
|
*/
|
||||||
|
get hasSecondLocation() {
|
||||||
|
return this.profile?.locLat2 && this.profile?.locLon2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the coordinate array for the second location
|
||||||
|
* @returns Array of [latitude, longitude] for map center
|
||||||
|
*/
|
||||||
|
get secondLocationCoords() {
|
||||||
|
return [this.profile?.locLat2, this.profile?.locLon2];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard map zoom level for profile location maps
|
||||||
|
* @returns Default zoom level for location display
|
||||||
|
*/
|
||||||
|
get mapZoom() {
|
||||||
|
return 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OpenStreetMap tile layer URL template
|
||||||
|
* @returns URL template for map tile fetching
|
||||||
|
*/
|
||||||
|
get tileLayerUrl() {
|
||||||
|
return "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user