Skip to content

Commit

Permalink
feat: create invoice custom section (#1917)
Browse files Browse the repository at this point in the history
* feat: create page

* feat: add default dialog

* feat: handle edition

* feat: add graphql for creation part

* chore: rename custom footer into custom section

* feat: add graphql for edition part

* refactor: use global layout
  • Loading branch information
keellyp authored Dec 24, 2024
1 parent 4f09267 commit 48cbbc5
Show file tree
Hide file tree
Showing 8 changed files with 830 additions and 4 deletions.
81 changes: 81 additions & 0 deletions src/components/settings/invoices/DefaultCustomSectionDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { forwardRef, useImperativeHandle, useRef, useState } from 'react'

import { Button, Dialog, DialogRef } from '~/components/designSystem'
import { useInternationalization } from '~/hooks/core/useInternationalization'

type ActionType = 'setDefault' | 'removeDefault'

type TDefaultCustomSectionDialogProps = {
type: ActionType
onConfirm: () => void
onCancel: () => void
}

export interface DefaultCustomSectionDialogRef {
openDialog: (props: TDefaultCustomSectionDialogProps) => unknown
closeDialog: () => unknown
}

export const DefaultCustomSectionDialog = forwardRef<DefaultCustomSectionDialogRef, unknown>(
(_props, ref) => {
const { translate } = useInternationalization()
const dialogRef = useRef<DialogRef>(null)
const [localData, setLocalData] = useState<TDefaultCustomSectionDialogProps>()

useImperativeHandle(ref, () => ({
openDialog: (props) => {
setLocalData(props)
dialogRef.current?.openDialog()
},
closeDialog: () => {
setLocalData(undefined)
dialogRef.current?.closeDialog()
},
}))

return (
<Dialog
ref={dialogRef}
title={
localData?.type === 'setDefault'
? translate('text_1732634307286ij3e2kb6c4v')
: translate('text_1732634307286y0yb9lyb70f')
}
description={translate(
localData?.type === 'setDefault'
? 'text_17326343072869mt7lostpsa'
: 'text_1732634307286joro3vhe88v',
)}
actions={({ closeDialog }) => (
<>
<Button
variant="quaternary"
onClick={async () => {
await localData?.onCancel()
closeDialog()
}}
>
{translate('text_62728ff857d47b013204c7e4')}
</Button>

<Button
variant="primary"
onClick={async () => {
await localData?.onConfirm()
closeDialog()
}}
data-test="set-invoice-default-section"
>
{localData?.type === 'setDefault'
? translate('text_1728574726495n9jdse2hnrf')
: translate('text_1728575305796o7kwackkbj6')}
</Button>
</>
)}
data-test="set-invoice-default-section-dialog"
/>
)
},
)

DefaultCustomSectionDialog.displayName = 'DefaultCustomSectionDialog'
108 changes: 108 additions & 0 deletions src/components/settings/invoices/PreviewCustomSectionDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { gql } from '@apollo/client'
import { forwardRef, useImperativeHandle, useRef, useState } from 'react'

import { Button, Drawer, DrawerRef, Typography } from '~/components/designSystem'
import {
CreateInvoiceCustomSectionInput,
useGetOrganizationCustomFooterForInvoiceLazyQuery,
} from '~/generated/graphql'
import { useInternationalization } from '~/hooks/core/useInternationalization'
import Logo from '~/public/images/logo/lago-logo-grey.svg'

gql`
query GetOrganizationCustomFooterForInvoice {
organization {
billingConfiguration {
invoiceFooter
}
}
}
`

type TPreviewCustomSectionDrawerProps = Pick<
CreateInvoiceCustomSectionInput,
'displayName' | 'details'
>

export interface PreviewCustomSectionDrawerRef {
openDrawer: (params: TPreviewCustomSectionDrawerProps) => void
closeDrawer: () => void
}

export const PreviewCustomSectionDrawer = forwardRef<PreviewCustomSectionDrawerRef>(
(_props, ref) => {
const { translate } = useInternationalization()
const drawerRef = useRef<DrawerRef>(null)
const [localData, setLocalData] = useState<
TPreviewCustomSectionDrawerProps & {
invoiceFooter?: string
}
>()

const [getOrganizationCustomFooter] = useGetOrganizationCustomFooterForInvoiceLazyQuery()

useImperativeHandle(ref, () => ({
openDrawer: async (args) => {
const { data } = await getOrganizationCustomFooter()

setLocalData({
...args,
invoiceFooter: data?.organization?.billingConfiguration?.invoiceFooter ?? undefined,
})

drawerRef.current?.openDrawer()
},
closeDrawer: () => drawerRef.current?.closeDrawer(),
}))

const hasLocalData = localData?.displayName || localData?.details

return (
<Drawer
ref={drawerRef}
withPadding={false}
stickyBottomBar={({ closeDrawer }) => (
<div className="flex justify-end">
<Button onClick={closeDrawer}>{translate('text_62f50d26c989ab03196884ae')}</Button>
</div>
)}
title={
<div className="flex flex-1 flex-row items-center justify-between gap-1">
<Typography variant="bodyHl" color="textSecondary">
{translate('text_17326350108761jc0z8eusa8')}
</Typography>
</div>
}
>
<div className="h-full bg-grey-100 pb-12 pl-12">
<div className="flex size-full flex-col justify-end bg-white px-12 py-8">
{hasLocalData && (
<div className="flex flex-col gap-1 pb-6 shadow-b">
{localData?.displayName && (
<Typography variant="captionHl" color="textSecondary">
{localData.displayName}
</Typography>
)}
{localData?.details && (
<Typography variant="caption">{localData.details}</Typography>
)}
</div>
)}

<Typography variant="caption" className="py-6">
{localData?.invoiceFooter}
</Typography>
<div className="ml-auto flex flex-row items-center gap-1">
<Typography className="font-email text-xs font-normal" color="grey500">
{translate('text_6419c64eace749372fc72b03')}
</Typography>
<Logo height="12px" />
</div>
</div>
</div>
</Drawer>
)
},
)

PreviewCustomSectionDrawer.displayName = 'PreviewCustomSectionDrawer'
21 changes: 21 additions & 0 deletions src/core/router/SettingRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ const InvoiceSettings = lazyLoad(
() =>
import(/* webpackChunkName: 'invoice-settings' */ '~/pages/settings/Invoices/InvoiceSettings'),
)

const CreateInvoiceCustomSection = lazyLoad(
() =>
import(
/* webpackChunkName: 'invoice-custom-section' */ '~/pages/settings/Invoices/CreateCustomSection'
),
)

const TaxesSettings = lazyLoad(
() => import(/* webpackChunkName: 'tax-settings' */ '~/pages/settings/TaxesSettings'),
)
Expand Down Expand Up @@ -164,8 +172,14 @@ export const EMAILS_SCENARIO_CONFIG_ROUTE = `${SETTINGS_ROUTE}/emails/config/:ty
export const XERO_INTEGRATION_ROUTE = `${INTEGRATIONS_ROUTE}/xero`
export const XERO_INTEGRATION_DETAILS_ROUTE = `${INTEGRATIONS_ROUTE}/xero/:integrationId/:tab`
export const DUNNINGS_SETTINGS_ROUTE = `${SETTINGS_ROUTE}/dunnings`

/**
* Creation routes
*/
export const CREATE_DUNNING_ROUTE = `${SETTINGS_ROUTE}/dunnings/create`
export const UPDATE_DUNNING_ROUTE = `${SETTINGS_ROUTE}/dunnings/:campaignId/edit`
export const CREATE_INVOICE_CUSTOM_SECTION = `${INVOICE_SETTINGS_ROUTE}/custom-section/create`
export const EDIT_INVOICE_CUSTOM_SECTION = `${INVOICE_SETTINGS_ROUTE}/custom-section/:sectionId/edit`

export const settingRoutes: CustomRouteObject[] = [
{
Expand Down Expand Up @@ -348,4 +362,11 @@ export const settingRoutes: CustomRouteObject[] = [
element: <CreateDunning />,
permissions: ['dunningCampaignsCreate', 'dunningCampaignsView', 'dunningCampaignsUpdate'],
},
{
path: [CREATE_INVOICE_CUSTOM_SECTION, EDIT_INVOICE_CUSTOM_SECTION],
private: true,
element: <CreateInvoiceCustomSection />,
// TODO: Add permissions
permissions: ['organizationInvoicesUpdate'],
},
]
Loading

0 comments on commit 48cbbc5

Please sign in to comment.