Browse Source

refactor: standardize notify helper usage and document migration workflow

- Refactor notify usage in GiftedDialog.vue, AccountViewView.vue, ClaimView.vue, and DataExportSection.vue:
  • Use notify as a property initialized in created() with createNotifyHelpers(this.$notify)
  • Remove getter-based notify patterns for consistency and lifecycle safety
  • Fix linter/type errors related to notify property initialization

- Add mandatory per-file migration workflow to doc/migration-progress-tracker.md:
  • For each file: (1) migrate to PlatformServiceMixin, (2) immediately standardize notify usage and fix linter/type errors
  • Clarifies this two-step process is required for every file, not as a global sweep

All migrated files are now consistent, maintainable, and ready for further migration work.
pull/142/head
Matthew Raymer 11 hours ago
parent
commit
6d85c54a02
  1. 20
      doc/migration-progress-tracker.md
  2. 2
      doc/migration-to-wa-sqlite.md
  3. 8
      src/components/DataExportSection.vue
  4. 148
      src/components/GiftedDialog.vue
  5. 1
      src/components/IdentitySection.vue
  6. 1
      src/utils/notificationUtils.ts
  7. 16
      src/views/AccountViewView.vue
  8. 5
      src/views/ClaimView.vue

20
doc/migration-progress-tracker.md

@ -1,5 +1,17 @@
# Migration Progress Tracker: PlatformServiceMixin & 52-File Migration # Migration Progress Tracker: PlatformServiceMixin & 52-File Migration
## Per-File Migration Workflow (MANDATORY)
For each file migrated:
1. **First**, migrate to PlatformServiceMixin (replace all databaseUtil usage, etc.).
2. **Immediately after**, standardize notify helper usage (property + created() pattern) and fix any related linter/type errors.
**This two-step process is to be followed for every file, not as a global sweep at the end.**
Anyone picking up this migration should follow this workflow for consistency and completeness.
---
## Overview ## Overview
This document tracks the progress of the 2-day sprint to complete PlatformServiceMixin implementation and migrate all 52 files from databaseUtil imports to PlatformServiceMixin usage. This document tracks the progress of the 2-day sprint to complete PlatformServiceMixin implementation and migrate all 52 files from databaseUtil imports to PlatformServiceMixin usage.
@ -176,7 +188,7 @@ export default class ComponentName extends Vue {
- [ ] UserProfileView.vue - [ ] UserProfileView.vue
### **Components (15 files) - Priority 2** ### **Components (15 files) - Priority 2**
**Progress**: 2/15 (13%) **Progress**: 3/15 (20%)
- [x] UserNameDialog.vue ✅ **MIGRATED** - [x] UserNameDialog.vue ✅ **MIGRATED**
- [ ] ActivityListItem.vue - [ ] ActivityListItem.vue
@ -190,7 +202,7 @@ export default class ComponentName extends Vue {
- [ ] EntitySummaryButton.vue - [ ] EntitySummaryButton.vue
- [x] FeedFilters.vue ✅ **MIGRATED** - [x] FeedFilters.vue ✅ **MIGRATED**
- [ ] GiftDetailsStep.vue - [ ] GiftDetailsStep.vue
- [ ] GiftedDialog.vue - [x] GiftedDialog.vue ✅ **MIGRATED**
- [ ] GiftedPrompts.vue - [ ] GiftedPrompts.vue
- [ ] HiddenDidDialog.vue - [ ] HiddenDidDialog.vue
- [ ] IconRenderer.vue - [ ] IconRenderer.vue
@ -273,8 +285,8 @@ find src -name "*.vue" -o -name "*.ts" | xargs grep -l "import.*databaseUtil" |
### **Overall Progress** ### **Overall Progress**
- **Total files to migrate**: 52 - **Total files to migrate**: 52
- **Files migrated**: 2 - **Files migrated**: 3
- **Progress**: 4% - **Progress**: 6%
--- ---

2
doc/migration-to-wa-sqlite.md

@ -231,7 +231,7 @@ import { logger } from '../utils/logger';
logger.debug('[Migration] Starting migration process...'); logger.debug('[Migration] Starting migration process...');
const result = await migrateAll(); const result = await migrateAll();
logger.debug('[Migration] Migration completed:', result); logger.debug('[Migration] Migration completed:', result);
``` ```
## Benefits of PlatformServiceMixin Approach ## Benefits of PlatformServiceMixin Approach

8
src/components/DataExportSection.vue

@ -98,9 +98,7 @@ export default class DataExportSection extends Vue {
* Notification helper for consistent notification patterns * Notification helper for consistent notification patterns
* Created as a getter to ensure $notify is available when called * Created as a getter to ensure $notify is available when called
*/ */
get notify() { notify!: ReturnType<typeof createNotifyHelpers>;
return createNotifyHelpers(this.$notify);
}
/** /**
* NOTE: PlatformServiceMixin provides both concise helpers (e.g. $contacts, capabilities) * NOTE: PlatformServiceMixin provides both concise helpers (e.g. $contacts, capabilities)
@ -156,5 +154,9 @@ export default class DataExportSection extends Vue {
this.isExporting = false; this.isExporting = false;
} }
} }
created() {
this.notify = createNotifyHelpers(this.$notify);
}
} }
</script> </script>

148
src/components/GiftedDialog.vue

@ -18,7 +18,7 @@
:to-project-id="toProjectId" :to-project-id="toProjectId"
:giver="giver" :giver="giver"
:receiver="receiver" :receiver="receiver"
:notify="$notify" :notify="notify"
@entity-selected="handleEntitySelected" @entity-selected="handleEntitySelected"
@cancel="cancel" @cancel="cancel"
/> />
@ -62,17 +62,16 @@ import {
getHeaders, getHeaders,
} from "../libs/endorserServer"; } from "../libs/endorserServer";
import * as libsUtil from "../libs/util"; import * as libsUtil from "../libs/util";
// Removed unused imports: db, retrieveSettingsForActiveAccount
import { Contact } from "../db/tables/contacts"; import { Contact } from "../db/tables/contacts";
import * as databaseUtil from "../db/databaseUtil";
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 EntityIcon from "../components/EntityIcon.vue"; import EntityIcon from "../components/EntityIcon.vue";
import ProjectIcon from "../components/ProjectIcon.vue"; import ProjectIcon from "../components/ProjectIcon.vue";
import EntitySelectionStep from "../components/EntitySelectionStep.vue"; import EntitySelectionStep from "../components/EntitySelectionStep.vue";
import GiftDetailsStep from "../components/GiftDetailsStep.vue"; import GiftDetailsStep from "../components/GiftDetailsStep.vue";
import { PlanData } from "../interfaces/records"; import { PlanData } from "../interfaces/records";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
@Component({ @Component({
components: { components: {
@ -81,9 +80,10 @@ import { PlanData } from "../interfaces/records";
EntitySelectionStep, EntitySelectionStep,
GiftDetailsStep, GiftDetailsStep,
}, },
mixins: [PlatformServiceMixin],
}) })
export default class GiftedDialog extends Vue { export default class GiftedDialog extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void; notify!: ReturnType<typeof createNotifyHelpers>;
@Prop() fromProjectId = ""; @Prop() fromProjectId = "";
@Prop() toProjectId = ""; @Prop() toProjectId = "";
@ -230,17 +230,11 @@ export default class GiftedDialog extends Vue {
this.updateEntityTypes(); this.updateEntityTypes();
try { try {
const settings = await databaseUtil.retrieveSettingsForActiveAccount(); const settings = await this.$settings();
this.apiServer = settings.apiServer || ""; this.apiServer = settings.apiServer || "";
this.activeDid = settings.activeDid || ""; this.activeDid = settings.activeDid || "";
const platformService = PlatformServiceFactory.getInstance(); this.allContacts = await this.$contacts();
const result = await platformService.dbQuery(`SELECT * FROM contacts`);
if (result) {
this.allContacts = databaseUtil.mapQueryResultToValues(
result,
) as unknown as Contact[];
}
this.allMyDids = await retrieveAccountDids(); this.allMyDids = await retrieveAccountDids();
@ -264,17 +258,11 @@ export default class GiftedDialog extends Vue {
} }
} catch (err: unknown) { } catch (err: unknown) {
logger.error("Error retrieving settings from database:", err); logger.error("Error retrieving settings from database:", err);
this.$notify( this.notify.error(
{ err instanceof Error
group: "alert", ? err.message
type: "danger", : "There was an error retrieving your settings.",
title: "Error", TIMEOUTS.MODAL,
text:
err instanceof Error
? err.message
: "There was an error retrieving your settings.",
},
-1,
); );
} }
@ -319,68 +307,37 @@ export default class GiftedDialog extends Vue {
async confirm() { async confirm() {
if (!this.activeDid) { if (!this.activeDid) {
this.$notify( this.notify.error(
{ "You must select an identifier before you can record a give.",
group: "alert", TIMEOUTS.STANDARD,
type: "danger",
title: "Error",
text: "You must select an identifier before you can record a give.",
},
3000,
); );
return; return;
} }
if (parseFloat(this.amountInput) < 0) { if (parseFloat(this.amountInput) < 0) {
this.$notify( this.notify.error("You may not send a negative number.", TIMEOUTS.SHORT);
{
group: "alert",
type: "danger",
text: "You may not send a negative number.",
title: "",
},
2000,
);
return; return;
} }
if (!this.description && !parseFloat(this.amountInput)) { if (!this.description && !parseFloat(this.amountInput)) {
this.$notify( this.notify.error(
{ `You must enter a description or some number of ${
group: "alert", this.libsUtil.UNIT_LONG[this.unitCode]
type: "danger", }.`,
title: "Error", TIMEOUTS.SHORT,
text: `You must enter a description or some number of ${
this.libsUtil.UNIT_LONG[this.unitCode]
}.`,
},
2000,
); );
return; return;
} }
// Check for person conflict // Check for person conflict
if (this.hasPersonConflict) { if (this.hasPersonConflict) {
this.$notify( this.notify.error(
{ "You cannot select the same person as both giver and recipient.",
group: "alert", TIMEOUTS.STANDARD,
type: "danger",
title: "Error",
text: "You cannot select the same person as both giver and recipient.",
},
3000,
); );
return; return;
} }
this.close(); this.close();
this.$notify( this.notify.toast("Recording the give...", undefined, TIMEOUTS.BRIEF);
{
group: "alert",
type: "toast",
text: "Recording the give...",
title: "",
},
1000,
);
// this is asynchronous, but we don't need to wait for it to complete // this is asynchronous, but we don't need to wait for it to complete
await this.recordGive( await this.recordGive(
(this.giver?.did as string) || null, (this.giver?.did as string) || null,
@ -460,25 +417,12 @@ export default class GiftedDialog extends Vue {
if (!result.success) { if (!result.success) {
const errorMessage = this.getGiveCreationErrorMessage(result); const errorMessage = this.getGiveCreationErrorMessage(result);
logger.error("Error with give creation result:", result); logger.error("Error with give creation result:", result);
this.$notify( this.notify.error(
{ errorMessage || "There was an error creating the give.",
group: "alert", TIMEOUTS.MODAL,
type: "danger",
title: "Error",
text: errorMessage || "There was an error creating the give.",
},
-1,
); );
} else { } else {
this.$notify( this.notify.success("That gift was recorded.", TIMEOUTS.VERY_LONG);
{
group: "alert",
type: "success",
title: "Success",
text: `That gift was recorded.`,
},
7000,
);
if (this.callbackOnSuccess) { if (this.callbackOnSuccess) {
this.callbackOnSuccess(amount); this.callbackOnSuccess(amount);
} }
@ -490,15 +434,7 @@ export default class GiftedDialog extends Vue {
error.userMessage || error.userMessage ||
serverMessageForUser(error) || serverMessageForUser(error) ||
"There was an error recording the give."; "There was an error recording the give.";
this.$notify( this.notify.error(errorMessage, TIMEOUTS.MODAL);
{
group: "alert",
type: "danger",
title: "Error",
text: errorMessage,
},
-1,
);
} }
} }
@ -518,15 +454,7 @@ export default class GiftedDialog extends Vue {
} }
explainData() { explainData() {
this.$notify( this.notify.info(libsUtil.PRIVACY_MESSAGE, TIMEOUTS.MODAL);
{
group: "alert",
type: "success",
title: "Data Sharing",
text: libsUtil.PRIVACY_MESSAGE,
},
-1,
);
} }
selectGiver(contact?: Contact) { selectGiver(contact?: Contact) {
@ -566,15 +494,7 @@ export default class GiftedDialog extends Vue {
} }
} catch (error) { } catch (error) {
logger.error("Error loading projects:", error); logger.error("Error loading projects:", error);
this.$notify( this.notify.error("Failed to load projects", TIMEOUTS.STANDARD);
{
group: "alert",
type: "danger",
title: "Error",
text: "Failed to load projects",
},
3000,
);
} }
} }
@ -696,6 +616,10 @@ export default class GiftedDialog extends Vue {
amountInput: this.amountInput, amountInput: this.amountInput,
}); });
} }
created() {
this.notify = createNotifyHelpers(this.$notify);
}
} }
</script> </script>

1
src/components/IdentitySection.vue

@ -185,3 +185,4 @@ export default class IdentitySection extends Vue {
} }
} }
</script> </script>

1
src/utils/notificationUtils.ts

@ -276,3 +276,4 @@ export const NotificationMixin = {
}, },
}, },
}; };

16
src/views/AccountViewView.vue

@ -817,6 +817,10 @@ import {
const inputImportFileNameRef = ref<Blob>(); const inputImportFileNameRef = ref<Blob>();
interface UserNameDialogRef {
open: (cb: (name?: string) => void) => void;
}
@Component({ @Component({
components: { components: {
EntityIcon, EntityIcon,
@ -908,6 +912,10 @@ export default class AccountViewView extends Vue {
private profileService!: ProfileService; private profileService!: ProfileService;
private notify!: ReturnType<typeof createNotifyHelpers>; private notify!: ReturnType<typeof createNotifyHelpers>;
created() {
this.notify = createNotifyHelpers(this.$notify);
}
/** /**
* Async function executed when the component is mounted. * Async function executed when the component is mounted.
* Initializes the component's state with values from the database, * Initializes the component's state with values from the database,
@ -916,7 +924,6 @@ export default class AccountViewView extends Vue {
* @throws Will display specific messages to the user based on different errors. * @throws Will display specific messages to the user based on different errors.
*/ */
async mounted(): Promise<void> { async mounted(): Promise<void> {
this.notify = createNotifyHelpers(this.$notify);
this.profileService = createProfileService( this.profileService = createProfileService(
this.axios, this.axios,
this.partnerApiServer, this.partnerApiServer,
@ -1601,7 +1608,7 @@ export default class AccountViewView extends Vue {
// IdentitySection event handlers // IdentitySection event handlers
onEditName() { onEditName() {
const dialog = this.$refs.userNameDialog as any; const dialog = this.$refs.userNameDialog as UserNameDialogRef | undefined;
if (dialog && typeof dialog.open === "function") { if (dialog && typeof dialog.open === "function") {
dialog.open((name?: string) => { dialog.open((name?: string) => {
if (name) this.givenName = name; if (name) this.givenName = name;
@ -1613,7 +1620,10 @@ export default class AccountViewView extends Vue {
title: "Dialog Error", title: "Dialog Error",
text: "Name dialog not available.", text: "Name dialog not available.",
}); });
console.error("UserNameDialog ref is missing or open() is not a function", dialog); logger.error(
"UserNameDialog ref is missing or open() is not a function",
dialog,
);
} }
} }
onShowQrCode() { onShowQrCode() {

5
src/views/ClaimView.vue

@ -578,8 +578,7 @@ export default class ClaimView extends Vue {
libsUtil = libsUtil; libsUtil = libsUtil;
serverUtil = serverUtil; serverUtil = serverUtil;
// Add notification helpers notify!: ReturnType<typeof createNotifyHelpers>;
private notify = createNotifyHelpers(this.$notify);
// ================================================= // =================================================
// COMPUTED PROPERTIES // COMPUTED PROPERTIES
@ -746,6 +745,8 @@ export default class ClaimView extends Vue {
this.windowDeepLink = `${APP_SERVER}/deep-link/claim/${claimId}`; this.windowDeepLink = `${APP_SERVER}/deep-link/claim/${claimId}`;
this.canShare = !!navigator.share; this.canShare = !!navigator.share;
this.notify = createNotifyHelpers(this.$notify);
} }
// insert a space before any capital letters except the initial letter // insert a space before any capital letters except the initial letter

Loading…
Cancel
Save