Browse Source

Fix registration status reactivity in HomeView

Resolved issue where registration banner persisted despite successful API registration.
Root cause was loadSettings() being called after initializeIdentity(), overwriting
updated isRegistered value with stale database data.

Changes:
- Remove redundant loadSettings() call from mounted() lifecycle
- Add $nextTick() to force template re-render after registration updates
- Create isUserRegistered computed property for template reactivity
- Clean up debugging console.log statements for production readiness
- Simplify template logic to use standard v-if/v-else pattern

Registration banner now properly disappears when users are registered, and
"Record something given by:" section appears correctly. Fix maintains existing
functionality while ensuring proper Vue reactivity.
pull/142/head
Matthew Raymer 1 week ago
parent
commit
216e245d60
  1. 3
      src/components/UsageLimitsSection.vue
  2. 14
      src/libs/endorserServer.ts
  3. 76
      src/utils/PlatformServiceMixin.ts
  4. 18
      src/views/AccountViewView.vue
  5. 43
      src/views/HomeView.vue
  6. 21
      src/views/IdentitySwitcherView.vue

3
src/components/UsageLimitsSection.vue

@ -1,6 +1,5 @@
<template>
<section
v-if="activeDid"
id="sectionUsageLimits"
class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
aria-labelledby="usageLimitsHeading"
@ -94,7 +93,7 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
export default class UsageLimitsSection extends Vue {
@Prop({ required: true }) loadingLimits!: boolean;
@Prop({ required: true }) limitsMessage!: string;
@Prop({ required: true }) activeDid!: string;
@Prop({ required: false }) activeDid?: string;
@Prop({ required: false }) endorserLimits?: any;
@Prop({ required: false }) imageLimits?: any;

14
src/libs/endorserServer.ts

@ -417,6 +417,7 @@ export async function getHeaders(
try {
let token;
const account = await retrieveAccountMetadata(did);
logger.debug(`[getHeaders] Account metadata for DID ${did}:`, !!account);
if (account?.passkeyCredIdHex) {
if (
passkeyAccessToken &&
@ -424,8 +425,10 @@ export async function getHeaders(
) {
// there's an active current passkey token
token = passkeyAccessToken;
logger.debug(`[getHeaders] Using cached passkey token for DID ${did}`);
} else {
// there's no current passkey token or it's expired
logger.debug(`[getHeaders] Generating new access token for DID ${did}`);
token = await accessToken(did);
passkeyAccessToken = token;
@ -434,9 +437,11 @@ export async function getHeaders(
Date.now() / 1000 + passkeyExpirationSeconds;
}
} else {
logger.debug(`[getHeaders] No passkey, generating access token for DID ${did}`);
token = await accessToken(did);
}
headers["Authorization"] = "Bearer " + token;
logger.debug(`[getHeaders] Successfully generated headers for DID ${did}`);
} catch (error) {
// This rarely happens: we've seen it when they have account info but the
// encryption secret got lost. But in most cases we want users to at
@ -460,6 +465,7 @@ export async function getHeaders(
}
} else {
// it's usually OK to request without auth; we assume we're only here when allowed
logger.debug(`[getHeaders] No DID provided, proceeding without authentication`);
}
return headers;
}
@ -1483,7 +1489,13 @@ export async function fetchEndorserRateLimits(
) {
const url = `${apiServer}/api/report/rateLimits`;
const headers = await getHeaders(issuerDid);
return await axios.get(url, { headers } as AxiosRequestConfig);
try {
const response = await axios.get(url, { headers } as AxiosRequestConfig);
return response;
} catch (error) {
logger.error(`[fetchEndorserRateLimits] Error for DID ${issuerDid}:`, errorStringForLog(error));
throw error;
}
}
/**

76
src/utils/PlatformServiceMixin.ts

@ -106,6 +106,8 @@ export const PlatformServiceMixin = {
return {
// Cache the platform service instance at component level
_platformService: null as PlatformService | null,
// Track the current activeDid for change detection
_currentActiveDid: null as string | null,
};
},
@ -122,6 +124,14 @@ export const PlatformServiceMixin = {
return (this as unknown as VueComponentWithMixin)._platformService!;
},
/**
* Current active DID from settings
* Used for change detection and component updates
*/
currentActiveDid(): string | null {
return (this as any)._currentActiveDid;
},
/**
* Access to in-memory logs array
* Provides direct access to memoryLogs without requiring databaseUtil import
@ -157,11 +167,43 @@ export const PlatformServiceMixin = {
},
},
watch: {
/**
* Watch for changes in the current activeDid
* Triggers component updates when user switches identities
*/
currentActiveDid: {
handler(newDid: string | null, oldDid: string | null) {
if (newDid !== oldDid) {
logger.debug(`[PlatformServiceMixin] ActiveDid changed from ${oldDid} to ${newDid}`);
// Clear caches that might be affected by the change
(this as any).$clearAllCaches();
}
},
immediate: true
}
},
methods: {
// =================================================
// SELF-CONTAINED UTILITY METHODS (no databaseUtil dependency)
// =================================================
/**
* Update the current activeDid and trigger change detection
* This method should be called when the user switches identities
*/
async $updateActiveDid(newDid: string | null): Promise<void> {
const oldDid = (this as any)._currentActiveDid;
(this as any)._currentActiveDid = newDid;
if (newDid !== oldDid) {
logger.debug(`[PlatformServiceMixin] ActiveDid updated from ${oldDid} to ${newDid}`);
// Clear caches that might be affected by the change
this.$clearAllCaches();
}
},
/**
* Self-contained implementation of mapColumnsToValues
* Maps database query results to objects with column names as keys
@ -711,6 +753,12 @@ export const PlatformServiceMixin = {
`UPDATE settings SET ${setParts.join(", ")} WHERE id = ?`,
params,
);
// Update activeDid tracking if it changed
if (changes.activeDid !== undefined) {
await this.$updateActiveDid(changes.activeDid);
}
return true;
} catch (error) {
logger.error("[PlatformServiceMixin] Error saving settings:", error);
@ -730,12 +778,17 @@ export const PlatformServiceMixin = {
changes: Partial<Settings>,
): Promise<boolean> {
try {
console.log('[DEBUG] $saveUserSettings - did:', did);
console.log('[DEBUG] $saveUserSettings - changes:', changes);
// Remove fields that shouldn't be updated
const { id, ...safeChanges } = changes;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
void id;
safeChanges.accountDid = did;
console.log('[DEBUG] $saveUserSettings - safeChanges:', safeChanges);
if (Object.keys(safeChanges).length === 0) return true;
const setParts: string[] = [];
@ -748,15 +801,21 @@ export const PlatformServiceMixin = {
}
});
console.log('[DEBUG] $saveUserSettings - setParts:', setParts);
console.log('[DEBUG] $saveUserSettings - params:', params);
if (setParts.length === 0) return true;
params.push(did);
await this.$dbExec(
`UPDATE settings SET ${setParts.join(", ")} WHERE accountDid = ?`,
params,
);
const sql = `UPDATE settings SET ${setParts.join(", ")} WHERE accountDid = ?`;
console.log('[DEBUG] $saveUserSettings - SQL:', sql);
console.log('[DEBUG] $saveUserSettings - Final params:', params);
await this.$dbExec(sql, params);
console.log('[DEBUG] $saveUserSettings - Database update successful');
return true;
} catch (error) {
console.log('[DEBUG] $saveUserSettings - Error:', error);
logger.error(
"[PlatformServiceMixin] Error saving user settings:",
error,
@ -774,9 +833,14 @@ export const PlatformServiceMixin = {
async $saveMySettings(changes: Partial<Settings>): Promise<boolean> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const currentDid = (this as any).activeDid;
console.log('[DEBUG] $saveMySettings - changes:', changes);
console.log('[DEBUG] $saveMySettings - currentDid:', currentDid);
if (!currentDid) {
console.log('[DEBUG] $saveMySettings - No DID, using $saveSettings');
return await this.$saveSettings(changes);
}
console.log('[DEBUG] $saveMySettings - Using $saveUserSettings for DID:', currentDid);
return await this.$saveUserSettings(currentDid, changes);
},
@ -1433,6 +1497,10 @@ declare module "@vue/runtime-core" {
isElectron: boolean;
capabilities: PlatformCapabilities;
// ActiveDid tracking
currentActiveDid: string | null;
$updateActiveDid(newDid: string | null): Promise<void>;
// Ultra-concise database methods (shortest possible names)
$db(sql: string, params?: unknown[]): Promise<QueryExecResult | undefined>;
$exec(sql: string, params?: unknown[]): Promise<DatabaseExecResult>;

18
src/views/AccountViewView.vue

@ -249,6 +249,7 @@
</section>
<UsageLimitsSection
v-if="activeDid"
:loading-limits="loadingLimits"
:limits-message="limitsMessage"
:active-did="activeDid"
@ -967,12 +968,12 @@ export default class AccountViewView extends Vue {
this.loadingProfile = false;
}
// Check limits for registered users
if (this.isRegistered && this.activeDid) {
console.log('[DEBUG] Calling checkLimits from mounted for registered user');
// Check limits for any user with an activeDid (this will also check registration status)
if (this.activeDid) {
console.log('[DEBUG] Calling checkLimits from mounted for user with activeDid');
await this.checkLimits();
} else {
console.log('[DEBUG] Not calling checkLimits - isRegistered:', this.isRegistered, 'activeDid:', this.activeDid);
console.log('[DEBUG] Not calling checkLimits - no activeDid available');
}
// Only check service worker on web platform - Capacitor/Electron don't support it
@ -1013,7 +1014,16 @@ export default class AccountViewView extends Vue {
* Initializes component state with values from the database or defaults.
*/
async initializeState(): Promise<void> {
console.log('[DEBUG] AccountViewView - initializeState called');
// First get the master settings to see the active DID
const masterSettings = await this.$settings();
console.log('[DEBUG] AccountViewView - Master settings activeDid:', masterSettings.activeDid);
// Then get the account-specific settings
const settings: AccountSettings = await this.$accountSettings();
console.log('[DEBUG] AccountViewView - Account settings loaded for DID:', settings.activeDid);
console.log('[DEBUG] AccountViewView - Account settings isRegistered:', settings?.isRegistered);
this.activeDid = settings.activeDid || "";
console.log('[DEBUG] initializeState - activeDid:', this.activeDid);

43
src/views/HomeView.vue

@ -83,7 +83,7 @@ Raymer * @version 1.0.0 */
-->
<div class="mb-4">
<div
v-if="!isRegistered"
v-if="!isUserRegistered"
id="noticeSomeoneMustRegisterYou"
class="bg-amber-200 rounded-md overflow-hidden text-center px-4 py-3 mb-4"
>
@ -460,7 +460,7 @@ export default class HomeView extends Vue {
async mounted() {
try {
await this.initializeIdentity();
await this.loadSettings();
// Settings already loaded in initializeIdentity()
await this.loadContacts();
// Registration check already handled in initializeIdentity()
await this.loadFeedData();
@ -471,6 +471,17 @@ export default class HomeView extends Vue {
}
}
/**
* Watch for changes in the current activeDid
* Reload settings when user switches identities
*/
async onActiveDidChanged(newDid: string | null, oldDid: string | null) {
if (newDid !== oldDid) {
// Re-initialize identity with new settings (loads settings internally)
await this.initializeIdentity();
}
}
/**
* Initializes user identity
* - Retrieves existing DIDs
@ -577,10 +588,13 @@ export default class HomeView extends Vue {
this.axios,
this.activeDid,
);
if (resp.status === 200) {
// Ultra-concise settings update with automatic cache invalidation!
await this.$saveMySettings({ isRegistered: true });
this.isRegistered = true;
// Force Vue to re-render the template
await this.$nextTick();
}
} catch (error) {
// Consolidate logging: Only log unexpected errors, not expected 400s
@ -671,17 +685,8 @@ export default class HomeView extends Vue {
* Called by mounted() and reloadFeedOnChange()
*/
private async loadSettings() {
// First get the active DID from master settings
const masterSettings = await this.$settings({
apiServer: "",
activeDid: "",
filterFeedByVisible: false,
filterFeedByNearby: false,
isRegistered: false,
});
// Then get the full merged settings including account-specific overrides
const settings = await this.$accountSettings(masterSettings.activeDid, {
// Use the current activeDid (set in initializeIdentity) to get user-specific settings
const settings = await this.$accountSettings(this.activeDid, {
apiServer: "",
activeDid: "",
filterFeedByVisible: false,
@ -743,6 +748,8 @@ export default class HomeView extends Vue {
// Ultra-concise settings update with automatic cache invalidation!
await this.$saveMySettings({ isRegistered: true });
this.isRegistered = true;
// Force Vue to re-render the template
await this.$nextTick();
}
} catch (e) {
// ignore the error... just keep us unregistered
@ -1827,5 +1834,15 @@ export default class HomeView extends Vue {
this.showProjectsDialog = true;
(this.$refs.customDialog as GiftedDialog).open();
}
/**
* Computed property for registration status
*
* @public
* Used in template for registration-dependent UI elements
*/
get isUserRegistered() {
return this.isRegistered;
}
}
</script>

21
src/views/IdentitySwitcherView.vue

@ -219,8 +219,27 @@ export default class IdentitySwitcherView extends Vue {
}
async switchAccount(did?: string) {
console.log('[DEBUG] IdentitySwitcher - switchAccount called with DID:', did);
console.log('[DEBUG] IdentitySwitcher - Current activeDid before switch:', this.activeDid);
// Save the new active DID to master settings
await this.$saveSettings({ activeDid: did });
this.$router.push({ name: "account" });
console.log('[DEBUG] IdentitySwitcher - Saved new activeDid to master settings');
// Check if we need to load user-specific settings for the new DID
if (did) {
console.log('[DEBUG] IdentitySwitcher - Loading user-specific settings for DID:', did);
try {
const userSettings = await this.$accountSettings(did);
console.log('[DEBUG] IdentitySwitcher - User settings loaded:', userSettings);
console.log('[DEBUG] IdentitySwitcher - User isRegistered:', userSettings.isRegistered);
} catch (error) {
console.log('[DEBUG] IdentitySwitcher - Error loading user settings:', error);
}
}
// Navigate to home page to trigger the watcher
this.$router.push({ name: "home" });
}
async deleteAccount(id: string) {

Loading…
Cancel
Save