Skip to content

Commit

Permalink
Merge pull request #1222 from themeum/subscription-rework
Browse files Browse the repository at this point in the history
Subscription Rework v2
  • Loading branch information
b-l-i-n-d authored Oct 17, 2024
2 parents b3e3d3d + 85b666e commit bd39074
Show file tree
Hide file tree
Showing 15 changed files with 391 additions and 316 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,9 @@ const ContentGeneration = ({ onClose }: { onClose: () => void }) => {
</MagicButton>
<MagicButton
variant="primary"
disabled={isLoading || isCreateNewCourse || !contents[pointer].title}
disabled={
isLoading || isCreateNewCourse || !contents[pointer].title || contents[pointer].counts?.topics === 0
}
onClick={() => {
saveAIGeneratedCourseContentMutation.mutate({
course_id: courseId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { css } from '@emotion/react';
import { useIsFetching } from '@tanstack/react-query';
import { __ } from '@wordpress/i18n';
import { useEffect, useState } from 'react';
import { useState } from 'react';
import { Controller, useFormContext } from 'react-hook-form';

import SVGIcon from '@Atoms/SVGIcon';
Expand Down Expand Up @@ -34,7 +34,6 @@ const CourseSettings = () => {

const isContentDripActive = form.watch('contentDripType');
const isBuddyPressEnabled = form.watch('enable_tutor_bp');
const priceCategory = form.watch('course_pricing_category');

const tabList: TabItem<string>[] = [
{
Expand Down Expand Up @@ -63,13 +62,6 @@ const CourseSettings = () => {
value: level.value,
}));

// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
if (priceCategory === 'subscription') {
form.setValue('enrollment_expiry', 0);
}
}, [priceCategory]);

return (
<div>
<label css={typography.caption()}>{__('Course Settings', 'tutor')}</label>
Expand Down Expand Up @@ -129,7 +121,6 @@ const CourseSettings = () => {
"Student's enrollment will be removed after this number of days. Set 0 for lifetime enrollment.",
'tutor',
)}
disabled={priceCategory === 'subscription'}
placeholder="0"
type="number"
isClearable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const Header = () => {
navigate('/basics', { state: { isError: true } });
};

if (data.course_pricing_category !== 'subscription' && data.course_price_type === 'paid') {
if (data.course_price_type === 'paid') {
if (tutorConfig.settings?.monetize_by === 'edd' && !data.course_product_id) {
navigateToBasicsWithError();
triggerAndFocus('course_product_id');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,30 @@ import { FormProvider, useFieldArray } from 'react-hook-form';

interface SubscriptionModalProps extends ModalProps {
closeModal: (props?: { action: 'CONFIRM' | 'CLOSE' }) => void;
expandedSubscriptionId?: string;
createEmptySubscriptionOnMount?: boolean;
}

export type SubscriptionFormDataWithSaved = SubscriptionFormData & { isSaved: boolean };

const courseId = getCourseId();

export default function SubscriptionModal({ title, subtitle, icon, closeModal }: SubscriptionModalProps) {
export default function SubscriptionModal({
title,
subtitle,
icon,
closeModal,
expandedSubscriptionId,
createEmptySubscriptionOnMount,
}: SubscriptionModalProps) {
const queryClient = useQueryClient();
const form = useFormWithGlobalError<{
subscriptions: SubscriptionFormDataWithSaved[];
}>({
defaultValues: {
subscriptions: [],
},
mode: 'onChange',
});

const {
Expand All @@ -70,7 +80,7 @@ export default function SubscriptionModal({ title, subtitle, icon, closeModal }:
keyName: '_id',
});

const [expendedSubscriptionId, setExpandedSubscriptionId] = useState<string>('');
const [expendedSubscriptionId, setExpandedSubscriptionId] = useState<string>(expandedSubscriptionId || '');
const [activeSortId, setActiveSortId] = useState<UniqueIdentifier | null>(null);

const isSubscriptionListLoading = !!useIsFetching({
Expand All @@ -85,8 +95,9 @@ export default function SubscriptionModal({ title, subtitle, icon, closeModal }:
const activeSubscription = form.getValues().subscriptions.find((item) => item.id === expendedSubscriptionId);

const dirtySubscriptionIndex =
subscriptionFields.findIndex((item) => !item.isSaved) ||
form.formState.dirtyFields.subscriptions?.findIndex((item) => isDefined(item));
subscriptionFields.findIndex((item) => !item.isSaved) !== -1
? subscriptionFields.findIndex((item) => !item.isSaved)
: form.formState.dirtyFields.subscriptions?.findIndex((item) => isDefined(item));

// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
Expand Down Expand Up @@ -117,16 +128,29 @@ export default function SubscriptionModal({ title, subtitle, icon, closeModal }:

const handleSaveSubscription = async (values: SubscriptionFormDataWithSaved) => {
try {
const payload = convertFormDataToSubscription({
...values,
id: values.isSaved ? values.id : '0',
assign_id: String(courseId),
});
const response = await saveSubscriptionMutation.mutateAsync(payload);
form.trigger();
const timeoutId = setTimeout(async () => {
const subscriptionErrors = form.formState.errors.subscriptions || [];

if (response.status_code === 200 || response.status_code === 201) {
setExpandedSubscriptionId((previous) => (previous === payload.id ? '' : payload.id || ''));
}
if (subscriptionErrors.length) {
return;
}

const payload = convertFormDataToSubscription({
...values,
id: values.isSaved ? values.id : '0',
assign_id: String(courseId),
});
const response = await saveSubscriptionMutation.mutateAsync(payload);

if (response.status_code === 200 || response.status_code === 201) {
setExpandedSubscriptionId((previous) => (previous === payload.id ? '' : payload.id || ''));
}
}, 0);

return () => {
clearTimeout(timeoutId);
};
} catch (error) {
form.reset();
}
Expand All @@ -143,6 +167,15 @@ export default function SubscriptionModal({ title, subtitle, icon, closeModal }:
}),
);

// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
useEffect(() => {
if (createEmptySubscriptionOnMount) {
const newId = nanoid();
appendSubscription({ ...defaultSubscriptionFormData, id: newId, isSaved: false });
setExpandedSubscriptionId(newId);
}
}, []);

return (
<FormProvider {...form}>
<ModalWrapper
Expand All @@ -160,14 +193,14 @@ export default function SubscriptionModal({ title, subtitle, icon, closeModal }:
activeSubscription ? form.reset() : closeModal({ action: 'CLOSE' });
}}
>
{activeSubscription ? __('Discard Changes', 'tutor') : __('Cancel', 'tutor')}
{activeSubscription?.isSaved ? __('Discard Changes', 'tutor') : __('Cancel', 'tutor')}
</Button>
<Button
loading={saveSubscriptionMutation.isPending}
variant="primary"
size="small"
onClick={() => {
if (dirtySubscriptionIndex && activeSubscription) {
if (dirtySubscriptionIndex !== -1 && activeSubscription) {
handleSaveSubscription(activeSubscription);
}
}}
Expand Down Expand Up @@ -241,7 +274,7 @@ export default function SubscriptionModal({ title, subtitle, icon, closeModal }:
}
: noop
}
isExpanded={expendedSubscriptionId === subscription.id}
isExpanded={activeSortId ? false : expendedSubscriptionId === subscription.id}
/>
);
}}
Expand All @@ -257,7 +290,7 @@ export default function SubscriptionModal({ title, subtitle, icon, closeModal }:
toggleCollapse={noop}
bgLight
onDiscard={noop}
isExpanded={expendedSubscriptionId === id}
isExpanded={false}
isOverlay
/>
);
Expand Down Expand Up @@ -298,7 +331,7 @@ const styles = {
container: css`
max-width: 640px;
width: 100%;
padding-top: ${spacing[40]};
padding-block: ${spacing[40]};
margin-inline: auto;
display: flex;
flex-direction: column;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { useModal } from '@/v3/shared/components/modals/Modal';
import { styleUtils } from '@/v3/shared/utils/style-utils';
import SVGIcon from '@Atoms/SVGIcon';
import { colorTokens, spacing } from '@Config/styles';
import { borderRadius, colorTokens, spacing } from '@Config/styles';
import { typography } from '@Config/typography';
import Show from '@Controls/Show';
import type { DurationUnit, SubscriptionFormData } from '@CourseBuilderServices/subscription';
import { css } from '@emotion/react';
import { __, sprintf } from '@wordpress/i18n';
import SubscriptionModal from '../modals/SubscriptionModal';

export function formatRepeatUnit(unit: Omit<DurationUnit, 'hour'>, value: number) {
switch (unit) {
Expand All @@ -24,27 +27,30 @@ export function formatRepeatUnit(unit: Omit<DurationUnit, 'hour'>, value: number
}

export function PreviewItem({ subscription }: { subscription: SubscriptionFormData }) {
const { showModal } = useModal();

return (
<div css={styles.item}>
<p css={styles.title}>
{subscription.plan_name}
<Show when={subscription.is_featured}>
<SVGIcon style={styles.featuredIcon} name="star" height={20} width={20} />
</Show>
</p>
<div css={styles.information}>
<Show when={subscription.payment_type === 'recurring'} fallback={<span>{__('Lifetime', 'tutor')}</span>}>
<span>
{sprintf(
__('Renew every %s %s', 'tutor'),
subscription.recurring_value.toString().padStart(2, '0'),
formatRepeatUnit(subscription.recurring_interval, Number(subscription.recurring_value)),
)}
</span>
</Show>
<div css={styles.wrapper}>
<div css={styles.item}>
<p css={styles.title}>
{subscription.plan_name}
<Show when={subscription.is_featured}>
<SVGIcon style={styles.featuredIcon} name="star" height={20} width={20} />
</Show>
</p>
<div css={styles.information}>
<Show when={subscription.payment_type === 'recurring'} fallback={<span>{__('Lifetime', 'tutor')}</span>}>
<span>
{sprintf(
__('Renew every %s %s', 'tutor'),
subscription.recurring_value.toString().padStart(2, '0'),
formatRepeatUnit(subscription.recurring_interval, Number(subscription.recurring_value)),
)}
</span>
</Show>

{/* @TODO: will be updated after confirmation */}
{/* <Show when={subscription.enable_free_trial}>
{/* @TODO: will be updated after confirmation */}
{/* <Show when={subscription.enable_free_trial}>
<span>•</span>
<span>
{sprintf(
Expand All @@ -55,40 +61,76 @@ export function PreviewItem({ subscription }: { subscription: SubscriptionFormDa
</span>
</Show> */}

<Show when={subscription.payment_type !== 'onetime'}>
<Show
when={subscription.recurring_limit === 'Until cancelled'}
fallback={
<>
<span></span>
<span>
{subscription.recurring_limit.toString().padStart(2, '0')} {__('Times', 'tutor')}
</span>
</>
}
>
<span></span>
<span>{__('Until Cancellation', 'tutor')}</span>
<Show when={subscription.payment_type !== 'onetime'}>
<Show
when={subscription.recurring_limit === 'Until cancelled'}
fallback={
<>
<span></span>
<span>
{subscription.recurring_limit.toString().padStart(2, '0')} {__('Times', 'tutor')}
</span>
</>
}
>
<span></span>
<span>{__('Until Cancellation', 'tutor')}</span>
</Show>
</Show>
</Show>
</div>
</div>
<button
type="button"
css={styles.editButton}
onClick={() => {
showModal({
component: SubscriptionModal,
props: {
title: __('Manage Subscription Plans', 'tutor'),
icon: <SVGIcon name="dollar-recurring" width={24} height={24} />,
expandedSubscriptionId: subscription.id,
},
});
}}
data-edit-button
>
<SVGIcon name="pen" width={19} height={19} />
</button>
</div>
);
}

const styles = {
wrapper: css`
display: flex;
justify-content: space-between;
align-items: center;
background-color: ${colorTokens.background.white};
padding: ${spacing[8]} ${spacing[12]};
[data-edit-button] {
opacity: 0;
transition: opacity 0.3s ease;
}
&:hover {
background-color: ${colorTokens.background.hover};
[data-edit-button] {
opacity: 1;
}
}
&:not(:last-of-type) {
border-bottom: 1px solid ${colorTokens.stroke.default};
}
`,
item: css`
background-color: ${colorTokens.background.white};
padding: ${spacing[8]} ${spacing[12]};
min-height: 48px;
display: flex;
flex-direction: column;
justify-content: center;
gap: ${spacing[4]};
&:not(:last-of-type) {
border-bottom: 1px solid ${colorTokens.stroke.default};
}
`,
title: css`
${typography.caption('medium')};
Expand All @@ -107,4 +149,18 @@ const styles = {
featuredIcon: css`
color: ${colorTokens.icon.brand};
`,
editButton: css`
${styleUtils.resetButton};
${styleUtils.flexCenter()};
width: 24px;
height: 24px;
border-radius: ${borderRadius[4]};
color: ${colorTokens.icon.default};
transition: color 0.3s ease, background 0.3s ease;
&:hover {
background: ${colorTokens.action.secondary.default};
color: ${colorTokens.icon.brand};
}
`,
};
Loading

0 comments on commit bd39074

Please sign in to comment.