Merge pull request 'feat: remove the 'lock' from the giving & receiving sides' (#229) from no-locks into master
Reviewed-on: #229
This commit was merged in pull request #229.
This commit is contained in:
@@ -21,7 +21,7 @@ export default defineConfig({
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: 4,
|
||||
workers: 3,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: [
|
||||
['list'],
|
||||
|
||||
@@ -165,15 +165,19 @@ export default class EntitySelectionStep extends Vue {
|
||||
*/
|
||||
get stepLabel(): string {
|
||||
if (this.stepType === "recipient") {
|
||||
return "Choose who received the gift";
|
||||
} else if (this.stepType === "giver") {
|
||||
if (this.shouldShowProjects) {
|
||||
return "Choose a project benefitted from";
|
||||
return "Choose recipient project";
|
||||
} else {
|
||||
return "Choose a person received from";
|
||||
return "Choose recipient person";
|
||||
}
|
||||
} else {
|
||||
// this.stepType === "giver"
|
||||
if (this.shouldShowProjects) {
|
||||
return "Choose giving project";
|
||||
} else {
|
||||
return "Choose giving person";
|
||||
}
|
||||
}
|
||||
return "Choose entity";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
/** * EntitySummaryButton.vue - Displays selected entity with edit capability *
|
||||
* Extracted from GiftedDialog.vue to handle entity summary display in the gift *
|
||||
details step with edit functionality. * * Features: * - Shows entity avatar
|
||||
(person or project) * - Displays entity name and role label * - Handles editable
|
||||
vs locked states * - Function props for parent control over edit behavior * -
|
||||
Supports both person and project entity types * - Template streamlined with
|
||||
computed CSS properties * * @author Matthew Raymer */
|
||||
/* EntitySummaryButton.vue - Displays selected entity with edit capability */
|
||||
<template>
|
||||
<component
|
||||
:is="editable ? 'button' : 'div'"
|
||||
:class="containerClasses"
|
||||
@click="handleClick"
|
||||
>
|
||||
<button :class="containerClasses" @click="handleClick">
|
||||
<!-- Entity Icon/Avatar -->
|
||||
<div>
|
||||
<template v-if="entityType === 'project'">
|
||||
@@ -47,14 +37,11 @@ computed CSS properties * * @author Matthew Raymer */
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<!-- Edit/Lock Icon -->
|
||||
<p class="ms-auto text-sm pe-1" :class="iconClasses">
|
||||
<font-awesome
|
||||
:icon="editable ? 'pen' : 'lock'"
|
||||
:title="editable ? 'Change' : 'Can\'t be changed'"
|
||||
/>
|
||||
<!-- Edit Icon -->
|
||||
<p class="ms-auto text-sm pe-1 text-blue-500">
|
||||
<font-awesome icon="pen" title="Change" />
|
||||
</p>
|
||||
</component>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -75,12 +62,12 @@ interface EntityData {
|
||||
}
|
||||
|
||||
/**
|
||||
* EntitySummaryButton - Displays selected entity with optional edit capability
|
||||
* EntitySummaryButton - Displays selected entity with edit capability
|
||||
*
|
||||
* Features:
|
||||
* - Shows entity avatar (person or project)
|
||||
* - Displays entity name and role label
|
||||
* - Handles editable vs locked states
|
||||
* - Always editable - click to change entity
|
||||
* - Function props for parent control over edit behavior
|
||||
* - Supports both person and project entity types
|
||||
* - Template streamlined with computed CSS properties
|
||||
@@ -104,13 +91,9 @@ export default class EntitySummaryButton extends Vue {
|
||||
@Prop({ required: true })
|
||||
label!: string;
|
||||
|
||||
/** Whether the entity can be edited */
|
||||
@Prop({ default: true })
|
||||
editable!: boolean;
|
||||
|
||||
/**
|
||||
* Function prop for handling edit requests
|
||||
* Called when the button is clicked and editable, allowing parent to control edit behavior
|
||||
* Called when the button is clicked, allowing parent to control edit behavior
|
||||
*/
|
||||
@Prop({ type: Function, default: () => {} })
|
||||
onEditRequested!: (data: {
|
||||
@@ -132,13 +115,6 @@ export default class EntitySummaryButton extends Vue {
|
||||
return this.entity !== null && "profileImageUrl" in this.entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computed CSS classes for the edit/lock icon
|
||||
*/
|
||||
get iconClasses(): string {
|
||||
return this.editable ? "text-blue-500" : "text-slate-400";
|
||||
}
|
||||
|
||||
/**
|
||||
* Computed CSS classes for the entity name
|
||||
*/
|
||||
@@ -172,16 +148,13 @@ export default class EntitySummaryButton extends Vue {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle click event - only call function prop if editable
|
||||
* Allows parent to control edit behavior and validation
|
||||
* Handle click event - call function prop to allow parent to control edit behavior
|
||||
*/
|
||||
handleClick(): void {
|
||||
if (this.editable) {
|
||||
this.onEditRequested({
|
||||
entityType: this.entityType,
|
||||
entity: this.entity,
|
||||
});
|
||||
}
|
||||
this.onEditRequested({
|
||||
entityType: this.entityType,
|
||||
entity: this.entity,
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -195,8 +168,4 @@ button {
|
||||
button:hover {
|
||||
background-color: #f1f5f9; /* hover:bg-slate-100 */
|
||||
}
|
||||
|
||||
div {
|
||||
cursor: default;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -16,7 +16,6 @@ control over updates and validation * * @author Matthew Raymer */
|
||||
:entity="giver"
|
||||
:entity-type="giverEntityType"
|
||||
:label="giverLabel"
|
||||
:editable="canEditGiver"
|
||||
:on-edit-requested="handleEditGiver"
|
||||
/>
|
||||
|
||||
@@ -25,7 +24,6 @@ control over updates and validation * * @author Matthew Raymer */
|
||||
:entity="receiver"
|
||||
:entity-type="recipientEntityType"
|
||||
:label="recipientLabel"
|
||||
:editable="canEditRecipient"
|
||||
:on-edit-requested="handleEditRecipient"
|
||||
/>
|
||||
</div>
|
||||
@@ -188,14 +186,6 @@ export default class GiftDetailsStep extends Vue {
|
||||
@Prop({ default: "" })
|
||||
toProjectId!: string;
|
||||
|
||||
/** Whether the giver is locked and cannot be edited */
|
||||
@Prop({ default: false })
|
||||
isGiverLocked!: boolean;
|
||||
|
||||
/** Whether the recipient is locked and cannot be edited */
|
||||
@Prop({ default: false })
|
||||
isRecipientLocked!: boolean;
|
||||
|
||||
/**
|
||||
* Function prop for handling description updates
|
||||
* Called when the description input changes, allowing parent to control validation
|
||||
@@ -281,21 +271,6 @@ export default class GiftDetailsStep extends Vue {
|
||||
: "Given to:";
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the giver can be edited
|
||||
*/
|
||||
get canEditGiver(): boolean {
|
||||
// If giver is locked via prop, it cannot be edited
|
||||
return !this.isGiverLocked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the recipient can be edited
|
||||
*/
|
||||
get canEditRecipient(): boolean {
|
||||
return !this.isRecipientLocked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computed CSS classes for submit button
|
||||
*/
|
||||
|
||||
@@ -48,8 +48,6 @@
|
||||
:offer-id="offerId"
|
||||
:from-project-id="fromProjectId"
|
||||
:to-project-id="toProjectId"
|
||||
:is-giver-locked="isGiverLocked"
|
||||
:is-recipient-locked="isRecipientLocked"
|
||||
:on-update-description="(desc: string) => (description = desc)"
|
||||
:on-update-amount="handleAmountUpdate"
|
||||
:on-update-unit-code="(code: string) => (unitCode = code)"
|
||||
@@ -140,23 +138,11 @@ export default class GiftedDialog extends Vue {
|
||||
stepType = "giver";
|
||||
unitCode = "HUR";
|
||||
visible = false;
|
||||
isGiverLocked = false;
|
||||
isRecipientLocked = false;
|
||||
|
||||
libsUtil = libsUtil;
|
||||
|
||||
didInfo = didInfo;
|
||||
|
||||
// Computed property to help debug template logic
|
||||
get shouldShowProjects() {
|
||||
const result =
|
||||
(this.stepType === "giver" &&
|
||||
this.currentGiverEntityType === "project") ||
|
||||
(this.stepType === "recipient" &&
|
||||
this.currentRecipientEntityType === "project");
|
||||
return result;
|
||||
}
|
||||
|
||||
// Computed property to check if current selection would create a conflict
|
||||
get hasPersonConflict() {
|
||||
// Only check for conflicts when both entities are persons
|
||||
@@ -265,40 +251,13 @@ export default class GiftedDialog extends Vue {
|
||||
const activeIdentity = await (this as any).$getActiveIdentity();
|
||||
this.activeDid = activeIdentity.activeDid || "";
|
||||
|
||||
// Determine if entities should be locked
|
||||
// An entity is locked if it's provided as an input property (has did or handleId)
|
||||
// For persons: locked if did is provided and not "You" (activeDid)
|
||||
// For projects: locked if handleId is provided
|
||||
// When entities come from ContactsView, ContactGiftingView, or ProjectViewView context,
|
||||
// they should be locked if they have a valid identifier (did or handleId)
|
||||
const isGiverProvided =
|
||||
giver &&
|
||||
((giver.did && giver.did !== this.activeDid) ||
|
||||
(giver.handleId && giver.handleId !== ""));
|
||||
const isReceiverProvided =
|
||||
receiver &&
|
||||
((receiver.did && receiver.did !== this.activeDid) ||
|
||||
(receiver.handleId && receiver.handleId !== ""));
|
||||
|
||||
// Lock entities that are provided (from context or explicitly set)
|
||||
// This ensures that when entities are chosen from ContactsView, ContactGiftingView,
|
||||
// or ProjectViewView, the other entity (giver or recipient) that was already set
|
||||
// from context is locked
|
||||
this.isGiverLocked = !!isGiverProvided;
|
||||
this.isRecipientLocked = !!isReceiverProvided;
|
||||
|
||||
// Determine if receiver should be locked (for step navigation logic)
|
||||
// Receiver is locked only if it's provided AND it's not "You" (activeDid)
|
||||
// "You" is treated as a default that can be changed
|
||||
const isReceiverLocked =
|
||||
receiver && receiver.did && receiver.did !== this.activeDid;
|
||||
|
||||
// Only skip Step 1 if both giver and receiver are provided AND receiver is locked
|
||||
// If receiver is "You" (default), still show Step 1 so user can change it
|
||||
this.firstStep = !(giver && isReceiverLocked);
|
||||
// If giver is provided but receiver is not locked, start with recipient selection
|
||||
// Otherwise, start with giver selection
|
||||
this.stepType = giver && !isReceiverLocked ? "recipient" : "giver";
|
||||
// Skip Step 1 if both giver and receiver are provided
|
||||
const hasGiver = giver && (!!giver.did || !!giver.handleId);
|
||||
const hasReceiver = receiver && (!!receiver.did || !!receiver.handleId);
|
||||
this.firstStep = !hasGiver || !hasReceiver;
|
||||
if (this.firstStep) {
|
||||
this.stepType = giver ? "receiver" : "giver";
|
||||
}
|
||||
|
||||
logger.debug("[GiftedDialog] Settings received:", {
|
||||
activeDid: this.activeDid,
|
||||
@@ -357,9 +316,6 @@ export default class GiftedDialog extends Vue {
|
||||
this.firstStep = true;
|
||||
// Reset to initial prop values
|
||||
this.currentGiverEntityType = this.initialGiverEntityType;
|
||||
// Reset lock states
|
||||
this.isGiverLocked = false;
|
||||
this.isRecipientLocked = false;
|
||||
}
|
||||
|
||||
async confirm() {
|
||||
@@ -692,13 +648,7 @@ export default class GiftedDialog extends Vue {
|
||||
entityType: string;
|
||||
currentEntity: { did: string; name: string };
|
||||
}) {
|
||||
// Prevent editing if the entity is locked
|
||||
if (data.entityType === "giver" && this.isGiverLocked) {
|
||||
return;
|
||||
}
|
||||
if (data.entityType === "recipient" && this.isRecipientLocked) {
|
||||
return;
|
||||
}
|
||||
// Always allow editing - go back to Step 1 to select a new entity
|
||||
this.goBackToStep1(data.entityType);
|
||||
}
|
||||
|
||||
|
||||
@@ -1088,7 +1088,7 @@ export default class ContactsView extends Vue {
|
||||
{
|
||||
group: "modal",
|
||||
type: "confirm",
|
||||
title: "Delete",
|
||||
title: "Confirm First?",
|
||||
text: message,
|
||||
onNo: async () => {
|
||||
this.showGiftedDialog(giverDid, recipientDid);
|
||||
|
||||
@@ -331,7 +331,7 @@ export default class OfferDetailsView extends Vue {
|
||||
get recipientAssignmentLabel() {
|
||||
return this.recipientDid
|
||||
? `This is offered to ${this.recipientName}`
|
||||
: "No recipient was chosen.";
|
||||
: "No named individual recipient was chosen.";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -282,9 +282,9 @@ test('Check User 0 can register a random person', async ({ page }) => {
|
||||
} catch (error) {
|
||||
console.log('Could not force close dialog, continuing...');
|
||||
}
|
||||
// Wait for Person button to be ready - simplified approach
|
||||
await page.waitForSelector('button:has-text("Person")', { timeout: 10000 });
|
||||
await page.getByRole('button', { name: 'Person' }).click();
|
||||
// Wait for Thank button to be ready - simplified approach
|
||||
await page.waitForSelector('button:has-text("Thank")', { timeout: 10000 });
|
||||
await page.getByRole('button', { name: 'Thank' }).click();
|
||||
await page.getByRole('listitem').filter({ hasText: UNNAMED_ENTITY_NAME }).locator('svg').click();
|
||||
await page.getByPlaceholder('What was given').fill('Gave me access!');
|
||||
await page.getByRole('button', { name: 'Sign & Send' }).click();
|
||||
|
||||
@@ -107,7 +107,7 @@ test('Record something given', async ({ page }) => {
|
||||
return !document.querySelector('.dialog-overlay');
|
||||
}, { timeout: 5000 });
|
||||
|
||||
await page.getByRole('button', { name: 'Person' }).click();
|
||||
await page.getByRole('button', { name: 'Thank' }).click();
|
||||
await page.getByRole('listitem').filter({ hasText: UNNAMED_ENTITY_NAME }).locator('svg').click();
|
||||
await page.getByPlaceholder('What was given').fill(finalTitle);
|
||||
await page.getByRole('spinbutton').fill(randomNonZeroNumber.toString());
|
||||
|
||||
@@ -116,7 +116,7 @@ test('Record 9 new gifts', async ({ page }) => {
|
||||
if (i === 0) {
|
||||
await page.getByTestId('closeOnboardingAndFinish').click();
|
||||
}
|
||||
await page.getByRole('button', { name: 'Person' }).click();
|
||||
await page.getByRole('button', { name: 'Thank' }).click();
|
||||
await page.getByRole('listitem').filter({ hasText: UNNAMED_ENTITY_NAME }).locator('svg').click();
|
||||
await page.getByPlaceholder('What was given').fill(finalTitles[i]);
|
||||
await page.getByRole('spinbutton').fill(finalNumbers[i].toString());
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { test, expect, Page } from '@playwright/test';
|
||||
import { importUser } from './testUtils';
|
||||
|
||||
async function testProjectGive(page: Page, selector: string) {
|
||||
async function testProjectGive(page: Page, isToProject: boolean) {
|
||||
const selector = isToProject ? 'gives-to' : 'gives-from';
|
||||
|
||||
// Generate a random string of a few characters
|
||||
const randomString = Math.random().toString(36).substring(2, 6);
|
||||
@@ -42,9 +43,9 @@ async function testProjectGive(page: Page, selector: string) {
|
||||
}
|
||||
|
||||
test('Record a give to a project', async ({ page }) => {
|
||||
await testProjectGive(page, 'gives-to');
|
||||
await testProjectGive(page, true);
|
||||
});
|
||||
|
||||
test('Record a give from a project', async ({ page }) => {
|
||||
await testProjectGive(page, 'gives-from');
|
||||
await testProjectGive(page, false);
|
||||
});
|
||||
|
||||
@@ -117,7 +117,7 @@ test('Add contact, record gift, confirm gift', async ({ page }) => {
|
||||
// Confirm that home shows contact in "Record Something…"
|
||||
await page.goto('./');
|
||||
await page.getByTestId('closeOnboardingAndFinish').click();
|
||||
await page.getByRole('button', { name: 'Person' }).click();
|
||||
await page.getByRole('button', { name: 'Thank' }).click();
|
||||
await expect(page.locator('#sectionGiftedGiver').getByRole('listitem').filter({ hasText: contactName })).toBeVisible();
|
||||
|
||||
// Record something given by new contact
|
||||
|
||||
Reference in New Issue
Block a user