Update .gitignore to include SHA256 checksum files and refresh address handling in hooks

- Added '*.tar.gz.sha256' to .gitignore to exclude SHA256 checksum files from version control.
- Updated SHA256 checksums for the latest portal backend and frontend tar.gz files to reflect new builds.
- Enhanced address handling in `useAddressEdit`, `useProfileData`, and `AddressConfirmation` components to invalidate catalog queries upon address updates, ensuring accurate server-personalized results.
- Introduced new query key for catalog queries in the API to streamline cache management.
This commit is contained in:
barsa 2025-12-15 10:32:07 +09:00
parent b193361a72
commit 9764ccfbad
11 changed files with 66 additions and 43 deletions

1
.gitignore vendored
View File

@ -156,6 +156,7 @@ prisma/migrations/dev.db*
# Large archive files
*.tar
*.tar.gz
*.tar.gz.sha256
*.zip
# API Documentation (contains sensitive API details)

View File

@ -1,6 +1,8 @@
"use client";
import { useCallback } from "react";
import { useQueryClient } from "@tanstack/react-query";
import { queryKeys } from "@/lib/api";
import { accountService } from "@/features/account/services/account.service";
import {
addressFormSchema,
@ -10,10 +12,17 @@ import {
import { useZodForm } from "@/hooks/useZodForm";
export function useAddressEdit(initial: AddressFormData) {
const handleSave = useCallback(async (formData: AddressFormData) => {
const requestData = addressFormToRequest(formData);
await accountService.updateAddress(requestData);
}, []);
const queryClient = useQueryClient();
const handleSave = useCallback(
async (formData: 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() });
},
[queryClient]
);
return useZodForm({
schema: addressFormSchema,

View File

@ -1,6 +1,8 @@
"use client";
import { useEffect, useState, useCallback } from "react";
import { useQueryClient } from "@tanstack/react-query";
import { queryKeys } from "@/lib/api";
import { useAuthStore } from "@/features/auth/services/auth.store";
import { accountService } from "@/features/account/services/account.service";
import { logger } from "@/lib/logger";
@ -10,6 +12,7 @@ import type { ProfileEditFormData, Address } from "@customer-portal/domain/custo
export function useProfileData() {
const { user } = useAuthStore();
const queryClient = useQueryClient();
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [isSavingProfile, setIsSavingProfile] = useState(false);
@ -112,6 +115,8 @@ export function useProfileData() {
phoneNumber: next.phoneNumber,
phoneCountryCode: next.phoneCountryCode,
});
// Address changes can affect server-personalized catalog results (eligibility).
await queryClient.invalidateQueries({ queryKey: queryKeys.catalog.all() });
setBillingInfo({ address: next });
setAddress(next);
return true;

View File

@ -6,11 +6,13 @@ import { Button } from "@/components/atoms/button";
import { SubCard } from "@/components/molecules/SubCard/SubCard";
import { useState, useEffect, useCallback } from "react";
import { useQueryClient } from "@tanstack/react-query";
import { accountService } from "@/features/account/services/account.service";
import { log } from "@/lib/logger";
import { StatusPill } from "@/components/atoms/status-pill";
import { MapPinIcon, PencilIcon, CheckIcon, XMarkIcon } from "@heroicons/react/24/outline";
import { COUNTRY_OPTIONS, getCountryName } from "@/lib/constants/countries";
import { queryKeys } from "@/lib/api";
// Use canonical Address type from domain
import type { Address } from "@customer-portal/domain/customer";
@ -37,6 +39,7 @@ export function AddressConfirmation({
orderType,
embedded = false,
}: AddressConfirmationProps) {
const queryClient = useQueryClient();
const [billingInfo, setBillingInfo] = useState<BillingInfo | null>(null);
const [loading, setLoading] = useState(true);
const [editing, setEditing] = useState(false);
@ -152,6 +155,9 @@ export function AddressConfirmation({
// Persist to server (WHMCS via BFF)
const updatedAddress = await accountService.updateAddress(sanitizedAddress);
// Address changes can affect server-personalized catalog results (eligibility).
await queryClient.invalidateQueries({ queryKey: queryKeys.catalog.all() });
// Rebuild BillingInfo from updated address
const updatedInfo: BillingInfo = {
company: null,

View File

@ -140,6 +140,7 @@ export const queryKeys = {
summary: () => ["dashboard", "summary"] as const,
},
catalog: {
all: () => ["catalog"] as const,
products: () => ["catalog", "products"] as const,
internet: {
combined: () => ["catalog", "internet", "combined"] as const,

View File

@ -284,8 +284,7 @@ export function createClient(options: CreateClientOptions = {}): ApiClient {
opts: RequestOptions = {}
): Promise<ApiResponse<T>> => {
const resolvedPath = applyPathParams(path, opts.params?.path);
const normalizedPath = normalizeApiPath(resolvedPath);
const url = new URL(normalizedPath, baseUrl);
const url = new URL(resolvedPath, baseUrl);
const queryString = buildQueryString(opts.params?.query);
if (queryString) {

View File

@ -42,7 +42,7 @@ We require a Customer Number (SF Number) at signup and gate checkout on the pres
- Mapping is stored: `portalUserId ↔ whmcsClientId ↔ sfAccountId`.
2. Add payment method (required before checkout)
- Portal shows an “Add payment method” CTA that opens WHMCS payment methods via SSO (`POST /auth/sso-link` → `index.php?rp=/account/paymentmethods`).
- Portal shows an “Add payment method” CTA that opens WHMCS payment methods via SSO (`POST /api/auth/sso-link` → `index.php?rp=/account/paymentmethods`).
- Portal checks `GET /billing/payment-methods/summary` to confirm presence before enabling checkout.
3. Browse catalog and configure
@ -138,7 +138,7 @@ Implementation notes:
- `GET /billing/payment-methods/summary` (new)
- Returns `{ hasPaymentMethod: boolean }` using WHMCS `GetPayMethods` for mapped client.
- `POST /auth/sso-link` (exists)
- `POST /api/auth/sso-link` (exists)
- Used to open WHMCS payment methods and invoice/pay pages.
### 2.5 Catalog (Salesforce Product2 as Source of Truth)

View File

@ -30,7 +30,7 @@ This roadmap references `PORTAL-ORDERING-PROVISIONING.md` (complete flows and ar
5. Portal UI: Address & payment method
- Address step after signup; `PATCH /api/me/address` to update address fields.
- Payment methods page/button: `POST /auth/sso-link` to WHMCS payment methods; show banner on dashboard until `GET /billing/payment-methods/summary` is true.
- Payment methods page/button: `POST /api/auth/sso-link` to WHMCS payment methods; show banner on dashboard until `GET /billing/payment-methods/summary` is true.
## Phase 3 Catalog

View File

@ -63,23 +63,23 @@ apps/portal/src/
### ✅ `lib/` - Truly Generic, Reusable Across Features
| File | Purpose | Used By |
|------|---------|---------|
| `lib/api/client.ts` | API client instance | All features |
| `lib/api/query-keys.ts` | React Query keys factory | All features |
| `lib/api/helpers.ts` | `getDataOrThrow`, `getDataOrDefault` | All features |
| `lib/utils/cn.ts` | Tailwind className merger | All components |
| `lib/utils/error-handling.ts` | Generic error parsing | All features |
| `lib/providers.tsx` | Root providers (QueryClient, Theme) | App root |
| File | Purpose | Used By |
| ----------------------------- | ------------------------------------ | -------------- |
| `lib/api/client.ts` | API client instance | All features |
| `lib/api/query-keys.ts` | React Query keys factory | All features |
| `lib/api/helpers.ts` | `getDataOrThrow`, `getDataOrDefault` | All features |
| `lib/utils/cn.ts` | Tailwind className merger | All components |
| `lib/utils/error-handling.ts` | Generic error parsing | All features |
| `lib/providers.tsx` | Root providers (QueryClient, Theme) | App root |
### ✅ `features/*/hooks/` - Feature-Specific Hooks
| File | Purpose | Used By |
|------|---------|---------|
| `features/billing/hooks/useBilling.ts` | Invoice queries & mutations | Billing pages only |
| `features/subscriptions/hooks/useSubscriptions.ts` | Subscription queries | Subscription pages only |
| `features/orders/hooks/useOrders.ts` | Order queries | Order pages only |
| `features/auth/hooks/useAuth.ts` | Auth state & actions | Auth-related components |
| File | Purpose | Used By |
| -------------------------------------------------- | --------------------------- | ----------------------- |
| `features/billing/hooks/useBilling.ts` | Invoice queries & mutations | Billing pages only |
| `features/subscriptions/hooks/useSubscriptions.ts` | Subscription queries | Subscription pages only |
| `features/orders/hooks/useOrders.ts` | Order queries | Order pages only |
| `features/auth/hooks/useAuth.ts` | Auth state & actions | Auth-related components |
---
@ -94,6 +94,7 @@ lib/
```
**Why this is bad:**
- Hard to find (is it in `lib` or `features`?)
- Breaks feature encapsulation
- Harder to delete features
@ -134,10 +135,7 @@ export function getDataOrThrow<T>(
return response.data;
}
export function getDataOrDefault<T>(
response: { data?: T; error?: unknown },
defaultValue: T
): T {
export function getDataOrDefault<T>(response: { data?: T; error?: unknown }, defaultValue: T): T {
return response.data ?? defaultValue;
}
@ -263,12 +261,12 @@ export function usePaymentMethods() {
export function useCreateInvoiceSsoLink() {
return useMutation({
mutationFn: async ({
invoiceId,
target
}: {
invoiceId: number;
target?: "view" | "download" | "pay"
mutationFn: async ({
invoiceId,
target,
}: {
invoiceId: number;
target?: "view" | "download" | "pay";
}) => {
const response = await apiClient.POST<InvoiceSsoLink>("/api/invoices/{id}/sso-link", {
params: {
@ -284,7 +282,7 @@ export function useCreateInvoiceSsoLink() {
export function useCreatePaymentMethodsSsoLink() {
return useMutation({
mutationFn: async () => {
const response = await apiClient.POST<InvoiceSsoLink>("/auth/sso-link", {
const response = await apiClient.POST<InvoiceSsoLink>("/api/auth/sso-link", {
body: { destination: "index.php?rp=/account/paymentmethods" },
});
return getDataOrThrow(response, "Failed to create payment methods SSO link");
@ -342,7 +340,7 @@ import { useInvoices } from "@/features/billing/hooks/useBilling";
function InvoicesPage() {
const { data: invoices, isLoading } = useInvoices({ status: "Unpaid" });
// ...
}
```
@ -354,11 +352,11 @@ function InvoicesPage() {
import { apiClient, queryKeys, getDataOrThrow } from "@/lib/api";
// ✅ Import domain types
import {
import {
type Invoice,
type InvoiceList,
invoiceSchema,
type InvoiceQueryParams
type InvoiceQueryParams,
} from "@customer-portal/domain/billing";
export function useInvoices(params?: InvoiceQueryParams) {
@ -373,10 +371,10 @@ export function useInvoices(params?: InvoiceQueryParams) {
import { apiClient, queryKeys, getDataOrThrow } from "@/lib/api";
// ✅ Domain types for subscriptions
import {
type Subscription,
import {
type Subscription,
subscriptionSchema,
type SubscriptionQueryParams
type SubscriptionQueryParams,
} from "@customer-portal/domain/subscriptions";
export function useSubscriptions(params?: SubscriptionQueryParams) {
@ -389,11 +387,13 @@ export function useSubscriptions(params?: SubscriptionQueryParams) {
## 📋 Benefits of This Structure
### 1. **Clear Separation of Concerns**
- `api/` - HTTP client & infrastructure
- `hooks/` - React Query abstractions
- `utils/` - Helper functions
### 2. **Clean Imports**
```typescript
// ❌ Before: Messy
import { apiClient, queryKeys, getDataOrDefault, getDataOrThrow } from "@/lib/api";
@ -413,12 +413,15 @@ import {
```
### 3. **Easy to Find Things**
- Need a query hook? → `lib/hooks/queries/`
- Need API utilities? → `lib/api/`
- Need to update a domain type? → `packages/domain/billing/`
### 4. **Testable**
Each piece can be tested independently:
- API helpers are pure functions
- Hooks can be tested with React Testing Library
- Domain logic is already in domain package
@ -440,4 +443,3 @@ import { invoiceSchema } from "@customer-portal/domain/billing";
```
Let me check if this old validation path exists and needs cleanup.

View File

@ -1 +1 @@
b9e6a7c804df143f276ec06e4411004e08475923b35e8c29fb20495b1a637e61 /home/barsa/projects/customer_portal/customer-portal/portal-backend.latest.tar.gz
32dd63df821868464fa1df1f9de8966b40b38da7cc969a37aa0aee1ef9c83215 /home/barsa/projects/customer_portal/customer-portal/portal-backend.latest.tar.gz

View File

@ -1 +1 @@
d342327a541914cf92d768189597fb2323e1faf55d2eadfb56edc8cf5cec7a75 /home/barsa/projects/customer_portal/customer-portal/portal-frontend.latest.tar.gz
c4ee8a17de6dfad930a2d8d983b2cc5055e2e0baa1625da59f33398325ddaa36 /home/barsa/projects/customer_portal/customer-portal/portal-frontend.latest.tar.gz