Skip to content

Commit

Permalink
feat(subscription): add end date
Browse files Browse the repository at this point in the history
  • Loading branch information
ansmonjol committed Sep 21, 2023
1 parent f7fdb6d commit 7a3fd99
Show file tree
Hide file tree
Showing 20 changed files with 609 additions and 358 deletions.
16 changes: 9 additions & 7 deletions ditto/base.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"text_6335e8900c69f8ebdfef5318": "Subscription date",
"text_642a94e522316cd9e1875224": "Subscription external ID (optional)",
"text_642ac1d1407baafb9e4390ee": "Type a subscription external id",
"text_642ac28c65c2180085afe31a": "ID used to define your own subscription ID from your backend instead of using the one defined by Lago",
Expand Down Expand Up @@ -82,11 +81,6 @@
"text_62bb10ad2a10bd182d00202d": "Invoice",
"text_62bdbf07117c3d1f178d6517": "See more",
"text_62bb10ad2a10bd182d002077": "Invoice template information saved",
"text_648b1765864e4aac3180c431": "12:00 AM",
"text_648b1828ead1c3004b930334": "Subscription date (based on UTC ±0)",
"text_648b1837da6496008dfe4b3c": "Time",
"text_648b18d3ffc4a80093c17058": "Time is invalid",
"text_648b280da2ff5a00723b6b88": "This means {{date}} at {{time}} UTC {{offset}} for your customer",
"text_6227a2e847fcd700e9038952": "API key copied to clipboard",
"text_62ce85fb3fb6842020331d83": "Find all <a rel=\"external\" target=\"_blank\" href=\"https://docs.getlago.com/api-reference/webhooks/messages\">the events</a> this webhook is listening",
"text_62728ff857d47b013204c726": "Settings",
Expand Down Expand Up @@ -321,7 +315,7 @@
"text_624efab67eb2570101d117e0": "Refresh the page",
"text_6250304370f0f700a8fdc27d": "Details",
"text_6250304370f0f700a8fdc283": "External ID",
"text_6250304370f0f700a8fdc28b": "Add a plan",
"text_6250304370f0f700a8fdc28b": "Assign a plan",
"text_6250304370f0f700a8fdc28d": "Subscriptions",
"text_6250304370f0f700a8fdc28f": "No plan linked to this customer, add a Plan to this customer to start a Subscription.",
"text_6250304370f0f700a8fdc291": "Invoices",
Expand Down Expand Up @@ -1616,6 +1610,14 @@
"text_634812d6f16b31ce5cbf4126": "Something went wrong",
"text_634812d6f16b31ce5cbf4128": "Please refresh the page or contact us if the error persists.",
"text_634812d6f16b31ce5cbf412a": "Refresh the page",
"text_64ef55a730b88e3d2117b3c4": "Start date in UTC±0",
"text_64ef55a730b88e3d2117b3cc": "End date in UTC±0 (optional)",
"text_64ef55a730b88e3d2117b3d4": "End date can’t be in the past and should be greater than start date",
"text_64ef55a730b88e3d2117b44e": "{{planName}} will be terminated on {{date}}",
"text_64ef81071c6da2010dd24b1d": "The subscription started on {{date}} at {{time}} UTC {{offset}} for your customer.",
"text_64ef8cc7c83f5d006131a488": "The subscription will start on {{date}} at {{time}} UTC {{offset}} for your customer.",
"text_64ef81071c6da2010dd24b1e": "It won’t end until you manually terminate it.",
"text_64ef81071c6da2010dd24b1f": "It will end on {{date}} at {{time}} UTC {{offset}}.",
"text_63eba8c65a6c8043feee2a01": "Update payment status",
"text_63eba8c65a6c8043feee2a02": "Invoice payment status successfully updated",
"text_63eba8c65a6c8043feee2a0d": "Update payment status",
Expand Down
2 changes: 2 additions & 0 deletions ditto/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -297,5 +297,7 @@ sources:
id: 65018c8af04a866c7bcc6cb8
- name: 👍 [Ready for dev] - Billable metrics - Weighted sum
id: 6500621fbbf7ebe75d11e890
- name: 👍 [Ready for dev] - Customers - Add subscription end date
id: 64ef55a23bdcf2d86567cbed
format: flat
variants: true
3 changes: 3 additions & 0 deletions ditto/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ module.exports = {
"project_63eba8199c936977d8e98991": {
"base": require('./-ready-for-dev---customer-invoice---update-invoice.payment_status__base.json')
},
"project_64ef55a23bdcf2d86567cbed": {
"base": require('./-ready-for-dev---customers---add-subscription-end-date__base.json')
},
"project_634687058efb4a10996fdbdc": {
"base": require('./-ready-for-dev---customers---invoice-detail-page__base.json')
},
Expand Down
228 changes: 113 additions & 115 deletions src/components/customers/subscriptions/AddSubscriptionDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import { useFormik } from 'formik'
import { DateTime } from 'luxon'
import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import styled from 'styled-components'
import { object, string } from 'yup'

import { Alert, Button, Drawer, DrawerRef, Typography } from '~/components/designSystem'
import { Alert, Button, Drawer, DrawerRef, Icon, Typography } from '~/components/designSystem'
import {
ButtonSelectorField,
ComboBoxField,
DatePickerField,
TextInput,
TextInputField,
} from '~/components/form'
import { TimePickerField } from '~/components/form/TimePicker'
import {
overwritePlanVar,
resetOverwritePlanVar,
SubscriptionUpdateInfo,
updateOverwritePlanVar,
} from '~/core/apolloClient'
import { dateErrorCodes } from '~/core/constants/form'
import { CREATE_PLAN_ROUTE } from '~/core/router'
import { getTimezoneConfig, TimeZonesConfig } from '~/core/timezone'
import { getTimezoneConfig } from '~/core/timezone'
import {
BillingTimeEnum,
CreateSubscriptionInput,
Expand All @@ -35,6 +35,8 @@ import { useAddSubscription } from '~/hooks/customer/useAddSubscription'
import { useOrganizationInfos } from '~/hooks/useOrganizationInfos'
import { Card, DrawerContent, DrawerSubmitButton, DrawerTitle, theme } from '~/styles'

import { SubscriptionDatesOffsetHelperComponent } from './SubscriptionDatesOffsetHelperComponent'

export interface AddSubscriptionDrawerRef {
openDialog: (existingSubscription?: SubscriptionUpdateInfo) => unknown
closeDialog: () => unknown
Expand All @@ -52,14 +54,9 @@ export const AddSubscriptionDrawer = forwardRef<
>(({ customerId, customerName, customerTimezone }: AddSubscriptionDrawerProps, ref) => {
const navigate = useNavigate()
const drawerRef = useRef<DrawerRef>(null)
const {
timezone: organizationTimezone,
timezoneConfig: orgaTimezoneConfig,
formatTimeOrgaTZ,
} = useOrganizationInfos()
const customerTimezoneConfig = getTimezoneConfig(customerTimezone)
const { formatTimeOrgaTZ } = useOrganizationInfos()
const GMT = getTimezoneConfig(TimezoneEnum.TzUtc).name
const currentDateRef = useRef<string>(DateTime.now().setZone(GMT).toISO())
const currentDateRef = useRef<string>(DateTime.now().setZone(GMT).startOf('day').toISO())

const [existingSubscription, setExistingSubscription] = useState<
SubscriptionUpdateInfo | undefined
Expand All @@ -71,12 +68,47 @@ export const AddSubscriptionDrawer = forwardRef<
planId: undefined,
name: '',
externalId: '',
subscriptionAt: currentDateRef?.current,
subscriptionAt: existingSubscription?.startDate || currentDateRef?.current,
endingAt: existingSubscription?.endDate || undefined,
billingTime: BillingTimeEnum.Calendar,
},
validationSchema: object().shape({
planId: string().required(''),
subscriptionAt: string().required(''),
endingAt: string()
.test({
test: function (value, { from, path }) {
// Value can be undefined
if (!value) {
return true
}

// Make sure value has correct format
if (!DateTime.fromISO(value).isValid) {
return this.createError({
path,
message: dateErrorCodes.wrongFormat,
})
}

// If subscription at is present
if (from && from[0] && from[0].value && from[0].value.subscriptionAt) {
const subscriptionAt = DateTime.fromISO(from[0].value.subscriptionAt)
const endingAt = DateTime.fromISO(value)

// Make sure endingAt is set later than subscriptionAt and in the future
if (endingAt <= subscriptionAt || DateTime.now().diff(endingAt, 'days').days >= 0) {
return this.createError({
path,
message: dateErrorCodes.shouldBeFutureAndBiggerThanSubscriptionAt,
})
}
}

return true
},
})
.nullable(),
}),
validateOnMount: true,
enableReinitialize: true,
Expand Down Expand Up @@ -121,10 +153,11 @@ export const AddSubscriptionDrawer = forwardRef<
const { subscriptionInput, updateInfo } = overwritePlanVar()

if (!!subscriptionInput) {
const { planId, name, billingTime, subscriptionAt } = subscriptionInput
const { planId, name, billingTime, subscriptionAt, endingAt } = subscriptionInput

formikProps.setValues({
subscriptionAt: subscriptionAt || currentDateRef?.current,
endingAt: endingAt || undefined,
planId: planId || '',
name: name || undefined,
billingTime: billingTime || BillingTimeEnum.Calendar,
Expand All @@ -143,57 +176,6 @@ export const AddSubscriptionDrawer = forwardRef<
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

const subscriptionAtHelperText = useMemo(() => {
if (
!formikProps.values.subscriptionAt ||
(customerTimezoneConfig?.offsetInMinute === 0 && orgaTimezoneConfig.offsetInMinute === 0)
)
return undefined

if (formikProps.values.subscriptionAt) {
if (!!customerTimezone) {
const date = DateTime.fromISO(formikProps.values.subscriptionAt)
.setZone(customerTimezoneConfig.name)
.toFormat('LLL. dd, yyyy')
const time = `${DateTime.fromISO(formikProps.values.subscriptionAt)
.setZone(customerTimezoneConfig.name)
.setLocale('en')
.toFormat('t')}`
const offset = TimeZonesConfig[customerTimezone].offset

if (customerTimezoneConfig?.offsetInMinute < 0) {
return translate('text_648b280da2ff5a00723b6b88', { date, time, offset })
} else if (customerTimezoneConfig?.offsetInMinute > 0) {
return translate('text_648b280da2ff5a00723b6b88', { date, time, offset })
}
} else if (!!organizationTimezone) {
const date = DateTime.fromISO(formikProps.values.subscriptionAt)
.setZone(orgaTimezoneConfig.name)
.toFormat('LLL. dd, yyyy')
const time = `${DateTime.fromISO(formikProps.values.subscriptionAt)
.setZone(orgaTimezoneConfig.name)
.setLocale('en')
.toFormat('t')}`
const offset = TimeZonesConfig[organizationTimezone].offset

if (orgaTimezoneConfig.offsetInMinute < 0) {
return translate('text_648b280da2ff5a00723b6b88', { date, time, offset })
} else if (orgaTimezoneConfig.offsetInMinute > 0) {
return translate('text_648b280da2ff5a00723b6b88', { date, time, offset })
}
}
}

return undefined
}, [
organizationTimezone,
customerTimezone,
customerTimezoneConfig,
formikProps.values.subscriptionAt,
orgaTimezoneConfig,
translate,
])

return (
<Drawer
ref={drawerRef}
Expand Down Expand Up @@ -298,53 +280,69 @@ export const AddSubscriptionDrawer = forwardRef<
/>

{!existingSubscription && (
<>
<div>
<InlineFields>
<DatePickerField
name="subscriptionAt"
label={translate('text_648b1828ead1c3004b930334')}
defaultZone={getTimezoneConfig(TimezoneEnum.TzUtc).name}
formikProps={formikProps}
/>
<TimePickerField
name="subscriptionAt"
label={translate('text_648b1837da6496008dfe4b3c')}
defaultZone={getTimezoneConfig(TimezoneEnum.TzUtc).name}
formikProps={formikProps}
/>
</InlineFields>
{!!subscriptionAtHelperText && (
<InlineFieldsHelperText variant="caption" color="grey600">
{subscriptionAtHelperText}
</InlineFieldsHelperText>
)}
</div>
<ButtonSelectorField
name="billingTime"
label={translate('text_62ea7cd44cd4b14bb9ac1db7')}
<ButtonSelectorField
name="billingTime"
label={translate('text_62ea7cd44cd4b14bb9ac1db7')}
formikProps={formikProps}
helperText={billingTimeHelper}
options={[
{
label:
selectedPlan?.interval === PlanInterval.Yearly
? translate('text_62ebd597d5d5130a03ced107')
: selectedPlan?.interval === PlanInterval.Weekly
? translate('text_62ebd597d5d5130a03ced101')
: selectedPlan?.interval === PlanInterval.Quarterly
? translate('text_64d6357b00dea100ad1cba27')
: translate('text_62ea7cd44cd4b14bb9ac1db9'),
value: BillingTimeEnum.Calendar,
},
{
label: translate('text_62ea7cd44cd4b14bb9ac1dbb'),
value: BillingTimeEnum.Anniversary,
},
]}
/>
)}

<div>
<InlineFields>
<DatePickerField
disabled={
!!existingSubscription && !!formikProps.initialValues.subscriptionAt
}
name="subscriptionAt"
label={translate('text_64ef55a730b88e3d2117b3c4')}
defaultZone={getTimezoneConfig(TimezoneEnum.TzUtc).name}
placement="auto"
formikProps={formikProps}
helperText={billingTimeHelper}
options={[
{
label:
selectedPlan?.interval === PlanInterval.Yearly
? translate('text_62ebd597d5d5130a03ced107')
: selectedPlan?.interval === PlanInterval.Weekly
? translate('text_62ebd597d5d5130a03ced101')
: selectedPlan?.interval === PlanInterval.Quarterly
? translate('text_64d6357b00dea100ad1cba27')
: translate('text_62ea7cd44cd4b14bb9ac1db9'),
value: BillingTimeEnum.Calendar,
},
{
label: translate('text_62ea7cd44cd4b14bb9ac1dbb'),
value: BillingTimeEnum.Anniversary,
},
]}
/>
</>
)}
<InlineFieldsIcon name="arrow-right" />
<DatePickerField
disablePast
name="endingAt"
label={translate('text_64ef55a730b88e3d2117b3cc')}
defaultZone={getTimezoneConfig(TimezoneEnum.TzUtc).name}
formikProps={formikProps}
placement="auto"
error={
formikProps.errors.endingAt ===
dateErrorCodes.shouldBeFutureAndBiggerThanSubscriptionAt
? translate('text_64ef55a730b88e3d2117b3d4')
: undefined
}
inputProps={{ cleanable: true }}
/>
</InlineFields>

{!formikProps.errors.endingAt && !formikProps.errors.subscriptionAt && (
<LocalSubscriptionDatesOffsetHelperComponent
customerTimezone={customerTimezone}
subscriptionAt={formikProps.values.subscriptionAt}
endingAt={formikProps.values.endingAt}
/>
)}
</div>
</>
)}

Expand Down Expand Up @@ -410,17 +408,17 @@ const InlineFields = styled.div`
display: flex;
gap: ${theme.spacing(3)};
> *:first-child {
flex: 1;
}
> *:first-child,
> *:last-child {
flex: 1;
max-width: 192px;
}
`

const InlineFieldsHelperText = styled(Typography)`
const InlineFieldsIcon = styled(Icon)`
margin-top: ${theme.spacing(10)};
`

const LocalSubscriptionDatesOffsetHelperComponent = styled(SubscriptionDatesOffsetHelperComponent)`
margin-top: ${theme.spacing(1)};
`

Expand Down
Loading

0 comments on commit 7a3fd99

Please sign in to comment.