}): void;
+ continue(): void;
+};
+
+const CATEGORY_ID_IN_URL = /\/company-categories\/([a-f0-9]{24})(?:\?|\/|$)/i;
+
+/**
+ * Stubs GET list and GET single category. Uses `req.url` string matching so all
+ * request shapes (query strings, rewritten hosts) are handled consistently.
+ */
+export function stubCompanyCategoriesReads(
+ getCategories: () => CompanyCategoryRow[],
+): void {
+ const reply = (req: InterceptRequest) => {
+ const raw = req.url;
+ const detailMatch = raw.match(CATEGORY_ID_IN_URL);
+ if (detailMatch) {
+ const id = detailMatch[1];
+ const cat = getCategories().find((c) => c.id === id);
+ req.reply({
+ statusCode: 200,
+ body: categoryDetailsResponse({
+ name: cat?.name ?? "Category",
+ description: cat?.description ?? "Default description long enough.",
+ thumbnail: cat?.thumbnail ?? "",
+ }),
+ });
+ return;
+ }
+
+ if (raw.includes("/company-categories")) {
+ req.reply({
+ statusCode: 200,
+ body: categoriesListResponse(getCategories()),
+ });
+ return;
+ }
+
+ req.continue();
+ };
+
+ cy.intercept("GET", /\/api\/proxy\/company-categories/, reply);
+ cy.intercept("GET", /\/admin\/v1\/company-categories/, reply);
+}
diff --git a/src/app/(companies)/companies/details/[id]/page.tsx b/src/app/(companies)/companies/details/[id]/page.tsx
index 67a77f8..3b39f1a 100644
--- a/src/app/(companies)/companies/details/[id]/page.tsx
+++ b/src/app/(companies)/companies/details/[id]/page.tsx
@@ -2,20 +2,56 @@
import Loading from "@/components/loading";
import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb";
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
-import IsVisible from "@/components/ui/IsVisible";
import Status from "@/components/ui/Status";
import { useCompanyDetails } from "@/hooks/queries";
+import { cn } from "@/lib/utils";
import { BreadcrumbItem } from "@/types/shared";
import { formatPhoneNumber, unixToDate } from "@/utils/shared";
import getSymbolFromCurrency from "currency-symbol-map";
import Link from "next/link";
-import { useRouter } from "next/navigation";
import { use } from "react";
interface IProps {
params: Promise<{ id: string }>;
}
+function SectionHeading({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+ {children}
+
+
+
+ );
+}
+
+function ExternalLinkIcon({ className }: { className?: string }) {
+ return (
+
+ );
+}
+
+const SHOWCASE_CENTERED_BODY = {
+ rootClassName: "flex h-full min-h-0 flex-col",
+ className:
+ "flex min-h-0 flex-1 flex-col items-center justify-center text-center",
+} as const;
+
const CompanyDetailsPage = ({ params }: IProps) => {
const { id } = use(params);
const breadcrumbItems: BreadcrumbItem[] = [
@@ -33,8 +69,9 @@ const CompanyDetailsPage = ({ params }: IProps) => {
},
];
const { data, isLoading } = useCompanyDetails(id);
- const router = useRouter();
if (isLoading) return ;
+ const company = data.data;
+ const website = company.website?.trim();
return (
<>
@@ -42,71 +79,142 @@ const CompanyDetailsPage = ({ params }: IProps) => {
- {data.data.name}
- {data.data.status}
+
+
+
+ Company
+
+
+ {company.name}
+
+
+
+ {company.status}
+ {website && (
+
+ Visit website
+
+
+ )}
+
}
+ className="!p-0"
>
-
-
-
- {`${data?.data?.currency} ${getSymbolFromCurrency(data?.data.currency ?? "USD")}`}
-
-
-
- {data?.data?.language}
-
-
- {unixToDate({ unix: data?.data?.created_at })}
-
-
- {data?.data?.annual_revenue}
-
-
- {data?.data?.email}
-
-
- {data?.data?.employee_count}
-
-
- {data?.data?.founded_year}
-
-
- {data?.data?.industry}
-
-
- {data?.data?.is_compliant ? "Yes" : "No"}
-
-
- {data?.data?.is_verified ? "Yes" : "No"}
-
-
- {formatPhoneNumber(data?.data?.phone ?? 0)}
-
-
- {data?.data?.registration_number}
-
-
- {data?.data?.tax_id}
-
-
- {data?.data?.type}
-
-
- {unixToDate({ unix: data?.data?.updated_at ?? 0 })}
-
-
-
-
+
+
Profile
+
+
+
+ {company.industry || "—"}
+
+
+
+
+ {company.type || "—"}
+
+
+
+
+ {company.language || "—"}
+
+
+
+
+ {company.founded_year || "—"}
+
+
+
+
+
+
+
Business
+
+
+
+ {`${company.currency || "—"} ${getSymbolFromCurrency(company.currency ?? "USD")}`}
+
+
+
+
+ {company.annual_revenue || "—"}
+
+
+
+
+ {company.employee_count || "—"}
+
+
+
- {data.data.website}
-
-
-
+
+ {company.registration_number || "—"}
+
+
+
+
+ {company.tax_id || "—"}
+
+
+
+
+ {company.is_compliant ? "Yes" : "No"}
+
+
+
+
+ {company.is_verified ? "Yes" : "No"}
+
+
+
+
+
+
+
Contact & timeline
+
+
+
+ {company.email || "—"}
+
+
+
+
+ {formatPhoneNumber(company.phone ?? 0)}
+
+
+
+
+
+
+
+
+ {website && (
+
+
+
+ {website}
+
+
+ )}
+
+
>
diff --git a/src/app/tenders/[details]/page.tsx b/src/app/tenders/[details]/page.tsx
index 7250468..1715592 100644
--- a/src/app/tenders/[details]/page.tsx
+++ b/src/app/tenders/[details]/page.tsx
@@ -22,6 +22,8 @@ interface IProps {
}>;
}
+const isValidDisplayCountryCode = (code: string) => /^[A-Za-z]{2,3}$/.test(code);
+
function SectionHeading({ children }: { children: React.ReactNode }) {
return (
@@ -65,9 +67,11 @@ const TenderDetails = ({ params }: IProps) => {
const { data, isLoading, isError } = useTenderDetailQuery(details);
const countryCodeNormalized =
data?.data.country_code?.trim().toUpperCase() ?? "";
- const countryCodeValid = isValidAlpha2CountryCode(countryCodeNormalized);
+ const hasValidCountryCodeForDisplay =
+ isValidDisplayCountryCode(countryCodeNormalized);
+ const canLoadCountryFlag = isValidAlpha2CountryCode(countryCodeNormalized);
const { data: flagData } = useGetFlagQuery(countryCodeNormalized, {
- enabled: countryCodeValid,
+ enabled: canLoadCountryFlag,
});
const breadcrumbItems = [
{ name: "Dashboard", href: "/" },
@@ -103,6 +107,14 @@ const TenderDetails = ({ params }: IProps) => {
const tender = data!.data;
const submissionUrl = tender.submission_url?.trim();
+ const currencyCode = tender.currency?.trim().toUpperCase() ?? "";
+ const hasValidCurrency = Boolean(currencyCode);
+ const hasValidEstimatedValue =
+ typeof tender.estimated_value === "number" &&
+ Number.isFinite(tender.estimated_value) &&
+ tender.estimated_value > 0;
+ const hasValidLocationCurrencyOrValue =
+ hasValidCountryCodeForDisplay || hasValidCurrency || hasValidEstimatedValue;
return (
<>
@@ -252,50 +264,73 @@ const TenderDetails = ({ params }: IProps) => {
-
-
Location & currency
-
-
-
-
- Country
-
-
-
-
-
-
-
- {tender.country_code}
-
- {flagData && (
-
- )}
+ {hasValidLocationCurrencyOrValue && (
+
+
Location & currency
+
+ {hasValidCountryCodeForDisplay && (
+
+
+
+ Country
+
+
+
+
+
+
+
+ {countryCodeNormalized}
+
+ {flagData && (
+
+ )}
+
+
-
-
+ )}
- {tender.currency && (
-
-
-
- Currency
-
-
-
- {tender.currency}
-
-
- {getSymbolFromCurrency(tender.currency ?? "USD")}
-
+ {hasValidCurrency && (
+
+
+
+ Currency
+
+
+
+ {currencyCode}
+
+
+ {getSymbolFromCurrency(currencyCode || "USD")}
+
+
-
- )}
+ )}
+
+ {hasValidEstimatedValue && (
+
+
+
+ Estimated value
+
+
+
+ {new Intl.NumberFormat().format(tender.estimated_value)}
+
+ {hasValidCurrency && (
+
+ {currencyCode}
+
+ )}
+
+
+ )}
+
-
+ )}
Description
diff --git a/src/components/FormElements/InputGroup/text-area.tsx b/src/components/FormElements/InputGroup/text-area.tsx
index 1294229..8b9d3c5 100644
--- a/src/components/FormElements/InputGroup/text-area.tsx
+++ b/src/components/FormElements/InputGroup/text-area.tsx
@@ -18,6 +18,7 @@ type PropsType
= {
className?: string;
icon?: React.ReactNode;
defaultValue?: string;
+ dataCy?: string;
handleChange?: (e: React.ChangeEvent) => void;
register?: UseFormRegister;
errors?: FieldErrors;
@@ -33,6 +34,7 @@ export function TextAreaGroup({
className,
icon,
defaultValue,
+ dataCy,
handleChange,
register,
errors,
@@ -77,6 +79,7 @@ export function TextAreaGroup({
required={required}
disabled={disabled}
data-active={active}
+ data-cy={dataCy}
/>
{icon}
diff --git a/src/components/FormElements/switch.tsx b/src/components/FormElements/switch.tsx
index 358ad2b..c94f557 100644
--- a/src/components/FormElements/switch.tsx
+++ b/src/components/FormElements/switch.tsx
@@ -8,6 +8,7 @@ type PropsType = {
backgroundSize?: "sm" | "default";
name?: string;
checked?: boolean;
+ dataCy?: string;
onToggle: (e: ToggleEvent) => void;
onChange: (e: ChangeEvent) => void;
};
@@ -18,6 +19,7 @@ export function Switch({
backgroundSize,
name,
checked = false,
+ dataCy,
onToggle,
onChange,
}: PropsType) {
@@ -37,6 +39,7 @@ export function Switch({
id={id}
className="peer sr-only"
checked={checked}
+ data-cy={dataCy}
/>