Add Service and Component Structure for Internet and SIM Offerings
- Introduced new controllers for internet eligibility and service health checks to enhance backend functionality. - Created service modules for internet, SIM, and VPN offerings, improving organization and maintainability. - Developed various components for internet and SIM configuration, including forms and plan cards, to streamline user interactions. - Implemented hooks for managing service configurations and eligibility checks, enhancing frontend data handling. - Updated utility functions for pricing and catalog operations to support new service structures and improve performance.
This commit is contained in:
parent
6bc7695b22
commit
38bb40b88b
@ -292,7 +292,7 @@ When running `pnpm dev:tools`, you get access to:
|
||||
|
||||
### Catalog & Orders
|
||||
|
||||
- `GET /api/catalog` - WHMCS GetProducts (cached 5-15m)
|
||||
- `GET /api/services/*` - Services catalog endpoints (internet/sim/vpn)
|
||||
- `POST /api/orders` - WHMCS AddOrder with idempotency
|
||||
|
||||
### Invoices
|
||||
|
||||
@ -30,7 +30,7 @@ import { AuthModule } from "@bff/modules/auth/auth.module.js";
|
||||
import { UsersModule } from "@bff/modules/users/users.module.js";
|
||||
import { MeStatusModule } from "@bff/modules/me-status/me-status.module.js";
|
||||
import { MappingsModule } from "@bff/modules/id-mappings/mappings.module.js";
|
||||
import { CatalogModule } from "@bff/modules/catalog/catalog.module.js";
|
||||
import { ServicesModule } from "@bff/modules/services/services.module.js";
|
||||
import { OrdersModule } from "@bff/modules/orders/orders.module.js";
|
||||
import { InvoicesModule } from "@bff/modules/invoices/invoices.module.js";
|
||||
import { SubscriptionsModule } from "@bff/modules/subscriptions/subscriptions.module.js";
|
||||
@ -84,7 +84,7 @@ import { HealthModule } from "@bff/modules/health/health.module.js";
|
||||
UsersModule,
|
||||
MeStatusModule,
|
||||
MappingsModule,
|
||||
CatalogModule,
|
||||
ServicesModule,
|
||||
OrdersModule,
|
||||
InvoicesModule,
|
||||
SubscriptionsModule,
|
||||
|
||||
@ -3,7 +3,7 @@ import { AuthModule } from "@bff/modules/auth/auth.module.js";
|
||||
import { UsersModule } from "@bff/modules/users/users.module.js";
|
||||
import { MeStatusModule } from "@bff/modules/me-status/me-status.module.js";
|
||||
import { MappingsModule } from "@bff/modules/id-mappings/mappings.module.js";
|
||||
import { CatalogModule } from "@bff/modules/catalog/catalog.module.js";
|
||||
import { ServicesModule } from "@bff/modules/services/services.module.js";
|
||||
import { OrdersModule } from "@bff/modules/orders/orders.module.js";
|
||||
import { InvoicesModule } from "@bff/modules/invoices/invoices.module.js";
|
||||
import { SubscriptionsModule } from "@bff/modules/subscriptions/subscriptions.module.js";
|
||||
@ -22,7 +22,7 @@ export const apiRoutes: Routes = [
|
||||
{ path: "", module: UsersModule },
|
||||
{ path: "", module: MeStatusModule },
|
||||
{ path: "", module: MappingsModule },
|
||||
{ path: "", module: CatalogModule },
|
||||
{ path: "", module: ServicesModule },
|
||||
{ path: "", module: OrdersModule },
|
||||
{ path: "", module: InvoicesModule },
|
||||
{ path: "", module: SubscriptionsModule },
|
||||
|
||||
13
apps/bff/src/infra/cache/README.md
vendored
13
apps/bff/src/infra/cache/README.md
vendored
@ -64,11 +64,13 @@ Redis-backed caching system with CDC (Change Data Capture) event-driven invalida
|
||||
**No TTL** - Cache persists indefinitely until CDC event triggers invalidation.
|
||||
|
||||
**Pros:**
|
||||
|
||||
- Real-time invalidation when data changes
|
||||
- Zero stale data for customer-visible fields
|
||||
- Optimal for frequently read, infrequently changed data
|
||||
|
||||
**Example:**
|
||||
|
||||
```typescript
|
||||
@Injectable()
|
||||
export class OrdersCacheService {
|
||||
@ -88,11 +90,13 @@ export class OrdersCacheService {
|
||||
**Fixed TTL** - Cache expires after a set duration.
|
||||
|
||||
**Pros:**
|
||||
|
||||
- Simple, predictable behavior
|
||||
- Good for external systems without CDC
|
||||
- Automatic cleanup of stale data
|
||||
|
||||
**Example:**
|
||||
|
||||
```typescript
|
||||
@Injectable()
|
||||
export class WhmcsCacheService {
|
||||
@ -160,7 +164,8 @@ All cache services track performance metrics:
|
||||
```
|
||||
|
||||
Access via health endpoints:
|
||||
- `GET /health/catalog/cache`
|
||||
|
||||
- `GET /api/health/services/cache`
|
||||
- `GET /health`
|
||||
|
||||
## Creating a New Cache Service
|
||||
@ -194,7 +199,7 @@ export class MyDomainCacheService {
|
||||
```typescript
|
||||
async getMyData(id: string, fetcher: () => Promise<MyData>): Promise<MyData> {
|
||||
const key = `mydomain:${id}`;
|
||||
|
||||
|
||||
// Check cache
|
||||
const cached = await this.cache.get<MyData>(key);
|
||||
if (cached) {
|
||||
@ -255,6 +260,7 @@ domain:type:identifier[:subkey]
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
- `orders:account:001xx000003EgI1AAK`
|
||||
- `orders:detail:80122000000D4UGAA0`
|
||||
- `catalog:internet:acc_001:jp`
|
||||
@ -288,7 +294,7 @@ Provides global `REDIS_CLIENT` using ioredis.
|
||||
GET /health
|
||||
|
||||
# Catalog cache metrics
|
||||
GET /health/catalog/cache
|
||||
GET /api/health/services/cache
|
||||
```
|
||||
|
||||
### Response Format
|
||||
@ -357,4 +363,3 @@ console.log(`${count} keys using ${usage} bytes`);
|
||||
- [Salesforce CDC Events](../../integrations/salesforce/events/README.md)
|
||||
- [Order Fulfillment Flow](../../modules/orders/docs/FULFILLMENT.md)
|
||||
- [Redis Configuration](../redis/README.md)
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import { ConfigService } from "@nestjs/config";
|
||||
import { Logger } from "nestjs-pino";
|
||||
import PubSubApiClientPkg from "salesforce-pubsub-api-client";
|
||||
import { SalesforceConnection } from "../services/salesforce-connection.service.js";
|
||||
import { CatalogCacheService } from "@bff/modules/catalog/services/catalog-cache.service.js";
|
||||
import { CatalogCacheService } from "@bff/modules/services/services/catalog-cache.service.js";
|
||||
import { RealtimeService } from "@bff/infra/realtime/realtime.service.js";
|
||||
import { AccountNotificationHandler } from "@bff/modules/notifications/account-cdc-listener.service.js";
|
||||
|
||||
@ -195,8 +195,8 @@ export class CatalogCdcSubscriber implements OnModuleInit, OnModuleDestroy {
|
||||
}
|
||||
);
|
||||
await this.invalidateAllCatalogs();
|
||||
// Full invalidation already implies all clients should refetch catalog
|
||||
this.realtime.publish("global:catalog", "catalog.changed", {
|
||||
// Full invalidation already implies all clients should refetch services
|
||||
this.realtime.publish("global:services", "services.changed", {
|
||||
reason: "product.cdc.fallback_full_invalidation",
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
@ -204,7 +204,7 @@ export class CatalogCdcSubscriber implements OnModuleInit, OnModuleDestroy {
|
||||
}
|
||||
|
||||
// Product changes can affect catalog results for all users
|
||||
this.realtime.publish("global:catalog", "catalog.changed", {
|
||||
this.realtime.publish("global:services", "services.changed", {
|
||||
reason: "product.cdc",
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
@ -249,14 +249,14 @@ export class CatalogCdcSubscriber implements OnModuleInit, OnModuleDestroy {
|
||||
}
|
||||
);
|
||||
await this.invalidateAllCatalogs();
|
||||
this.realtime.publish("global:catalog", "catalog.changed", {
|
||||
this.realtime.publish("global:services", "services.changed", {
|
||||
reason: "pricebook.cdc.fallback_full_invalidation",
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.realtime.publish("global:catalog", "catalog.changed", {
|
||||
this.realtime.publish("global:services", "services.changed", {
|
||||
reason: "pricebook.cdc",
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
@ -316,7 +316,7 @@ export class CatalogCdcSubscriber implements OnModuleInit, OnModuleDestroy {
|
||||
}
|
||||
|
||||
// Notify connected portals immediately (multi-instance safe via Redis pub/sub)
|
||||
this.realtime.publish(`account:sf:${accountId}`, "catalog.eligibility.changed", {
|
||||
this.realtime.publish(`account:sf:${accountId}`, "services.eligibility.changed", {
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import { Module, forwardRef } from "@nestjs/common";
|
||||
import { ConfigModule } from "@nestjs/config";
|
||||
import { IntegrationsModule } from "@bff/integrations/integrations.module.js";
|
||||
import { OrdersModule } from "@bff/modules/orders/orders.module.js";
|
||||
import { CatalogModule } from "@bff/modules/catalog/catalog.module.js";
|
||||
import { ServicesModule } from "@bff/modules/services/services.module.js";
|
||||
import { NotificationsModule } from "@bff/modules/notifications/notifications.module.js";
|
||||
import { CatalogCdcSubscriber } from "./catalog-cdc.subscriber.js";
|
||||
import { OrderCdcSubscriber } from "./order-cdc.subscriber.js";
|
||||
@ -12,7 +12,7 @@ import { OrderCdcSubscriber } from "./order-cdc.subscriber.js";
|
||||
ConfigModule,
|
||||
forwardRef(() => IntegrationsModule),
|
||||
forwardRef(() => OrdersModule),
|
||||
forwardRef(() => CatalogModule),
|
||||
forwardRef(() => ServicesModule),
|
||||
forwardRef(() => NotificationsModule),
|
||||
],
|
||||
providers: [
|
||||
|
||||
@ -33,7 +33,7 @@ import type {
|
||||
} from "@customer-portal/domain/payments";
|
||||
import type { WhmcsGetClientsProductsParams } from "@customer-portal/domain/subscriptions";
|
||||
import type { WhmcsProductListResponse } from "@customer-portal/domain/subscriptions";
|
||||
import type { WhmcsCatalogProductListResponse } from "@customer-portal/domain/catalog";
|
||||
import type { WhmcsCatalogProductListResponse } from "@customer-portal/domain/services";
|
||||
import type { WhmcsErrorResponse } from "@customer-portal/domain/common";
|
||||
import type { WhmcsRequestOptions, WhmcsConnectionStats } from "../types/connection.types.js";
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ import type {
|
||||
import {
|
||||
Providers as CatalogProviders,
|
||||
type WhmcsCatalogProductNormalized,
|
||||
} from "@customer-portal/domain/catalog";
|
||||
} from "@customer-portal/domain/services";
|
||||
import { WhmcsConnectionOrchestratorService } from "../connection/services/whmcs-connection-orchestrator.service.js";
|
||||
import { WhmcsCacheService } from "../cache/whmcs-cache.service.js";
|
||||
import type { WhmcsCreateSsoTokenParams } from "@customer-portal/domain/customer";
|
||||
|
||||
@ -20,7 +20,7 @@ import { WhmcsOrderService } from "./services/whmcs-order.service.js";
|
||||
import type { WhmcsAddClientParams, WhmcsClientResponse } from "@customer-portal/domain/customer";
|
||||
import type { WhmcsGetClientsProductsParams } from "@customer-portal/domain/subscriptions";
|
||||
import type { WhmcsProductListResponse } from "@customer-portal/domain/subscriptions";
|
||||
import type { WhmcsCatalogProductNormalized } from "@customer-portal/domain/catalog";
|
||||
import type { WhmcsCatalogProductNormalized } from "@customer-portal/domain/services";
|
||||
import { Logger } from "nestjs-pino";
|
||||
|
||||
@Injectable()
|
||||
|
||||
@ -3,7 +3,7 @@ import { MeStatusController } from "./me-status.controller.js";
|
||||
import { MeStatusService } from "./me-status.service.js";
|
||||
import { UsersModule } from "@bff/modules/users/users.module.js";
|
||||
import { OrdersModule } from "@bff/modules/orders/orders.module.js";
|
||||
import { CatalogModule } from "@bff/modules/catalog/catalog.module.js";
|
||||
import { ServicesModule } from "@bff/modules/services/services.module.js";
|
||||
import { VerificationModule } from "@bff/modules/verification/verification.module.js";
|
||||
import { WhmcsModule } from "@bff/integrations/whmcs/whmcs.module.js";
|
||||
import { MappingsModule } from "@bff/modules/id-mappings/mappings.module.js";
|
||||
@ -14,7 +14,7 @@ import { SalesforceModule } from "@bff/integrations/salesforce/salesforce.module
|
||||
imports: [
|
||||
UsersModule,
|
||||
OrdersModule,
|
||||
CatalogModule,
|
||||
ServicesModule,
|
||||
VerificationModule,
|
||||
WhmcsModule,
|
||||
MappingsModule,
|
||||
|
||||
@ -2,7 +2,7 @@ import { Injectable, Inject } from "@nestjs/common";
|
||||
import { Logger } from "nestjs-pino";
|
||||
import { UsersFacade } from "@bff/modules/users/application/users.facade.js";
|
||||
import { OrderOrchestrator } from "@bff/modules/orders/services/order-orchestrator.service.js";
|
||||
import { InternetCatalogService } from "@bff/modules/catalog/services/internet-catalog.service.js";
|
||||
import { InternetCatalogService } from "@bff/modules/services/services/internet-catalog.service.js";
|
||||
import { ResidenceCardService } from "@bff/modules/verification/residence-card.service.js";
|
||||
import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js";
|
||||
import { WhmcsPaymentService } from "@bff/integrations/whmcs/services/whmcs-payment.service.js";
|
||||
@ -15,7 +15,7 @@ import {
|
||||
type PaymentMethodsStatus,
|
||||
} from "@customer-portal/domain/dashboard";
|
||||
import { NOTIFICATION_SOURCE, NOTIFICATION_TYPE } from "@customer-portal/domain/notifications";
|
||||
import type { InternetEligibilityDetails } from "@customer-portal/domain/catalog";
|
||||
import type { InternetEligibilityDetails } from "@customer-portal/domain/services";
|
||||
import type { ResidenceCardVerification } from "@customer-portal/domain/customer";
|
||||
import type { OrderSummary } from "@customer-portal/domain/orders";
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import { MappingsModule } from "@bff/modules/id-mappings/mappings.module.js";
|
||||
import { UsersModule } from "@bff/modules/users/users.module.js";
|
||||
import { CoreConfigModule } from "@bff/core/config/config.module.js";
|
||||
import { DatabaseModule } from "@bff/core/database/database.module.js";
|
||||
import { CatalogModule } from "@bff/modules/catalog/catalog.module.js";
|
||||
import { ServicesModule } from "@bff/modules/services/services.module.js";
|
||||
import { CacheModule } from "@bff/infra/cache/cache.module.js";
|
||||
import { VerificationModule } from "@bff/modules/verification/verification.module.js";
|
||||
import { NotificationsModule } from "@bff/modules/notifications/notifications.module.js";
|
||||
@ -39,7 +39,7 @@ import { OrderFieldConfigModule } from "./config/order-field-config.module.js";
|
||||
UsersModule,
|
||||
CoreConfigModule,
|
||||
DatabaseModule,
|
||||
CatalogModule,
|
||||
ServicesModule,
|
||||
CacheModule,
|
||||
VerificationModule,
|
||||
NotificationsModule,
|
||||
|
||||
@ -20,10 +20,10 @@ import type {
|
||||
SimCatalogProduct,
|
||||
SimActivationFeeCatalogItem,
|
||||
VpnCatalogProduct,
|
||||
} from "@customer-portal/domain/catalog";
|
||||
import { InternetCatalogService } from "@bff/modules/catalog/services/internet-catalog.service.js";
|
||||
import { SimCatalogService } from "@bff/modules/catalog/services/sim-catalog.service.js";
|
||||
import { VpnCatalogService } from "@bff/modules/catalog/services/vpn-catalog.service.js";
|
||||
} from "@customer-portal/domain/services";
|
||||
import { InternetCatalogService } from "@bff/modules/services/services/internet-catalog.service.js";
|
||||
import { SimCatalogService } from "@bff/modules/services/services/sim-catalog.service.js";
|
||||
import { VpnCatalogService } from "@bff/modules/services/services/vpn-catalog.service.js";
|
||||
import { getErrorMessage } from "@bff/core/utils/error.util.js";
|
||||
|
||||
@Injectable()
|
||||
|
||||
@ -5,7 +5,7 @@ import { SalesforceConnection } from "@bff/integrations/salesforce/services/sale
|
||||
import type {
|
||||
SalesforceProduct2Record,
|
||||
SalesforcePricebookEntryRecord,
|
||||
} from "@customer-portal/domain/catalog";
|
||||
} from "@customer-portal/domain/services";
|
||||
import type { SalesforceResponse } from "@customer-portal/domain/common";
|
||||
import {
|
||||
assertSalesforceId,
|
||||
|
||||
@ -13,8 +13,8 @@ import {
|
||||
import type { Providers } from "@customer-portal/domain/subscriptions";
|
||||
|
||||
type WhmcsProduct = Providers.WhmcsRaw.WhmcsProductRaw;
|
||||
import { SimCatalogService } from "@bff/modules/catalog/services/sim-catalog.service.js";
|
||||
import { InternetCatalogService } from "@bff/modules/catalog/services/internet-catalog.service.js";
|
||||
import { SimCatalogService } from "@bff/modules/services/services/sim-catalog.service.js";
|
||||
import { InternetCatalogService } from "@bff/modules/services/services/internet-catalog.service.js";
|
||||
import { OrderPricebookService, type PricebookProductMeta } from "./order-pricebook.service.js";
|
||||
import { PaymentValidatorService } from "./payment-validator.service.js";
|
||||
import { ResidenceCardService } from "@bff/modules/verification/residence-card.service.js";
|
||||
|
||||
@ -67,14 +67,14 @@ export class RealtimeController {
|
||||
}
|
||||
);
|
||||
|
||||
const globalCatalogStream = this.realtime.subscribe("global:catalog", {
|
||||
const globalServicesStream = this.realtime.subscribe("global:services", {
|
||||
// Avoid duplicate ready/heartbeat noise on the combined stream.
|
||||
readyEvent: null,
|
||||
heartbeatEvent: null,
|
||||
heartbeatMs: 0,
|
||||
});
|
||||
|
||||
return merge(accountStream, globalCatalogStream).pipe(
|
||||
return merge(accountStream, globalServicesStream).pipe(
|
||||
finalize(() => {
|
||||
this.limiter.release(req.user.id);
|
||||
this.logger.debug("Account realtime stream disconnected", {
|
||||
|
||||
@ -5,7 +5,7 @@ import type { RequestWithUser } from "@bff/modules/auth/auth.types.js";
|
||||
import { RateLimit, RateLimitGuard } from "@bff/core/rate-limiting/index.js";
|
||||
import { InternetCatalogService } from "./services/internet-catalog.service.js";
|
||||
import { addressSchema } from "@customer-portal/domain/customer";
|
||||
import type { InternetEligibilityDetails } from "@customer-portal/domain/catalog";
|
||||
import type { InternetEligibilityDetails } from "@customer-portal/domain/services";
|
||||
|
||||
const eligibilityRequestSchema = z.object({
|
||||
notes: z.string().trim().max(2000).optional(),
|
||||
@ -21,10 +21,10 @@ type EligibilityRequest = z.infer<typeof eligibilityRequestSchema>;
|
||||
* - fetching current Salesforce eligibility value
|
||||
* - requesting a (manual) eligibility/availability check
|
||||
*
|
||||
* Note: CatalogController is @Public, so we keep these endpoints in a separate controller
|
||||
* Note: ServicesController is @Public, so we keep these endpoints in a separate controller
|
||||
* to ensure GlobalAuthGuard enforces authentication.
|
||||
*/
|
||||
@Controller("catalog/internet")
|
||||
@Controller("services/internet")
|
||||
@UseGuards(RateLimitGuard)
|
||||
export class InternetEligibilityController {
|
||||
constructor(private readonly internetCatalog: InternetCatalogService) {}
|
||||
@ -3,7 +3,7 @@ import { CatalogCacheService } from "./services/catalog-cache.service.js";
|
||||
import type { CatalogCacheSnapshot } from "./services/catalog-cache.service.js";
|
||||
import { Public } from "@bff/modules/auth/decorators/public.decorator.js";
|
||||
|
||||
interface CatalogCacheHealthResponse {
|
||||
interface ServicesCacheHealthResponse {
|
||||
timestamp: string;
|
||||
metrics: CatalogCacheSnapshot;
|
||||
ttl: {
|
||||
@ -14,13 +14,13 @@ interface CatalogCacheHealthResponse {
|
||||
};
|
||||
}
|
||||
|
||||
@Controller("health/catalog")
|
||||
@Controller("health/services")
|
||||
@Public()
|
||||
export class CatalogHealthController {
|
||||
export class ServicesHealthController {
|
||||
constructor(private readonly catalogCache: CatalogCacheService) {}
|
||||
|
||||
@Get("cache")
|
||||
getCacheMetrics(): CatalogCacheHealthResponse {
|
||||
getCacheMetrics(): ServicesCacheHealthResponse {
|
||||
const ttl = this.catalogCache.getTtlConfiguration();
|
||||
return {
|
||||
timestamp: new Date().toISOString(),
|
||||
@ -12,16 +12,16 @@ import {
|
||||
type SimCatalogCollection,
|
||||
type SimCatalogProduct,
|
||||
type VpnCatalogProduct,
|
||||
} from "@customer-portal/domain/catalog";
|
||||
} from "@customer-portal/domain/services";
|
||||
import { InternetCatalogService } from "./services/internet-catalog.service.js";
|
||||
import { SimCatalogService } from "./services/sim-catalog.service.js";
|
||||
import { VpnCatalogService } from "./services/vpn-catalog.service.js";
|
||||
import { SalesforceReadThrottleGuard } from "@bff/integrations/salesforce/guards/salesforce-read-throttle.guard.js";
|
||||
|
||||
@Controller("catalog")
|
||||
@Public() // Allow public access - catalog can be browsed without authentication
|
||||
@Controller("services")
|
||||
@Public() // Allow public access - services can be browsed without authentication
|
||||
@UseGuards(SalesforceReadThrottleGuard, RateLimitGuard)
|
||||
export class CatalogController {
|
||||
export class ServicesController {
|
||||
constructor(
|
||||
private internetCatalog: InternetCatalogService,
|
||||
private simCatalog: SimCatalogService,
|
||||
@ -1,6 +1,6 @@
|
||||
import { Module, forwardRef } from "@nestjs/common";
|
||||
import { CatalogController } from "./catalog.controller.js";
|
||||
import { CatalogHealthController } from "./catalog-health.controller.js";
|
||||
import { ServicesController } from "./services.controller.js";
|
||||
import { ServicesHealthController } from "./services-health.controller.js";
|
||||
import { InternetEligibilityController } from "./internet-eligibility.controller.js";
|
||||
import { IntegrationsModule } from "@bff/integrations/integrations.module.js";
|
||||
import { MappingsModule } from "@bff/modules/id-mappings/mappings.module.js";
|
||||
@ -22,7 +22,7 @@ import { CatalogCacheService } from "./services/catalog-cache.service.js";
|
||||
CacheModule,
|
||||
QueueModule,
|
||||
],
|
||||
controllers: [CatalogController, CatalogHealthController, InternetEligibilityController],
|
||||
controllers: [ServicesController, ServicesHealthController, InternetEligibilityController],
|
||||
providers: [
|
||||
BaseCatalogService,
|
||||
InternetCatalogService,
|
||||
@ -32,4 +32,4 @@ import { CatalogCacheService } from "./services/catalog-cache.service.js";
|
||||
],
|
||||
exports: [InternetCatalogService, SimCatalogService, VpnCatalogService, CatalogCacheService],
|
||||
})
|
||||
export class CatalogModule {}
|
||||
export class ServicesModule {}
|
||||
@ -14,8 +14,8 @@ import { getErrorMessage } from "@bff/core/utils/error.util.js";
|
||||
import type {
|
||||
SalesforceProduct2WithPricebookEntries,
|
||||
SalesforcePricebookEntryRecord,
|
||||
} from "@customer-portal/domain/catalog";
|
||||
import { Providers as CatalogProviders } from "@customer-portal/domain/catalog";
|
||||
} from "@customer-portal/domain/services";
|
||||
import { Providers as CatalogProviders } from "@customer-portal/domain/services";
|
||||
import type { SalesforceResponse } from "@customer-portal/domain/common";
|
||||
|
||||
@Injectable()
|
||||
@ -9,14 +9,14 @@ import type {
|
||||
InternetAddonCatalogItem,
|
||||
InternetEligibilityDetails,
|
||||
InternetEligibilityStatus,
|
||||
} from "@customer-portal/domain/catalog";
|
||||
} from "@customer-portal/domain/services";
|
||||
import {
|
||||
Providers as CatalogProviders,
|
||||
enrichInternetPlanMetadata,
|
||||
inferAddonTypeFromSku,
|
||||
inferInstallationTermFromSku,
|
||||
internetEligibilityDetailsSchema,
|
||||
} from "@customer-portal/domain/catalog";
|
||||
} from "@customer-portal/domain/services";
|
||||
import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js";
|
||||
import { SalesforceConnection } from "@bff/integrations/salesforce/services/salesforce-connection.service.js";
|
||||
import { OpportunityResolutionService } from "@bff/integrations/salesforce/services/opportunity-resolution.service.js";
|
||||
@ -6,8 +6,8 @@ import type {
|
||||
SalesforceProduct2WithPricebookEntries,
|
||||
SimCatalogProduct,
|
||||
SimActivationFeeCatalogItem,
|
||||
} from "@customer-portal/domain/catalog";
|
||||
import { Providers as CatalogProviders } from "@customer-portal/domain/catalog";
|
||||
} from "@customer-portal/domain/services";
|
||||
import { Providers as CatalogProviders } from "@customer-portal/domain/services";
|
||||
import { MappingsService } from "@bff/modules/id-mappings/mappings.service.js";
|
||||
import { SalesforceConnection } from "@bff/integrations/salesforce/services/salesforce-connection.service.js";
|
||||
import { Logger } from "nestjs-pino";
|
||||
@ -6,8 +6,8 @@ import { BaseCatalogService } from "./base-catalog.service.js";
|
||||
import type {
|
||||
SalesforceProduct2WithPricebookEntries,
|
||||
VpnCatalogProduct,
|
||||
} from "@customer-portal/domain/catalog";
|
||||
import { Providers as CatalogProviders } from "@customer-portal/domain/catalog";
|
||||
} from "@customer-portal/domain/services";
|
||||
import { Providers as CatalogProviders } from "@customer-portal/domain/services";
|
||||
|
||||
@Injectable()
|
||||
export class VpnCatalogService extends BaseCatalogService {
|
||||
@ -12,8 +12,8 @@ import { SimScheduleService } from "./sim-schedule.service.js";
|
||||
import { SimActionRunnerService } from "./sim-action-runner.service.js";
|
||||
import { SimManagementQueueService } from "../queue/sim-management.queue.js";
|
||||
import { SimApiNotificationService } from "./sim-api-notification.service.js";
|
||||
import { SimCatalogService } from "@bff/modules/catalog/services/sim-catalog.service.js";
|
||||
import type { SimCatalogProduct } from "@customer-portal/domain/catalog";
|
||||
import { SimCatalogService } from "@bff/modules/services/services/sim-catalog.service.js";
|
||||
import type { SimCatalogProduct } from "@customer-portal/domain/services";
|
||||
|
||||
// Mapping from Salesforce SKU to Freebit plan code
|
||||
const SKU_TO_FREEBIT_PLAN_CODE: Record<string, string> = {
|
||||
|
||||
@ -27,7 +27,7 @@ import { SimManagementQueueService } from "./queue/sim-management.queue.js";
|
||||
import { SimManagementProcessor } from "./queue/sim-management.processor.js";
|
||||
import { SimVoiceOptionsService } from "./services/sim-voice-options.service.js";
|
||||
import { SimCallHistoryService } from "./services/sim-call-history.service.js";
|
||||
import { CatalogModule } from "@bff/modules/catalog/catalog.module.js";
|
||||
import { ServicesModule } from "@bff/modules/services/services.module.js";
|
||||
import { NotificationsModule } from "@bff/modules/notifications/notifications.module.js";
|
||||
|
||||
@Module({
|
||||
@ -37,7 +37,7 @@ import { NotificationsModule } from "@bff/modules/notifications/notifications.mo
|
||||
SalesforceModule,
|
||||
MappingsModule,
|
||||
EmailModule,
|
||||
CatalogModule,
|
||||
ServicesModule,
|
||||
SftpModule,
|
||||
NotificationsModule,
|
||||
],
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
* Configure internet plan for unauthenticated users.
|
||||
*/
|
||||
|
||||
import { PublicInternetConfigureView } from "@/features/catalog/views/PublicInternetConfigure";
|
||||
import { RedirectAuthenticatedToAccountServices } from "@/features/catalog/components/common/RedirectAuthenticatedToAccountServices";
|
||||
import { PublicInternetConfigureView } from "@/features/services/views/PublicInternetConfigure";
|
||||
import { RedirectAuthenticatedToAccountServices } from "@/features/services/components/common/RedirectAuthenticatedToAccountServices";
|
||||
|
||||
export default function PublicInternetConfigurePage() {
|
||||
return (
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
* Displays internet plans for unauthenticated users.
|
||||
*/
|
||||
|
||||
import { PublicInternetPlansView } from "@/features/catalog/views/PublicInternetPlans";
|
||||
import { RedirectAuthenticatedToAccountServices } from "@/features/catalog/components/common/RedirectAuthenticatedToAccountServices";
|
||||
import { PublicInternetPlansView } from "@/features/services/views/PublicInternetPlans";
|
||||
import { RedirectAuthenticatedToAccountServices } from "@/features/services/components/common/RedirectAuthenticatedToAccountServices";
|
||||
|
||||
export default function PublicInternetPlansPage() {
|
||||
return (
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ServicesGrid } from "@/features/catalog/components/common/ServicesGrid";
|
||||
import { ServicesGrid } from "@/features/services/components/common/ServicesGrid";
|
||||
|
||||
interface ServicesPageProps {
|
||||
basePath?: string;
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
* Configure SIM plan for unauthenticated users.
|
||||
*/
|
||||
|
||||
import { PublicSimConfigureView } from "@/features/catalog/views/PublicSimConfigure";
|
||||
import { RedirectAuthenticatedToAccountServices } from "@/features/catalog/components/common/RedirectAuthenticatedToAccountServices";
|
||||
import { PublicSimConfigureView } from "@/features/services/views/PublicSimConfigure";
|
||||
import { RedirectAuthenticatedToAccountServices } from "@/features/services/components/common/RedirectAuthenticatedToAccountServices";
|
||||
|
||||
export default function PublicSimConfigurePage() {
|
||||
return (
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
* Displays SIM plans for unauthenticated users.
|
||||
*/
|
||||
|
||||
import { PublicSimPlansView } from "@/features/catalog/views/PublicSimPlans";
|
||||
import { RedirectAuthenticatedToAccountServices } from "@/features/catalog/components/common/RedirectAuthenticatedToAccountServices";
|
||||
import { PublicSimPlansView } from "@/features/services/views/PublicSimPlans";
|
||||
import { RedirectAuthenticatedToAccountServices } from "@/features/services/components/common/RedirectAuthenticatedToAccountServices";
|
||||
|
||||
export default function PublicSimPlansPage() {
|
||||
return (
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
* Displays VPN plans for unauthenticated users.
|
||||
*/
|
||||
|
||||
import { PublicVpnPlansView } from "@/features/catalog/views/PublicVpnPlans";
|
||||
import { RedirectAuthenticatedToAccountServices } from "@/features/catalog/components/common/RedirectAuthenticatedToAccountServices";
|
||||
import { PublicVpnPlansView } from "@/features/services/views/PublicVpnPlans";
|
||||
import { RedirectAuthenticatedToAccountServices } from "@/features/services/components/common/RedirectAuthenticatedToAccountServices";
|
||||
|
||||
export default function PublicVpnPlansPage() {
|
||||
return (
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { InternetConfigureContainer } from "@/features/catalog/views/InternetConfigure";
|
||||
import { InternetConfigureContainer } from "@/features/services/views/InternetConfigure";
|
||||
|
||||
export default function AccountInternetConfigurePage() {
|
||||
return <InternetConfigureContainer />;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { InternetPlansContainer } from "@/features/catalog/views/InternetPlans";
|
||||
import { InternetPlansContainer } from "@/features/services/views/InternetPlans";
|
||||
|
||||
export default function AccountInternetPlansPage() {
|
||||
return <InternetPlansContainer />;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
export default function AccountShopLayout({ children }: { children: ReactNode }) {
|
||||
export default function AccountServicesLayout({ children }: { children: ReactNode }) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ServicesGrid } from "@/features/catalog/components/common/ServicesGrid";
|
||||
import { ServicesGrid } from "@/features/services/components/common/ServicesGrid";
|
||||
|
||||
export default function AccountShopPage() {
|
||||
export default function AccountServicesPage() {
|
||||
return (
|
||||
<div className="py-8">
|
||||
<div className="max-w-[var(--cp-page-max-width)] mx-auto px-4 sm:px-6 md:px-8">
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { SimConfigureContainer } from "@/features/catalog/views/SimConfigure";
|
||||
import { SimConfigureContainer } from "@/features/services/views/SimConfigure";
|
||||
|
||||
export default function AccountSimConfigurePage() {
|
||||
return <SimConfigureContainer />;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { SimPlansContainer } from "@/features/catalog/views/SimPlans";
|
||||
import { SimPlansContainer } from "@/features/services/views/SimPlans";
|
||||
|
||||
export default function AccountSimPlansPage() {
|
||||
return <SimPlansContainer />;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { VpnPlansView } from "@/features/catalog/views/VpnPlans";
|
||||
import { VpnPlansView } from "@/features/services/views/VpnPlans";
|
||||
|
||||
export default function AccountVpnPlansPage() {
|
||||
return <VpnPlansView />;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import SubscriptionsListContainer from "@/features/subscriptions/views/SubscriptionsList";
|
||||
|
||||
export default function AccountServicesPage() {
|
||||
export default function AccountSubscriptionsPage() {
|
||||
return <SubscriptionsListContainer />;
|
||||
}
|
||||
@ -98,7 +98,7 @@ export function AppShell({ children }: AppShellProps) {
|
||||
useEffect(() => {
|
||||
setExpandedItems(prev => {
|
||||
const next = new Set(prev);
|
||||
if (pathname.startsWith("/account/services")) next.add("My Services");
|
||||
if (pathname.startsWith("/account/subscriptions")) next.add("Subscriptions");
|
||||
if (pathname.startsWith("/account/billing")) next.add("Billing");
|
||||
if (pathname.startsWith("/account/support")) next.add("Support");
|
||||
if (pathname.startsWith("/account/settings")) next.add("Settings");
|
||||
|
||||
@ -37,9 +37,9 @@ export const baseNavigation: NavigationItem[] = [
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "My Services",
|
||||
name: "Subscriptions",
|
||||
icon: ServerIcon,
|
||||
children: [{ name: "All Services", href: "/account/my-services" }],
|
||||
children: [{ name: "All Subscriptions", href: "/account/subscriptions" }],
|
||||
},
|
||||
{ name: "Services", href: "/account/services", icon: Squares2X2Icon },
|
||||
{
|
||||
@ -64,17 +64,17 @@ export function computeNavigation(activeSubscriptions?: Subscription[]): Navigat
|
||||
children: item.children ? [...item.children] : undefined,
|
||||
}));
|
||||
|
||||
const subIdx = nav.findIndex(n => n.name === "My Services");
|
||||
const subIdx = nav.findIndex(n => n.name === "Subscriptions");
|
||||
if (subIdx >= 0) {
|
||||
const dynamicChildren = (activeSubscriptions || []).map(sub => ({
|
||||
name: truncate(sub.productName || `Subscription ${sub.id}`, 28),
|
||||
href: `/account/my-services/${sub.id}`,
|
||||
href: `/account/subscriptions/${sub.id}`,
|
||||
tooltip: sub.productName || `Subscription ${sub.id}`,
|
||||
}));
|
||||
|
||||
nav[subIdx] = {
|
||||
...nav[subIdx],
|
||||
children: [{ name: "All Services", href: "/account/my-services" }, ...dynamicChildren],
|
||||
children: [{ name: "All Subscriptions", href: "/account/subscriptions" }, ...dynamicChildren],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import { SubCard } from "@/components/molecules/SubCard/SubCard";
|
||||
import { MapPinIcon, PencilIcon, CheckIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import { AddressForm, type AddressFormProps } from "@/features/catalog/components";
|
||||
import { AddressForm, type AddressFormProps } from "@/features/services/components";
|
||||
import type { Address } from "@customer-portal/domain/customer";
|
||||
import { getCountryName } from "@/lib/constants/countries";
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ export function useAddressEdit(initial: AddressFormData) {
|
||||
const requestData = addressFormToRequest(formData);
|
||||
await accountService.updateAddress(requestData);
|
||||
// Address changes can affect server-personalized catalog results (eligibility).
|
||||
await queryClient.invalidateQueries({ queryKey: queryKeys.catalog.all() });
|
||||
await queryClient.invalidateQueries({ queryKey: queryKeys.services.all() });
|
||||
},
|
||||
[queryClient]
|
||||
);
|
||||
|
||||
@ -113,7 +113,7 @@ export function useProfileData() {
|
||||
phoneCountryCode: next.phoneCountryCode,
|
||||
});
|
||||
// Address changes can affect server-personalized catalog results (eligibility).
|
||||
await queryClient.invalidateQueries({ queryKey: queryKeys.catalog.all() });
|
||||
await queryClient.invalidateQueries({ queryKey: queryKeys.services.all() });
|
||||
setBillingInfo({ address: next });
|
||||
setAddress(next);
|
||||
return true;
|
||||
|
||||
@ -14,7 +14,7 @@ import {
|
||||
import { useAuthStore } from "@/features/auth/services/auth.store";
|
||||
import { accountService } from "@/features/account/services/account.service";
|
||||
import { useProfileEdit } from "@/features/account/hooks/useProfileEdit";
|
||||
import { AddressForm } from "@/features/catalog/components/base/AddressForm";
|
||||
import { AddressForm } from "@/features/services/components/base/AddressForm";
|
||||
import { Button } from "@/components/atoms/button";
|
||||
import { StatusPill } from "@/components/atoms/status-pill";
|
||||
import { useAddressEdit } from "@/features/account/hooks/useAddressEdit";
|
||||
|
||||
@ -72,7 +72,7 @@ export function InvoiceItems({ items = [], currency }: InvoiceItemsProps) {
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
Service #{item.serviceId}
|
||||
Subscription #{item.serviceId}
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-muted text-muted-foreground">
|
||||
@ -104,7 +104,7 @@ export function InvoiceItems({ items = [], currency }: InvoiceItemsProps) {
|
||||
|
||||
if (isLinked) {
|
||||
return (
|
||||
<Link key={item.id} href={`/account/services/${item.serviceId}`} className="block">
|
||||
<Link key={item.id} href={`/account/subscriptions/${item.serviceId}`} className="block">
|
||||
{itemContent}
|
||||
</Link>
|
||||
);
|
||||
|
||||
@ -25,7 +25,7 @@ export function InvoiceItemRow({
|
||||
? "border-blue-200 bg-blue-50 hover:bg-blue-100 cursor-pointer hover:shadow-sm"
|
||||
: "border-gray-200 bg-gray-50"
|
||||
}`}
|
||||
onClick={serviceId ? () => router.push(`/account/services/${serviceId}`) : undefined}
|
||||
onClick={serviceId ? () => router.push(`/account/subscriptions/${serviceId}`) : undefined}
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div
|
||||
@ -39,7 +39,7 @@ export function InvoiceItemRow({
|
||||
)}
|
||||
{serviceId && (
|
||||
<div className="text-xs text-blue-700 mt-1 font-medium">
|
||||
Service #{serviceId} • Click to view
|
||||
Subscription #{serviceId} • Click to view
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -1 +0,0 @@
|
||||
export { catalogService } from "./catalog.service";
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./catalog.utils";
|
||||
export * from "./pricing";
|
||||
@ -10,15 +10,15 @@ import { Button } from "@/components/atoms/button";
|
||||
import { AlertBanner } from "@/components/molecules/AlertBanner/AlertBanner";
|
||||
import { InlineToast } from "@/components/atoms/inline-toast";
|
||||
import { StatusPill } from "@/components/atoms/status-pill";
|
||||
import { AddressConfirmation } from "@/features/catalog/components/base/AddressConfirmation";
|
||||
import { AddressConfirmation } from "@/features/services/components/base/AddressConfirmation";
|
||||
import { useCheckoutStore } from "@/features/checkout/stores/checkout.store";
|
||||
import { ordersService } from "@/features/orders/services/orders.service";
|
||||
import { usePaymentMethods } from "@/features/billing/hooks/useBilling";
|
||||
import { usePaymentRefresh } from "@/features/billing/hooks/usePaymentRefresh";
|
||||
import { useActiveSubscriptions } from "@/features/subscriptions/hooks/useSubscriptions";
|
||||
import { ACTIVE_INTERNET_SUBSCRIPTION_WARNING } from "@/features/checkout/constants";
|
||||
import { useInternetEligibility } from "@/features/catalog/hooks/useInternetEligibility";
|
||||
import { useRequestInternetEligibilityCheck } from "@/features/catalog/hooks/useInternetEligibility";
|
||||
import { useInternetEligibility } from "@/features/services/hooks/useInternetEligibility";
|
||||
import { useRequestInternetEligibilityCheck } from "@/features/services/hooks/useInternetEligibility";
|
||||
import {
|
||||
useResidenceCardVerification,
|
||||
useSubmitResidenceCard,
|
||||
|
||||
@ -4,7 +4,7 @@ import { useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { ShoppingCartIcon } from "@heroicons/react/24/outline";
|
||||
import { Button } from "@/components/atoms/button";
|
||||
import { useServicesBasePath } from "@/features/catalog/hooks/useServicesBasePath";
|
||||
import { useServicesBasePath } from "@/features/services/hooks/useServicesBasePath";
|
||||
|
||||
/**
|
||||
* EmptyCartRedirect - Shown when checkout is accessed without a cart
|
||||
|
||||
@ -40,7 +40,7 @@ export function getActivityNavigationPath(activity: Activity): string | null {
|
||||
case "invoice_paid":
|
||||
return `/account/billing/invoices/${activity.relatedId}`;
|
||||
case "service_activated":
|
||||
return `/account/services/${activity.relatedId}`;
|
||||
return `/account/subscriptions/${activity.relatedId}`;
|
||||
case "case_created":
|
||||
case "case_closed":
|
||||
return `/account/support/${activity.relatedId}`;
|
||||
|
||||
@ -10,7 +10,7 @@ import { ErrorState } from "@/components/atoms/error-state";
|
||||
import { PageLayout } from "@/components/templates";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { InlineToast } from "@/components/atoms/inline-toast";
|
||||
import { useInternetEligibility } from "@/features/catalog/hooks";
|
||||
import { useInternetEligibility } from "@/features/services/hooks";
|
||||
|
||||
export function DashboardView() {
|
||||
const { user, isAuthenticated, loading: authLoading, clearLoading } = useAuthStore();
|
||||
|
||||
@ -42,15 +42,15 @@ export function AccountEventsListener() {
|
||||
const parsed = JSON.parse(event.data) as RealtimeEventEnvelope;
|
||||
if (!parsed || typeof parsed !== "object") return;
|
||||
|
||||
if (parsed.event === "catalog.eligibility.changed") {
|
||||
logger.info("Received catalog.eligibility.changed; invalidating catalog queries");
|
||||
void queryClient.invalidateQueries({ queryKey: queryKeys.catalog.all() });
|
||||
if (parsed.event === "services.eligibility.changed") {
|
||||
logger.info("Received services.eligibility.changed; invalidating services queries");
|
||||
void queryClient.invalidateQueries({ queryKey: queryKeys.services.all() });
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsed.event === "catalog.changed") {
|
||||
logger.info("Received catalog.changed; invalidating catalog queries");
|
||||
void queryClient.invalidateQueries({ queryKey: queryKeys.catalog.all() });
|
||||
if (parsed.event === "services.changed") {
|
||||
logger.info("Received services.changed; invalidating services queries");
|
||||
void queryClient.invalidateQueries({ queryKey: queryKeys.services.all() });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { CheckCircleIcon } from "@heroicons/react/24/solid";
|
||||
import type { CatalogProductBase } from "@customer-portal/domain/catalog";
|
||||
import type { CatalogProductBase } from "@customer-portal/domain/services";
|
||||
interface AddonGroupProps {
|
||||
addons: Array<CatalogProductBase & { bundledAddonId?: string; isBundledAddon?: boolean }>;
|
||||
selectedAddonSkus: string[];
|
||||
@ -156,7 +156,7 @@ export function AddressConfirmation({
|
||||
const updatedAddress = await accountService.updateAddress(sanitizedAddress);
|
||||
|
||||
// Address changes can affect server-personalized catalog results (eligibility).
|
||||
await queryClient.invalidateQueries({ queryKey: queryKeys.catalog.all() });
|
||||
await queryClient.invalidateQueries({ queryKey: queryKeys.services.all() });
|
||||
|
||||
// Rebuild BillingInfo from updated address
|
||||
const updatedInfo: BillingInfo = {
|
||||
@ -10,7 +10,7 @@ import { Button } from "@/components/atoms/button";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
// Align with shared catalog contracts
|
||||
import type { CatalogProductBase } from "@customer-portal/domain/catalog";
|
||||
import type { CatalogProductBase } from "@customer-portal/domain/services";
|
||||
import type { CheckoutTotals } from "@customer-portal/domain/orders";
|
||||
|
||||
// Enhanced order item representation for UI summary
|
||||
@ -1,5 +1,5 @@
|
||||
import { ArrowLeftIcon, ArrowRightIcon } from "@heroicons/react/24/outline";
|
||||
import type { CatalogProductBase } from "@customer-portal/domain/catalog";
|
||||
import type { CatalogProductBase } from "@customer-portal/domain/services";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Button } from "@/components/atoms/button";
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
import { ReactNode } from "react";
|
||||
import { CurrencyYenIcon, InformationCircleIcon } from "@heroicons/react/24/outline";
|
||||
import { Formatting } from "@customer-portal/domain/toolkit";
|
||||
import type { PricingTier } from "@customer-portal/domain/catalog";
|
||||
import type { PricingTier } from "@customer-portal/domain/services";
|
||||
|
||||
const { formatCurrency } = Formatting;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import type { InternetInstallationCatalogItem } from "@customer-portal/domain/catalog";
|
||||
import { CardPricing } from "@/features/catalog/components/base/CardPricing";
|
||||
import type { InternetInstallationCatalogItem } from "@customer-portal/domain/services";
|
||||
import { CardPricing } from "@/features/services/components/base/CardPricing";
|
||||
|
||||
interface InstallationOptionsProps {
|
||||
installations: InternetInstallationCatalogItem[];
|
||||
@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { InternetConfigureContainer } from "./configure";
|
||||
import type { UseInternetConfigureResult } from "@/features/catalog/hooks/useInternetConfigure";
|
||||
import type { UseInternetConfigureResult } from "@/features/services/hooks/useInternetConfigure";
|
||||
|
||||
interface Props extends UseInternetConfigureResult {
|
||||
onConfirm: () => void;
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import { Home, Building2, Zap } from "lucide-react";
|
||||
import { Button } from "@/components/atoms/button";
|
||||
import { CardBadge } from "@/features/catalog/components/base/CardBadge";
|
||||
import { CardBadge } from "@/features/services/components/base/CardBadge";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface TierInfo {
|
||||
@ -6,15 +6,15 @@ import { ArrowRightIcon, CheckIcon } from "@heroicons/react/24/outline";
|
||||
import type {
|
||||
InternetPlanCatalogItem,
|
||||
InternetInstallationCatalogItem,
|
||||
} from "@customer-portal/domain/catalog";
|
||||
} from "@customer-portal/domain/services";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { CardPricing } from "@/features/catalog/components/base/CardPricing";
|
||||
import { CardBadge } from "@/features/catalog/components/base/CardBadge";
|
||||
import type { BadgeVariant } from "@/features/catalog/components/base/CardBadge";
|
||||
import { useCatalogStore } from "@/features/catalog/services/catalog.store";
|
||||
import { CardPricing } from "@/features/services/components/base/CardPricing";
|
||||
import { CardBadge } from "@/features/services/components/base/CardBadge";
|
||||
import type { BadgeVariant } from "@/features/services/components/base/CardBadge";
|
||||
import { useCatalogStore } from "@/features/services/services/services.store";
|
||||
import { IS_DEVELOPMENT } from "@/config/environment";
|
||||
import { parsePlanName } from "@/features/catalog/components/internet/utils/planName";
|
||||
import { useServicesBasePath } from "@/features/catalog/hooks/useServicesBasePath";
|
||||
import { parsePlanName } from "@/features/services/components/internet/utils/planName";
|
||||
import { useServicesBasePath } from "@/features/services/hooks/useServicesBasePath";
|
||||
|
||||
interface InternetPlanCardProps {
|
||||
plan: InternetPlanCatalogItem;
|
||||
@ -2,10 +2,10 @@
|
||||
|
||||
import { BoltIcon } from "@heroicons/react/24/outline";
|
||||
import { Button } from "@/components/atoms/button";
|
||||
import { CardBadge } from "@/features/catalog/components/base/CardBadge";
|
||||
import { CardBadge } from "@/features/services/components/base/CardBadge";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { TierInfo } from "@/features/catalog/components/internet/InternetOfferingCard";
|
||||
import { InternetModalShell } from "@/features/catalog/components/internet/InternetModalShell";
|
||||
import type { TierInfo } from "@/features/services/components/internet/InternetOfferingCard";
|
||||
import { InternetModalShell } from "@/features/services/components/internet/InternetModalShell";
|
||||
|
||||
interface InternetTierPricingModalProps {
|
||||
isOpen: boolean;
|
||||
@ -2,10 +2,10 @@
|
||||
|
||||
import { ArrowLeftIcon } from "@heroicons/react/24/outline";
|
||||
import { Button } from "@/components/atoms/button";
|
||||
import { CardBadge } from "@/features/catalog/components/base/CardBadge";
|
||||
import type { BadgeVariant } from "@/features/catalog/components/base/CardBadge";
|
||||
import { parsePlanName } from "@/features/catalog/components/internet/utils/planName";
|
||||
import type { InternetPlanCatalogItem } from "@customer-portal/domain/catalog";
|
||||
import { CardBadge } from "@/features/services/components/base/CardBadge";
|
||||
import type { BadgeVariant } from "@/features/services/components/base/CardBadge";
|
||||
import { parsePlanName } from "@/features/services/components/internet/utils/planName";
|
||||
import type { InternetPlanCatalogItem } from "@customer-portal/domain/services";
|
||||
|
||||
interface PlanHeaderProps {
|
||||
plan: InternetPlanCatalogItem;
|
||||
@ -3,7 +3,7 @@
|
||||
import { useState } from "react";
|
||||
import { ChevronDown, ChevronUp, Home, Building2, Zap, Info, X } from "lucide-react";
|
||||
import { Button } from "@/components/atoms/button";
|
||||
import { CardBadge } from "@/features/catalog/components/base/CardBadge";
|
||||
import { CardBadge } from "@/features/services/components/base/CardBadge";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface TierInfo {
|
||||
@ -8,7 +8,7 @@ import type {
|
||||
InternetPlanCatalogItem,
|
||||
InternetInstallationCatalogItem,
|
||||
InternetAddonCatalogItem,
|
||||
} from "@customer-portal/domain/catalog";
|
||||
} from "@customer-portal/domain/services";
|
||||
import type { AccessModeValue } from "@customer-portal/domain/orders";
|
||||
import { ConfigureLoadingSkeleton } from "./components/ConfigureLoadingSkeleton";
|
||||
import { ServiceConfigurationStep } from "./steps/ServiceConfigurationStep";
|
||||
@ -16,8 +16,8 @@ import { InstallationStep } from "./steps/InstallationStep";
|
||||
import { AddonsStep } from "./steps/AddonsStep";
|
||||
import { ReviewOrderStep } from "./steps/ReviewOrderStep";
|
||||
import { useConfigureState } from "./hooks/useConfigureState";
|
||||
import { useServicesBasePath } from "@/features/catalog/hooks/useServicesBasePath";
|
||||
import { PlanHeader } from "@/features/catalog/components/internet/PlanHeader";
|
||||
import { useServicesBasePath } from "@/features/services/hooks/useServicesBasePath";
|
||||
import { PlanHeader } from "@/features/services/components/internet/PlanHeader";
|
||||
|
||||
interface Props {
|
||||
plan: InternetPlanCatalogItem | null;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user