diff --git a/web/apps/admin/src/pages/admins/AdminsPage.tsx b/web/apps/admin/src/pages/admins/AdminsPage.tsx index c82e4d79b..7b8140ebf 100644 --- a/web/apps/admin/src/pages/admins/AdminsPage.tsx +++ b/web/apps/admin/src/pages/admins/AdminsPage.tsx @@ -1,5 +1,6 @@ import { AdminsView, useAdminPaths } from "@raystack/frontier/admin"; import { useNavigate } from "react-router-dom"; +import AdminsIcon from "~/assets/icons/admins.svg?react"; export function AdminsPage() { const navigate = useNavigate(); @@ -8,6 +9,7 @@ export function AdminsPage() { return ( navigate(`/${paths.organizations}/${orgId}`)} + icon={} /> ); } diff --git a/web/apps/admin/src/pages/plans/PlansPage.tsx b/web/apps/admin/src/pages/plans/PlansPage.tsx index a79d074f5..ce78058b8 100644 --- a/web/apps/admin/src/pages/plans/PlansPage.tsx +++ b/web/apps/admin/src/pages/plans/PlansPage.tsx @@ -1,5 +1,6 @@ import { PlansView } from "@raystack/frontier/admin"; import { useNavigate, useParams } from "react-router-dom"; +import PlansIcon from "~/assets/icons/plans.svg?react"; export function PlansPage() { const { planId } = useParams(); @@ -10,6 +11,7 @@ export function PlansPage() { selectedPlanId={planId} onCloseDetail={() => navigate("/plans")} onSelectPlan={(id: string) => navigate(`/plans/${id}`)} + icon={} /> ); } diff --git a/web/apps/admin/src/pages/preferences/PreferencesPage.tsx b/web/apps/admin/src/pages/preferences/PreferencesPage.tsx index 49f040800..43c716895 100644 --- a/web/apps/admin/src/pages/preferences/PreferencesPage.tsx +++ b/web/apps/admin/src/pages/preferences/PreferencesPage.tsx @@ -1,5 +1,6 @@ import { useParams, useNavigate } from "react-router-dom"; import { PreferencesView } from "@raystack/frontier/admin"; +import PreferencesIcon from "~/assets/icons/preferences.svg?react"; export function PreferencesPage() { const { name } = useParams(); @@ -10,6 +11,7 @@ export function PreferencesPage() { selectedPreferenceName={name} onCloseDetail={() => navigate("/preferences")} onSelectPreference={(prefName: string) => navigate(`/preferences/${prefName}`)} + icon={} /> ); } diff --git a/web/apps/admin/src/pages/products/ProductsPage.tsx b/web/apps/admin/src/pages/products/ProductsPage.tsx index 141459943..a36dd0bab 100644 --- a/web/apps/admin/src/pages/products/ProductsPage.tsx +++ b/web/apps/admin/src/pages/products/ProductsPage.tsx @@ -1,5 +1,6 @@ import { ProductsView } from "@raystack/frontier/admin"; import { useParams, useNavigate } from "react-router-dom"; +import ProductsIcon from "~/assets/icons/products.svg?react"; export function ProductsPage() { const { productId } = useParams(); @@ -11,6 +12,7 @@ export function ProductsPage() { onSelectProduct={(id) => navigate(`/products/${encodeURIComponent(id)}`)} onCloseDetail={() => navigate("/products")} onNavigateToPrices={(id) => navigate(`/products/${encodeURIComponent(id)}/prices`)} + icon={} /> ); } diff --git a/web/apps/admin/src/pages/roles/RolesPage.tsx b/web/apps/admin/src/pages/roles/RolesPage.tsx index bbdd2ae95..ae4b8248d 100644 --- a/web/apps/admin/src/pages/roles/RolesPage.tsx +++ b/web/apps/admin/src/pages/roles/RolesPage.tsx @@ -1,5 +1,6 @@ import { RolesView } from "@raystack/frontier/admin"; import { useParams, useNavigate } from "react-router-dom"; +import RolesIcon from "~/assets/icons/roles.svg?react"; export function RolesPage() { const { roleId } = useParams(); @@ -10,6 +11,7 @@ export function RolesPage() { selectedRoleId={roleId} onSelectRole={(id) => navigate(`/roles/${encodeURIComponent(id)}`)} onCloseDetail={() => navigate("/roles")} + icon={} /> ); } diff --git a/web/apps/admin/src/pages/webhooks/WebhooksPage.tsx b/web/apps/admin/src/pages/webhooks/WebhooksPage.tsx index a467f6a99..510d6c0ab 100644 --- a/web/apps/admin/src/pages/webhooks/WebhooksPage.tsx +++ b/web/apps/admin/src/pages/webhooks/WebhooksPage.tsx @@ -2,6 +2,7 @@ import { useContext } from "react"; import { useMatch, useParams, useNavigate } from "react-router-dom"; import { WebhooksView } from "@raystack/frontier/admin"; import { AppContext } from "~/contexts/App"; +import WebhooksIcon from "~/assets/icons/webhooks.svg?react"; export function WebhooksPage() { const { config } = useContext(AppContext); @@ -19,6 +20,7 @@ export function WebhooksPage() { onSelectWebhook={(id: string) => navigate(`/webhooks/${encodeURIComponent(id)}`)} onOpenCreate={() => navigate("/webhooks/create")} enableDelete={enableDelete} + icon={} /> ); } diff --git a/web/sdk/admin/components/PageHeader.tsx b/web/sdk/admin/components/PageHeader.tsx index bd973f61e..9d0588526 100644 --- a/web/sdk/admin/components/PageHeader.tsx +++ b/web/sdk/admin/components/PageHeader.tsx @@ -1,9 +1,10 @@ -import type { CSSProperties, PropsWithChildren } from "react"; +import type { CSSProperties, PropsWithChildren, ReactNode } from "react"; import { Flex, Text } from "@raystack/apsara-v1"; import styles from "./page-header.module.css"; export type PageHeaderTypes = { title: string; + icon?: ReactNode; breadcrumb: { name: string; href?: string }[]; // eslint-disable-next-line no-unused-vars -- callback param name is for type documentation onBreadcrumbClick?: (item: { name: string; href?: string }) => void; @@ -13,6 +14,7 @@ export type PageHeaderTypes = { export function PageHeader({ title, + icon, breadcrumb, onBreadcrumbClick, children, @@ -25,11 +27,12 @@ export function PageHeader({ align="center" justify="between" className={className} - style={{ padding: "16px 24px", ...style }} + style={{ padding: "16px 24px", minHeight: "var(--rs-space-12)", ...style }} {...props} > - + + {icon} {title} {breadcrumb.map((item) => item.href && onBreadcrumbClick ? ( diff --git a/web/sdk/admin/views/admins/index.tsx b/web/sdk/admin/views/admins/index.tsx index c30917825..a00431fa6 100644 --- a/web/sdk/admin/views/admins/index.tsx +++ b/web/sdk/admin/views/admins/index.tsx @@ -1,4 +1,5 @@ import { DataTable, EmptyState, Flex } from "@raystack/apsara-v1"; +import type { ReactNode } from "react"; import { getColumns } from "./columns"; import styles from "./admins.module.css"; import { useQuery } from "@connectrpc/connect-query"; @@ -24,9 +25,11 @@ const NoAdmins = () => { export type AdminsViewProps = { onNavigateToOrg?: (orgId: string) => void; + /** Icon rendered in the page header next to the title. */ + icon?: ReactNode; }; -export default function AdminsView({ onNavigateToOrg }: AdminsViewProps = {}) { +export default function AdminsView({ onNavigateToOrg, icon }: AdminsViewProps = {}) { const t = useTerminology(); const { data: platformUsersData, @@ -68,6 +71,7 @@ export default function AdminsView({ onNavigateToOrg }: AdminsViewProps = {}) { diff --git a/web/sdk/admin/views/audit-logs/audit-logs.module.css b/web/sdk/admin/views/audit-logs/audit-logs.module.css index 1a804ed1b..3f96b462c 100644 --- a/web/sdk/admin/views/audit-logs/audit-logs.module.css +++ b/web/sdk/admin/views/audit-logs/audit-logs.module.css @@ -5,6 +5,7 @@ display: flex; align-items: center; justify-content: space-between; + min-height: var(--rs-space-12); } .table { diff --git a/web/sdk/admin/views/invoices/invoices.module.css b/web/sdk/admin/views/invoices/invoices.module.css index 11c2c8fc7..f2bc00e9a 100644 --- a/web/sdk/admin/views/invoices/invoices.module.css +++ b/web/sdk/admin/views/invoices/invoices.module.css @@ -5,6 +5,7 @@ display: flex; align-items: center; justify-content: space-between; + min-height: var(--rs-space-12); } .table { diff --git a/web/sdk/admin/views/organizations/list/list.module.css b/web/sdk/admin/views/organizations/list/list.module.css index 91d53154e..8306a41b5 100644 --- a/web/sdk/admin/views/organizations/list/list.module.css +++ b/web/sdk/admin/views/organizations/list/list.module.css @@ -5,6 +5,7 @@ display: flex; align-items: center; justify-content: space-between; + min-height: var(--rs-space-12); } .table { diff --git a/web/sdk/admin/views/plans/header.tsx b/web/sdk/admin/views/plans/header.tsx deleted file mode 100644 index 69302bd6a..000000000 --- a/web/sdk/admin/views/plans/header.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { DataTable } from "@raystack/apsara-v1"; -import { PageHeader } from "../../components/PageHeader"; -import styles from "./plans.module.css"; - -export const PlanHeader = ({ header }: any) => { - return ( - - - - ); -}; diff --git a/web/sdk/admin/views/plans/index.tsx b/web/sdk/admin/views/plans/index.tsx index 76354c8ac..5e1e30fa9 100644 --- a/web/sdk/admin/views/plans/index.tsx +++ b/web/sdk/admin/views/plans/index.tsx @@ -1,21 +1,17 @@ import { EmptyState, Flex, DataTable, Drawer } from "@raystack/apsara-v1"; +import type { ReactNode } from "react"; import type { Plan } from "@raystack/proton/frontier"; import { reduceByKey } from "../../utils/helper"; import { getColumns } from "./columns"; -import { PlanHeader } from "./header"; import { ExclamationTriangleIcon } from "@radix-ui/react-icons"; import styles from "./plans.module.css"; +import { PageHeader } from "../../components/PageHeader"; import { PageTitle } from "../../components/PageTitle"; import { SheetHeader } from "../../components/SheetHeader"; import { useQuery } from "@connectrpc/connect-query"; import { FrontierServiceQueries } from "@raystack/proton/frontier"; import PlanDetails from "./details"; -const pageHeader = { - title: "Plans", - breadcrumb: [], -}; - export type PlansViewProps = { /** When set, opens the detail sheet for this plan. */ selectedPlanId?: string; @@ -25,6 +21,8 @@ export type PlansViewProps = { onSelectPlan?: (planId: string) => void; /** App name displayed in the page title. */ appName?: string; + /** Icon rendered in the page header next to the title. */ + icon?: ReactNode; }; export default function PlansView({ @@ -32,6 +30,7 @@ export default function PlansView({ onCloseDetail, onSelectPlan, appName, + icon, }: PlansViewProps = {}) { const { data: plansResponse, @@ -70,7 +69,14 @@ export default function PlansView({ > - + + + void; onSelectPreference?: (name: string) => void; + /** Icon rendered in the page header next to the title. */ + icon?: ReactNode; }; export default function PreferencesView({ selectedPreferenceName, onCloseDetail, onSelectPreference, + icon, }: PreferencesViewProps = {}) { const transport = useTransport(); @@ -85,6 +89,7 @@ export default function PreferencesView({ traits={traits} isLoading={isLoading} onSelectPreference={onSelectPreference} + icon={icon} /> ); diff --git a/web/sdk/admin/views/preferences/index.tsx b/web/sdk/admin/views/preferences/index.tsx index e3cfa148e..45d6b13cf 100644 --- a/web/sdk/admin/views/preferences/index.tsx +++ b/web/sdk/admin/views/preferences/index.tsx @@ -1,4 +1,5 @@ import { EmptyState, DataTable, Flex } from "@raystack/apsara-v1"; +import type { ReactNode } from "react"; import { Preference, PreferenceTrait } from "@raystack/proton/frontier"; import { PageHeader } from "../../components/PageHeader"; import { getColumns } from "./columns"; @@ -15,6 +16,7 @@ export type PreferencesListProps = { traits: PreferenceTrait[]; isLoading: boolean; onSelectPreference?: (name: string) => void; + icon?: ReactNode; }; export default function PreferencesList({ @@ -22,6 +24,7 @@ export default function PreferencesList({ traits, isLoading, onSelectPreference, + icon, }: PreferencesListProps) { const columns = getColumns({ traits, @@ -40,6 +43,7 @@ export default function PreferencesList({ diff --git a/web/sdk/admin/views/products/header.tsx b/web/sdk/admin/views/products/header.tsx index 7a8af2bd1..a958968ab 100644 --- a/web/sdk/admin/views/products/header.tsx +++ b/web/sdk/admin/views/products/header.tsx @@ -1,3 +1,4 @@ +import type { ReactNode } from "react"; import { DataTable } from "@raystack/apsara-v1"; import { PageHeader } from "../../components/PageHeader"; import styles from "./products.module.css"; @@ -13,14 +14,17 @@ const defaultPageHeader = { export const ProductsHeader = ({ header = defaultPageHeader, onBreadcrumbClick, + icon, }: { header?: typeof defaultPageHeader; // eslint-disable-next-line no-unused-vars -- callback param name is for type documentation onBreadcrumbClick?: (item: { name: string; href?: string }) => void; + icon?: ReactNode; }) => { return ( void; /** App name displayed in the page title. */ appName?: string; + /** Icon rendered in the page header next to the title. */ + icon?: ReactNode; }; export default function ProductsView({ @@ -29,6 +32,7 @@ export default function ProductsView({ onCloseDetail, onNavigateToPrices, appName, + icon, }: ProductsViewProps = {}) { const { data: productsResponse, @@ -78,7 +82,7 @@ export default function ProductsView({ onRowClick={handleRowClick} > - + { - return ( - - - - ); -}; diff --git a/web/sdk/admin/views/roles/index.tsx b/web/sdk/admin/views/roles/index.tsx index 8c55c5794..42601e83c 100644 --- a/web/sdk/admin/views/roles/index.tsx +++ b/web/sdk/admin/views/roles/index.tsx @@ -1,10 +1,10 @@ import { EmptyState, Flex, DataTable, Drawer } from "@raystack/apsara-v1"; -import { useCallback, useState } from "react"; +import { useCallback, useState, type ReactNode } from "react"; import { reduceByKey } from "../../utils/helper"; import { getColumns } from "./columns"; -import { RolesHeader } from "./header"; import { ExclamationTriangleIcon } from "@radix-ui/react-icons"; +import { PageHeader } from "../../components/PageHeader"; import { PageTitle } from "../../components/PageTitle"; import styles from "./roles.module.css"; import { SheetHeader } from "../../components/SheetHeader"; @@ -22,6 +22,8 @@ export type RolesViewProps = { onCloseDetail?: () => void; /** App name displayed in the page title. */ appName?: string; + /** Icon rendered in the page header next to the title. */ + icon?: ReactNode; }; export default function RolesView({ @@ -29,6 +31,7 @@ export default function RolesView({ onSelectRole, onCloseDetail, appName, + icon, }: RolesViewProps = {}) { const [internalRoleId, setInternalRoleId] = useState(); @@ -82,7 +85,14 @@ export default function RolesView({ isLoading={isLoading}> - + + + void; -}; - -export const WebhooksHeader = ({ header = pageHeader, onOpenCreate }: WebhooksHeaderProps) => { - const handleCreate = () => onOpenCreate?.(); - - return ( - - - - - ); -}; diff --git a/web/sdk/admin/views/webhooks/webhooks/index.tsx b/web/sdk/admin/views/webhooks/webhooks/index.tsx index 6ea92337d..f40d731f8 100644 --- a/web/sdk/admin/views/webhooks/webhooks/index.tsx +++ b/web/sdk/admin/views/webhooks/webhooks/index.tsx @@ -1,10 +1,10 @@ -import { Flex, DataTable, EmptyState } from "@raystack/apsara-v1"; -import { useCallback } from "react"; +import { Button, Flex, DataTable, EmptyState } from "@raystack/apsara-v1"; +import { useCallback, type ReactNode } from "react"; import { getColumns } from "./columns"; -import { WebhooksHeader } from "./header"; import styles from "./webhooks.module.css"; import { useWebhookQueries } from "./hooks/useWebhookQueries"; -import { ExclamationTriangleIcon } from "@radix-ui/react-icons"; +import { ExclamationTriangleIcon, PlusIcon } from "@radix-ui/react-icons"; +import { PageHeader } from "../../../components/PageHeader"; import CreateWebhooks from "./create"; import UpdateWebhooks from "./update"; @@ -21,6 +21,8 @@ export type WebhooksViewProps = { onOpenCreate?: () => void; /** When true, shows the delete option for webhooks. Defaults to `false`. */ enableDelete?: boolean; + /** Icon rendered in the page header next to the title. */ + icon?: ReactNode; }; export default function WebhooksView({ @@ -30,6 +32,7 @@ export default function WebhooksView({ onSelectWebhook, onOpenCreate, enableDelete = false, + icon, }: WebhooksViewProps = {}) { const { listWebhooks: { @@ -80,7 +83,24 @@ export default function WebhooksView({ mode="client" > - + + + +