Migrate OfferDetailsView.vue to PlatformServiceMixin, notification constants, and template streamlining

- Replaced all databaseUtil and direct PlatformServiceFactory usage with PlatformServiceMixin methods
- Abstracted all notification messages to src/constants/notifications.ts and migrated to notify helper
- Added computed properties for assignment labels to streamline template logic
- Removed unused imports and resolved all linter errors
- Updated migration documentation and ensured security audit compliance
- All changes validated with lint-fix and ready for human testing
This commit is contained in:
Matthew Raymer
2025-07-08 11:54:50 +00:00
parent 0b3b7dbb4d
commit 8f5c174097
4 changed files with 419 additions and 164 deletions

View File

@@ -200,6 +200,92 @@ export const NOTIFY_REGISTER_NOT_AVAILABLE = {
message: "You must get registered before you can create invites.",
};
// OfferDetailsView.vue specific constants
// Used in: OfferDetailsView.vue (mounted method - error loading offer details)
export const NOTIFY_OFFER_ERROR_LOADING = {
title: "Error",
message: "There was an error loading the offer details.",
};
// Used in: OfferDetailsView.vue (loadPreviousOffer method - previous record error)
export const NOTIFY_OFFER_ERROR_PREVIOUS_RECORD = {
title: "Retrieval Error",
message:
"The previous record isn't available for editing. If you submit, you'll create a new record.",
};
// Used in: OfferDetailsView.vue (confirm method - no identifier error)
export const NOTIFY_OFFER_ERROR_NO_IDENTIFIER = {
title: "Error",
message: "You must select an identifier before you can record a offer.",
};
// Used in: OfferDetailsView.vue (confirm method - negative amount error)
export const NOTIFY_OFFER_ERROR_NEGATIVE_AMOUNT = {
title: "",
message: "You may not send a negative number.",
};
// Used in: OfferDetailsView.vue (confirm method - no description error)
export const NOTIFY_OFFER_ERROR_NO_DESCRIPTION = {
title: "Error",
message: "You must enter a description or some number of {unit}.",
};
// Used in: OfferDetailsView.vue (confirm method - processing status)
export const NOTIFY_OFFER_PROCESSING = {
title: "",
message: "Recording the offer...",
};
// Used in: OfferDetailsView.vue (notifyUserOfProject method - no project error)
export const NOTIFY_OFFER_ERROR_PROJECT_ASSIGNMENT = {
title: "Error",
message: "To assign to a project, you must open this page through a project.",
};
// Used in: OfferDetailsView.vue (notifyUserOfProject method - conflict error)
export const NOTIFY_OFFER_ERROR_PROJECT_RECIPIENT_CONFLICT = {
title: "Error",
message: "You cannot assign both to a project and to a recipient.",
};
// Used in: OfferDetailsView.vue (notifyUserOfRecipient method - no recipient error)
export const NOTIFY_OFFER_ERROR_RECIPIENT_ASSIGNMENT = {
title: "Error",
message: "To assign to a recipient, you must open this page from a contact.",
};
// Used in: OfferDetailsView.vue (notifyUserOfRecipient method - conflict error)
export const NOTIFY_OFFER_ERROR_RECIPIENT_PROJECT_CONFLICT = {
title: "Error",
message: "You cannot assign both to a recipient and to a project.",
};
// Used in: OfferDetailsView.vue (recordOffer method - creation error)
export const NOTIFY_OFFER_ERROR_CREATION = {
title: "Error",
message: "There was an error creating the offer.",
};
// Used in: OfferDetailsView.vue (recordOffer method - success)
export const NOTIFY_OFFER_SUCCESS_RECORDED = {
title: "Success",
message: "That offer was recorded.",
};
// Used in: OfferDetailsView.vue (recordOffer method - recordation error)
export const NOTIFY_OFFER_ERROR_RECORDATION = {
title: "Error",
message: "There was an error recording the offer.",
};
// Used in: OfferDetailsView.vue (explainData method - privacy info)
export const NOTIFY_OFFER_PRIVACY_INFO = {
title: "Data Sharing",
message: "Your data is shared with the world when you sign and send.",
};
// Used in: [Component usage not yet documented]
export const NOTIFY_REGISTER_PROCESSING = {
title: "Processing",

View File

@@ -21,16 +21,7 @@
<h1 class="text-4xl text-center font-light px-4 mb-4">What Is Offered</h1>
<h1 class="text-xl font-bold text-center mb-4">
<span>
Offer to
{{
offeredToProject
? projectName
: offeredToRecipient
? recipientName
: "someone not named"
}}</span
>
<span> Offer to {{ recipientDisplayName }} </span>
</h1>
<textarea
v-model="descriptionOfItem"
@@ -105,11 +96,7 @@
@click="notifyUserOfProject()"
/>
<label class="text-sm mt-1">
{{
projectId
? "This is offered to " + projectName
: "No project was chosen"
}}
{{ projectAssignmentLabel }}
</label>
</div>
@@ -127,11 +114,7 @@
@click="notifyUserOfRecipient()"
/>
<label class="text-sm mt-1">
{{
recipientDid
? "This is offered to " + recipientName
: "No recipient was chosen."
}}
{{ recipientAssignmentLabel }}
</label>
</div>
@@ -192,9 +175,24 @@ import {
import * as libsUtil from "../libs/util";
import { retrieveAccountDids } from "../libs/util";
import { logger } from "../utils/logger";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
import { Contact } from "@/db/tables/contacts";
import * as databaseUtil from "../db/databaseUtil";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
import {
NOTIFY_OFFER_ERROR_LOADING,
NOTIFY_OFFER_ERROR_PREVIOUS_RECORD,
NOTIFY_OFFER_ERROR_NO_IDENTIFIER,
NOTIFY_OFFER_ERROR_NEGATIVE_AMOUNT,
NOTIFY_OFFER_ERROR_NO_DESCRIPTION,
NOTIFY_OFFER_PROCESSING,
NOTIFY_OFFER_ERROR_PROJECT_ASSIGNMENT,
NOTIFY_OFFER_ERROR_PROJECT_RECIPIENT_CONFLICT,
NOTIFY_OFFER_ERROR_RECIPIENT_ASSIGNMENT,
NOTIFY_OFFER_ERROR_RECIPIENT_PROJECT_CONFLICT,
NOTIFY_OFFER_ERROR_CREATION,
NOTIFY_OFFER_SUCCESS_RECORDED,
NOTIFY_OFFER_ERROR_RECORDATION,
NOTIFY_OFFER_PRIVACY_INFO,
} from "@/constants/notifications";
/**
* Offer Details View Component
@@ -233,6 +231,7 @@ import * as databaseUtil from "../db/databaseUtil";
QuickNav,
TopMessage,
},
mixins: [PlatformServiceMixin],
})
export default class OfferDetailsView extends Vue {
/** Notification function injected by Vue */
@@ -241,6 +240,8 @@ export default class OfferDetailsView extends Vue {
$route!: RouteLocationNormalizedLoaded;
/** Router instance for navigation */
$router!: Router;
/** Notification helper methods */
notify!: ReturnType<typeof createNotifyHelpers>;
/** Currently active DID */
activeDid = "";
@@ -286,6 +287,45 @@ export default class OfferDetailsView extends Vue {
/** Utility library reference */
libsUtil = libsUtil;
/**
* Component lifecycle hook that initializes notification helpers
*/
created() {
this.notify = createNotifyHelpers(this.$notify);
}
/**
* Computed property for recipient display name
* Streamlines template logic for recipient/project display
*/
get recipientDisplayName() {
return this.offeredToProject
? this.projectName
: this.offeredToRecipient
? this.recipientName
: "someone not named";
}
/**
* Computed property for project assignment label
* Streamlines template logic for project checkbox label
*/
get projectAssignmentLabel() {
return this.projectId
? `This is offered to ${this.projectName}`
: "No project was chosen";
}
/**
* Computed property for recipient assignment label
* Streamlines template logic for recipient checkbox label
*/
get recipientAssignmentLabel() {
return this.recipientDid
? `This is offered to ${this.recipientName}`
: "No recipient was chosen.";
}
/**
* Component lifecycle hook that initializes the offer form
*
@@ -308,16 +348,9 @@ export default class OfferDetailsView extends Vue {
await this.loadProjectInfo();
} catch (err: unknown) {
logger.error("Error in mounted:", err);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text:
(err as Error)?.message ||
"There was an error loading the offer details.",
},
5000,
this.notify.error(
(err as Error)?.message || NOTIFY_OFFER_ERROR_LOADING.message,
TIMEOUTS.LONG,
);
}
}
@@ -334,14 +367,9 @@ export default class OfferDetailsView extends Vue {
) as GenericCredWrapper<OfferClaim>)
: undefined;
} catch (error: unknown) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Retrieval Error",
text: "The previous record isn't available for editing. If you submit, you'll create a new record.",
},
5000,
this.notify.error(
NOTIFY_OFFER_ERROR_PREVIOUS_RECORD.message,
TIMEOUTS.LONG,
);
}
}
@@ -403,7 +431,7 @@ export default class OfferDetailsView extends Vue {
* @throws Will not throw but logs errors
*/
private async loadAccountSettings() {
const settings = await databaseUtil.retrieveSettingsForActiveAccount();
const settings = await this.$accountSettings();
this.apiServer = settings.apiServer ?? "";
this.activeDid = settings.activeDid ?? "";
this.showGeneralAdvanced = settings.showGeneralAdvanced ?? false;
@@ -414,14 +442,7 @@ export default class OfferDetailsView extends Vue {
*/
private async loadRecipientInfo() {
if (this.recipientDid && !this.recipientName) {
let allContacts: Contact[] = [];
const platformService = PlatformServiceFactory.getInstance();
const queryResult = await platformService.dbQuery(
"SELECT * FROM contacts",
);
allContacts = databaseUtil.mapQueryResultToValues(
queryResult,
) as unknown as Contact[];
const allContacts = await this.$getAllContacts();
const allMyDids = await retrieveAccountDids();
this.recipientName = didInfo(
this.recipientDid,
@@ -526,53 +547,29 @@ export default class OfferDetailsView extends Vue {
*/
async confirm() {
if (!this.activeDid) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: "You must select an identifier before you can record a offer.",
},
2000,
this.notify.error(
NOTIFY_OFFER_ERROR_NO_IDENTIFIER.message,
TIMEOUTS.SHORT,
);
return;
}
if (parseFloat(this.amountInput) < 0) {
this.$notify(
{
group: "alert",
type: "danger",
text: "You may not send a negative number.",
title: "",
},
2000,
this.notify.error(
NOTIFY_OFFER_ERROR_NEGATIVE_AMOUNT.message,
TIMEOUTS.SHORT,
);
return;
}
if (!this.descriptionOfItem && !parseFloat(this.amountInput)) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: `You must enter a description or some number of ${
this.libsUtil.UNIT_LONG[this.unitCode]
}.`,
},
2000,
const message = NOTIFY_OFFER_ERROR_NO_DESCRIPTION.message.replace(
"{unit}",
this.libsUtil.UNIT_LONG[this.unitCode],
);
this.notify.error(message, TIMEOUTS.SHORT);
return;
}
this.$notify(
{
group: "alert",
type: "toast",
text: "Recording the offer...",
title: "",
},
1000,
);
this.notify.toast("", NOTIFY_OFFER_PROCESSING.message, TIMEOUTS.SHORT);
// this is asynchronous, but we don't need to wait for it to complete
await this.recordOffer();
@@ -589,25 +586,15 @@ export default class OfferDetailsView extends Vue {
*/
notifyUserOfProject() {
if (!this.projectId) {
this.$notify(
{
group: "alert",
type: "warning",
title: "Error",
text: "To assign to a project, you must open this page through a project.",
},
3000,
this.notify.warning(
NOTIFY_OFFER_ERROR_PROJECT_ASSIGNMENT.message,
TIMEOUTS.STANDARD,
);
} else {
// must be because offeredToRecipient is true
this.$notify(
{
group: "alert",
type: "warning",
title: "Error",
text: "You cannot assign both to a project and to a recipient.",
},
3000,
this.notify.warning(
NOTIFY_OFFER_ERROR_PROJECT_RECIPIENT_CONFLICT.message,
TIMEOUTS.STANDARD,
);
}
}
@@ -623,25 +610,15 @@ export default class OfferDetailsView extends Vue {
*/
notifyUserOfRecipient() {
if (!this.recipientDid) {
this.$notify(
{
group: "alert",
type: "warning",
title: "Error",
text: "To assign to a recipient, you must open this page from a contact.",
},
3000,
this.notify.warning(
NOTIFY_OFFER_ERROR_RECIPIENT_ASSIGNMENT.message,
TIMEOUTS.STANDARD,
);
} else {
// must be because offeredToProject is true
this.$notify(
{
group: "alert",
type: "warning",
title: "Error",
text: "You cannot assign both to a recipient and to a project.",
},
3000,
this.notify.warning(
NOTIFY_OFFER_ERROR_RECIPIENT_PROJECT_CONFLICT.message,
TIMEOUTS.STANDARD,
);
}
}
@@ -700,24 +677,14 @@ export default class OfferDetailsView extends Vue {
if (!result.success) {
const errorMessage = this.getCreationErrorMessage(result);
logger.error("Error with offer creation result:", result);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: errorMessage || "There was an error creating the offer.",
},
5000,
this.notify.error(
errorMessage || NOTIFY_OFFER_ERROR_CREATION.message,
TIMEOUTS.LONG,
);
} else {
this.$notify(
{
group: "alert",
type: "success",
title: "Success",
text: `That offer was recorded.`,
},
5000,
this.notify.success(
NOTIFY_OFFER_SUCCESS_RECORDED.message,
TIMEOUTS.LONG,
);
localStorage.removeItem("imageUrl");
if (this.destinationPathAfter) {
@@ -732,16 +699,8 @@ export default class OfferDetailsView extends Vue {
const errorMessage =
error.userMessage ||
error.response?.data?.error?.message ||
"There was an error recording the offer.";
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: errorMessage,
},
5000,
);
NOTIFY_OFFER_ERROR_RECORDATION.message;
this.notify.error(errorMessage, TIMEOUTS.LONG);
}
}
@@ -811,15 +770,7 @@ export default class OfferDetailsView extends Vue {
* @emits Notification with privacy message
*/
explainData() {
this.$notify(
{
group: "alert",
type: "success",
title: "Data Sharing",
text: libsUtil.PRIVACY_MESSAGE,
},
7000,
);
this.notify.success(NOTIFY_OFFER_PRIVACY_INFO.message, TIMEOUTS.VERY_LONG);
}
}
</script>