mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 14:23:32 +00:00
[PM-27252] Upgrade Dialog Should not Show in Self Host (#17051)
* fix(billing): update and refactor observable logic * tests(billing): add additional expects for dialog * fix(billing): update for claude feedback * tests(billing): update test conditions and comments
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
import { mock, mockReset } from "jest-mock-extended";
|
import { mock, mockReset } from "jest-mock-extended";
|
||||||
import * as rxjs from "rxjs";
|
import { of, BehaviorSubject } from "rxjs";
|
||||||
import { of } from "rxjs";
|
|
||||||
|
|
||||||
import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service";
|
import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service";
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
@@ -8,6 +7,7 @@ import { AccountService, Account } from "@bitwarden/common/auth/abstractions/acc
|
|||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { SyncService } from "@bitwarden/common/platform/sync/sync.service";
|
import { SyncService } from "@bitwarden/common/platform/sync/sync.service";
|
||||||
import { DialogRef, DialogService } from "@bitwarden/components";
|
import { DialogRef, DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
@@ -28,6 +28,7 @@ describe("UnifiedUpgradePromptService", () => {
|
|||||||
const mockDialogService = mock<DialogService>();
|
const mockDialogService = mock<DialogService>();
|
||||||
const mockOrganizationService = mock<OrganizationService>();
|
const mockOrganizationService = mock<OrganizationService>();
|
||||||
const mockDialogOpen = jest.spyOn(UnifiedUpgradeDialogComponent, "open");
|
const mockDialogOpen = jest.spyOn(UnifiedUpgradeDialogComponent, "open");
|
||||||
|
const mockPlatformUtilsService = mock<PlatformUtilsService>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a mock DialogRef that implements the required properties for testing
|
* Creates a mock DialogRef that implements the required properties for testing
|
||||||
@@ -57,33 +58,33 @@ describe("UnifiedUpgradePromptService", () => {
|
|||||||
mockSyncService,
|
mockSyncService,
|
||||||
mockDialogService,
|
mockDialogService,
|
||||||
mockOrganizationService,
|
mockOrganizationService,
|
||||||
|
mockPlatformUtilsService,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mockAccount: Account = {
|
const mockAccount: Account = {
|
||||||
id: "test-user-id",
|
id: "test-user-id",
|
||||||
} as Account;
|
} as Account;
|
||||||
const accountSubject = new rxjs.BehaviorSubject(mockAccount);
|
const accountSubject = new BehaviorSubject<Account | null>(mockAccount);
|
||||||
|
|
||||||
describe("initialization", () => {
|
describe("initialization", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
mockAccountService.activeAccount$ = accountSubject.asObservable();
|
||||||
|
mockPlatformUtilsService.isSelfHost.mockReturnValue(false);
|
||||||
|
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
|
||||||
|
|
||||||
setupTestService();
|
setupTestService();
|
||||||
});
|
});
|
||||||
it("should be created", () => {
|
it("should be created", () => {
|
||||||
expect(sut).toBeTruthy();
|
expect(sut).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should subscribe to account and feature flag observables on construction", () => {
|
|
||||||
expect(mockConfigService.getFeatureFlag$).toHaveBeenCalledWith(
|
|
||||||
FeatureFlag.PM24996_ImplementUpgradeFromFreeDialog,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("displayUpgradePromptConditionally", () => {
|
describe("displayUpgradePromptConditionally", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(() => {
|
||||||
mockAccountService.activeAccount$ = accountSubject.asObservable();
|
mockAccountService.activeAccount$ = accountSubject.asObservable();
|
||||||
mockDialogOpen.mockReset();
|
mockDialogOpen.mockReset();
|
||||||
|
mockReset(mockDialogService);
|
||||||
mockReset(mockConfigService);
|
mockReset(mockConfigService);
|
||||||
mockReset(mockBillingService);
|
mockReset(mockBillingService);
|
||||||
mockReset(mockVaultProfileService);
|
mockReset(mockVaultProfileService);
|
||||||
@@ -93,20 +94,48 @@ describe("UnifiedUpgradePromptService", () => {
|
|||||||
// Mock sync service methods
|
// Mock sync service methods
|
||||||
mockSyncService.fullSync.mockResolvedValue(true);
|
mockSyncService.fullSync.mockResolvedValue(true);
|
||||||
mockSyncService.lastSync$.mockReturnValue(of(new Date()));
|
mockSyncService.lastSync$.mockReturnValue(of(new Date()));
|
||||||
|
mockReset(mockPlatformUtilsService);
|
||||||
|
});
|
||||||
|
it("should subscribe to account and feature flag observables when checking display conditions", async () => {
|
||||||
|
// Arrange
|
||||||
|
mockPlatformUtilsService.isSelfHost.mockReturnValue(false);
|
||||||
|
mockOrganizationService.memberOrganizations$.mockReturnValue(of([]));
|
||||||
|
mockConfigService.getFeatureFlag$.mockReturnValue(of(false));
|
||||||
|
mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false));
|
||||||
|
|
||||||
|
setupTestService();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await sut.displayUpgradePromptConditionally();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(mockConfigService.getFeatureFlag$).toHaveBeenCalledWith(
|
||||||
|
FeatureFlag.PM24996_ImplementUpgradeFromFreeDialog,
|
||||||
|
);
|
||||||
|
expect(mockAccountService.activeAccount$).toBeDefined();
|
||||||
});
|
});
|
||||||
it("should not show dialog when feature flag is disabled", async () => {
|
it("should not show dialog when feature flag is disabled", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
|
mockPlatformUtilsService.isSelfHost.mockReturnValue(false);
|
||||||
|
mockOrganizationService.memberOrganizations$.mockReturnValue(of([]));
|
||||||
mockConfigService.getFeatureFlag$.mockReturnValue(of(false));
|
mockConfigService.getFeatureFlag$.mockReturnValue(of(false));
|
||||||
|
mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false));
|
||||||
|
const recentDate = new Date();
|
||||||
|
recentDate.setMinutes(recentDate.getMinutes() - 3); // 3 minutes old
|
||||||
|
mockVaultProfileService.getProfileCreationDate.mockResolvedValue(recentDate);
|
||||||
|
|
||||||
setupTestService();
|
setupTestService();
|
||||||
// Act
|
// Act
|
||||||
const result = await sut.displayUpgradePromptConditionally();
|
const result = await sut.displayUpgradePromptConditionally();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
|
expect(mockDialogOpen).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not show dialog when user has premium", async () => {
|
it("should not show dialog when user has premium", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
|
mockPlatformUtilsService.isSelfHost.mockReturnValue(false);
|
||||||
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
|
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
|
||||||
mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(true));
|
mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(true));
|
||||||
mockOrganizationService.memberOrganizations$.mockReturnValue(of([]));
|
mockOrganizationService.memberOrganizations$.mockReturnValue(of([]));
|
||||||
@@ -117,6 +146,7 @@ describe("UnifiedUpgradePromptService", () => {
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
|
expect(mockDialogOpen).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not show dialog when user has any organization membership", async () => {
|
it("should not show dialog when user has any organization membership", async () => {
|
||||||
@@ -124,6 +154,7 @@ describe("UnifiedUpgradePromptService", () => {
|
|||||||
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
|
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
|
||||||
mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false));
|
mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false));
|
||||||
mockOrganizationService.memberOrganizations$.mockReturnValue(of([{ id: "org1" } as any]));
|
mockOrganizationService.memberOrganizations$.mockReturnValue(of([{ id: "org1" } as any]));
|
||||||
|
mockPlatformUtilsService.isSelfHost.mockReturnValue(false);
|
||||||
setupTestService();
|
setupTestService();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
@@ -131,6 +162,7 @@ describe("UnifiedUpgradePromptService", () => {
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
|
expect(mockDialogOpen).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not show dialog when profile is older than 5 minutes", async () => {
|
it("should not show dialog when profile is older than 5 minutes", async () => {
|
||||||
@@ -141,6 +173,7 @@ describe("UnifiedUpgradePromptService", () => {
|
|||||||
const oldDate = new Date();
|
const oldDate = new Date();
|
||||||
oldDate.setMinutes(oldDate.getMinutes() - 10); // 10 minutes old
|
oldDate.setMinutes(oldDate.getMinutes() - 10); // 10 minutes old
|
||||||
mockVaultProfileService.getProfileCreationDate.mockResolvedValue(oldDate);
|
mockVaultProfileService.getProfileCreationDate.mockResolvedValue(oldDate);
|
||||||
|
mockPlatformUtilsService.isSelfHost.mockReturnValue(false);
|
||||||
setupTestService();
|
setupTestService();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
@@ -148,6 +181,7 @@ describe("UnifiedUpgradePromptService", () => {
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
|
expect(mockDialogOpen).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show dialog when all conditions are met", async () => {
|
it("should show dialog when all conditions are met", async () => {
|
||||||
@@ -158,6 +192,7 @@ describe("UnifiedUpgradePromptService", () => {
|
|||||||
const recentDate = new Date();
|
const recentDate = new Date();
|
||||||
recentDate.setMinutes(recentDate.getMinutes() - 3); // 3 minutes old
|
recentDate.setMinutes(recentDate.getMinutes() - 3); // 3 minutes old
|
||||||
mockVaultProfileService.getProfileCreationDate.mockResolvedValue(recentDate);
|
mockVaultProfileService.getProfileCreationDate.mockResolvedValue(recentDate);
|
||||||
|
mockPlatformUtilsService.isSelfHost.mockReturnValue(false);
|
||||||
|
|
||||||
const expectedResult = { status: UnifiedUpgradeDialogStatus.Closed };
|
const expectedResult = { status: UnifiedUpgradeDialogStatus.Closed };
|
||||||
mockDialogOpenMethod(createMockDialogRef(expectedResult));
|
mockDialogOpenMethod(createMockDialogRef(expectedResult));
|
||||||
@@ -182,6 +217,7 @@ describe("UnifiedUpgradePromptService", () => {
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
|
expect(mockDialogOpen).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not show dialog when profile creation date is unavailable", async () => {
|
it("should not show dialog when profile creation date is unavailable", async () => {
|
||||||
@@ -190,6 +226,8 @@ describe("UnifiedUpgradePromptService", () => {
|
|||||||
mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false));
|
mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false));
|
||||||
mockOrganizationService.memberOrganizations$.mockReturnValue(of([]));
|
mockOrganizationService.memberOrganizations$.mockReturnValue(of([]));
|
||||||
mockVaultProfileService.getProfileCreationDate.mockResolvedValue(null);
|
mockVaultProfileService.getProfileCreationDate.mockResolvedValue(null);
|
||||||
|
mockPlatformUtilsService.isSelfHost.mockReturnValue(false);
|
||||||
|
|
||||||
setupTestService();
|
setupTestService();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
@@ -197,6 +235,26 @@ describe("UnifiedUpgradePromptService", () => {
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
|
expect(mockDialogOpen).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not show dialog when running in self-hosted environment", async () => {
|
||||||
|
// Arrange
|
||||||
|
mockConfigService.getFeatureFlag$.mockReturnValue(of(true));
|
||||||
|
mockOrganizationService.memberOrganizations$.mockReturnValue(of([]));
|
||||||
|
mockBillingService.hasPremiumFromAnySource$.mockReturnValue(of(false));
|
||||||
|
const recentDate = new Date();
|
||||||
|
recentDate.setMinutes(recentDate.getMinutes() - 3); // 3 minutes old
|
||||||
|
mockVaultProfileService.getProfileCreationDate.mockResolvedValue(recentDate);
|
||||||
|
mockPlatformUtilsService.isSelfHost.mockReturnValue(true);
|
||||||
|
setupTestService();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const result = await sut.displayUpgradePromptConditionally();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(result).toBeNull();
|
||||||
|
expect(mockDialogOpen).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
import { combineLatest, firstValueFrom, timeout } from "rxjs";
|
import { combineLatest, firstValueFrom, timeout, from, Observable, of } from "rxjs";
|
||||||
import { filter, switchMap, take } from "rxjs/operators";
|
import { filter, switchMap, take, map } from "rxjs/operators";
|
||||||
|
|
||||||
import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service";
|
import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service";
|
||||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||||
@@ -8,7 +8,9 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
|||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { SyncService } from "@bitwarden/common/platform/sync/sync.service";
|
import { SyncService } from "@bitwarden/common/platform/sync/sync.service";
|
||||||
|
import { UserId } from "@bitwarden/common/types/guid";
|
||||||
import { DialogRef, DialogService } from "@bitwarden/components";
|
import { DialogRef, DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -29,63 +31,37 @@ export class UnifiedUpgradePromptService {
|
|||||||
private syncService: SyncService,
|
private syncService: SyncService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
private organizationService: OrganizationService,
|
private organizationService: OrganizationService,
|
||||||
|
private platformUtilsService: PlatformUtilsService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private shouldShowPrompt$ = combineLatest([
|
private shouldShowPrompt$: Observable<boolean> = this.accountService.activeAccount$.pipe(
|
||||||
this.accountService.activeAccount$,
|
switchMap((account) => {
|
||||||
this.configService.getFeatureFlag$(FeatureFlag.PM24996_ImplementUpgradeFromFreeDialog),
|
// Check self-hosted first before any other operations
|
||||||
]).pipe(
|
if (this.platformUtilsService.isSelfHost()) {
|
||||||
switchMap(async ([account, isFlagEnabled]) => {
|
return of(false);
|
||||||
if (!account || !account?.id) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Early return if feature flag is disabled
|
|
||||||
if (!isFlagEnabled) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for sync to complete to ensure organizations are fully loaded
|
if (!account) {
|
||||||
// Also force a sync to ensure we have the latest data
|
return of(false);
|
||||||
await this.syncService.fullSync(false);
|
}
|
||||||
|
|
||||||
// Wait for the sync to complete with timeout to prevent hanging
|
const isProfileLessThanFiveMinutesOld = from(
|
||||||
await firstValueFrom(
|
this.isProfileLessThanFiveMinutesOld(account.id),
|
||||||
this.syncService.lastSync$(account.id).pipe(
|
|
||||||
filter((lastSync) => lastSync !== null),
|
|
||||||
take(1),
|
|
||||||
timeout(30000), // 30 second timeout
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
const hasOrganizations = from(this.hasOrganizations(account.id));
|
||||||
|
|
||||||
// Check if user has premium
|
return combineLatest([
|
||||||
const hasPremium = await firstValueFrom(
|
isProfileLessThanFiveMinutesOld,
|
||||||
|
hasOrganizations,
|
||||||
this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id),
|
this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id),
|
||||||
|
this.configService.getFeatureFlag$(FeatureFlag.PM24996_ImplementUpgradeFromFreeDialog),
|
||||||
|
]).pipe(
|
||||||
|
map(([isProfileLessThanFiveMinutesOld, hasOrganizations, hasPremium, isFlagEnabled]) => {
|
||||||
|
return (
|
||||||
|
isProfileLessThanFiveMinutesOld && !hasOrganizations && !hasPremium && isFlagEnabled
|
||||||
|
);
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Early return if user already has premium
|
|
||||||
if (hasPremium) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if user has any organization membership (any status including pending)
|
|
||||||
// Try using memberOrganizations$ which might have different filtering logic
|
|
||||||
const memberOrganizations = await firstValueFrom(
|
|
||||||
this.organizationService.memberOrganizations$(account.id),
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasOrganizations = memberOrganizations.length > 0;
|
|
||||||
|
|
||||||
// Early return if user has any organization status
|
|
||||||
if (hasOrganizations) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check profile age only if needed
|
|
||||||
const isProfileLessThanFiveMinutesOld = await this.isProfileLessThanFiveMinutesOld(
|
|
||||||
account.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
return isFlagEnabled && !hasPremium && !hasOrganizations && isProfileLessThanFiveMinutesOld;
|
|
||||||
}),
|
}),
|
||||||
take(1),
|
take(1),
|
||||||
);
|
);
|
||||||
@@ -119,7 +95,7 @@ export class UnifiedUpgradePromptService {
|
|||||||
const nowInMs = new Date().getTime();
|
const nowInMs = new Date().getTime();
|
||||||
|
|
||||||
const differenceInMs = nowInMs - createdAtInMs;
|
const differenceInMs = nowInMs - createdAtInMs;
|
||||||
const msInAMinute = 1000 * 60; // Milliseconds in a minute for conversion 1 minute = 60 seconds * 1000 ms
|
const msInAMinute = 1000 * 60; // 60 seconds * 1000ms
|
||||||
const differenceInMinutes = Math.round(differenceInMs / msInAMinute);
|
const differenceInMinutes = Math.round(differenceInMs / msInAMinute);
|
||||||
|
|
||||||
return differenceInMinutes <= 5;
|
return differenceInMinutes <= 5;
|
||||||
@@ -141,4 +117,32 @@ export class UnifiedUpgradePromptService {
|
|||||||
// Return the result or null if the dialog was dismissed without a result
|
// Return the result or null if the dialog was dismissed without a result
|
||||||
return result || null;
|
return result || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the user has any organization associated with their account
|
||||||
|
* @param userId User ID to check
|
||||||
|
* @returns Promise that resolves to true if user has any organizations, false otherwise
|
||||||
|
*/
|
||||||
|
private async hasOrganizations(userId: UserId): Promise<boolean> {
|
||||||
|
// Wait for sync to complete to ensure organizations are fully loaded
|
||||||
|
// Also force a sync to ensure we have the latest data
|
||||||
|
await this.syncService.fullSync(false);
|
||||||
|
|
||||||
|
// Wait for the sync to complete with timeout to prevent hanging
|
||||||
|
await firstValueFrom(
|
||||||
|
this.syncService.lastSync$(userId).pipe(
|
||||||
|
filter((lastSync) => lastSync !== null),
|
||||||
|
take(1),
|
||||||
|
timeout(30000), // 30 second timeout
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if user has any organization membership (any status including pending)
|
||||||
|
// Try using memberOrganizations$ which might have different filtering logic
|
||||||
|
const memberOrganizations = await firstValueFrom(
|
||||||
|
this.organizationService.memberOrganizations$(userId),
|
||||||
|
);
|
||||||
|
|
||||||
|
return memberOrganizations.length > 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user