Compare commits

...

7 Commits

Author SHA1 Message Date
Jose Olarte III
949487629d Merge branch 'master' into playwright-test-updates 2025-08-14 21:21:10 +08:00
Matthew Raymer
79593f12b4 fix(types): resolve notification system type safety issues
- Replace $notify any types with proper NotifyFunction interface
- Import NotifyFunction type from utils/notify
- Eliminate 5 TypeScript any type warnings
- Improve type safety for notification system across components

Reduces lint warnings from 25 to 20 by addressing high-impact,
low-effort notification type issues. Maintains full functionality
while improving code quality and IntelliSense support.
2025-08-14 11:05:34 +00:00
1ac60dc5ba Merge pull request 'Fix: notify getting called before it's initialized' (#160) from claim-view-error-handling into master
Reviewed-on: #160
2025-08-14 05:18:27 -04:00
31396dd555 Merge pull request 'Switch to CSS-based text-truncate' (#156) from notification-line-wrapping into master
Reviewed-on: #156
2025-08-14 03:39:28 -04:00
Jose Olarte III
1dc534b61f Fix: update element locators
- Offer dialog was recently updated to use the component version of inputAmount
2025-08-12 15:09:30 +08:00
Jose Olarte III
c969c536bf Fix: notify getting called before it's initialized
- Initialize notify earlier inside created()
2025-08-11 17:50:25 +08:00
Jose Olarte III
5cf1759653 Fix: switch to CSS-based text-truncate
- Eliminate dependence on arbitrary maxlength for truncation
- Ensure truncation is purely visual, and does not touch content
2025-07-29 19:34:40 +08:00
10 changed files with 67 additions and 40 deletions

View File

@@ -27,9 +27,13 @@
v-if="notification.type === 'toast'"
class="w-full max-w-sm mx-auto mb-3 overflow-hidden bg-slate-900/90 text-white rounded-lg shadow-md"
>
<div class="w-full px-4 py-3">
<span class="font-semibold">{{ notification.title }}</span>
<p class="text-sm">{{ notification.text }}</p>
<div class="w-full px-4 py-3 overflow-hidden">
<h4 class="font-semibold text-ellipsis overflow-hidden">
{{ notification.title }}
</h4>
<p class="text-sm text-ellipsis overflow-hidden">
{{ notification.text }}
</p>
</div>
</div>
@@ -46,9 +50,15 @@
></font-awesome>
</div>
<div class="relative w-full pl-4 pr-8 py-2 text-slate-900">
<span class="font-semibold">{{ notification.title }}</span>
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
<div
class="relative w-full pl-4 pr-8 py-2 text-slate-900 overflow-hidden"
>
<h4 class="font-semibold text-ellipsis overflow-hidden">
{{ notification.title }}
</h4>
<p class="text-sm text-ellipsis overflow-hidden">
{{ notification.text }}
</p>
<button
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-slate-200 text-slate-600"
@@ -72,9 +82,15 @@
></font-awesome>
</div>
<div class="relative w-full pl-4 pr-8 py-2 text-emerald-900">
<span class="font-semibold">{{ notification.title }}</span>
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
<div
class="relative w-full pl-4 pr-8 py-2 text-emerald-900 overflow-hidden"
>
<h4 class="font-semibold text-ellipsis overflow-hidden">
{{ notification.title }}
</h4>
<p class="text-sm text-ellipsis overflow-hidden">
{{ notification.text }}
</p>
<button
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-emerald-200 text-emerald-600"
@@ -98,9 +114,15 @@
></font-awesome>
</div>
<div class="relative w-full pl-4 pr-8 py-2 text-amber-900">
<span class="font-semibold">{{ notification.title }}</span>
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
<div
class="relative w-full pl-4 pr-8 py-2 text-amber-900 overflow-hidden"
>
<h4 class="font-semibold text-ellipsis overflow-hidden">
{{ notification.title }}
</h4>
<p class="text-sm text-ellipsis overflow-hidden">
{{ notification.text }}
</p>
<button
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-amber-200 text-amber-600"
@@ -124,9 +146,15 @@
></font-awesome>
</div>
<div class="relative w-full pl-4 pr-8 py-2 text-rose-900">
<span class="font-semibold">{{ notification.title }}</span>
<p class="text-sm">{{ truncateLongWords(notification.text) }}</p>
<div
class="relative w-full pl-4 pr-8 py-2 text-rose-900 overflow-hidden"
>
<h4 class="font-semibold text-ellipsis overflow-hidden">
{{ notification.title }}
</h4>
<p class="text-sm text-ellipsis overflow-hidden">
{{ notification.text }}
</p>
<button
class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-rose-200 text-rose-600"
@@ -349,13 +377,6 @@ export default class App extends Vue {
stopAsking = false;
truncateLongWords(sentence: string) {
return sentence
.split(" ")
.map((word) => (word.length > 30 ? word.slice(0, 30) + "..." : word))
.join(" ");
}
async turnOffNotifications(
notification: NotificationIface,
): Promise<boolean> {

View File

@@ -252,7 +252,7 @@ import { GiveRecordWithContactInfo } from "@/interfaces/give";
import EntityIcon from "./EntityIcon.vue";
import { isHiddenDid } from "../libs/endorserServer";
import ProjectIcon from "./ProjectIcon.vue";
import { createNotifyHelpers } from "@/utils/notify";
import { createNotifyHelpers, NotifyFunction } from "@/utils/notify";
import {
NOTIFY_PERSON_HIDDEN,
NOTIFY_UNKNOWN_PERSON,
@@ -273,7 +273,7 @@ export default class ActivityListItem extends Vue {
isHiddenDid = isHiddenDid;
notify!: ReturnType<typeof createNotifyHelpers>;
$notify!: (notification: any, timeout?: number) => void;
$notify!: NotifyFunction;
created() {
this.notify = createNotifyHelpers(this.$notify);

View File

@@ -80,7 +80,7 @@ import EntitySelectionStep from "../components/EntitySelectionStep.vue";
import GiftDetailsStep from "../components/GiftDetailsStep.vue";
import { PlanData } from "../interfaces/records";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
import { createNotifyHelpers, TIMEOUTS, NotifyFunction } from "@/utils/notify";
import {
NOTIFY_GIFT_ERROR_NEGATIVE_AMOUNT,
NOTIFY_GIFT_ERROR_NO_DESCRIPTION,
@@ -98,7 +98,7 @@ import {
mixins: [PlatformServiceMixin],
})
export default class GiftedDialog extends Vue {
$notify!: (notification: any, timeout?: number) => void;
$notify!: NotifyFunction;
notify!: ReturnType<typeof createNotifyHelpers>;
/**

View File

@@ -282,7 +282,7 @@ import {
NOTIFY_IMAGE_DIALOG_UNSUPPORTED_FORMAT,
createImageDialogCameraErrorMessage,
} from "../constants/notifications";
import { createNotifyHelpers, TIMEOUTS } from "../utils/notify";
import { createNotifyHelpers, TIMEOUTS, NotifyFunction } from "../utils/notify";
const inputImageFileNameRef = ref<Blob>();
@@ -291,7 +291,7 @@ const inputImageFileNameRef = ref<Blob>();
mixins: [PlatformServiceMixin],
})
export default class ImageMethodDialog extends Vue {
$notify!: (notification: any, timeout?: number) => void;
$notify!: NotifyFunction;
$router!: Router;
notify = createNotifyHelpers(this.$notify);

View File

@@ -15,10 +15,15 @@ const TEST_USER_0_MNEMONIC =
"rigid shrug mobile smart veteran half all pond toilet brave review universe ship congress found yard skate elite apology jar uniform subway slender luggage";
export async function testBecomeUser0() {
const [addr, privateHex, publicHex, deriPath] = deriveAddress(TEST_USER_0_MNEMONIC);
const [addr, privateHex, publicHex, deriPath] =
deriveAddress(TEST_USER_0_MNEMONIC);
const identity0 = newIdentifier(addr, publicHex, privateHex, deriPath);
await saveNewIdentity(identity0, TEST_USER_0_MNEMONIC, DEFAULT_ROOT_DERIVATION_PATH);
await saveNewIdentity(
identity0,
TEST_USER_0_MNEMONIC,
DEFAULT_ROOT_DERIVATION_PATH,
);
const platformService = await PlatformServiceFactory.getInstance();
await platformService.updateDidSpecificSettings(identity0.did, {
isRegistered: true,
@@ -35,7 +40,8 @@ export async function testBecomeUser0() {
* @throws Error if registration fails or database access fails
*/
export async function testServerRegisterUser() {
const [addr, privateHex, publicHex, deriPath] = deriveAddress(TEST_USER_0_MNEMONIC);
const [addr, privateHex, publicHex, deriPath] =
deriveAddress(TEST_USER_0_MNEMONIC);
const identity0 = newIdentifier(addr, publicHex, privateHex, deriPath);

View File

@@ -724,6 +724,8 @@ export default class ClaimView extends Vue {
}
async created() {
this.notify = createNotifyHelpers(this.$notify);
const settings = await this.$accountSettings();
this.activeDid = settings.activeDid || "";
@@ -754,8 +756,6 @@ export default class ClaimView extends Vue {
this.windowDeepLink = `${APP_SERVER}/deep-link/claim/${claimId}`;
this.canShare = !!navigator.share;
this.notify = createNotifyHelpers(this.$notify);
}
// insert a space before any capital letters except the initial letter

View File

@@ -143,7 +143,7 @@ import {
QR_TIMEOUT_STANDARD,
QR_TIMEOUT_LONG,
} from "@/constants/notifications";
import { createNotifyHelpers } from "../utils/notify";
import { createNotifyHelpers, NotifyFunction } from "../utils/notify";
interface QRScanResult {
rawValue?: string;
@@ -191,7 +191,7 @@ interface IUserNameDialog {
* @since 2024
*/
export default class ContactQRScanFull extends Vue {
$notify!: (notification: any, timeout?: number) => void;
$notify!: NotifyFunction;
$router!: Router;
// Notification helper system

View File

@@ -87,7 +87,7 @@ import {
import { logger } from "../utils/logger";
import { Account, AccountEncrypted } from "../db/tables/accounts";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
import { createNotifyHelpers, TIMEOUTS, NotifyFunction } from "@/utils/notify";
import {
NOTIFY_ACCOUNT_DERIVATION_SUCCESS,
NOTIFY_ACCOUNT_DERIVATION_ERROR,
@@ -100,7 +100,7 @@ import {
export default class ImportAccountView extends Vue {
$route!: RouteLocationNormalizedLoaded;
$router!: Router;
$notify!: (notification: any, timeout?: number) => void;
$notify!: NotifyFunction;
notify!: ReturnType<typeof createNotifyHelpers>;

View File

@@ -23,7 +23,7 @@ test('Record an offer', async ({ page }) => {
await page.locator('button', { hasText: 'Edit' }).isVisible(); // since the 'edit' takes longer to show, wait for that (lest the click miss)
await page.getByTestId('offerButton').click();
await page.getByTestId('inputDescription').fill(description);
await page.getByTestId('inputOfferAmount').fill(randomNonZeroNumber.toString());
await page.getByTestId('inputOfferAmount').locator('input').fill(randomNonZeroNumber.toString());
expect(page.getByRole('button', { name: 'Sign & Send' }));
await page.getByRole('button', { name: 'Sign & Send' }).click();
await expect(page.getByText('That offer was recorded.')).toBeVisible();

View File

@@ -36,7 +36,7 @@ test('New offers for another user', async ({ page }) => {
const randomString1 = Math.random().toString(36).substring(2, 5);
await page.getByTestId('offerButton').click();
await page.getByTestId('inputDescription').fill(`help of ${randomString1} from #000`);
await page.getByTestId('inputOfferAmount').fill('1');
await page.getByTestId('inputOfferAmount').locator('input').fill('1');
await page.getByRole('button', { name: 'Sign & Send' }).click();
await expect(page.getByText('That offer was recorded.')).toBeVisible();
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert
@@ -46,7 +46,7 @@ test('New offers for another user', async ({ page }) => {
const randomString2 = Math.random().toString(36).substring(2, 5);
await page.getByTestId('offerButton').click();
await page.getByTestId('inputDescription').fill(`help of ${randomString2} from #000`);
await page.getByTestId('inputOfferAmount').fill('3');
await page.getByTestId('inputOfferAmount').locator('input').fill('3');
await page.getByRole('button', { name: 'Sign & Send' }).click();
await expect(page.getByText('That offer was recorded.')).toBeVisible();
await page.locator('div[role="alert"] button > svg.fa-xmark').click(); // dismiss info alert