Compare commits

...

8 Commits

Author SHA1 Message Date
cf44ec1a1d Merge branch 'master' into nearby-filter 2025-08-18 07:52:54 -04:00
Matthew Raymer
f85c190557 Merge branch 'master' of ssh://173.199.124.46:222/trent_larson/crowd-funder-for-time-pwa 2025-08-18 07:29:10 +00:00
Matthew Raymer
bc9d3cdda5 fix(profile): resolve map loading and profile deletion issues
- Fix Leaflet icon initialization error causing "Cannot read properties of undefined (reading 'Default')"
- Add proper Leaflet icon configuration with CDN fallbacks
- Implement map ready state management to prevent infinite loading
- Add comprehensive error handling and debugging for map lifecycle events
- Fix profile deletion treating HTTP 204 (No Content) as error instead of success
- Enhance error logging and user feedback throughout profile operations
- Add fallback timeout mechanisms for map initialization failures
- Improve error messages to show specific API failure reasons

Resolves map rendering issues and profile deletion failures by properly
handling HTTP status codes and Leaflet component initialization.
2025-08-18 07:28:58 +00:00
1a03dbb24c Merge pull request 'Replaced IconRenderer with FontAwesome' (#157) from replace-iconrenderer into master
Reviewed-on: #157
2025-08-18 02:35:29 -04:00
Matthew Raymer
68c0459533 refactor(settings): simplify updateSettings calls in HomeView.vue, FeedFilters.vue
- Remove conditional activeDid checks around $updateSettings calls in FeedFilters.vue
- Call $updateSettings unconditionally, letting implementation handle missing activeDid
- Maintain functional behavior while simplifying code structure
2025-08-15 08:16:11 +00:00
Matthew Raymer
b761088839 refactor(logging): replace console.* and reclassify log levels in HomeView.vue, FeedFilters.vue
- Remove all console.* calls from FeedFilters.vue
- Reclassify 12 logger.info calls to logger.debug in HomeView.vue for diagnostic messages
- Add logger import to FeedFilters.vue
- Maintain existing logging patterns and behavior
2025-08-15 08:15:44 +00:00
Matthew Raymer
23b4460376 Merge branch 'master' into nearby-filter 2025-08-15 07:22:28 +00:00
Matthew Raymer
9196081f34 fix(home): resolve nearby filter not refreshing feed view
- Fix FeedFilters component missing activeDid context for settings updates
- Update reloadFeedOnChange to retrieve actual settings without defaults
- Add comprehensive logging throughout feed refresh process for debugging
- Ensure filter state changes immediately trigger feed refresh without page reload

The issue was caused by FeedFilters component calling $updateSettings() without
the activeDid parameter, causing settings to be saved to wrong location. Now
properly passes activeDid from HomeView and uses $accountSettings() for
accurate user-specific settings retrieval.

Closes filter refresh issue where turning ON nearby filter required page reload
2025-08-12 06:56:18 +00:00
5 changed files with 454 additions and 43 deletions

View File

@@ -0,0 +1,138 @@
# Agent Contract — TimeSafari Logging (Unified, MANDATORY)
**Scope:** Applies to all diffs and generated code in this workspace unless explicitly exempted below.
**Goal:** One consistent, rest-parameter logging style using the project logger; no `console.*` in production code.
## NonNegotiables (DO THIS)
- You **MUST** use the project logger; **DO NOT** use any `console.*` methods.
- Import exactly as:
- `import {{ logger }} from '@/utils/logger'`
- If `@` alias is unavailable, compute the correct relative path (do not fail).
- Call signatures use **rest parameters**: `logger.info(message, ...args)`
- Prefer primitives/IDs and small objects in `...args`; **never build a throwaway object** just to “wrap context”.
- Production defaults: Web = `warn+`, Electron = `error`, Dev/Capacitor = `info+` (override via `VITE_LOG_LEVEL`).
- **Database persistence:** `info|warn|error` are persisted; `debug` is not. Use `logger.toDb(msg, level?)` for DB-only.
## Available Logger API (Authoritative)
- `logger.debug(message, ...args)` — verbose internals, timings, input/output shapes
- `logger.log(message, ...args)` — synonym of `info` for general info
- `logger.info(message, ...args)` — lifecycle, state changes, success paths
- `logger.warn(message, ...args)` — recoverable issues, retries, degraded mode
- `logger.error(message, ...args)` — failures, thrown exceptions, aborts
- `logger.toDb(message, level?)` — DB-only entry (default level = `info`)
- `logger.toConsoleAndDb(message, isError)` — console + DB (use sparingly)
- `logger.withContext(componentName)` — returns a scoped logger
## Level Guidelines (Use These Heuristics)
### DEBUG
Use for method entry/exit, computed values, filters, loops, retries, and external call payload sizes.
```ts
logger.debug('[HomeView] reloadFeedOnChange() called');
logger.debug('[HomeView] Current filter settings', settings.filterFeedByVisible, settings.filterFeedByNearby, settings.searchBoxes?.length ?? 0);
logger.debug('[FeedFilters] Toggling nearby filter', this.isNearby, this.settingChanged, this.activeDid);
```
**Avoid:** Vague messages (`'Processing data'`).
### INFO
Use for user-visible lifecycle and completed operations.
```ts
logger.info('[StartView] Component mounted', process.env.VITE_PLATFORM);
logger.info('[StartView] User selected new seed generation');
logger.info('[SearchAreaView] Search box stored', searchBox.name, searchBox.bbox);
logger.info('[ContactQRScanShowView] Contact registration OK', contact.did);
```
**Avoid:** Diagnostic details that belong in `debug`.
### WARN
Use for recoverable issues, fallbacks, unexpected-but-handled conditions.
```ts
logger.warn('[ContactQRScanShowView] Invalid scan result no value', resultType);
logger.warn('[ContactQRScanShowView] Invalid QR format no JWT in URL');
logger.warn('[ContactQRScanShowView] JWT missing "own" field');
```
**Avoid:** Hard failures (those are `error`).
### ERROR
Use for unrecoverable failures, data integrity issues, and thrown exceptions.
```ts
logger.error('[HomeView Settings] initializeIdentity() failed', err);
logger.error('[StartView] Failed to load initialization data', error);
logger.error('[ContactQRScanShowView] Error processing contact QR', error, rawValue);
```
**Avoid:** Expected user cancels (use `info`/`debug`).
## Context Hygiene (Consistent, Minimal, Helpful)
- **Component context:** Prefer scoped logger.
```ts
const log = logger.withContext('UserService');
log.info('User created', userId);
log.error('Failed to create user', error);
```
If not using `withContext`, prefix message with `[ComponentName]`.
- **Emojis:** Optional and minimal for visual scanning. Recommended set:
- Start/finish: 🚀 / ✅ • Retry/loop: 🔄 • External call: 📡 • Data/metrics: 📊 • Inspection: 🔍
- **Sensitive data:** Never log secrets (tokens, keys, passwords) or payloads >10KB. Prefer IDs over objects; redact/hash when needed.
## Migration — AutoRewrites (Apply Every Time)
- Exact transforms:
- `console.debug(...)` → `logger.debug(...)`
- `console.log(...)` → `logger.log(...)` (or `logger.info(...)` when clearly stateful)
- `console.info(...)` → `logger.info(...)`
- `console.warn(...)` → `logger.warn(...)`
- `console.error(...)` → `logger.error(...)`
- Multi-arg handling:
- First arg becomes `message` (stringify safely if non-string).
- Remaining args map 1:1 to `...args`:
`console.info(msg, a, b)` → `logger.info(String(msg), a, b)`
- Sole `Error`:
- `console.error(err)` → `logger.error(err.message, err)`
- **Object-wrapping cleanup:** Replace `{{ userId, meta }}` wrappers with separate args:
`logger.info('User signed in', userId, meta)`
## DB Logging Rules
- `debug` **never** persists automatically.
- `info|warn|error` persist automatically.
- For DB-only events (no console), call `logger.toDb('Message', 'info'|'warn'|'error')`.
## Exceptions (Tightly Scoped)
Allowed paths (still prefer logger):
- `**/*.test.*`, `**/*.spec.*`
- `scripts/dev/**`, `scripts/migrate/**`
To intentionally keep `console.*`, add a pragma on the previous line:
```ts
// cursor:allow-console reason="short justification"
console.log('temporary output');
```
Without the pragma, rewrite to `logger.*`.
## CI & Diff Enforcement
- Do not introduce `console.*` anywhere outside allowed, pragmad spots.
- If an import is missing, insert it and resolve alias/relative path correctly.
- Enforce rest-parameter call shape in reviews; replace object-wrapped context.
- Ensure environment log level rules remain intact (`VITE_LOG_LEVEL` respected).
## Quick Before/After
**Before**
```ts
console.log('User signed in', user.id, meta);
console.error('Failed to update profile', err);
console.info('Filter toggled', this.hasVisibleDid);
```
**After**
```ts
import {{ logger }} from '@/utils/logger';
logger.info('User signed in', user.id, meta);
logger.error('Failed to update profile', err);
logger.debug('[FeedFilters] Filter toggled', this.hasVisibleDid);
```
## Checklist (for every PR)
- [ ] No `console.*` (or properly pragmad in the allowed locations)
- [ ] Correct import path for `logger`
- [ ] Rest-parameter call shape (`message, ...args`)
- [ ] Right level chosen (debug/info/warn/error)
- [ ] No secrets / oversized payloads / throwaway context objects
- [ ] Component context provided (scoped logger or `[Component]` prefix)
_Unified on: 2025-08-15 08:11:46Z_

View File

@@ -101,6 +101,7 @@ import {
import { Router } from "vue-router";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { logger } from "@/utils/logger";
@Component({
components: {
@@ -119,11 +120,13 @@ export default class FeedFilters extends Vue {
isNearby = false;
settingChanged = false;
visible = false;
activeDid = "";
async open(onCloseIfChanged: () => void) {
async open(onCloseIfChanged: () => void, activeDid: string) {
this.onCloseIfChanged = onCloseIfChanged;
this.activeDid = activeDid;
const settings = await this.$settings();
const settings = await this.$accountSettings(activeDid);
this.hasVisibleDid = !!settings.filterFeedByVisible;
this.isNearby = !!settings.filterFeedByNearby;
if (settings.searchBoxes && settings.searchBoxes.length > 0) {
@@ -137,6 +140,7 @@ export default class FeedFilters extends Vue {
async toggleHasVisibleDid() {
this.settingChanged = true;
this.hasVisibleDid = !this.hasVisibleDid;
await this.$updateSettings({
filterFeedByVisible: this.hasVisibleDid,
});
@@ -145,9 +149,18 @@ export default class FeedFilters extends Vue {
async toggleNearby() {
this.settingChanged = true;
this.isNearby = !this.isNearby;
logger.debug("[FeedFilters] 🔄 Toggling nearby filter:", {
newValue: this.isNearby,
settingChanged: this.settingChanged,
activeDid: this.activeDid,
});
await this.$updateSettings({
filterFeedByNearby: this.isNearby,
});
logger.debug("[FeedFilters] ✅ Nearby filter updated in settings");
}
async clearAll() {
@@ -179,13 +192,20 @@ export default class FeedFilters extends Vue {
}
close() {
logger.debug("[FeedFilters] 🚪 Closing dialog:", {
settingChanged: this.settingChanged,
hasCallback: !!this.onCloseIfChanged,
});
if (this.settingChanged) {
logger.debug("[FeedFilters] 🔄 Settings changed, calling callback");
this.onCloseIfChanged();
}
this.visible = false;
}
done() {
logger.debug("[FeedFilters] ✅ Done button clicked");
this.close();
}
}

View File

@@ -124,17 +124,55 @@ export class ProfileService {
async deleteProfile(activeDid: string): Promise<boolean> {
try {
const headers = await getHeaders(activeDid);
const response = await this.axios.delete(
`${this.partnerApiServer}/api/partner/userProfile`,
{ headers },
);
logger.debug("Attempting to delete profile for DID:", activeDid);
logger.debug("Using partner API server:", this.partnerApiServer);
logger.debug("Request headers:", headers);
const url = `${this.partnerApiServer}/api/partner/userProfile`;
logger.debug("DELETE request URL:", url);
const response = await this.axios.delete(url, { headers });
if (response.status === 200) {
if (response.status === 200 || response.status === 204) {
logger.debug("Profile deleted successfully");
return true;
} else {
throw new Error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_NOT_DELETED);
logger.error("Unexpected response status when deleting profile:", {
status: response.status,
statusText: response.statusText,
data: response.data
});
throw new Error(`Profile not deleted - HTTP ${response.status}: ${response.statusText}`);
}
} catch (error) {
if (this.isApiError(error) && error.response) {
const response = error.response as any; // Type assertion for error response
logger.error("API error deleting profile:", {
status: response.status,
statusText: response.statusText,
data: response.data,
url: (error as any).config?.url
});
// Handle specific HTTP status codes
if (response.status === 204) {
logger.debug("Profile deleted successfully (204 No Content)");
return true; // 204 is success for DELETE operations
} else if (response.status === 404) {
logger.warn("Profile not found - may already be deleted");
return true; // Consider this a success if profile doesn't exist
} else if (response.status === 400) {
logger.error("Bad request when deleting profile:", response.data);
throw new Error(`Profile deletion failed: ${response.data?.message || 'Bad request'}`);
} else if (response.status === 401) {
logger.error("Unauthorized to delete profile");
throw new Error("You are not authorized to delete this profile");
} else if (response.status === 403) {
logger.error("Forbidden to delete profile");
throw new Error("You are not allowed to delete this profile");
}
}
logger.error("Error deleting profile:", errorStringForLog(error));
handleApiError(error as AxiosError, "/api/partner/userProfile");
return false;

View File

@@ -174,14 +174,16 @@
:aria-busy="loadingProfile || savingProfile"
></textarea>
<div class="flex items-center mb-4" @click="toggleUserProfileLocation">
<input
v-model="includeUserProfileLocation"
type="checkbox"
class="mr-2"
/>
<label for="includeUserProfileLocation">Include Location</label>
</div>
<div class="flex items-center mb-4">
<input
v-model="includeUserProfileLocation"
type="checkbox"
class="mr-2"
@change="onLocationCheckboxChange"
/>
<label for="includeUserProfileLocation">Include Location</label>
<span class="text-xs text-slate-400 ml-2">(Debug: {{ isMapReady ? 'Map Ready' : 'Map Loading' }})</span>
</div>
<div v-if="includeUserProfileLocation" class="mb-4 aspect-video">
<p class="text-sm mb-2 text-slate-500">
The location you choose will be shared with the world until you remove
@@ -194,6 +196,7 @@
class="!z-40 rounded-md"
@click="onProfileMapClick"
@ready="onMapReady"
@mounted="onMapMounted"
>
<l-tile-layer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
@@ -751,6 +754,7 @@ import "dexie-export-import";
// @ts-expect-error - they aren't exporting it but it's there
import { ImportProgress } from "dexie-export-import";
import { LeafletMouseEvent } from "leaflet";
import * as L from "leaflet";
import * as R from "ramda";
import { IIdentifier } from "@veramo/core";
import { ref } from "vue";
@@ -902,6 +906,7 @@ export default class AccountViewView extends Vue {
warnIfProdServer: boolean = false;
warnIfTestServer: boolean = false;
zoom: number = 2;
isMapReady: boolean = false;
// Limits and validation properties
endorserLimits: EndorserRateLimits | null = null;
@@ -913,6 +918,17 @@ export default class AccountViewView extends Vue {
created() {
this.notify = createNotifyHelpers(this.$notify);
// Fix Leaflet icon issues in modern bundlers
// This prevents the "Cannot read properties of undefined (reading 'Default')" error
if (L.Icon.Default) {
delete (L.Icon.Default.prototype as any)._getIconUrl;
L.Icon.Default.mergeOptions({
iconRetinaUrl: 'https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon-2x.png',
iconUrl: 'https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon.png',
shadowUrl: 'https://unpkg.com/leaflet@1.7.1/dist/images/marker-shadow.png',
});
}
}
/**
@@ -939,10 +955,16 @@ export default class AccountViewView extends Vue {
this.userProfileLatitude = profile.latitude;
this.userProfileLongitude = profile.longitude;
this.includeUserProfileLocation = profile.includeLocation;
// Initialize map ready state if location is included
if (profile.includeLocation) {
this.isMapReady = false; // Will be set to true when map is ready
}
} else {
// Profile not created yet; leave defaults
}
} catch (error) {
logger.error("Error loading profile:", error);
this.notify.error(
ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_NOT_AVAILABLE,
);
@@ -1518,9 +1540,45 @@ export default class AccountViewView extends Vue {
}
onMapReady(map: L.Map): void {
// doing this here instead of on the l-map element avoids a recentering after a drag then zoom at startup
const zoom = this.userProfileLatitude && this.userProfileLongitude ? 12 : 2;
map.setView([this.userProfileLatitude, this.userProfileLongitude], zoom);
try {
logger.debug("Map ready event fired, map object:", map);
// doing this here instead of on the l-map element avoids a recentering after a drag then zoom at startup
const zoom = this.userProfileLatitude && this.userProfileLongitude ? 12 : 2;
const lat = this.userProfileLatitude || 0;
const lng = this.userProfileLongitude || 0;
map.setView([lat, lng], zoom);
this.isMapReady = true;
logger.debug("Map ready state set to true, coordinates:", [lat, lng], "zoom:", zoom);
} catch (error) {
logger.error("Error in onMapReady:", error);
this.isMapReady = true; // Set to true even on error to prevent infinite loading
}
}
onMapMounted(): void {
logger.debug("Map component mounted");
// Check if map ref is available
const mapRef = this.$refs.profileMap;
logger.debug("Map ref:", mapRef);
// Try to set map ready after component is mounted
setTimeout(() => {
this.isMapReady = true;
logger.debug("Map ready set to true after mounted");
}, 500);
}
// Fallback method to handle map initialization failures
private handleMapInitFailure(): void {
logger.debug("Starting map initialization timeout (5 seconds)");
setTimeout(() => {
if (!this.isMapReady) {
logger.warn("Map failed to initialize, forcing ready state");
this.isMapReady = true;
} else {
logger.debug("Map initialized successfully, timeout not needed");
}
}, 5000); // 5 second timeout
}
showProfileInfo(): void {
@@ -1532,13 +1590,16 @@ export default class AccountViewView extends Vue {
async saveProfile(): Promise<void> {
this.savingProfile = true;
const profileData: ProfileData = {
description: this.userProfileDesc,
latitude: this.userProfileLatitude,
longitude: this.userProfileLongitude,
includeLocation: this.includeUserProfileLocation,
};
try {
const profileData: ProfileData = {
description: this.userProfileDesc,
latitude: this.userProfileLatitude,
longitude: this.userProfileLongitude,
includeLocation: this.includeUserProfileLocation,
};
logger.debug("Saving profile data:", profileData);
const success = await this.profileService.saveProfile(
this.activeDid,
profileData,
@@ -1549,6 +1610,7 @@ export default class AccountViewView extends Vue {
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_SAVE_ERROR);
}
} catch (error) {
logger.error("Error saving profile:", error);
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_SAVE_ERROR);
} finally {
this.savingProfile = false;
@@ -1556,15 +1618,25 @@ export default class AccountViewView extends Vue {
}
toggleUserProfileLocation(): void {
const updated = this.profileService.toggleProfileLocation({
description: this.userProfileDesc,
latitude: this.userProfileLatitude,
longitude: this.userProfileLongitude,
includeLocation: this.includeUserProfileLocation,
});
this.userProfileLatitude = updated.latitude;
this.userProfileLongitude = updated.longitude;
this.includeUserProfileLocation = updated.includeLocation;
try {
const updated = this.profileService.toggleProfileLocation({
description: this.userProfileDesc,
latitude: this.userProfileLatitude,
longitude: this.userProfileLongitude,
includeLocation: this.includeUserProfileLocation,
});
this.userProfileLatitude = updated.latitude;
this.userProfileLongitude = updated.longitude;
this.includeUserProfileLocation = updated.includeLocation;
// Reset map ready state when toggling location
if (!updated.includeLocation) {
this.isMapReady = false;
}
} catch (error) {
logger.error("Error in toggleUserProfileLocation:", error);
this.notify.error("Failed to toggle location setting");
}
}
confirmEraseLatLong(): void {
@@ -1592,6 +1664,7 @@ export default class AccountViewView extends Vue {
async deleteProfile(): Promise<void> {
try {
logger.debug("Attempting to delete profile for DID:", this.activeDid);
const success = await this.profileService.deleteProfile(this.activeDid);
if (success) {
this.notify.success(ACCOUNT_VIEW_CONSTANTS.SUCCESS.PROFILE_DELETED);
@@ -1599,11 +1672,20 @@ export default class AccountViewView extends Vue {
this.userProfileLatitude = 0;
this.userProfileLongitude = 0;
this.includeUserProfileLocation = false;
this.isMapReady = false; // Reset map state
logger.debug("Profile deleted successfully, UI state reset");
} else {
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_DELETE_ERROR);
}
} catch (error) {
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_DELETE_ERROR);
logger.error("Error in deleteProfile component method:", error);
// Show more specific error message if available
if (error instanceof Error) {
this.notify.error(error.message);
} else {
this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_DELETE_ERROR);
}
}
}
@@ -1616,8 +1698,43 @@ export default class AccountViewView extends Vue {
}
onProfileMapClick(event: LeafletMouseEvent) {
this.userProfileLatitude = event.latlng.lat;
this.userProfileLongitude = event.latlng.lng;
try {
if (event && event.latlng) {
this.userProfileLatitude = event.latlng.lat;
this.userProfileLongitude = event.latlng.lng;
}
} catch (error) {
logger.error("Error in onProfileMapClick:", error);
}
}
onLocationCheckboxChange(): void {
try {
logger.debug("Location checkbox changed, new value:", this.includeUserProfileLocation);
if (!this.includeUserProfileLocation) {
// Location checkbox was unchecked, clean up map state
this.isMapReady = false;
this.userProfileLatitude = 0;
this.userProfileLongitude = 0;
logger.debug("Location unchecked, map state reset");
} else {
// Location checkbox was checked, start map initialization timeout
this.isMapReady = false;
logger.debug("Location checked, starting map initialization timeout");
// Try to set map ready after a short delay to allow Vue to render
setTimeout(() => {
if (!this.isMapReady) {
logger.debug("Setting map ready after timeout");
this.isMapReady = true;
}
}, 1000); // 1 second delay
this.handleMapInitFailure();
}
} catch (error) {
logger.error("Error in onLocationCheckboxChange:", error);
}
}
// IdentitySection event handlers

View File

@@ -476,7 +476,7 @@ export default class HomeView extends Vue {
// Re-initialize identity with new settings (loads settings internally)
await this.initializeIdentity();
} else {
logger.info(
logger.debug(
"[HomeView Settings Trace] 📍 DID unchanged, skipping re-initialization",
);
}
@@ -756,17 +756,34 @@ export default class HomeView extends Vue {
* Called by FeedFilters component when filters change
*/
async reloadFeedOnChange() {
const settings = await this.$accountSettings(this.activeDid, {
filterFeedByVisible: false,
filterFeedByNearby: false,
logger.debug("[HomeView] 🔄 reloadFeedOnChange() called - refreshing feed");
// Get current settings without overriding with defaults
const settings = await this.$accountSettings(this.activeDid);
logger.debug("[HomeView] 📊 Current filter settings:", {
filterFeedByVisible: settings.filterFeedByVisible,
filterFeedByNearby: settings.filterFeedByNearby,
searchBoxes: settings.searchBoxes?.length || 0,
});
this.isFeedFilteredByVisible = !!settings.filterFeedByVisible;
this.isFeedFilteredByNearby = !!settings.filterFeedByNearby;
this.isAnyFeedFilterOn = checkIsAnyFeedFilterOn(settings);
logger.debug("[HomeView] 🎯 Updated filter states:", {
isFeedFilteredByVisible: this.isFeedFilteredByVisible,
isFeedFilteredByNearby: this.isFeedFilteredByNearby,
isAnyFeedFilterOn: this.isAnyFeedFilterOn,
});
this.feedData = [];
this.feedPreviousOldestId = undefined;
logger.debug("[HomeView] 🧹 Cleared feed data, calling updateAllFeed()");
await this.updateAllFeed();
logger.debug("[HomeView] ✅ Feed refresh completed");
}
/**
@@ -845,6 +862,14 @@ export default class HomeView extends Vue {
* - this.feedLastViewedClaimId (via updateFeedLastViewedId)
*/
async updateAllFeed() {
logger.debug("[HomeView] 🚀 updateAllFeed() called", {
isFeedLoading: this.isFeedLoading,
currentFeedDataLength: this.feedData.length,
isAnyFeedFilterOn: this.isAnyFeedFilterOn,
isFeedFilteredByVisible: this.isFeedFilteredByVisible,
isFeedFilteredByNearby: this.isFeedFilteredByNearby,
});
this.isFeedLoading = true;
let endOfResults = true;
@@ -853,21 +878,37 @@ export default class HomeView extends Vue {
this.apiServer,
this.feedPreviousOldestId,
);
logger.debug("[HomeView] 📡 Retrieved gives from API", {
resultsCount: results.data.length,
endOfResults,
});
if (results.data.length > 0) {
endOfResults = false;
// gather any contacts that user has blocked from view
await this.processFeedResults(results.data);
await this.updateFeedLastViewedId(results.data);
logger.debug("[HomeView] 📝 Processed feed results", {
processedCount: this.feedData.length,
});
}
} catch (e) {
logger.error("[HomeView] ❌ Error in updateAllFeed:", e);
this.handleFeedError(e);
}
if (this.feedData.length === 0 && !endOfResults) {
logger.debug("[HomeView] 🔄 No results after filtering, retrying...");
await this.updateAllFeed();
}
this.isFeedLoading = false;
logger.debug("[HomeView] ✅ updateAllFeed() completed", {
finalFeedDataLength: this.feedData.length,
isFeedLoading: this.isFeedLoading,
});
}
/**
@@ -892,12 +933,35 @@ export default class HomeView extends Vue {
* @param records Array of feed records to process
*/
private async processFeedResults(records: GiveSummaryRecord[]) {
logger.debug("[HomeView] 📝 Processing feed results:", {
inputRecords: records.length,
currentFilters: {
isAnyFeedFilterOn: this.isAnyFeedFilterOn,
isFeedFilteredByVisible: this.isFeedFilteredByVisible,
isFeedFilteredByNearby: this.isFeedFilteredByNearby,
},
});
let processedCount = 0;
let filteredCount = 0;
for (const record of records) {
const processedRecord = await this.processRecord(record);
if (processedRecord) {
this.feedData.push(processedRecord);
processedCount++;
} else {
filteredCount++;
}
}
logger.debug("[HomeView] 📊 Feed processing results:", {
processed: processedCount,
filtered: filteredCount,
total: records.length,
finalFeedLength: this.feedData.length,
});
this.feedPreviousOldestId = records[records.length - 1].jwtId;
}
@@ -931,7 +995,7 @@ export default class HomeView extends Vue {
* - this.feedData (via createFeedRecord)
*
* @param record The record to process
* @returns Processed record with contact info if it passes filters, null otherwise
* @returns Processed record if it passes filters, null otherwise
*/
private async processRecord(
record: GiveSummaryRecord,
@@ -941,13 +1005,28 @@ export default class HomeView extends Vue {
const recipientDid = this.extractRecipientDid(claim);
const fulfillsPlan = await this.getFulfillsPlan(record);
// Log record details for debugging
logger.debug("[HomeView] 🔍 Processing record:", {
recordId: record.jwtId,
hasFulfillsPlan: !!fulfillsPlan,
fulfillsPlanHandleId: record.fulfillsPlanHandleId,
filters: {
isAnyFeedFilterOn: this.isAnyFeedFilterOn,
isFeedFilteredByVisible: this.isFeedFilteredByNearby,
isFeedFilteredByNearby: this.isFeedFilteredByNearby,
},
});
if (!this.shouldIncludeRecord(record, fulfillsPlan)) {
logger.debug("[HomeView] ❌ Record filtered out:", record.jwtId);
return null;
}
const provider = this.extractProvider(claim);
const providedByPlan = await this.getProvidedByPlan(provider);
logger.debug("[HomeView] ✅ Record included:", record.jwtId);
return this.createFeedRecord(
record,
claim,
@@ -1096,6 +1175,22 @@ export default class HomeView extends Vue {
}
}
// Add debug logging for nearby filter
if (this.isFeedFilteredByNearby && record.fulfillsPlanHandleId) {
logger.debug("[HomeView] 🔍 Nearby filter check:", {
recordId: record.jwtId,
hasFulfillsPlan: !!fulfillsPlan,
hasLocation: !!(fulfillsPlan?.locLat && fulfillsPlan?.locLon),
location: fulfillsPlan
? { lat: fulfillsPlan.locLat, lon: fulfillsPlan.locLon }
: null,
inSearchBox: fulfillsPlan
? this.latLongInAnySearchBox(fulfillsPlan.locLat, fulfillsPlan.locLon)
: null,
finalResult: anyMatch,
});
}
return anyMatch;
}
@@ -1531,7 +1626,10 @@ export default class HomeView extends Vue {
* Called by template click handler
*/
openFeedFilters() {
(this.$refs.feedFilters as FeedFilters).open(this.reloadFeedOnChange);
(this.$refs.feedFilters as FeedFilters).open(
this.reloadFeedOnChange,
this.activeDid,
);
}
/**