diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 80e8d8912..936f8307b 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -28,6 +28,10 @@ const navigationItems: NavigationItemsTypes[] = [ name: "Groups", to: `/groups`, }, + { + name: "Plans", + to: `/plans`, + }, { name: "Roles", to: `/roles`, diff --git a/ui/src/containers/billingplans.list/columns.tsx b/ui/src/containers/billingplans.list/columns.tsx new file mode 100644 index 000000000..4dae1138d --- /dev/null +++ b/ui/src/containers/billingplans.list/columns.tsx @@ -0,0 +1,48 @@ +import { V1Beta1Plan } from "@raystack/frontier"; +import type { ColumnDef } from "@tanstack/react-table"; +import { createColumnHelper } from "@tanstack/react-table"; +import { Link } from "react-router-dom"; + +const columnHelper = createColumnHelper(); +export const getColumns: ( + plans: V1Beta1Plan[] +) => ColumnDef[] = (plans: V1Beta1Plan[]) => { + return [ + columnHelper.accessor("id", { + header: "ID", + //@ts-ignore + filterVariant: "text", + cell: ({ row, getValue }) => { + return {getValue()}; + }, + }), + { + header: "Title", + accessorKey: "title", + filterVariant: "text", + cell: (info) => info.getValue(), + }, + { + header: "Interval", + accessorKey: "interval", + filterVariant: "text", + cell: (info) => info.getValue(), + footer: (props) => props.column.id, + }, + { + header: "Create At", + accessorKey: "created_at", + meta: { + headerFilter: false, + }, + cell: (info) => + new Date(info.getValue() as Date).toLocaleString("en", { + month: "long", + day: "numeric", + year: "numeric", + }), + + footer: (props) => props.column.id, + }, + ]; +}; diff --git a/ui/src/containers/billingplans.list/details.tsx b/ui/src/containers/billingplans.list/details.tsx new file mode 100644 index 000000000..453226bb1 --- /dev/null +++ b/ui/src/containers/billingplans.list/details.tsx @@ -0,0 +1,44 @@ +import { Flex, Grid, Text } from "@raystack/apsara"; +import { usePlan } from "."; +export default function PlanDetails() { + const { plan } = usePlan(); + + return ( + + {plan?.name} + + + Name + {plan?.name} + + + Interval + {plan?.interval} + + + Created At + + {new Date(plan?.created_at as any).toLocaleString("en", { + month: "long", + day: "numeric", + year: "numeric", + })} + + + + + ); +} + +const css = { + row: { height: "32px", display: "flex", alignItems: "center" }, +}; diff --git a/ui/src/containers/billingplans.list/header.tsx b/ui/src/containers/billingplans.list/header.tsx new file mode 100644 index 000000000..63fe9d23a --- /dev/null +++ b/ui/src/containers/billingplans.list/header.tsx @@ -0,0 +1,17 @@ +import { DataTable, useTable } from "@raystack/apsara"; +import { useNavigate } from "react-router-dom"; +import PageHeader from "~/components/page-header"; + +export const PlanHeader = ({ header }: any) => { + const navigate = useNavigate(); + const { filteredColumns, table } = useTable(); + const isFiltered = filteredColumns.length > 0; + + return ( + + {isFiltered ? : } + + + + ); +}; diff --git a/ui/src/containers/billingplans.list/index.tsx b/ui/src/containers/billingplans.list/index.tsx new file mode 100644 index 000000000..acb8254bc --- /dev/null +++ b/ui/src/containers/billingplans.list/index.tsx @@ -0,0 +1,79 @@ +import { DataTable, EmptyState, Flex } from "@raystack/apsara"; +import { useFrontier } from "@raystack/frontier/react"; +import { useEffect, useState } from "react"; +import { Outlet, useOutletContext, useParams } from "react-router-dom"; + +import { V1Beta1Plan } from "@raystack/frontier"; +import { reduceByKey } from "~/utils/helper"; +import { getColumns } from "./columns"; +import { PlanHeader } from "./header"; + +const pageHeader = { + title: "Plans", + breadcrumb: [], +}; + +type ContextType = { plan: V1Beta1Plan | null }; +export default function PlanList() { + const { client } = useFrontier(); + const [plans, setPlans] = useState([]); + + useEffect(() => { + async function getAllPlans() { + const { + // @ts-ignore + data: { plans }, + } = await client?.frontierServiceListPlans(); + setPlans(plans); + } + getAllPlans(); + }, []); + + let { planId } = useParams(); + + const userMapByName = reduceByKey(plans ?? [], "id"); + + const tableStyle = plans?.length + ? { width: "100%" } + : { width: "100%", height: "100%" }; + + return ( + + + + + + + + + + + + ); +} + +export function usePlan() { + return useOutletContext(); +} + +export const noDataChildren = ( + +
+

0 plan created

+
+); + +export const TableDetailContainer = ({ children }: any) => ( +
{children}
+); diff --git a/ui/src/containers/groups.list/columns.tsx b/ui/src/containers/groups.list/columns.tsx index 880fc9aa9..3c65b4d2b 100644 --- a/ui/src/containers/groups.list/columns.tsx +++ b/ui/src/containers/groups.list/columns.tsx @@ -31,15 +31,15 @@ export const getColumns: ( }, }), { - header: "Name", - accessorKey: "name", + header: "Title", + accessorKey: "title", cell: (info: any) => info.getValue(), filterVariant: "text", }, { - header: "Slug", - accessorKey: "slug", - cell: (info: any) => info.getValue(), + header: "Organization Id", + accessorKey: "org_id", + cell: (info) => info.getValue(), filterVariant: "text", }, { diff --git a/ui/src/containers/groups.list/details.tsx b/ui/src/containers/groups.list/details.tsx index eeb3eff5a..8ad5181ae 100644 --- a/ui/src/containers/groups.list/details.tsx +++ b/ui/src/containers/groups.list/details.tsx @@ -21,6 +21,10 @@ export default function GroupDetails() { Name {group?.name} + + Organization Id + {group?.org_id} + Created At diff --git a/ui/src/containers/organisations.list/billingaccounts/columns.tsx b/ui/src/containers/organisations.list/billingaccounts/columns.tsx new file mode 100644 index 000000000..91280297b --- /dev/null +++ b/ui/src/containers/organisations.list/billingaccounts/columns.tsx @@ -0,0 +1,71 @@ +import { V1Beta1BillingAccount } from "@raystack/frontier"; +import type { ColumnDef } from "@tanstack/react-table"; +import { createColumnHelper } from "@tanstack/react-table"; +import { Link, useParams } from "react-router-dom"; + +const columnHelper = createColumnHelper(); +export const getColumns: ( + billingAccounts: V1Beta1BillingAccount[] +) => ColumnDef[] = ( + billingAccounts: V1Beta1BillingAccount[] +) => { + let { organisationId } = useParams(); + return [ + columnHelper.accessor("id", { + header: "ID", + //@ts-ignore + filterVariant: "text", + cell: ({ row, getValue }) => { + return ( + + {getValue()} + + ); + }, + }), + { + header: "Organization Id", + accessorKey: "org_id", + cell: (info) => info.getValue(), + filterVariant: "text", + }, + { + header: "Title", + accessorKey: "name", + cell: (info) => info.getValue(), + filterVariant: "text", + }, + { + header: "Provider", + accessorKey: "provider", + cell: (info) => info.getValue(), + filterVariant: "text", + }, + { + header: "State", + accessorKey: "state", + cell: (info) => info.getValue(), + filterVariant: "text", + }, + + { + header: "Create At", + accessorKey: "created_at", + meta: { + headerFilter: false, + }, + cell: (info) => + new Date(info.getValue() as Date).toLocaleString("en", { + month: "long", + day: "numeric", + year: "numeric", + }), + + footer: (props) => props.column.id, + }, + ]; +}; diff --git a/ui/src/containers/organisations.list/billingaccounts/details.tsx b/ui/src/containers/organisations.list/billingaccounts/details.tsx new file mode 100644 index 000000000..30db3be94 --- /dev/null +++ b/ui/src/containers/organisations.list/billingaccounts/details.tsx @@ -0,0 +1,69 @@ +import { Flex, Grid, Text } from "@raystack/apsara"; +import { NavLink, useParams } from "react-router-dom"; +import { usebillingaccount } from "."; + +export default function BillingAccountDetails() { + const { billingaccount } = usebillingaccount(); + let { organisationId, billingaccountId } = useParams(); + return ( + + {billingaccount?.name} + + + Name + {billingaccount?.name} + + + Subscriptions + + + Go to subscriptions + + + + + Organization Id + {billingaccount?.org_id} + + + Provider + {billingaccount?.provider} + + + State + {billingaccount?.state} + + + Created At + + {new Date(billingaccount?.created_at as any).toLocaleString("en", { + month: "long", + day: "numeric", + year: "numeric", + })} + + + + + ); +} + +const css = { + row: { height: "32px", display: "flex", alignItems: "center" }, +}; diff --git a/ui/src/containers/organisations.list/billingaccounts/index.tsx b/ui/src/containers/organisations.list/billingaccounts/index.tsx new file mode 100644 index 000000000..f2da54059 --- /dev/null +++ b/ui/src/containers/organisations.list/billingaccounts/index.tsx @@ -0,0 +1,106 @@ +import { DataTable, EmptyState, Flex } from "@raystack/apsara"; +import { V1Beta1BillingAccount, V1Beta1Organization } from "@raystack/frontier"; +import { useFrontier } from "@raystack/frontier/react"; +import { useEffect, useState } from "react"; +import { Outlet, useOutletContext, useParams } from "react-router-dom"; +import { reduceByKey } from "~/utils/helper"; +import { OrganizationsHeader } from "../header"; +import { getColumns } from "./columns"; + +type ContextType = { billingaccount: V1Beta1BillingAccount | null }; +export default function OrganisationBillingAccounts() { + const { client } = useFrontier(); + let { organisationId } = useParams(); + const [organisation, setOrganisation] = useState(); + const [billingAccounts, setBillingAccounts] = useState([]); + + const pageHeader = { + title: "Organizations", + breadcrumb: [ + { + href: `/organisations`, + name: `Organizations list`, + }, + { + href: `/organisations/${organisationId}`, + name: `${organisation?.name}`, + }, + { + href: ``, + name: `Organizations Billing Accounts`, + }, + ], + }; + + useEffect(() => { + async function getOrganization() { + const { + // @ts-ignore + data: { organization }, + } = await client?.frontierServiceGetOrganization(organisationId ?? ""); + setOrganisation(organization); + } + getOrganization(); + }, [organisationId]); + + useEffect(() => { + async function getOrganizationBillingAccounts() { + const { + // @ts-ignore + data: { billing_accounts }, + } = await client?.frontierServiceListBillingAccounts( + organisationId ?? "" + ); + setBillingAccounts(billing_accounts); + } + getOrganizationBillingAccounts(); + }, [organisationId ?? ""]); + + let { billingaccountId } = useParams(); + const billingAccountsMapByName = reduceByKey(billingAccounts ?? [], "id"); + + const tableStyle = billingAccounts?.length + ? { width: "100%" } + : { width: "100%", height: "100%" }; + + return ( + + + + + + + + + + + + ); +} + +export function usebillingaccount() { + return useOutletContext(); +} +export const noDataChildren = ( + +
+

0 billing account created

+
+); + +export const TableDetailContainer = ({ children }: any) => ( +
{children}
+); diff --git a/ui/src/containers/organisations.list/billingaccounts/subscriptions/columns.tsx b/ui/src/containers/organisations.list/billingaccounts/subscriptions/columns.tsx new file mode 100644 index 000000000..942d25d3c --- /dev/null +++ b/ui/src/containers/organisations.list/billingaccounts/subscriptions/columns.tsx @@ -0,0 +1,67 @@ +import { V1Beta1Subscription } from "@raystack/frontier"; +import type { ColumnDef } from "@tanstack/react-table"; +import { createColumnHelper } from "@tanstack/react-table"; + +const columnHelper = createColumnHelper(); +export const getColumns: ( + subscriptions: V1Beta1Subscription[] +) => ColumnDef[] = ( + subscriptions: V1Beta1Subscription[] +) => { + return [ + { + header: "Title", + accessorKey: "title", + cell: (info) => info.getValue(), + filterVariant: "text", + }, + { + header: "Customer Id", + accessorKey: "customer_id", + cell: (info) => info.getValue(), + filterVariant: "text", + }, + { + header: "Provider Id", + accessorKey: "provider_id", + cell: (info) => info.getValue(), + filterVariant: "text", + }, + { + header: "Plan Id", + accessorKey: "plan_id", + cell: (info) => info.getValue(), + filterVariant: "text", + }, + { + header: "Create At", + accessorKey: "created_at", + meta: { + headerFilter: false, + }, + cell: (info) => + new Date(info.getValue() as Date).toLocaleString("en", { + month: "long", + day: "numeric", + year: "numeric", + }), + + footer: (props) => props.column.id, + }, + { + header: "Ended At", + accessorKey: "ended_at", + meta: { + headerFilter: false, + }, + cell: (info) => + new Date(info.getValue() as Date).toLocaleString("en", { + month: "long", + day: "numeric", + year: "numeric", + }), + + footer: (props) => props.column.id, + }, + ]; +}; diff --git a/ui/src/containers/organisations.list/billingaccounts/subscriptions/index.tsx b/ui/src/containers/organisations.list/billingaccounts/subscriptions/index.tsx new file mode 100644 index 000000000..51db202a9 --- /dev/null +++ b/ui/src/containers/organisations.list/billingaccounts/subscriptions/index.tsx @@ -0,0 +1,95 @@ +import { DataTable, EmptyState, Flex } from "@raystack/apsara"; +import { V1Beta1Organization } from "@raystack/frontier"; +import { useFrontier } from "@raystack/frontier/react"; +import { useEffect, useState } from "react"; +import { useParams } from "react-router-dom"; +import { OrganizationsHeader } from "../../header"; +import { getColumns } from "./columns"; + +export default function OrganisationBASubscriptions() { + const { client } = useFrontier(); + let { organisationId, billingaccountId } = useParams(); + const [organisation, setOrganisation] = useState(); + const [subscriptions, setSubscriptions] = useState([]); + + const pageHeader = { + title: "Organizations", + breadcrumb: [ + { + href: `/organisations`, + name: `Organizations list`, + }, + { + href: `/organisations/${organisationId}`, + name: `${organisation?.name}`, + }, + { + href: `/organisations/${organisationId}/billingaccounts/${billingaccountId}`, + name: `${billingaccountId}`, + }, + { + href: "", + name: `Organizations Billing Account's subsctriptions`, + }, + ], + }; + + useEffect(() => { + async function getOrganization() { + const { + // @ts-ignore + data: { organization }, + } = await client?.frontierServiceGetOrganization(organisationId ?? ""); + setOrganisation(organization); + } + getOrganization(); + }, [organisationId]); + + useEffect(() => { + async function getOrganizationSubscriptions() { + const { + // @ts-ignore + data: { subscriptions }, + } = await client?.frontierServiceListSubscriptions( + organisationId ?? "", + billingaccountId ?? "" + ); + setSubscriptions(subscriptions); + } + getOrganizationSubscriptions(); + }, [organisationId ?? ""]); + + let { userId } = useParams(); + const tableStyle = subscriptions?.length + ? { width: "100%" } + : { width: "100%", height: "100%" }; + + return ( + + + + + + + + + ); +} + +export const noDataChildren = ( + +
+

0 subsctription created

+
+); + +export const TableDetailContainer = ({ children }: any) => ( +
{children}
+); diff --git a/ui/src/containers/organisations.list/details.tsx b/ui/src/containers/organisations.list/details.tsx index e066f7458..2734cbe14 100644 --- a/ui/src/containers/organisations.list/details.tsx +++ b/ui/src/containers/organisations.list/details.tsx @@ -3,7 +3,7 @@ import { V1Beta1Organization, V1Beta1User } from "@raystack/frontier"; import { useFrontier } from "@raystack/frontier/react"; import { ColumnDef } from "@tanstack/table-core"; import { useCallback, useEffect, useState } from "react"; -import { useNavigate, useParams } from "react-router-dom"; +import { NavLink, useNavigate, useParams } from "react-router-dom"; import PageHeader from "~/components/page-header"; type DetailsProps = { @@ -176,15 +176,58 @@ export default function OrganisationDetails() { - + + + Users + + + Projects + + + Service Users + + + Billing Accounts + + + + {detailList.map((detailItem) => ( diff --git a/ui/src/containers/organisations.list/projects/columns.tsx b/ui/src/containers/organisations.list/projects/columns.tsx index b3808d7fd..e1e2a8b0e 100644 --- a/ui/src/containers/organisations.list/projects/columns.tsx +++ b/ui/src/containers/organisations.list/projects/columns.tsx @@ -23,11 +23,10 @@ export const getColumns: ( filterVariant: "text", }, { - header: "Name", - accessorKey: "name", - filterVariant: "text", + header: "Organization Id", + accessorKey: "org_id", cell: (info) => info.getValue(), - footer: (props) => props.column.id, + filterVariant: "text", }, { header: "Create At", diff --git a/ui/src/containers/organisations.list/serviceusers/columns.tsx b/ui/src/containers/organisations.list/serviceusers/columns.tsx index 2ed8ca2c3..114d93f6b 100644 --- a/ui/src/containers/organisations.list/serviceusers/columns.tsx +++ b/ui/src/containers/organisations.list/serviceusers/columns.tsx @@ -16,8 +16,8 @@ export const getColumns: ( }, }), { - header: "Name", - accessorKey: "name", + header: "Title", + accessorKey: "title", filterVariant: "text", cell: (info) => info.getValue(), }, diff --git a/ui/src/containers/organisations.list/users/columns.tsx b/ui/src/containers/organisations.list/users/columns.tsx index 2f88d33ac..b148b891f 100644 --- a/ui/src/containers/organisations.list/users/columns.tsx +++ b/ui/src/containers/organisations.list/users/columns.tsx @@ -17,8 +17,8 @@ export const getColumns: ( }, }), { - header: "Name", - accessorKey: "name", + header: "Title", + accessorKey: "title", filterVariant: "text", cell: (info) => info.getValue(), }, diff --git a/ui/src/containers/projects.list/columns.tsx b/ui/src/containers/projects.list/columns.tsx index b3808d7fd..e332bb077 100644 --- a/ui/src/containers/projects.list/columns.tsx +++ b/ui/src/containers/projects.list/columns.tsx @@ -16,6 +16,7 @@ export const getColumns: ( return {getValue()}; }, }), + { header: "Title", accessorKey: "title", @@ -23,12 +24,12 @@ export const getColumns: ( filterVariant: "text", }, { - header: "Name", - accessorKey: "name", - filterVariant: "text", + header: "Organization Id", + accessorKey: "org_id", cell: (info) => info.getValue(), - footer: (props) => props.column.id, + filterVariant: "text", }, + { header: "Create At", accessorKey: "created_at", diff --git a/ui/src/containers/projects.list/details.tsx b/ui/src/containers/projects.list/details.tsx index 64a8b95da..17aaa9b50 100644 --- a/ui/src/containers/projects.list/details.tsx +++ b/ui/src/containers/projects.list/details.tsx @@ -3,7 +3,7 @@ import { V1Beta1Project, V1Beta1User } from "@raystack/frontier"; import { useFrontier } from "@raystack/frontier/react"; import { ColumnDef } from "@tanstack/table-core"; import { useEffect, useState } from "react"; -import { useParams } from "react-router-dom"; +import { NavLink, useParams } from "react-router-dom"; import PageHeader from "~/components/page-header"; type DetailsProps = { @@ -70,6 +70,10 @@ export default function ProjectDetails() { key: "Slug", value: project?.name, }, + { + key: "Organization Id", + value: project?.org_id, + }, { key: "Created At", value: new Date(project?.created_at as any).toLocaleString("en", { @@ -78,6 +82,7 @@ export default function ProjectDetails() { year: "numeric", }), }, + { key: "Users", value: ( @@ -100,7 +105,18 @@ export default function ProjectDetails() { title={pageHeader.title} breadcrumb={pageHeader.breadcrumb} style={{ borderBottom: "1px solid var(--border-base)" }} - /> + > + + Users + + {detailList.map((detailItem) => ( diff --git a/ui/src/containers/users.list/columns.tsx b/ui/src/containers/users.list/columns.tsx index 2f88d33ac..b148b891f 100644 --- a/ui/src/containers/users.list/columns.tsx +++ b/ui/src/containers/users.list/columns.tsx @@ -17,8 +17,8 @@ export const getColumns: ( }, }), { - header: "Name", - accessorKey: "name", + header: "Title", + accessorKey: "title", filterVariant: "text", cell: (info) => info.getValue(), }, diff --git a/ui/src/routes.tsx b/ui/src/routes.tsx index e87608c92..790faca28 100644 --- a/ui/src/routes.tsx +++ b/ui/src/routes.tsx @@ -3,6 +3,8 @@ import { MagicLinkVerify, useFrontier } from "@raystack/frontier/react"; import { memo, useEffect, useState } from "react"; import { Route, Routes } from "react-router-dom"; import App from "./App"; +import PlanList from "./containers/billingplans.list"; +import PlanDetails from "./containers/billingplans.list/details"; import Dashboard from "./containers/dashboard"; import NewGroup from "./containers/groups.create"; import Groups from "./containers/groups.list"; @@ -11,6 +13,9 @@ import Login from "./containers/login"; import MagicLink from "./containers/magiclink"; import NewOrganisation from "./containers/organisations.create"; import Organisations from "./containers/organisations.list"; +import OrganisationBillingAccounts from "./containers/organisations.list/billingaccounts"; +import BillingAccountDetails from "./containers/organisations.list/billingaccounts/details"; +import OrganisationBASubscriptions from "./containers/organisations.list/billingaccounts/subscriptions"; import OrganisationDetails from "./containers/organisations.list/details"; import OrganisationProjects from "./containers/organisations.list/projects"; import OrganisationServiceUsers from "./containers/organisations.list/serviceusers"; @@ -68,6 +73,16 @@ export default memo(() => { path="organisations/:organisationId/serviceusers" element={} /> + } + > + } /> + + } + /> } @@ -84,6 +99,10 @@ export default memo(() => { } /> + }> + } /> + + }> } /> } />