Skip to content

Commit

Permalink
Add billing profile selection sidepanel (#632)
Browse files Browse the repository at this point in the history
  • Loading branch information
pixelfact authored Nov 14, 2024
1 parent d2a114f commit 8705e71
Show file tree
Hide file tree
Showing 18 changed files with 289 additions and 12 deletions.
17 changes: 17 additions & 0 deletions app/my-dashboard/_sections/activity-section/activity-section.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { ChevronRight } from "lucide-react";
import { useMemo, useState } from "react";

import { Contributions } from "@/app/my-dashboard/_features/contributions/contributions";
import { RewardsTable } from "@/app/my-dashboard/_features/rewards-table/rewards-table";

import { Button } from "@/design-system/atoms/button/variants/button-default";
import { Typo } from "@/design-system/atoms/typo";
import { Tabs } from "@/design-system/molecules/tabs/tabs";

import { useSidePanelsContext } from "@/shared/features/side-panels/side-panels.context";
import { useRequestPaymentFlow } from "@/shared/panels/_flows/request-payment-flow/request-payment-flow.context";
import { Translate } from "@/shared/translation/components/translate/translate";

enum ActivityTabs {
Expand All @@ -17,6 +20,7 @@ enum ActivityTabs {

export function ActivitySection() {
const { close } = useSidePanelsContext();
const { open: openRequestPaymentFlow } = useRequestPaymentFlow();
const [toggleActivityView, setToggleActivityView] = useState<ActivityTabs>(ActivityTabs.CONTRIBUTIONS);

const renderActivityView = useMemo(() => {
Expand Down Expand Up @@ -71,6 +75,19 @@ export function ActivitySection() {
]}
selectedId={toggleActivityView}
/>
{/*TODO move this button to the right bloc and add rewards count*/}
<Button
variant={"primary"}
endIcon={{ component: ChevronRight }}
isTextButton
size={"md"}
translate={{ token: "myDashboard:detail.requestPayment.trigger" }}
onClick={() => openRequestPaymentFlow({})}
classNames={{
base: "max-w-full overflow-hidden",
label: "whitespace-nowrap text-ellipsis overflow-hidden",
}}
/>
</div>
</div>

Expand Down
3 changes: 3 additions & 0 deletions app/my-dashboard/_translations/my-dashboard.en.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,8 @@
"cancelReward": "Cancel"
},
"rewardsCount": "Rewards"
},
"requestPayment": {
"trigger": "Request payment ({{count}})"
}
}
23 changes: 13 additions & 10 deletions app/my-dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { withClientOnly } from "@/shared/components/client-only/client-only";
import { ScrollView } from "@/shared/components/scroll-view/scroll-view";
import { PageContent } from "@/shared/features/page-content/page-content";
import { PageWrapper } from "@/shared/features/page-wrapper/page-wrapper";
import { RequestPaymentFlowProvider } from "@/shared/panels/_flows/request-payment-flow/request-payment-flow.context";
import { ContributionsSidepanel } from "@/shared/panels/contribution-sidepanel/contributions-sidepanel";
import { ContributorSidepanel } from "@/shared/panels/contributor-sidepanel/contributor-sidepanel";
import { PosthogCaptureOnMount } from "@/shared/tracking/posthog/posthog-capture-on-mount/posthog-capture-on-mount";
Expand All @@ -26,18 +27,20 @@ function MyDashboardPage() {
],
}}
>
<PosthogCaptureOnMount eventName={"my_dashboard_viewed"} />
<RequestPaymentFlowProvider>
<PosthogCaptureOnMount eventName={"my_dashboard_viewed"} />

<AnimatedColumn className="h-full">
<ScrollView className="flex flex-col gap-md">
<PageContent classNames={{ base: "tablet:overflow-hidden" }}>
<ActivitySection />
</PageContent>
</ScrollView>
</AnimatedColumn>
<AnimatedColumn className="h-full">
<ScrollView className="flex flex-col gap-md">
<PageContent classNames={{ base: "tablet:overflow-hidden" }}>
<ActivitySection />
</PageContent>
</ScrollView>
</AnimatedColumn>

<ContributorSidepanel />
<ContributionsSidepanel />
<ContributorSidepanel />
<ContributionsSidepanel />
</RequestPaymentFlowProvider>
</PageWrapper>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ export * from "./use-get-billing-profile-by-id";
export * from "./use-get-billing-profile-payout-info-by-id";
export * from "./use-upload-billing-profile-invoice-by-id";
export * from "./use-accept-or-decline-billing-profile-mandate-by-id";
export * from "./use-get-billing-profile-invoiceable-rewards";
export * from "./use-get-my-billing-profiles";
9 changes: 9 additions & 0 deletions core/domain/billing-profile/billing-profile.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,12 @@ export enum BillingProfileType {
Individual = "INDIVIDUAL",
SelfEmployed = "SELF_EMPLOYED",
}

export type BillingProfileTypeUnion = components["schemas"]["BillingProfileResponse"]["type"];

export enum BillingProfileRole {
Admin = "ADMIN",
Member = "MEMBER",
}

export type BillingProfileRoleUnion = components["schemas"]["ShortBillingProfileResponse"]["role"];
4 changes: 4 additions & 0 deletions shared/features/_translations/features.translate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@ import enPayoutStatus from "@/shared/features/payout-status/_translations/payout
import { enPopoversTranslations } from "@/shared/features/popovers/_translations/popovers.translate";
import enSocialLinkTranslate from "@/shared/features/social-link/social-translate/social-translate.en.json";
import { enCellTranslation } from "@/shared/features/table/cell/_translations/cell.translate";
import enAddNewBBillingProfileCard from "@/shared/panels/_flows/request-payment-flow/_panels/_components/add-new-billing-profile-card/add-new-billing-profile-card.en.json";
import enBillingProfileCard from "@/shared/panels/_flows/request-payment-flow/_panels/_components/billing-profile-card/billing-profile-card.en.json";

export const enFeaturesTranslations = {
features: {
amountSelector: enAmountSelector,
invoices: enInvoices,
billingProfileCard: enBillingProfileCard,
addNewBillingProfileCard: enAddNewBBillingProfileCard,
contributionPopover: enContributionPopover,
notifications: enNotifications,
githubMissingPermissionsAlert: enGithubMissingPermissionsAlert,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"addNewBillingProfile": "Add a new billing profile"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ChevronRight, CirclePlus } from "lucide-react";

import { Avatar } from "@/design-system/atoms/avatar";
import { Icon } from "@/design-system/atoms/icon";
import { Paper } from "@/design-system/atoms/paper";
import { Typo } from "@/design-system/atoms/typo";

export function AddNewBillingProfileCard({ onClick }: { onClick: () => void }) {
return (
<Paper
size={"lg"}
background={"primary-alt"}
border="primary"
classNames={{ base: "flex gap-md justify-between items-center" }}
onClick={onClick}
>
<div className="flex items-center gap-lg">
<Avatar shape="squared" size="lg" iconProps={{ component: CirclePlus }} />
<Typo
size={"sm"}
weight="medium"
color={"primary"}
translate={{ token: "features:addNewBillingProfileCard.addNewBillingProfile" }}
/>
</div>
<Icon component={ChevronRight} />
</Paper>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"requestableRewardCount_one": "{{count}} reward",
"requestableRewardCount_other": "{{count}} rewards"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Ban, ChevronRight, Users } from "lucide-react";

import { BillingProfileRole } from "@/core/domain/billing-profile/billing-profile.types";

import { Avatar } from "@/design-system/atoms/avatar";
import { Badge } from "@/design-system/atoms/badge";
import { Icon, IconPort } from "@/design-system/atoms/icon";
import { Paper } from "@/design-system/atoms/paper";
import { Typo } from "@/design-system/atoms/typo";

import { cn } from "@/shared/helpers/cn";
import { BillingProfileCardProps } from "@/shared/panels/_flows/request-payment-flow/_panels/_components/billing-profile-card/billing-profile-card.types";
import { UseBillingProfileIcons } from "@/shared/panels/_flows/request-payment-flow/_panels/hooks/use-billing-profile-icons/use-billing-profile-icons";

export function BillingProfileCard({
type,
role,
enabled,
name,
requestableRewardCount,
isDisabled,
onClick,
}: BillingProfileCardProps) {
const { billingProfilesIcons } = UseBillingProfileIcons();

function getIconProps(): IconPort {
if (!enabled) {
return { component: Ban };
}

if (role === BillingProfileRole.Member) {
return { component: Users };
}

return billingProfilesIcons[type];
}
return (
<Paper
size={"lg"}
background={"primary-alt"}
border="primary"
classNames={{ base: cn("flex gap-md justify-between items-center", { "pointer-events-none": isDisabled }) }}
onClick={isDisabled ? undefined : onClick}
>
<div className="flex gap-lg">
<Avatar shape="squared" size="lg" iconProps={getIconProps()} />
<div className="flex flex-col gap-xs">
<Typo size={"sm"} weight="medium" color={"primary"}>
{name}
</Typo>
<Typo
size={"xs"}
color={"secondary"}
translate={{ token: `common:billingProfileType.${role === BillingProfileRole.Member ? "EMPLOYEE" : type}` }}
/>
</div>
</div>
<div className="flex items-center gap-lg">
<Badge
size="xs"
color={isDisabled ? "grey" : "brand"}
translate={{ token: "features:billingProfileCard.requestableRewardCount", count: requestableRewardCount }}
/>
{isDisabled ? <div className="w-4" /> : <Icon component={ChevronRight} />}
</div>
</Paper>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { BillingProfileRoleUnion, BillingProfileTypeUnion } from "@/core/domain/billing-profile/billing-profile.types";

export interface BillingProfileCardProps {
type: BillingProfileTypeUnion;
role: BillingProfileRoleUnion;
enabled: boolean;
name: string;
requestableRewardCount: number;
isDisabled?: boolean;
onClick?: () => void;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { useSinglePanelContext } from "@/shared/features/side-panels/side-panel/side-panel";

export function useBillingProfileSelection() {
return useSinglePanelContext("billing-profile-selection");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { useMemo } from "react";

import { BillingProfileReactQueryAdapter } from "@/core/application/react-query-adapter/billing-profile";

import { Skeleton } from "@/design-system/atoms/skeleton";

import { SidePanelBody } from "@/shared/features/side-panels/side-panel-body/side-panel-body";
import { SidePanelHeader } from "@/shared/features/side-panels/side-panel-header/side-panel-header";
import { useSidePanel } from "@/shared/features/side-panels/side-panel/side-panel";
import { marketplaceRouting } from "@/shared/helpers/marketplace-routing";
import { AddNewBillingProfileCard } from "@/shared/panels/_flows/request-payment-flow/_panels/_components/add-new-billing-profile-card/add-new-billing-profile-card";
import { BillingProfileCard } from "@/shared/panels/_flows/request-payment-flow/_panels/_components/billing-profile-card/billing-profile-card";
import { useBillingProfileSelection } from "@/shared/panels/_flows/request-payment-flow/_panels/billing-profile-selection/billing-profile-selection.hooks";
import { useRequestPaymentFlow } from "@/shared/panels/_flows/request-payment-flow/request-payment-flow.context";

function Content() {
const { selectBillingProfile } = useRequestPaymentFlow();

const { data, isLoading } = BillingProfileReactQueryAdapter.client.useGetMyBillingProfiles({});

const { billingProfiles } = data || {};

function handleClick(billingProfileId: string) {
selectBillingProfile(billingProfileId);
// TODO open reward sidePanel
}

function handleAddNewBillingProfile() {
window.open(marketplaceRouting("/settings/profile"), "_blank");
}

const renderBody = useMemo(() => {
if (isLoading) {
return (
<div className={"grid gap-md"}>
<Skeleton classNames={{ base: "h-16" }} />
<Skeleton classNames={{ base: "h-16" }} />
<Skeleton classNames={{ base: "h-16" }} />
<Skeleton classNames={{ base: "h-16" }} />
</div>
);
}

return (
<>
{billingProfiles?.map(billingProfile => (
<BillingProfileCard
key={billingProfile.id}
name={billingProfile.name}
requestableRewardCount={billingProfile.requestableRewardCount}
type={billingProfile.type}
role={billingProfile.role}
enabled={billingProfile.enabled}
isDisabled={billingProfile.requestableRewardCount === 0}
onClick={() => handleClick(billingProfile.id)}
/>
))}
<AddNewBillingProfileCard onClick={handleAddNewBillingProfile} />
</>
);
}, [billingProfiles, isLoading]);

return (
<>
<SidePanelHeader
title={{
translate: {
token: "panels:singleContributionSelection.title",
},
}}
canClose
/>

<SidePanelBody>{renderBody}</SidePanelBody>
</>
);
}

export function BillingProfileSelection() {
const { name } = useBillingProfileSelection();
const { Panel } = useSidePanel({ name });

return (
<Panel>
<Content />
</Panel>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export interface BillingProfileSelectionProps {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { BriefcaseBusiness, Crown, User } from "lucide-react";
import { useMemo } from "react";

import { BillingProfileType, BillingProfileTypeUnion } from "@/core/domain/billing-profile/billing-profile.types";

import { IconPort } from "@/design-system/atoms/icon";

export function UseBillingProfileIcons() {
const billingProfilesIcons: Record<BillingProfileTypeUnion, IconPort> = useMemo(() => {
return {
[BillingProfileType.Individual]: { component: User },
[BillingProfileType.SelfEmployed]: { component: BriefcaseBusiness },
[BillingProfileType.Company]: { component: Crown },
};
}, []);
return { billingProfilesIcons };
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import { PropsWithChildren, createContext, useContext, useState } from "react";

import { BillingProfileSelection } from "@/shared/panels/_flows/request-payment-flow/_panels/billing-profile-selection/billing-profile-selection";
import { useBillingProfileSelection } from "@/shared/panels/_flows/request-payment-flow/_panels/billing-profile-selection/billing-profile-selection.hooks";

import { OpenProps, RequestPaymentFlowContextInterface, SelectedState } from "./request-payment-flow.types";

export const RequestPaymentFlowContext = createContext<RequestPaymentFlowContextInterface>({
Expand All @@ -18,6 +21,8 @@ export function RequestPaymentFlowProvider({ children }: PropsWithChildren) {
rewardIds: [],
});

const { open: openBillingProfileSelection } = useBillingProfileSelection();

function open(props?: OpenProps) {
if (props?.initialState) {
// open the right panel -> open the selected rewards panels
Expand All @@ -26,7 +31,7 @@ export function RequestPaymentFlowProvider({ children }: PropsWithChildren) {
billingProfileId: props.initialState.selectedBillingProfileId,
});
} else {
// open the right panel -> open the billing profile selection
openBillingProfileSelection();
}
}

Expand Down Expand Up @@ -55,6 +60,7 @@ export function RequestPaymentFlowProvider({ children }: PropsWithChildren) {
}}
>
{children}
<BillingProfileSelection />
</RequestPaymentFlowContext.Provider>
);
}
Expand Down
Loading

0 comments on commit 8705e71

Please sign in to comment.