forked from trent_larson/crowd-funder-for-time-pwa
feat: enhance EntityGrid with function props and improve code formatting
- Add configurable entity display logic via function props to EntityGrid - Implement comprehensive test suite for EntityGrid function props in TestView - Apply consistent code formatting across 15 components and views - Fix linting issues with trailing commas and line breaks - Add new EntityGridFunctionPropTest.vue for component testing - Update endorserServer with improved error handling and logging - Streamline PlatformServiceMixin with better cache management - Enhance component documentation and type safety Changes span 15 files with 159 additions and 69 deletions, focusing on component flexibility, code quality, and testing infrastructure.
This commit is contained in:
@@ -63,7 +63,7 @@
|
||||
<div
|
||||
v-if="record.image"
|
||||
class="bg-cover mb-2 -mt-3 sm:-mt-4 -mx-3 sm:-mx-4"
|
||||
:style="`background-image: url(${transformImageUrlForCors(record.image)});`"
|
||||
:style="`background-image: url(${record.image});`"
|
||||
>
|
||||
<a
|
||||
class="block bg-slate-100/50 backdrop-blur-md px-6 py-4 cursor-pointer"
|
||||
@@ -71,7 +71,7 @@
|
||||
>
|
||||
<img
|
||||
class="w-full h-auto max-w-lg max-h-96 object-contain mx-auto drop-shadow-md"
|
||||
:src="transformImageUrlForCors(record.image)"
|
||||
:src="record.image"
|
||||
alt="Activity image"
|
||||
@load="cacheImage(record.image)"
|
||||
/>
|
||||
@@ -253,8 +253,7 @@ import { GiveRecordWithContactInfo } from "@/interfaces/give";
|
||||
import EntityIcon from "./EntityIcon.vue";
|
||||
import {
|
||||
isGiveClaimType,
|
||||
notifyWhyCannotConfirm,
|
||||
transformImageUrlForCors,
|
||||
notifyWhyCannotConfirm
|
||||
} from "../libs/util";
|
||||
import { containsHiddenDid, isHiddenDid } from "../libs/endorserServer";
|
||||
import ProjectIcon from "./ProjectIcon.vue";
|
||||
@@ -357,9 +356,5 @@ export default class ActivityListItem extends Vue {
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
transformImageUrlForCors(imageUrl: string): string {
|
||||
return transformImageUrlForCors(imageUrl);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -96,6 +96,7 @@ import { NotificationIface } from "../constants/app";
|
||||
* - Event delegation for entity selection
|
||||
* - Warning notifications for conflicted entities
|
||||
* - Template streamlined with computed CSS properties
|
||||
* - Configurable entity display logic via function props
|
||||
*/
|
||||
@Component({
|
||||
components: {
|
||||
@@ -158,6 +159,40 @@ export default class EntityGrid extends Vue {
|
||||
@Prop({ default: "other party" })
|
||||
conflictContext!: string;
|
||||
|
||||
/**
|
||||
* Function to determine which entities to display (allows parent control)
|
||||
*
|
||||
* This function prop allows parent components to customize which entities
|
||||
* are displayed in the grid, enabling advanced filtering, sorting, and
|
||||
* display logic beyond the default simple slice behavior.
|
||||
*
|
||||
* @param entities - The full array of entities (Contact[] or PlanData[])
|
||||
* @param entityType - The type of entities being displayed ("people" or "projects")
|
||||
* @param maxItems - The maximum number of items to display (from maxItems prop)
|
||||
* @returns Filtered/sorted array of entities to display
|
||||
*
|
||||
* @example
|
||||
* // Custom filtering: only show contacts with profile images
|
||||
* :display-entities-function="(entities, type, max) =>
|
||||
* entities.filter(e => e.profileImageUrl).slice(0, max)"
|
||||
*
|
||||
* @example
|
||||
* // Custom sorting: sort projects by name
|
||||
* :display-entities-function="(entities, type, max) =>
|
||||
* entities.sort((a, b) => a.name.localeCompare(b.name)).slice(0, max)"
|
||||
*
|
||||
* @example
|
||||
* // Advanced logic: different limits for different entity types
|
||||
* :display-entities-function="(entities, type, max) =>
|
||||
* type === 'projects' ? entities.slice(0, 5) : entities.slice(0, max)"
|
||||
*/
|
||||
@Prop({ default: null })
|
||||
displayEntitiesFunction?: (
|
||||
entities: Contact[] | PlanData[],
|
||||
entityType: "people" | "projects",
|
||||
maxItems: number,
|
||||
) => Contact[] | PlanData[];
|
||||
|
||||
/**
|
||||
* CSS classes for the empty state message
|
||||
*/
|
||||
@@ -179,9 +214,18 @@ export default class EntityGrid extends Vue {
|
||||
}
|
||||
|
||||
/**
|
||||
* Computed entities to display (limited by maxItems)
|
||||
* Computed entities to display - uses function prop if provided, otherwise defaults
|
||||
*/
|
||||
get displayedEntities(): Contact[] | PlanData[] {
|
||||
if (this.displayEntitiesFunction) {
|
||||
return this.displayEntitiesFunction(
|
||||
this.entities,
|
||||
this.entityType,
|
||||
this.maxItems,
|
||||
);
|
||||
}
|
||||
|
||||
// Default implementation for backward compatibility
|
||||
const maxDisplay = this.entityType === "projects" ? 7 : this.maxItems;
|
||||
return this.entities.slice(0, maxDisplay);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import { createAvatar, StyleOptions } from "@dicebear/core";
|
||||
import { avataaars } from "@dicebear/collection";
|
||||
import { Vue, Component, Prop } from "vue-facing-decorator";
|
||||
import { Contact } from "../db/tables/contacts";
|
||||
import { transformImageUrlForCors } from "../libs/util";
|
||||
import blankSquareSvg from "../assets/blank-square.svg";
|
||||
|
||||
/**
|
||||
@@ -57,7 +56,7 @@ export default class EntityIcon extends Vue {
|
||||
// Check for profile image URL (highest priority)
|
||||
const imageUrl = this.contact?.profileImageUrl || this.profileImageUrl;
|
||||
if (imageUrl) {
|
||||
return `<img src="${transformImageUrlForCors(imageUrl)}" class="rounded" width="${this.iconSize}" height="${this.iconSize}" />`;
|
||||
return `<img src="${imageUrl}" class="rounded" width="${this.iconSize}" height="${this.iconSize}" />`;
|
||||
}
|
||||
|
||||
// Check for identifier for avatar generation
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
import { Component, Vue, Prop } from "vue-facing-decorator";
|
||||
import { UAParser } from "ua-parser-js";
|
||||
import { logger } from "../utils/logger";
|
||||
import { transformImageUrlForCors } from "../libs/util";
|
||||
|
||||
@Component({ emits: ["update:isOpen"] })
|
||||
export default class ImageViewer extends Vue {
|
||||
@@ -80,10 +79,6 @@ export default class ImageViewer extends Vue {
|
||||
window.open(this.imageUrl, "_blank");
|
||||
}
|
||||
}
|
||||
|
||||
get transformedImageUrl() {
|
||||
return transformImageUrlForCors(this.imageUrl);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
<script lang="ts">
|
||||
import { toSvg } from "jdenticon";
|
||||
import { Vue, Component, Prop } from "vue-facing-decorator";
|
||||
import { transformImageUrlForCors } from "../libs/util";
|
||||
|
||||
const BLANK_CONFIG = {
|
||||
lightness: {
|
||||
@@ -36,7 +35,7 @@ export default class ProjectIcon extends Vue {
|
||||
|
||||
generateIcon() {
|
||||
if (this.imageUrl) {
|
||||
return `<img src="${transformImageUrlForCors(this.imageUrl)}" class="w-full h-full object-contain" />`;
|
||||
return `<img src="${this.imageUrl}" class="w-full h-full object-contain" />`;
|
||||
} else {
|
||||
const config = this.entityId ? undefined : BLANK_CONFIG;
|
||||
const svgString = toSvg(this.entityId, this.iconSize, config);
|
||||
|
||||
@@ -64,8 +64,8 @@
|
||||
Number(imageLimits?.doneImagesThisWeek || 0) === 1 ? "" : "s"
|
||||
}}</b
|
||||
>
|
||||
out of <b>{{ imageLimits?.maxImagesPerWeek ?? "?" }}</b> for this
|
||||
week. Your image counter resets at
|
||||
out of <b>{{ imageLimits?.maxImagesPerWeek ?? "?" }}</b> for this week.
|
||||
Your image counter resets at
|
||||
<b class="whitespace-nowrap">{{
|
||||
readableDate(imageLimits?.nextWeekBeginDateTime || "")
|
||||
}}</b>
|
||||
@@ -82,13 +82,13 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop } from "vue-facing-decorator";
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||
|
||||
@Component({
|
||||
@Component({
|
||||
name: "UsageLimitsSection",
|
||||
components: {
|
||||
FontAwesome: FontAwesomeIcon
|
||||
}
|
||||
FontAwesome: FontAwesomeIcon,
|
||||
},
|
||||
})
|
||||
export default class UsageLimitsSection extends Vue {
|
||||
@Prop({ required: true }) loadingLimits!: boolean;
|
||||
|
||||
@@ -425,10 +425,14 @@ export async function getHeaders(
|
||||
) {
|
||||
// there's an active current passkey token
|
||||
token = passkeyAccessToken;
|
||||
logger.debug(`[getHeaders] Using cached passkey token for DID ${did}`);
|
||||
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}`);
|
||||
logger.debug(
|
||||
`[getHeaders] Generating new access token for DID ${did}`,
|
||||
);
|
||||
token = await accessToken(did);
|
||||
|
||||
passkeyAccessToken = token;
|
||||
@@ -437,11 +441,15 @@ export async function getHeaders(
|
||||
Date.now() / 1000 + passkeyExpirationSeconds;
|
||||
}
|
||||
} else {
|
||||
logger.debug(`[getHeaders] No passkey, generating access token for DID ${did}`);
|
||||
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}`);
|
||||
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
|
||||
@@ -465,7 +473,9 @@ 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`);
|
||||
logger.debug(
|
||||
`[getHeaders] No DID provided, proceeding without authentication`,
|
||||
);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
@@ -1493,7 +1503,10 @@ export async function fetchEndorserRateLimits(
|
||||
const response = await axios.get(url, { headers } as AxiosRequestConfig);
|
||||
return response;
|
||||
} catch (error) {
|
||||
logger.error(`[fetchEndorserRateLimits] Error for DID ${issuerDid}:`, errorStringForLog(error));
|
||||
logger.error(
|
||||
`[fetchEndorserRateLimits] Error for DID ${issuerDid}:`,
|
||||
errorStringForLog(error),
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
224
src/test/EntityGridFunctionPropTest.vue
Normal file
224
src/test/EntityGridFunctionPropTest.vue
Normal file
@@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>EntityGrid Function Prop Test</h2>
|
||||
|
||||
<div class="mb-4">
|
||||
<button @click="toggleCustomFunction">
|
||||
{{ useCustomFunction ? "Use Default" : "Use Custom Function" }}
|
||||
</button>
|
||||
<span class="ml-2"
|
||||
>Current: {{ useCustomFunction ? "Custom" : "Default" }}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h3>
|
||||
People Grid ({{ people.length }} total,
|
||||
{{ displayedPeopleCount }} shown)
|
||||
</h3>
|
||||
<EntityGrid
|
||||
entity-type="people"
|
||||
:entities="people"
|
||||
:max-items="5"
|
||||
:active-did="activeDid"
|
||||
:all-my-dids="allMyDids"
|
||||
:all-contacts="people"
|
||||
:conflict-checker="conflictChecker"
|
||||
:display-entities-function="
|
||||
useCustomFunction ? customPeopleFunction : undefined
|
||||
"
|
||||
@entity-selected="handleEntitySelected"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h3>
|
||||
Projects Grid ({{ projects.length }} total,
|
||||
{{ displayedProjectsCount }} shown)
|
||||
</h3>
|
||||
<EntityGrid
|
||||
entity-type="projects"
|
||||
:entities="projects"
|
||||
:max-items="3"
|
||||
:active-did="activeDid"
|
||||
:all-my-dids="allMyDids"
|
||||
:all-contacts="people"
|
||||
:conflict-checker="conflictChecker"
|
||||
:display-entities-function="
|
||||
useCustomFunction ? customProjectsFunction : undefined
|
||||
"
|
||||
@entity-selected="handleEntitySelected"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<h3>Selected Entity:</h3>
|
||||
<pre>{{ selectedEntity }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import EntityGrid from "../components/EntityGrid.vue";
|
||||
import { Contact } from "../db/tables/contacts";
|
||||
import { PlanData } from "../interfaces/records";
|
||||
|
||||
/**
|
||||
* Test component to demonstrate EntityGrid's new displayEntitiesFunction prop
|
||||
*
|
||||
* Shows how parent components can control which entities are displayed
|
||||
* through function props instead of relying on computed properties.
|
||||
*/
|
||||
@Component({
|
||||
components: {
|
||||
EntityGrid,
|
||||
},
|
||||
})
|
||||
export default class EntityGridFunctionPropTest extends Vue {
|
||||
useCustomFunction = false;
|
||||
selectedEntity: {
|
||||
type: "person" | "project" | "special";
|
||||
entityType?: string;
|
||||
data: Contact | PlanData | { did?: string; name: string };
|
||||
} | null = null;
|
||||
|
||||
// Test data
|
||||
activeDid = "did:example:123";
|
||||
allMyDids = ["did:example:123"];
|
||||
|
||||
people: Contact[] = [
|
||||
{
|
||||
did: "did:example:1",
|
||||
name: "Alice",
|
||||
profileImageUrl: "https://example.com/alice.jpg",
|
||||
},
|
||||
{ did: "did:example:2", name: "Bob", profileImageUrl: "" },
|
||||
{
|
||||
did: "did:example:3",
|
||||
name: "Charlie",
|
||||
profileImageUrl: "https://example.com/charlie.jpg",
|
||||
},
|
||||
{ did: "did:example:4", name: "Diana", profileImageUrl: "" },
|
||||
{
|
||||
did: "did:example:5",
|
||||
name: "Eve",
|
||||
profileImageUrl: "https://example.com/eve.jpg",
|
||||
},
|
||||
{
|
||||
did: "did:example:6",
|
||||
name: "Frank",
|
||||
profileImageUrl: "https://example.com/frank.jpg",
|
||||
},
|
||||
{ did: "did:example:7", name: "Grace", profileImageUrl: "" },
|
||||
];
|
||||
|
||||
projects: PlanData[] = [
|
||||
{
|
||||
handleId: "proj1",
|
||||
name: "Zebra Project",
|
||||
description: "A project about zebras",
|
||||
issuerDid: "did:example:1",
|
||||
},
|
||||
{
|
||||
handleId: "proj2",
|
||||
name: "Alpha Project",
|
||||
description: "The first project",
|
||||
issuerDid: "did:example:2",
|
||||
},
|
||||
{
|
||||
handleId: "proj3",
|
||||
name: "Beta Project",
|
||||
description: "The second project",
|
||||
issuerDid: "did:example:3",
|
||||
},
|
||||
{
|
||||
handleId: "proj4",
|
||||
name: "Gamma Project",
|
||||
description: "The third project",
|
||||
issuerDid: "did:example:4",
|
||||
},
|
||||
{
|
||||
handleId: "proj5",
|
||||
name: "Delta Project",
|
||||
description: "The fourth project",
|
||||
issuerDid: "did:example:5",
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Custom function for people: only show those with profile images
|
||||
*/
|
||||
customPeopleFunction = (
|
||||
entities: Contact[],
|
||||
_entityType: string,
|
||||
maxItems: number,
|
||||
): Contact[] => {
|
||||
return entities
|
||||
.filter((person) => person.profileImageUrl)
|
||||
.slice(0, maxItems);
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom function for projects: sort by name and limit to 3
|
||||
*/
|
||||
customProjectsFunction = (
|
||||
entities: PlanData[],
|
||||
_entityType: string,
|
||||
_maxItems: number,
|
||||
): PlanData[] => {
|
||||
return entities.sort((a, b) => a.name.localeCompare(b.name)).slice(0, 3);
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple conflict checker for testing
|
||||
*/
|
||||
conflictChecker = (did: string): boolean => {
|
||||
return did === this.activeDid;
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle between custom and default display functions
|
||||
*/
|
||||
toggleCustomFunction(): void {
|
||||
this.useCustomFunction = !this.useCustomFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle entity selection
|
||||
*/
|
||||
handleEntitySelected(event: {
|
||||
type: "person" | "project" | "special";
|
||||
entityType?: string;
|
||||
data: Contact | PlanData | { did?: string; name: string };
|
||||
}): void {
|
||||
this.selectedEntity = event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computed properties to show display counts
|
||||
*/
|
||||
get displayedPeopleCount(): number {
|
||||
if (this.useCustomFunction) {
|
||||
return this.customPeopleFunction(this.people, "people", 5).length;
|
||||
}
|
||||
return Math.min(5, this.people.length);
|
||||
}
|
||||
|
||||
get displayedProjectsCount(): number {
|
||||
if (this.useCustomFunction) {
|
||||
return this.customProjectsFunction(this.projects, "projects", 3).length;
|
||||
}
|
||||
return Math.min(7, this.projects.length);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
pre {
|
||||
background: #f5f5f5;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -8,10 +8,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Options, Vue } from "vue-facing-decorator";
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||
|
||||
@Options({
|
||||
@Component({
|
||||
mixins: [PlatformServiceMixin],
|
||||
})
|
||||
export default class PlatformServiceMixinTest extends Vue {
|
||||
|
||||
@@ -175,13 +175,15 @@ export const PlatformServiceMixin = {
|
||||
currentActiveDid: {
|
||||
handler(newDid: string | null, oldDid: string | null) {
|
||||
if (newDid !== oldDid) {
|
||||
logger.debug(`[PlatformServiceMixin] ActiveDid changed from ${oldDid} to ${newDid}`);
|
||||
logger.debug(
|
||||
`[PlatformServiceMixin] ActiveDid changed from ${oldDid} to ${newDid}`,
|
||||
);
|
||||
// Clear caches that might be affected by the change
|
||||
(this as any).$clearAllCaches();
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
@@ -196,9 +198,11 @@ export const PlatformServiceMixin = {
|
||||
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}`);
|
||||
logger.debug(
|
||||
`[PlatformServiceMixin] ActiveDid updated from ${oldDid} to ${newDid}`,
|
||||
);
|
||||
// Clear caches that might be affected by the change
|
||||
this.$clearAllCaches();
|
||||
}
|
||||
@@ -800,7 +804,7 @@ export const PlatformServiceMixin = {
|
||||
|
||||
params.push(did);
|
||||
const sql = `UPDATE settings SET ${setParts.join(", ")} WHERE accountDid = ?`;
|
||||
|
||||
|
||||
await this.$dbExec(sql, params);
|
||||
return true;
|
||||
} catch (error) {
|
||||
@@ -821,7 +825,7 @@ 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;
|
||||
|
||||
|
||||
if (!currentDid) {
|
||||
return await this.$saveSettings(changes);
|
||||
}
|
||||
|
||||
@@ -1011,9 +1011,6 @@ export default class AccountViewView extends Vue {
|
||||
* Initializes component state with values from the database or defaults.
|
||||
*/
|
||||
async initializeState(): Promise<void> {
|
||||
// First get the master settings to see the active DID
|
||||
const masterSettings = await this.$settings();
|
||||
|
||||
// Then get the account-specific settings
|
||||
const settings: AccountSettings = await this.$accountSettings();
|
||||
|
||||
@@ -1391,7 +1388,7 @@ export default class AccountViewView extends Vue {
|
||||
this.loadingLimits = true;
|
||||
try {
|
||||
const did = this.activeDid;
|
||||
|
||||
|
||||
if (!did) {
|
||||
this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_IDENTIFIER;
|
||||
return;
|
||||
@@ -1404,7 +1401,7 @@ export default class AccountViewView extends Vue {
|
||||
});
|
||||
|
||||
const imageResp = await fetchImageRateLimits(this.axios, did);
|
||||
|
||||
|
||||
if (imageResp.status === 200) {
|
||||
this.imageLimits = imageResp.data;
|
||||
} else {
|
||||
@@ -1422,7 +1419,7 @@ export default class AccountViewView extends Vue {
|
||||
this.axios,
|
||||
did,
|
||||
);
|
||||
|
||||
|
||||
if (endorserResp.status === 200) {
|
||||
this.endorserLimits = endorserResp.data;
|
||||
} else {
|
||||
|
||||
@@ -588,7 +588,7 @@ 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 });
|
||||
@@ -1837,7 +1837,7 @@ export default class HomeView extends Vue {
|
||||
|
||||
/**
|
||||
* Computed property for registration status
|
||||
*
|
||||
*
|
||||
* @public
|
||||
* Used in template for registration-dependent UI elements
|
||||
*/
|
||||
|
||||
@@ -221,7 +221,7 @@ export default class IdentitySwitcherView extends Vue {
|
||||
async switchAccount(did?: string) {
|
||||
// Save the new active DID to master settings
|
||||
await this.$saveSettings({ activeDid: did });
|
||||
|
||||
|
||||
// Check if we need to load user-specific settings for the new DID
|
||||
if (did) {
|
||||
try {
|
||||
@@ -230,7 +230,7 @@ export default class IdentitySwitcherView extends Vue {
|
||||
// Handle error silently - user settings will be loaded when needed
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Navigate to home page to trigger the watcher
|
||||
this.$router.push({ name: "home" });
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<span v-if="hasImage" class="flex justify-between">
|
||||
<a :href="imageUrl" target="_blank" class="text-blue-500 ml-4">
|
||||
<img
|
||||
:src="transformImageUrlForCors(imageUrl)"
|
||||
:src="imageUrl"
|
||||
class="h-24 rounded-xl"
|
||||
/>
|
||||
</a>
|
||||
@@ -261,8 +261,7 @@ import {
|
||||
} from "../libs/endorserServer";
|
||||
import {
|
||||
retrieveAccountCount,
|
||||
retrieveFullyDecryptedAccount,
|
||||
transformImageUrlForCors,
|
||||
retrieveFullyDecryptedAccount
|
||||
} from "../libs/util";
|
||||
|
||||
import {
|
||||
@@ -861,12 +860,6 @@ export default class NewEditProjectView extends Vue {
|
||||
this.longitude = event.latlng.lng;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms image URLs to avoid CORS issues in development
|
||||
* @param imageUrl - Original image URL
|
||||
* @returns Transformed URL for proxy or original URL
|
||||
*/
|
||||
transformImageUrlForCors = transformImageUrlForCors;
|
||||
|
||||
/**
|
||||
* Computed property for character count display
|
||||
|
||||
@@ -562,7 +562,7 @@
|
||||
<div v-if="give.fullClaim.image" class="flex justify-center">
|
||||
<a :href="give.fullClaim.image" target="_blank">
|
||||
<img
|
||||
:src="transformImageUrlForCors(give.fullClaim.image)"
|
||||
:src="give.fullClaim.image"
|
||||
class="h-24 mt-2 rounded-xl"
|
||||
/>
|
||||
</a>
|
||||
@@ -607,7 +607,6 @@ import { retrieveAccountDids } from "../libs/util";
|
||||
import HiddenDidDialog from "../components/HiddenDidDialog.vue";
|
||||
import { logger } from "../utils/logger";
|
||||
import { useClipboard } from "@vueuse/core";
|
||||
import { transformImageUrlForCors } from "../libs/util";
|
||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
||||
import { NOTIFY_CONFIRM_CLAIM } from "@/constants/notifications";
|
||||
@@ -1444,12 +1443,5 @@ export default class ProjectViewView extends Vue {
|
||||
this.givesTotalsByUnit.find((total) => total.unit === "HUR")?.amount || 0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms image URLs to avoid CORS issues in development
|
||||
* @param imageUrl - Original image URL
|
||||
* @returns Transformed URL for proxy or original URL
|
||||
*/
|
||||
transformImageUrlForCors = transformImageUrlForCors;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -157,6 +157,52 @@
|
||||
{{ simpleEncryptionTestResultDisplay }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
<h2 class="text-xl font-bold mb-4">Component Tests</h2>
|
||||
Interactive tests for Vue components and their functionality.
|
||||
|
||||
<div class="mt-4">
|
||||
<h3 class="text-lg font-semibold mb-2">EntityGrid Function Props</h3>
|
||||
<p class="text-sm text-gray-600 mb-3">
|
||||
Test the new function prop functionality in EntityGrid component.
|
||||
</p>
|
||||
<button
|
||||
:class="primaryButtonClasses"
|
||||
@click="showEntityGridTest = !showEntityGridTest"
|
||||
>
|
||||
{{ showEntityGridTest ? "Hide" : "Show" }} EntityGrid Function Prop
|
||||
Test
|
||||
</button>
|
||||
|
||||
<div
|
||||
v-if="showEntityGridTest"
|
||||
class="mt-4 p-4 border border-gray-300 rounded-md bg-gray-50"
|
||||
>
|
||||
<EntityGridFunctionPropTest />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<h3 class="text-lg font-semibold mb-2">Platform Service Mixin</h3>
|
||||
<p class="text-sm text-gray-600 mb-3">
|
||||
Test database operations through PlatformServiceMixin.
|
||||
</p>
|
||||
<button
|
||||
:class="primaryButtonClasses"
|
||||
@click="showPlatformServiceTest = !showPlatformServiceTest"
|
||||
>
|
||||
{{ showPlatformServiceTest ? "Hide" : "Show" }} Platform Service Test
|
||||
</button>
|
||||
|
||||
<div
|
||||
v-if="showPlatformServiceTest"
|
||||
class="mt-4 p-4 border border-gray-300 rounded-md bg-gray-50"
|
||||
>
|
||||
<PlatformServiceMixinTest />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -192,6 +238,8 @@ import {
|
||||
import { logger } from "../utils/logger";
|
||||
import { Account } from "../db/tables/accounts";
|
||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||
import EntityGridFunctionPropTest from "../test/EntityGridFunctionPropTest.vue";
|
||||
import PlatformServiceMixinTest from "../test/PlatformServiceMixinTest.vue";
|
||||
|
||||
const inputFileNameRef = ref<Blob>();
|
||||
|
||||
@@ -231,7 +279,11 @@ const TEST_PAYLOAD = {
|
||||
* @author Matthew Raymer
|
||||
*/
|
||||
@Component({
|
||||
components: { QuickNav },
|
||||
components: {
|
||||
QuickNav,
|
||||
EntityGridFunctionPropTest,
|
||||
PlatformServiceMixinTest,
|
||||
},
|
||||
mixins: [PlatformServiceMixin],
|
||||
})
|
||||
export default class Help extends Vue {
|
||||
@@ -258,6 +310,10 @@ export default class Help extends Vue {
|
||||
|
||||
cryptoLib = cryptoLib;
|
||||
|
||||
// for component tests
|
||||
showEntityGridTest = false;
|
||||
showPlatformServiceTest = false;
|
||||
|
||||
/**
|
||||
* Computed properties for template streamlining
|
||||
* Eliminates repeated classes and logic in template
|
||||
@@ -469,7 +525,10 @@ export default class Help extends Vue {
|
||||
* Method to trigger notification test
|
||||
* Centralizes notification testing logic
|
||||
*/
|
||||
triggerTestNotification(config: any) {
|
||||
triggerTestNotification(config: {
|
||||
notification: NotificationIface;
|
||||
timeout?: number;
|
||||
}) {
|
||||
this.$notify(config.notification, config.timeout);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user