Merge branch 'build-improvement' into performance-optimizations-testing
This commit is contained in:
@@ -113,10 +113,11 @@ appearing in shared links during development.
|
|||||||
- ✅ **Type-Safe Configuration**: Full TypeScript support
|
- ✅ **Type-Safe Configuration**: Full TypeScript support
|
||||||
|
|
||||||
### Quick Reference
|
### Quick Reference
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// For sharing functionality (always production)
|
// For sharing functionality (environment-specific)
|
||||||
import { PROD_SHARE_DOMAIN } from "@/constants/app";
|
import { APP_SERVER } from "@/constants/app";
|
||||||
const shareLink = `${PROD_SHARE_DOMAIN}/deep-link/claim/123`;
|
const shareLink = `${APP_SERVER}/deep-link/claim/123`;
|
||||||
|
|
||||||
// For internal operations (environment-specific)
|
// For internal operations (environment-specific)
|
||||||
import { APP_SERVER } from "@/constants/app";
|
import { APP_SERVER } from "@/constants/app";
|
||||||
@@ -124,6 +125,7 @@ const apiUrl = `${APP_SERVER}/api/claim/123`;
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
- [Domain Configuration System](docs/domain-configuration.md) - Complete guide
|
- [Domain Configuration System](docs/domain-configuration.md) - Complete guide
|
||||||
- [Constants and Configuration](src/constants/app.ts) - Core constants
|
- [Constants and Configuration](src/constants/app.ts) - Core constants
|
||||||
|
|
||||||
|
|||||||
@@ -2,33 +2,30 @@
|
|||||||
|
|
||||||
**Author**: Matthew Raymer
|
**Author**: Matthew Raymer
|
||||||
**Date**: 2025-01-27
|
**Date**: 2025-01-27
|
||||||
**Status**: ✅ **COMPLETE** - Domain configuration system implemented
|
**Status**: ✅ **UPDATED** - Simplified to use APP_SERVER for all functionality
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
TimeSafari uses a centralized domain configuration system to ensure consistent
|
TimeSafari uses a centralized domain configuration system to ensure consistent
|
||||||
URL generation across all environments. This system prevents localhost URLs from
|
URL generation across all environments. This system provides a single point of
|
||||||
appearing in shared links during development and provides a single point of
|
control for domain changes and uses environment-specific configuration for all
|
||||||
control for domain changes.
|
functionality including sharing.
|
||||||
|
|
||||||
## Problem Solved
|
## Problem Solved
|
||||||
|
|
||||||
### Issue: Localhost URLs in Shared Links
|
### Issue: Inconsistent Domain Usage
|
||||||
|
|
||||||
Previously, copy link buttons and deep link generation used the environment-
|
Previously, the system used separate constants for different types of URLs:
|
||||||
specific `APP_SERVER` constant, which resulted in:
|
|
||||||
|
|
||||||
- **Development**: `http://localhost:8080/deep-link/claim/123`
|
- **Internal Operations**: Used `APP_SERVER` (environment-specific)
|
||||||
- **Test**: `https://test.timesafari.app/deep-link/claim/123`
|
- **Sharing**: Used separate constants (removed)
|
||||||
- **Production**: `https://timesafari.app/deep-link/claim/123`
|
|
||||||
|
|
||||||
This caused problems when users in development mode shared links, as the
|
This created complexity and confusion about when to use which constant.
|
||||||
localhost URLs wouldn't work for other users.
|
|
||||||
|
|
||||||
### Solution: Production Domain for Sharing
|
### Solution: Unified Domain Configuration
|
||||||
|
|
||||||
All sharing functionality now uses the `PROD_SHARE_DOMAIN` constant, which
|
All functionality now uses the `APP_SERVER` constant, which provides
|
||||||
always points to the production domain regardless of the current environment.
|
environment-specific URLs that can be configured per environment.
|
||||||
|
|
||||||
## Implementation
|
## Implementation
|
||||||
|
|
||||||
@@ -43,27 +40,28 @@ export enum AppString {
|
|||||||
// ... other constants ...
|
// ... other constants ...
|
||||||
}
|
}
|
||||||
|
|
||||||
// Production domain for sharing links (always use production URL for sharing)
|
// Environment-specific server URL for all functionality
|
||||||
export const PROD_SHARE_DOMAIN = AppString.PROD_PUSH_SERVER;
|
export const APP_SERVER =
|
||||||
|
import.meta.env.VITE_APP_SERVER || "https://timesafari.app";
|
||||||
```
|
```
|
||||||
|
|
||||||
### Usage Pattern
|
### Usage Pattern
|
||||||
|
|
||||||
All components that generate shareable links follow this pattern:
|
All components that generate URLs follow this pattern:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { PROD_SHARE_DOMAIN } from "@/constants/app";
|
import { APP_SERVER } from "@/constants/app";
|
||||||
|
|
||||||
// In component class
|
// In component class
|
||||||
PROD_SHARE_DOMAIN = PROD_SHARE_DOMAIN;
|
APP_SERVER = APP_SERVER;
|
||||||
|
|
||||||
// In methods
|
// In methods
|
||||||
const deepLink = `${PROD_SHARE_DOMAIN}/deep-link/claim/${claimId}`;
|
const deepLink = `${APP_SERVER}/deep-link/claim/${claimId}`;
|
||||||
```
|
```
|
||||||
|
|
||||||
### Components Updated
|
### Components Updated
|
||||||
|
|
||||||
The following components and services were updated to use `PROD_SHARE_DOMAIN`:
|
The following components and services use `APP_SERVER`:
|
||||||
|
|
||||||
#### Views
|
#### Views
|
||||||
- `ClaimView.vue` - Claim and certificate links
|
- `ClaimView.vue` - Claim and certificate links
|
||||||
@@ -82,17 +80,28 @@ The following components and services were updated to use `PROD_SHARE_DOMAIN`:
|
|||||||
|
|
||||||
## Configuration Management
|
## Configuration Management
|
||||||
|
|
||||||
### Changing the Production Domain
|
### Environment-Specific Configuration
|
||||||
|
|
||||||
To change the production domain for all sharing functionality:
|
The system uses environment variables to configure domains:
|
||||||
|
|
||||||
1. **Update the constant** in `src/constants/app.ts`:
|
```bash
|
||||||
```typescript
|
# Development
|
||||||
export enum AppString {
|
VITE_APP_SERVER=http://localhost:8080
|
||||||
// ... other constants ...
|
|
||||||
PROD_PUSH_SERVER = "https://your-new-domain.com",
|
# Test
|
||||||
// ... other constants ...
|
VITE_APP_SERVER=https://test.timesafari.app
|
||||||
}
|
|
||||||
|
# Production
|
||||||
|
VITE_APP_SERVER=https://timesafari.app
|
||||||
|
```
|
||||||
|
|
||||||
|
### Changing the Domain
|
||||||
|
|
||||||
|
To change the domain for all functionality:
|
||||||
|
|
||||||
|
1. **Update environment variables** for the target environment:
|
||||||
|
```bash
|
||||||
|
VITE_APP_SERVER=https://your-new-domain.com
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Rebuild the application** for all platforms:
|
2. **Rebuild the application** for all platforms:
|
||||||
@@ -102,46 +111,32 @@ To change the production domain for all sharing functionality:
|
|||||||
npm run build:electron
|
npm run build:electron
|
||||||
```
|
```
|
||||||
|
|
||||||
### Environment-Specific Configuration
|
|
||||||
|
|
||||||
The system maintains environment-specific configuration for internal operations
|
|
||||||
while using production domains for sharing:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Internal operations use environment-specific URLs
|
|
||||||
export const APP_SERVER =
|
|
||||||
import.meta.env.VITE_APP_SERVER || "https://timesafari.app";
|
|
||||||
|
|
||||||
// Sharing always uses production URLs
|
|
||||||
export const PROD_SHARE_DOMAIN = AppString.PROD_PUSH_SERVER;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Benefits
|
## Benefits
|
||||||
|
|
||||||
### ✅ Consistent User Experience
|
### ✅ Simplified Configuration
|
||||||
|
|
||||||
- All shared links work for all users regardless of environment
|
- Single constant for all URL generation
|
||||||
- No more broken localhost links in development
|
- No confusion about which constant to use
|
||||||
- Consistent behavior across all platforms
|
- Consistent behavior across all functionality
|
||||||
|
|
||||||
|
### ✅ Environment Flexibility
|
||||||
|
|
||||||
|
- Easy to configure different domains per environment
|
||||||
|
- Support for development, test, and production environments
|
||||||
|
- Environment-specific sharing URLs when needed
|
||||||
|
|
||||||
### ✅ Maintainability
|
### ✅ Maintainability
|
||||||
|
|
||||||
- Single source of truth for production domain
|
- Single source of truth for domain configuration
|
||||||
- Easy to change domain across entire application
|
- Easy to change domain across entire application
|
||||||
- Clear separation between internal and sharing URLs
|
- Clear pattern for implementing new URL functionality
|
||||||
|
|
||||||
### ✅ Developer Experience
|
### ✅ Developer Experience
|
||||||
|
|
||||||
- No need to remember which environment URLs work for sharing
|
- Simple, consistent pattern for URL generation
|
||||||
- Clear pattern for implementing new sharing functionality
|
- Clear documentation and examples
|
||||||
- Type-safe configuration with TypeScript
|
- Type-safe configuration with TypeScript
|
||||||
|
|
||||||
### ✅ Security
|
|
||||||
|
|
||||||
- No accidental exposure of internal development URLs
|
|
||||||
- Controlled domain configuration
|
|
||||||
- Clear audit trail for domain changes
|
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
### Manual Testing
|
### Manual Testing
|
||||||
@@ -150,7 +145,7 @@ export const PROD_SHARE_DOMAIN = AppString.PROD_PUSH_SERVER;
|
|||||||
```bash
|
```bash
|
||||||
npm run dev
|
npm run dev
|
||||||
# Navigate to any page with copy link buttons
|
# Navigate to any page with copy link buttons
|
||||||
# Verify links use production domain, not localhost
|
# Verify links use configured domain
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Production Build**:
|
2. **Production Build**:
|
||||||
@@ -164,27 +159,19 @@ export const PROD_SHARE_DOMAIN = AppString.PROD_PUSH_SERVER;
|
|||||||
|
|
||||||
The implementation includes comprehensive linting to ensure:
|
The implementation includes comprehensive linting to ensure:
|
||||||
|
|
||||||
- All components properly import `PROD_SHARE_DOMAIN`
|
- All components properly import `APP_SERVER`
|
||||||
- No hardcoded URLs in sharing functionality
|
- No hardcoded URLs in functionality
|
||||||
- Consistent usage patterns across the codebase
|
- Consistent usage patterns across the codebase
|
||||||
|
|
||||||
## Migration Notes
|
## Implementation Pattern
|
||||||
|
|
||||||
### Before Implementation
|
### Current Approach
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ❌ Hardcoded URLs
|
// ✅ Single constant for all functionality
|
||||||
const deepLink = "https://timesafari.app/deep-link/claim/123";
|
import { APP_SERVER } from "@/constants/app";
|
||||||
|
const shareLink = `${APP_SERVER}/deep-link/claim/123`;
|
||||||
// ❌ Environment-specific URLs
|
const apiUrl = `${APP_SERVER}/api/claim/123`;
|
||||||
const deepLink = `${APP_SERVER}/deep-link/claim/123`;
|
|
||||||
```
|
|
||||||
|
|
||||||
### After Implementation
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ✅ Configurable production URLs
|
|
||||||
const deepLink = `${PROD_SHARE_DOMAIN}/deep-link/claim/123`;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Future Enhancements
|
## Future Enhancements
|
||||||
@@ -208,6 +195,7 @@ const deepLink = `${PROD_SHARE_DOMAIN}/deep-link/claim/123`;
|
|||||||
```
|
```
|
||||||
|
|
||||||
3. **Platform-Specific Domains**:
|
3. **Platform-Specific Domains**:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
export const getPlatformShareDomain = () => {
|
export const getPlatformShareDomain = () => {
|
||||||
const platform = process.env.VITE_PLATFORM;
|
const platform = process.env.VITE_PLATFORM;
|
||||||
@@ -229,5 +217,5 @@ const deepLink = `${PROD_SHARE_DOMAIN}/deep-link/claim/123`;
|
|||||||
---
|
---
|
||||||
|
|
||||||
**Last Updated**: 2025-01-27
|
**Last Updated**: 2025-01-27
|
||||||
**Version**: 1.0
|
**Version**: 2.0
|
||||||
**Maintainer**: Matthew Raymer
|
**Maintainer**: Matthew Raymer
|
||||||
@@ -47,9 +47,6 @@ export const DEFAULT_PARTNER_API_SERVER =
|
|||||||
export const DEFAULT_PUSH_SERVER =
|
export const DEFAULT_PUSH_SERVER =
|
||||||
import.meta.env.VITE_DEFAULT_PUSH_SERVER || AppString.PROD_PUSH_SERVER;
|
import.meta.env.VITE_DEFAULT_PUSH_SERVER || AppString.PROD_PUSH_SERVER;
|
||||||
|
|
||||||
// Production domain for sharing links (always use production URL for sharing)
|
|
||||||
export const PROD_SHARE_DOMAIN = AppString.PROD_PUSH_SERVER;
|
|
||||||
|
|
||||||
export const IMAGE_TYPE_PROFILE = "profile";
|
export const IMAGE_TYPE_PROFILE = "profile";
|
||||||
|
|
||||||
export const PASSKEYS_ENABLED =
|
export const PASSKEYS_ENABLED =
|
||||||
|
|||||||
@@ -32,6 +32,17 @@ export type ContactWithJsonStrings = Omit<Contact, "contactMethods"> & {
|
|||||||
contactMethods?: string;
|
contactMethods?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is for those cases (eg. with a DB) where field values may be all primitives or may be JSON values.
|
||||||
|
* See src/db/databaseUtil.ts parseJsonField for more details.
|
||||||
|
*
|
||||||
|
* This is so that we can reuse most of the type and don't have to maintain another copy.
|
||||||
|
* Another approach uses typescript conditionals: https://chatgpt.com/share/6855cdc3-ab5c-8007-8525-726612016eb2
|
||||||
|
*/
|
||||||
|
export type ContactMaybeWithJsonStrings = Omit<Contact, "contactMethods"> & {
|
||||||
|
contactMethods?: string | Array<ContactMethod>;
|
||||||
|
};
|
||||||
|
|
||||||
export const ContactSchema = {
|
export const ContactSchema = {
|
||||||
contacts: "&did, name", // no need to key by other things
|
contacts: "&did, name", // no need to key by other things
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ import {
|
|||||||
type SettingsWithJsonStrings,
|
type SettingsWithJsonStrings,
|
||||||
} from "@/db/tables/settings";
|
} from "@/db/tables/settings";
|
||||||
import { logger } from "@/utils/logger";
|
import { logger } from "@/utils/logger";
|
||||||
import { Contact } from "@/db/tables/contacts";
|
import { Contact, ContactMaybeWithJsonStrings } from "@/db/tables/contacts";
|
||||||
import { Account } from "@/db/tables/accounts";
|
import { Account } from "@/db/tables/accounts";
|
||||||
import { Temp } from "@/db/tables/temp";
|
import { Temp } from "@/db/tables/temp";
|
||||||
import { QueryExecResult, DatabaseExecResult } from "@/interfaces/database";
|
import { QueryExecResult, DatabaseExecResult } from "@/interfaces/database";
|
||||||
@@ -642,15 +642,81 @@ export const PlatformServiceMixin = {
|
|||||||
// CACHED SPECIALIZED SHORTCUTS (massive performance boost)
|
// CACHED SPECIALIZED SHORTCUTS (massive performance boost)
|
||||||
// =================================================
|
// =================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize contact data by parsing JSON strings into proper objects
|
||||||
|
* Handles the contactMethods field which can be either a JSON string or an array
|
||||||
|
* @param rawContacts Raw contact data from database
|
||||||
|
* @returns Normalized Contact[] array
|
||||||
|
*/
|
||||||
|
$normalizeContacts(rawContacts: ContactMaybeWithJsonStrings[]): Contact[] {
|
||||||
|
return rawContacts.map((contact) => {
|
||||||
|
// Create a new contact object with proper typing
|
||||||
|
const normalizedContact: Contact = {
|
||||||
|
did: contact.did,
|
||||||
|
iViewContent: contact.iViewContent,
|
||||||
|
name: contact.name,
|
||||||
|
nextPubKeyHashB64: contact.nextPubKeyHashB64,
|
||||||
|
notes: contact.notes,
|
||||||
|
profileImageUrl: contact.profileImageUrl,
|
||||||
|
publicKeyBase64: contact.publicKeyBase64,
|
||||||
|
seesMe: contact.seesMe,
|
||||||
|
registered: contact.registered,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle contactMethods field which can be a JSON string or an array
|
||||||
|
if (contact.contactMethods !== undefined) {
|
||||||
|
if (typeof contact.contactMethods === "string") {
|
||||||
|
// Parse JSON string into array
|
||||||
|
normalizedContact.contactMethods = this._parseJsonField(
|
||||||
|
contact.contactMethods,
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
} else if (Array.isArray(contact.contactMethods)) {
|
||||||
|
// Validate that each item in the array is a proper ContactMethod object
|
||||||
|
normalizedContact.contactMethods = contact.contactMethods.filter(
|
||||||
|
(method) => {
|
||||||
|
const isValid =
|
||||||
|
method &&
|
||||||
|
typeof method === "object" &&
|
||||||
|
typeof method.label === "string" &&
|
||||||
|
typeof method.type === "string" &&
|
||||||
|
typeof method.value === "string";
|
||||||
|
|
||||||
|
if (!isValid && method !== undefined) {
|
||||||
|
console.warn(
|
||||||
|
"[ContactNormalization] Invalid contact method:",
|
||||||
|
method,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Invalid data, use empty array
|
||||||
|
normalizedContact.contactMethods = [];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No contactMethods, use empty array
|
||||||
|
normalizedContact.contactMethods = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalizedContact;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load all contacts (always fresh) - $contacts()
|
* Load all contacts (always fresh) - $contacts()
|
||||||
* Always fetches fresh data from database for consistency
|
* Always fetches fresh data from database for consistency
|
||||||
* @returns Promise<Contact[]> Array of contact objects
|
* Handles JSON string/object duality for contactMethods field
|
||||||
|
* @returns Promise<Contact[]> Array of normalized contact objects
|
||||||
*/
|
*/
|
||||||
async $contacts(): Promise<Contact[]> {
|
async $contacts(): Promise<Contact[]> {
|
||||||
return (await this.$query(
|
const rawContacts = (await this.$query(
|
||||||
"SELECT * FROM contacts ORDER BY name",
|
"SELECT * FROM contacts ORDER BY name",
|
||||||
)) as Contact[];
|
)) as ContactMaybeWithJsonStrings[];
|
||||||
|
|
||||||
|
return this.$normalizeContacts(rawContacts);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1026,7 +1092,13 @@ export const PlatformServiceMixin = {
|
|||||||
Object.entries(changes).forEach(([key, value]) => {
|
Object.entries(changes).forEach(([key, value]) => {
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
setParts.push(`${key} = ?`);
|
setParts.push(`${key} = ?`);
|
||||||
params.push(value);
|
|
||||||
|
// Handle contactMethods field - convert array to JSON string
|
||||||
|
if (key === "contactMethods" && Array.isArray(value)) {
|
||||||
|
params.push(JSON.stringify(value));
|
||||||
|
} else {
|
||||||
|
params.push(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1048,45 +1120,36 @@ export const PlatformServiceMixin = {
|
|||||||
/**
|
/**
|
||||||
* Get all contacts as typed objects - $getAllContacts()
|
* Get all contacts as typed objects - $getAllContacts()
|
||||||
* Eliminates verbose query + mapping patterns
|
* Eliminates verbose query + mapping patterns
|
||||||
* @returns Promise<Contact[]> Array of contact objects
|
* Handles JSON string/object duality for contactMethods field
|
||||||
|
* @returns Promise<Contact[]> Array of normalized contact objects
|
||||||
*/
|
*/
|
||||||
async $getAllContacts(): Promise<Contact[]> {
|
async $getAllContacts(): Promise<Contact[]> {
|
||||||
const results = await this.$dbQuery(
|
const rawContacts = (await this.$query(
|
||||||
"SELECT did, name, publicKeyBase64, seesMe, registered, nextPubKeyHashB64, profileImageUrl FROM contacts ORDER BY name",
|
"SELECT * FROM contacts ORDER BY name",
|
||||||
);
|
)) as ContactMaybeWithJsonStrings[];
|
||||||
|
|
||||||
return this.$mapResults(results, (row: unknown[]) => ({
|
return this.$normalizeContacts(rawContacts);
|
||||||
did: row[0] as string,
|
|
||||||
name: row[1] as string,
|
|
||||||
publicKeyBase64: row[2] as string,
|
|
||||||
seesMe: Boolean(row[3]),
|
|
||||||
registered: Boolean(row[4]),
|
|
||||||
nextPubKeyHashB64: row[5] as string,
|
|
||||||
profileImageUrl: row[6] as string,
|
|
||||||
}));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get single contact by DID - $getContact()
|
* Get single contact by DID - $getContact()
|
||||||
* Eliminates verbose single contact query patterns
|
* Eliminates verbose single contact query patterns
|
||||||
|
* Handles JSON string/object duality for contactMethods field
|
||||||
* @param did Contact DID to retrieve
|
* @param did Contact DID to retrieve
|
||||||
* @returns Promise<Contact | null> Contact object or null if not found
|
* @returns Promise<Contact | null> Normalized contact object or null if not found
|
||||||
*/
|
*/
|
||||||
async $getContact(did: string): Promise<Contact | null> {
|
async $getContact(did: string): Promise<Contact | null> {
|
||||||
const results = await this.$dbQuery(
|
const rawContacts = (await this.$query(
|
||||||
"SELECT * FROM contacts WHERE did = ?",
|
"SELECT * FROM contacts WHERE did = ?",
|
||||||
[did],
|
[did],
|
||||||
);
|
)) as ContactMaybeWithJsonStrings[];
|
||||||
|
|
||||||
if (!results || !results.values || results.values.length === 0) {
|
if (rawContacts.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const contactData = this._mapColumnsToValues(
|
const normalizedContacts = this.$normalizeContacts(rawContacts);
|
||||||
results.columns,
|
return normalizedContacts[0];
|
||||||
results.values,
|
|
||||||
);
|
|
||||||
return contactData.length > 0 ? (contactData[0] as Contact) : null;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1681,6 +1744,7 @@ declare module "@vue/runtime-core" {
|
|||||||
$contactCount(): Promise<number>;
|
$contactCount(): Promise<number>;
|
||||||
$settings(defaults?: Settings): Promise<Settings>;
|
$settings(defaults?: Settings): Promise<Settings>;
|
||||||
$accountSettings(did?: string, defaults?: Settings): Promise<Settings>;
|
$accountSettings(did?: string, defaults?: Settings): Promise<Settings>;
|
||||||
|
$normalizeContacts(rawContacts: ContactMaybeWithJsonStrings[]): Contact[];
|
||||||
|
|
||||||
// Settings update shortcuts (eliminate 90% boilerplate)
|
// Settings update shortcuts (eliminate 90% boilerplate)
|
||||||
$saveSettings(changes: Partial<Settings>): Promise<boolean>;
|
$saveSettings(changes: Partial<Settings>): Promise<boolean>;
|
||||||
|
|||||||
@@ -239,7 +239,21 @@ export default class ContactEditView extends Vue {
|
|||||||
this.contact = contact;
|
this.contact = contact;
|
||||||
this.contactName = contact.name || "";
|
this.contactName = contact.name || "";
|
||||||
this.contactNotes = contact.notes || "";
|
this.contactNotes = contact.notes || "";
|
||||||
this.contactMethods = contact.contactMethods || [];
|
|
||||||
|
// Ensure contactMethods is a valid array of ContactMethod objects
|
||||||
|
if (Array.isArray(contact.contactMethods)) {
|
||||||
|
this.contactMethods = contact.contactMethods.filter((method) => {
|
||||||
|
return (
|
||||||
|
method &&
|
||||||
|
typeof method === "object" &&
|
||||||
|
typeof method.label === "string" &&
|
||||||
|
typeof method.type === "string" &&
|
||||||
|
typeof method.value === "string"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.contactMethods = [];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.notify.error(
|
this.notify.error(
|
||||||
`${NOTIFY_CONTACT_NOT_FOUND.message} ${contactDid}`,
|
`${NOTIFY_CONTACT_NOT_FOUND.message} ${contactDid}`,
|
||||||
|
|||||||
Reference in New Issue
Block a user