diff --git a/apps/bff/src/infra/audit/audit.service.ts b/apps/bff/src/infra/audit/audit.service.ts index b8ad887c..725fbf56 100644 --- a/apps/bff/src/infra/audit/audit.service.ts +++ b/apps/bff/src/infra/audit/audit.service.ts @@ -142,8 +142,6 @@ export class AuditService { select: { id: true, email: true, - firstName: true, - lastName: true, }, }, }, diff --git a/apps/bff/src/modules/auth/infra/workflows/workflows/signup-workflow.service.ts b/apps/bff/src/modules/auth/infra/workflows/workflows/signup-workflow.service.ts index a76fc9cf..79b74909 100644 --- a/apps/bff/src/modules/auth/infra/workflows/workflows/signup-workflow.service.ts +++ b/apps/bff/src/modules/auth/infra/workflows/workflows/signup-workflow.service.ts @@ -277,10 +277,6 @@ export class SignupWorkflowService { data: { email, passwordHash, - firstName, - lastName, - company: company || null, - phone: phone || null, emailVerified: false, failedLoginAttempts: 0, lockedUntil: null, diff --git a/apps/bff/src/modules/auth/presentation/http/guards/global-auth.guard.ts b/apps/bff/src/modules/auth/presentation/http/guards/global-auth.guard.ts index dba8bd1f..6e1717d6 100644 --- a/apps/bff/src/modules/auth/presentation/http/guards/global-auth.guard.ts +++ b/apps/bff/src/modules/auth/presentation/http/guards/global-auth.guard.ts @@ -83,7 +83,16 @@ export class GlobalAuthGuard extends AuthGuard("jwt") implements CanActivate { this.logger.debug(`Authenticated access to: ${route}`); return true; } catch (error) { - this.logger.error(`Authentication error for route ${route}: ${getErrorMessage(error)}`); + if (error instanceof UnauthorizedException) { + const token = extractTokenFromRequest(request); + const log = + typeof token === "string" + ? () => this.logger.warn(`Unauthorized access attempt to ${route}`) + : () => this.logger.debug(`Unauthenticated request blocked for ${route}`); + log(); + } else { + this.logger.error(`Authentication error for route ${route}: ${getErrorMessage(error)}`); + } throw error; } } diff --git a/apps/bff/src/modules/currency/currency.controller.ts b/apps/bff/src/modules/currency/currency.controller.ts index caaca492..7cc67c05 100644 --- a/apps/bff/src/modules/currency/currency.controller.ts +++ b/apps/bff/src/modules/currency/currency.controller.ts @@ -1,10 +1,12 @@ import { Controller, Get } from "@nestjs/common"; +import { Public } from "@bff/modules/auth/decorators/public.decorator"; import { WhmcsCurrencyService } from "../../integrations/whmcs/services/whmcs-currency.service"; @Controller("currency") export class CurrencyController { constructor(private readonly currencyService: WhmcsCurrencyService) {} + @Public() @Get("default") getDefaultCurrency() { const defaultCurrency = this.currencyService.getDefaultCurrency(); @@ -17,6 +19,7 @@ export class CurrencyController { }; } + @Public() @Get("all") getAllCurrencies() { return this.currencyService.getAllCurrencies(); diff --git a/apps/portal/src/features/catalog/components/internet/configure/InternetConfigureContainer.tsx b/apps/portal/src/features/catalog/components/internet/configure/InternetConfigureContainer.tsx index 3c5c7bc9..e7afbd24 100644 --- a/apps/portal/src/features/catalog/components/internet/configure/InternetConfigureContainer.tsx +++ b/apps/portal/src/features/catalog/components/internet/configure/InternetConfigureContainer.tsx @@ -2,7 +2,8 @@ import { PageLayout } from "@/components/templates/PageLayout"; import { ProgressSteps } from "@/components/molecules"; -import { ServerIcon } from "@heroicons/react/24/outline"; +import { Button } from "@/components/atoms/button"; +import { ServerIcon, ArrowLeftIcon } from "@heroicons/react/24/outline"; import type { InternetPlanCatalogItem, InternetInstallationCatalogItem, @@ -84,6 +85,19 @@ export function InternetConfigureContainer({ description="Set up your internet service options" >
+
+ +
+ {/* Plan Header */} diff --git a/apps/portal/src/features/catalog/components/internet/configure/steps/AddonsStep.tsx b/apps/portal/src/features/catalog/components/internet/configure/steps/AddonsStep.tsx index 6be9ddd8..b969d623 100644 --- a/apps/portal/src/features/catalog/components/internet/configure/steps/AddonsStep.tsx +++ b/apps/portal/src/features/catalog/components/internet/configure/steps/AddonsStep.tsx @@ -47,13 +47,15 @@ export function AddonsStep({ />
- -
diff --git a/apps/portal/src/features/catalog/components/internet/configure/steps/InstallationStep.tsx b/apps/portal/src/features/catalog/components/internet/configure/steps/InstallationStep.tsx index f0e94399..79a5321a 100644 --- a/apps/portal/src/features/catalog/components/internet/configure/steps/InstallationStep.tsx +++ b/apps/portal/src/features/catalog/components/internet/configure/steps/InstallationStep.tsx @@ -48,13 +48,19 @@ export function InstallationStep({ />
- -
diff --git a/apps/portal/src/features/catalog/components/internet/configure/steps/ReviewOrderStep.tsx b/apps/portal/src/features/catalog/components/internet/configure/steps/ReviewOrderStep.tsx index b7ad0e79..8297b0a1 100644 --- a/apps/portal/src/features/catalog/components/internet/configure/steps/ReviewOrderStep.tsx +++ b/apps/portal/src/features/catalog/components/internet/configure/steps/ReviewOrderStep.tsx @@ -65,13 +65,22 @@ export function ReviewOrderStep({
- -
diff --git a/apps/portal/src/features/catalog/components/internet/configure/steps/ServiceConfigurationStep.tsx b/apps/portal/src/features/catalog/components/internet/configure/steps/ServiceConfigurationStep.tsx index 4ffba141..915093ef 100644 --- a/apps/portal/src/features/catalog/components/internet/configure/steps/ServiceConfigurationStep.tsx +++ b/apps/portal/src/features/catalog/components/internet/configure/steps/ServiceConfigurationStep.tsx @@ -61,10 +61,9 @@ export function ServiceConfigurationStep({ plan, mode, setMode, isTransitioning, @@ -122,12 +121,14 @@ function ModeSelectionCard({ return ( @@ -221,9 +227,11 @@ export function SimConfigureView({ errors={errors} />
-
@@ -252,14 +260,15 @@ export function SimConfigureView({ - @@ -297,14 +306,15 @@ export function SimConfigureView({ - @@ -333,9 +343,8 @@ export function SimConfigureView({ @@ -490,13 +498,17 @@ export function SimConfigureView({ variant="outline" size="lg" className="px-8 py-4 text-lg" + leftIcon={} > - Back to Number Porting - diff --git a/apps/portal/src/features/catalog/components/sim/SimTypeSelector.tsx b/apps/portal/src/features/catalog/components/sim/SimTypeSelector.tsx index c6e2e0f4..dfdc3c05 100644 --- a/apps/portal/src/features/catalog/components/sim/SimTypeSelector.tsx +++ b/apps/portal/src/features/catalog/components/sim/SimTypeSelector.tsx @@ -19,9 +19,9 @@ export function SimTypeSelector({
{/* EID Input for eSIM */} - {simType === "eSIM" && ( +

eSIM Device Information

@@ -91,7 +96,7 @@ export function SimTypeSelector({

- )} +
); } diff --git a/apps/portal/src/features/catalog/hooks/useSimConfigure.ts b/apps/portal/src/features/catalog/hooks/useSimConfigure.ts index 3e0c43e7..b270672f 100644 --- a/apps/portal/src/features/catalog/hooks/useSimConfigure.ts +++ b/apps/portal/src/features/catalog/hooks/useSimConfigure.ts @@ -83,6 +83,16 @@ const parsePortingGenderParam = (value: string | null): MnpData["portingGender"] return undefined; }; +const MIN_STEP = 1; +const MAX_STEP = 5; + +const parseStepParam = (value: string | null): number => { + if (!value) return MIN_STEP; + const parsed = Number.parseInt(value, 10); + if (Number.isNaN(parsed)) return MIN_STEP; + return Math.min(Math.max(parsed, MIN_STEP), MAX_STEP); +}; + export function useSimConfigure(planId?: string): UseSimConfigureResult { const searchParams = useSearchParams(); const { data: simData, isLoading: simLoading } = useSimCatalog(); @@ -90,7 +100,9 @@ export function useSimConfigure(planId?: string): UseSimConfigureResult { const configureParams = useSimConfigureParams(); // Step orchestration state - const [currentStep, setCurrentStep] = useState(0); + const [currentStep, setCurrentStep] = useState(() => + parseStepParam(searchParams.get("step")) + ); const [isTransitioning, setIsTransitioning] = useState(false); // Initialize form with Zod @@ -349,7 +361,7 @@ export function useSimConfigure(planId?: string): UseSimConfigureResult { const transitionToStep = useCallback((nextStep: number) => { setIsTransitioning(true); setTimeout(() => { - setCurrentStep(nextStep); + setCurrentStep(Math.min(Math.max(nextStep, MIN_STEP), MAX_STEP)); setIsTransitioning(false); }, 150); }, []); diff --git a/apps/portal/src/features/checkout/hooks/useCheckout.ts b/apps/portal/src/features/checkout/hooks/useCheckout.ts index 79e01eb0..14adfc76 100644 --- a/apps/portal/src/features/checkout/hooks/useCheckout.ts +++ b/apps/portal/src/features/checkout/hooks/useCheckout.ts @@ -23,6 +23,12 @@ import { // Use domain Address type import type { Address } from "@customer-portal/domain/customer"; +const ACTIVE_INTERNET_WARNING_MESSAGE = + "You already have an active Internet subscription. Please contact support to modify your service."; +const DEVELOPMENT_WARNING_SUFFIX = + "Development mode override allows checkout to continue for testing."; +const isDevEnvironment = process.env.NODE_ENV === "development"; + export function useCheckout() { const params = useSearchParams(); const router = useRouter(); @@ -36,6 +42,17 @@ export function useCheckout() { // Load active subscriptions to enforce business rules client-side before submission const { data: activeSubs } = useActiveSubscriptions(); + const hasActiveInternetSubscription = useMemo(() => { + if (!Array.isArray(activeSubs)) return false; + return activeSubs.some( + subscription => + String(subscription.groupName || subscription.productName || "") + .toLowerCase() + .includes("internet") && + String(subscription.status || "").toLowerCase() === "active" + ); + }, [activeSubs]); + const [activeInternetWarning, setActiveInternetWarning] = useState(null); const { data: paymentMethods, @@ -91,6 +108,18 @@ export function useCheckout() { } }, [orderType, params]); + useEffect(() => { + if (orderType !== ORDER_TYPE.INTERNET || !hasActiveInternetSubscription) { + setActiveInternetWarning(null); + return; + } + + const warningMessage = isDevEnvironment + ? `${ACTIVE_INTERNET_WARNING_MESSAGE} ${DEVELOPMENT_WARNING_SUFFIX}` + : ACTIVE_INTERNET_WARNING_MESSAGE; + setActiveInternetWarning(warningMessage); + }, [orderType, hasActiveInternetSubscription]); + useEffect(() => { let mounted = true; @@ -160,18 +189,8 @@ export function useCheckout() { }; // Client-side guard: prevent Internet orders if an Internet subscription already exists - if (orderType === "Internet" && Array.isArray(activeSubs)) { - const hasActiveInternet = activeSubs.some( - s => - String(s.groupName || s.productName || "") - .toLowerCase() - .includes("internet") && String(s.status || "").toLowerCase() === "active" - ); - if (hasActiveInternet) { - throw new Error( - "You already have an active Internet subscription. Please contact support to modify your service." - ); - } + if (orderType === ORDER_TYPE.INTERNET && hasActiveInternetSubscription && !isDevEnvironment) { + throw new Error(ACTIVE_INTERNET_WARNING_MESSAGE); } const response = await ordersService.createOrder(orderData); @@ -183,7 +202,7 @@ export function useCheckout() { } finally { setSubmitting(false); } - }, [checkoutState, orderType, activeSubs, router]); + }, [checkoutState, orderType, hasActiveInternetSubscription, router]); const confirmAddress = useCallback((address?: Address) => { setAddressConfirmed(true); @@ -218,5 +237,6 @@ export function useCheckout() { markAddressIncomplete, handleSubmitOrder, navigateBackToConfigure, + activeInternetWarning, } as const; } diff --git a/apps/portal/src/features/checkout/views/CheckoutContainer.tsx b/apps/portal/src/features/checkout/views/CheckoutContainer.tsx index bb208ba8..0610ea6e 100644 --- a/apps/portal/src/features/checkout/views/CheckoutContainer.tsx +++ b/apps/portal/src/features/checkout/views/CheckoutContainer.tsx @@ -25,6 +25,7 @@ export function CheckoutContainer() { markAddressIncomplete, handleSubmitOrder, navigateBackToConfigure, + activeInternetWarning, } = useCheckout(); if (isLoading(checkoutState)) { @@ -98,6 +99,16 @@ export function CheckoutContainer() { tone={paymentRefresh.toast.tone} /> + {activeInternetWarning && ( + + {activeInternetWarning} + + )} +
diff --git a/cookies.txt b/cookies.txt new file mode 100644 index 00000000..c31d9899 --- /dev/null +++ b/cookies.txt @@ -0,0 +1,4 @@ +# Netscape HTTP Cookie File +# https://curl.se/docs/http-cookies.html +# This file was generated by libcurl! Edit at your own risk. +