Compare commits

...

14 Commits

Author SHA1 Message Date
Ahmed Bouhuolia
161d60393a Merge pull request #629 from bigcapitalhq/details-subscription
fix: Add subscription plans offer text
2024-08-25 19:44:34 +02:00
Ahmed Bouhuolia
79413fa85e fix: Add subscription plans offer text 2024-08-25 19:43:54 +02:00
Ahmed Bouhuolia
58552c6c94 Merge pull request #628 from bigcapitalhq/fix-webapp-env-variables
fix: Make webapp package env variables dynamic
2024-08-25 18:21:55 +02:00
Ahmed Bouhuolia
2072e35cfa fix: Make webapp package env variables dynamic 2024-08-25 18:21:08 +02:00
Ahmed Bouhuolia
1eaac9d691 Merge remote-tracking branch 'refs/remotes/origin/develop' into develop 2024-08-25 14:18:04 +02:00
Ahmed Bouhuolia
a916e8a0cb fix: syntax error 2024-08-25 14:17:23 +02:00
Ahmed Bouhuolia
a56f560036 Merge pull request #627 from bigcapitalhq/fix-style-tweeks-in-onboarding-page
fix: Style tweaks in onboarding page
2024-08-25 13:20:39 +02:00
Ahmed Bouhuolia
0fb886936c Merge pull request #626 from bigcapitalhq/disable-sms-service
fix: Disable sms service until Twilo integration
2024-08-25 13:20:14 +02:00
Ahmed Bouhuolia
670136916f fix: Style tweaks in onboarding page 2024-08-25 13:19:16 +02:00
Ahmed Bouhuolia
768297f137 fix: Disable sms service until Twilo integration 2024-08-25 13:07:07 +02:00
Ahmed Bouhuolia
ef505a0a62 Merge pull request #625 from bigcapitalhq/manual-journal-number-prefix
fix: Add prefix J-00001 to manual journals increments
2024-08-25 13:05:52 +02:00
Ahmed Bouhuolia
42d40620ec fix: add prefix J-00001 to manual journals increments 2024-08-25 13:05:28 +02:00
Ahmed Bouhuolia
959ef7a691 Merge branch 'listen-payment-webhooks' into develop 2024-08-24 21:52:04 +02:00
Ahmed Bouhuolia
e44ebb700a Merge pull request #623 from bigcapitalhq/listen-payment-webhooks
fix: Listen to payment webhooks
2024-08-24 21:49:36 +02:00
22 changed files with 270 additions and 141 deletions

View File

@@ -246,8 +246,11 @@ module.exports = {
apiKey: process.env.LOOPS_API_KEY,
},
oneClickDemoAccounts: parseBoolean(
process.env.ONE_CLICK_DEMO_ACCOUNTS,
false
),
/**
* One-click demo accounts.
*/
oneClickDemoAccounts: {
enable: parseBoolean(process.env.ONE_CLICK_DEMO_ACCOUNTS, false),
demoUrl: process.env.ONE_CLICK_DEMO_ACCOUNTS_URL || '',
},
};

View File

@@ -15,6 +15,7 @@ export default class SeedSettings extends TenantSeeder {
// Manual journals settings.
{ group: 'manual_journals', key: 'next_number', value: '00001' },
{ group: 'manual_journals', key: 'number_prefix', value: 'J-' },
{ group: 'manual_journals', key: 'auto_increment', value: true },
// Sale invoices settings.

View File

@@ -76,6 +76,10 @@ export interface IAuthSendedResetPassword {
export interface IAuthGetMetaPOJO {
signupDisabled: boolean;
oneClickDemo: {
enable: boolean;
demoUrl: string;
};
}
export interface IAuthSignUpVerifingEventPayload {

View File

@@ -11,6 +11,10 @@ export class GetAuthMeta {
public async getAuthMeta(): Promise<IAuthGetMetaPOJO> {
return {
signupDisabled: config.signupRestrictions.disabled,
oneClickDemo: {
enable: config.oneClickDemoAccounts.enable,
demoUrl: config.oneClickDemoAccounts.demoUrl,
},
};
}
}

View File

@@ -18,7 +18,10 @@ export class LemonResumeSubscription {
* @param {string} subscriptionSlug - Subscription slug by default main subscription.
* @returns {Promise<void>}
*/
public async resumeSubscription(tenantId: number, subscriptionSlug: string = 'main') {
public async resumeSubscription(
tenantId: number,
subscriptionSlug: string = 'main'
) {
configureLemonSqueezy();
const subscription = await PlanSubscription.query().findOne({
@@ -34,7 +37,7 @@ export class LemonResumeSubscription {
cancelled: false,
});
if (returnedSub.error) {
throw new ServiceError(ٌٌُERRORS.SOMETHING_WENT_WRONG_WITH_LS);
throw new ServiceError(ERRORS.SOMETHING_WENT_WRONG_WITH_LS);
}
// Triggers `onSubscriptionResume` event.
await this.eventPublisher.emitAsync(

View File

@@ -20,7 +20,6 @@ import { queryConfig } from '../hooks/query/base';
import { EnsureUserEmailVerified } from './Guards/EnsureUserEmailVerified';
import { EnsureAuthNotAuthenticated } from './Guards/EnsureAuthNotAuthenticated';
import { EnsureUserEmailNotVerified } from './Guards/EnsureUserEmailNotVerified';
import { EnsureOneClickDemoAccountEnabled } from '@/containers/OneClickDemo/EnsureOneClickDemoAccountEnabled';
const EmailConfirmation = LazyLoader({
loader: () => import('@/containers/Authentication/EmailConfirmation'),
@@ -31,6 +30,7 @@ const RegisterVerify = LazyLoader({
const OneClickDemoPage = LazyLoader({
loader: () => import('@/containers/OneClickDemo/OneClickDemoPage'),
});
/**
* App inner.
*/
@@ -40,13 +40,7 @@ function AppInsider({ history }) {
<DashboardThemeProvider>
<Router history={history}>
<Switch>
<Route path={'/one_click_demo'}>
<EnsureOneClickDemoAccountEnabled>
<EnsureAuthNotAuthenticated>
<OneClickDemoPage />
</EnsureAuthNotAuthenticated>
</EnsureOneClickDemoAccountEnabled>
</Route>
<Route path={'/one_click_demo'} children={<OneClickDemoPage />} />
<Route path={'/auth/register/verify'}>
<EnsureAuthenticated>
<EnsureUserEmailNotVerified>

View File

@@ -1,6 +1,7 @@
// @ts-nocheck
import React from 'react';
import { useApplicationBoot } from '@/components';
import { useAuthMetadata } from '@/hooks/query/authentication';
/**
* Private pages provider.
@@ -9,7 +10,10 @@ export function PrivatePagesProvider({
// #ownProps
children,
}) {
const { isLoading } = useApplicationBoot();
const { isLoading: isAppBootLoading } = useApplicationBoot();
const { isLoading: isAuthMetaLoading } = useAuthMetadata();
const isLoading = isAppBootLoading || isAuthMetaLoading;
return <React.Fragment>{!isLoading ? children : null}</React.Fragment>;
}

View File

@@ -1,6 +0,0 @@
export const Config = {
oneClickDemo: {
enable: process.env.REACT_APP_ONE_CLICK_DEMO_ENABLE === 'true',
demoUrl: process.env.REACT_APP_DEMO_ACCOUNT_URL || '',
},
};

View File

@@ -54,9 +54,9 @@ export default [
disabled: false,
href: '/preferences/items',
},
{
text: <T id={'sms_integration.label'} />,
disabled: false,
href: '/preferences/sms-message',
},
// {
// text: <T id={'sms_integration.label'} />,
// disabled: false,
// href: '/preferences/sms-message',
// },
];

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { Redirect } from 'react-router-dom';
import { Config } from '@/config';
import { useOneClickDemoBoot } from './OneClickDemoBoot';
interface EnsureOneClickDemoAccountEnabledProps {
children: React.ReactNode;
@@ -11,9 +11,10 @@ export const EnsureOneClickDemoAccountEnabled = ({
children,
redirectTo = '/',
}: EnsureOneClickDemoAccountEnabledProps) => {
const enabeld = Config.oneClickDemo.enable || false;
const { authMeta } = useOneClickDemoBoot();
const enabled = authMeta?.meta?.one_click_demo?.enable || false;
if (!enabeld) {
if (!enabled) {
return <Redirect to={{ pathname: redirectTo }} />;
}
return <>{children}</>;

View File

@@ -0,0 +1,45 @@
import React, { createContext, useContext, ReactNode } from 'react';
import { useAuthMetadata } from '@/hooks/query/authentication';
interface OneClickDemoContextType {
authMeta: any;
}
const OneClickDemoContext = createContext<OneClickDemoContextType>(
{} as OneClickDemoContextType,
);
export const useOneClickDemoBoot = () => {
const context = useContext(OneClickDemoContext);
if (!context) {
throw new Error(
'useOneClickDemo must be used within a OneClickDemoProvider',
);
}
return context;
};
interface OneClickDemoBootProps {
children: ReactNode;
}
export const OneClickDemoBoot: React.FC<OneClickDemoBootProps> = ({
children,
}) => {
const { isLoading: isAuthMetaLoading, data: authMeta } = useAuthMetadata();
const value = {
isAuthMetaLoading,
authMeta,
};
if (isAuthMetaLoading) {
return null;
}
return (
<OneClickDemoContext.Provider value={value}>
{children}
</OneClickDemoContext.Provider>
);
};

View File

@@ -1,97 +1,16 @@
// @ts-nocheck
import { Button, Intent, ProgressBar, Text } from '@blueprintjs/core';
import { useEffect, useState } from 'react';
import {
useCreateOneClickDemo,
useOneClickDemoSignin,
} from '@/hooks/query/oneclick-demo';
import { Box, Icon, Stack } from '@/components';
import { useJob } from '@/hooks/query';
import style from './OneClickDemoPage.module.scss';
import { EnsureAuthNotAuthenticated } from '@/components/Guards/EnsureAuthNotAuthenticated';
import { EnsureOneClickDemoAccountEnabled } from './EnsureOneClickDemoAccountEnabled';
import { OneClickDemoBoot } from './OneClickDemoBoot';
import { OneClickDemoPageContent } from './OneClickDemoPageContent';
export default function OneClickDemoPage() {
const {
mutateAsync: createOneClickDemo,
isLoading: isCreateOneClickLoading,
} = useCreateOneClickDemo();
const {
mutateAsync: oneClickDemoSignIn,
isLoading: isOneclickDemoSigningIn,
} = useOneClickDemoSignin();
// Job states.
const [demoId, setDemoId] = useState<string>('');
const [buildJobId, setBuildJobId] = useState<string>('');
const [isJobDone, setIsJobDone] = useState<boolean>(false);
const {
data: { running, completed },
} = useJob(buildJobId, {
refetchInterval: 2000,
enabled: !isJobDone && !!buildJobId,
});
useEffect(() => {
if (completed) {
setIsJobDone(true);
}
}, [completed, setIsJobDone]);
// One the job done request sign-in using the demo id.
useEffect(() => {
if (isJobDone) {
oneClickDemoSignIn({ demoId }).then((res) => {
debugger;
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isJobDone]);
const handleCreateAccountBtnClick = () => {
createOneClickDemo({})
.then(({ data: { data } }) => {
setBuildJobId(data?.build_job?.job_id);
setDemoId(data?.demo_id);
})
.catch(() => {});
};
const isLoading = running || isOneclickDemoSigningIn;
return (
<Box className={style.root}>
<Box className={style.inner}>
<Stack align={'center'} spacing={40}>
<Icon icon="bigcapital" height={37} width={228} />
{isLoading && (
<Stack align={'center'} spacing={15}>
<ProgressBar stripes value={null} className={style.progressBar} />
{isOneclickDemoSigningIn && (
<Text className={style.waitingText}>
It's signin-in to your demo account, Just a second!
</Text>
)}
{running && (
<Text className={style.waitingText}>
We're preparing temporary environment for trial, It typically
take few seconds. Do not close or refresh the page.
</Text>
)}
</Stack>
)}
</Stack>
{!isLoading && (
<Button
className={style.oneClickBtn}
intent={Intent.NONE}
onClick={handleCreateAccountBtnClick}
loading={isCreateOneClickLoading}
>
Create Demo Account
</Button>
)}
</Box>
</Box>
<EnsureAuthNotAuthenticated>
<OneClickDemoBoot>
<EnsureOneClickDemoAccountEnabled>
<OneClickDemoPageContent />
</EnsureOneClickDemoAccountEnabled>
</OneClickDemoBoot>
</EnsureAuthNotAuthenticated>
);
}

View File

@@ -0,0 +1,97 @@
// @ts-nocheck
import { Button, Intent, ProgressBar, Text } from '@blueprintjs/core';
import { useEffect, useState } from 'react';
import {
useCreateOneClickDemo,
useOneClickDemoSignin,
} from '@/hooks/query/oneclick-demo';
import { Box, Icon, Stack } from '@/components';
import { useJob } from '@/hooks/query';
import style from './OneClickDemoPage.module.scss';
export function OneClickDemoPageContent() {
const {
mutateAsync: createOneClickDemo,
isLoading: isCreateOneClickLoading,
} = useCreateOneClickDemo();
const {
mutateAsync: oneClickDemoSignIn,
isLoading: isOneclickDemoSigningIn,
} = useOneClickDemoSignin();
// Job states.
const [demoId, setDemoId] = useState<string>('');
const [buildJobId, setBuildJobId] = useState<string>('');
const [isJobDone, setIsJobDone] = useState<boolean>(false);
const {
data: { running, completed },
} = useJob(buildJobId, {
refetchInterval: 2000,
enabled: !isJobDone && !!buildJobId,
});
useEffect(() => {
if (completed) {
setIsJobDone(true);
}
}, [completed, setIsJobDone]);
// One the job done request sign-in using the demo id.
useEffect(() => {
if (isJobDone) {
oneClickDemoSignIn({ demoId }).then((res) => {
debugger;
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isJobDone]);
const handleCreateAccountBtnClick = () => {
createOneClickDemo({})
.then(({ data: { data } }) => {
setBuildJobId(data?.build_job?.job_id);
setDemoId(data?.demo_id);
})
.catch(() => {});
};
const isLoading = running || isOneclickDemoSigningIn;
return (
<Box className={style.root}>
<Box className={style.inner}>
<Stack align={'center'} spacing={40}>
<Icon icon="bigcapital" height={37} width={228} />
{isLoading && (
<Stack align={'center'} spacing={15}>
<ProgressBar stripes value={null} className={style.progressBar} />
{isOneclickDemoSigningIn && (
<Text className={style.waitingText}>
It's signin-in to your demo account, Just a second!
</Text>
)}
{running && (
<Text className={style.waitingText}>
We're preparing temporary environment for trial, It typically
take few seconds. Do not close or refresh the page.
</Text>
)}
</Stack>
)}
</Stack>
{!isLoading && (
<Button
className={style.oneClickBtn}
intent={Intent.NONE}
onClick={handleCreateAccountBtnClick}
loading={isCreateOneClickLoading}
>
Create Demo Account
</Button>
)}
</Box>
</Box>
);
}

View File

@@ -2,9 +2,9 @@
width: 100%;
margin-bottom: 2rem;
background: transparent;
color: rgba(255, 255, 255, 0.75);
border: 1px solid rgb(255, 255, 255, 0.3);
padding: 10px;
color: rgba(255, 255, 255, 0.6);
border: 1px solid rgb(255, 255, 255, 0.2);
padding: 8px;
font-size: 13px;
font-weight: 500;
border-radius: 5px;
@@ -12,11 +12,11 @@
transition: border 0.15s ease-in-out, color 0.15s ease-in-out;
&:hover {
color: rgba(255, 255, 255, 0.95);
border: 1px solid rgb(255, 255, 255, 0.6);
color: rgba(255, 255, 255, 0.7);
border: 1px solid rgb(255, 255, 255, 0.3);
}
}
.demoButtonLabel{
color: rgba(255, 255, 255, 0.75);
color: rgba(255, 255, 255, 0.5);
font-size: 13px;
}

View File

@@ -6,7 +6,7 @@ import { Icon, For, FormattedMessage as T, Stack } from '@/components';
import { getFooterLinks } from '@/constants/footerLinks';
import { useAuthActions } from '@/hooks/state';
import style from './SetupLeftSection.module.scss';
import { Config } from '@/config';
import { useAuthMetadata } from '@/hooks/query';
/**
* Footer item link.
@@ -28,13 +28,16 @@ function SetupLeftSectionFooter() {
// Retrieve the footer links.
const footerLinks = getFooterLinks();
const { data: authMeta } = useAuthMetadata();
const demoUrl = authMeta?.meta?.one_click_demo?.demo_url;
const handleDemoBtnClick = () => {
window.open(Config.oneClickDemo.demoUrl);
window.open(demoUrl);
};
return (
<div className={'content__footer'}>
{Config.oneClickDemo.demoUrl && (
{demoUrl && (
<Stack spacing={16}>
<Text className={style.demoButtonLabel}>Not Now?</Text>
<button className={style.demoButton} onClick={handleDemoBtnClick}>

View File

@@ -0,0 +1,20 @@
.container {
text-align: center;
margin-bottom: 1.15rem;
}
.iconText {
display: inline-flex;
font-size: 14px;
margin-right: 16px;
color: #00824d;
&:last-child {
margin-right: 0; /* Remove the margin on the last item */
}
}
.icon {
margin-right: 2px;
}

View File

@@ -0,0 +1,35 @@
import styles from './SubscriptionPlansOfferChecks.module.scss';
export function SubscriptionPlansOfferChecks() {
return (
<div className={styles.container}>
<span className={styles.iconText}>
<svg
xmlns="http://www.w3.org/2000/svg"
height="18px"
width="18px"
viewBox="0 -960 960 960"
fill="rgb(0, 130, 77)"
className={styles.icon}
>
<path d="M378-225 133-470l66-66 179 180 382-382 66 65-448 448Z"></path>
</svg>
14-day free trial
</span>
<span className={styles.iconText}>
<svg
xmlns="http://www.w3.org/2000/svg"
height="18px"
width="18px"
viewBox="0 -960 960 960"
fill="rgb(0, 130, 77)"
className={styles.icon}
>
<path d="M378-225 133-470l66-66 179 180 382-382 66 65-448 448Z"></path>
</svg>
24/7 online support
</span>
</div>
);
}

View File

@@ -24,7 +24,7 @@ function SubscriptionPlansPeriodSwitcherRoot({
);
};
return (
<Group position={'center'} spacing={10} style={{ marginBottom: '1.2rem' }}>
<Group position={'center'} spacing={10} style={{ marginBottom: '1.6rem' }}>
<Text>Pay Monthly</Text>
<Switch
large

View File

@@ -1,6 +1,7 @@
import { Callout } from '@blueprintjs/core';
import { SubscriptionPlans } from './SubscriptionPlans';
import { SubscriptionPlansPeriodSwitcher } from './SubscriptionPlansPeriodSwitcher';
import { SubscriptionPlansOfferChecks } from './SubscriptionPlansOfferChecks';
/**
* Billing plans.
@@ -14,6 +15,7 @@ export function SubscriptionPlansSection() {
include applicable taxes.
</Callout>
<SubscriptionPlansOfferChecks />
<SubscriptionPlansPeriodSwitcher />
<SubscriptionPlans />
</section>

View File

@@ -100,7 +100,7 @@ export const useAuthResetPassword = (props) => {
/**
* Fetches the authentication page metadata.
*/
export const useAuthMetadata = (props) => {
export const useAuthMetadata = (props = {}) => {
return useRequestQuery(
[t.AUTH_METADATA_PAGE],
{

View File

@@ -6,7 +6,7 @@ import Roles from '../containers/Preferences/Users/Roles/RolesForm/RolesFormPage
import Accountant from '@/containers/Preferences/Accountant/Accountant';
import Currencies from '@/containers/Preferences/Currencies/Currencies';
import Item from '@/containers/Preferences/Item';
import SMSIntegration from '../containers/Preferences/SMSIntegration';
// import SMSIntegration from '../containers/Preferences/SMSIntegration';
import DefaultRoute from '../containers/Preferences/DefaultRoute';
import Warehouses from '../containers/Preferences/Warehouses';
import Branches from '../containers/Preferences/Branches';
@@ -85,11 +85,11 @@ export default [
component: Item,
exact: true,
},
{
path: `${BASE_URL}/sms-message`,
component: SMSIntegration,
exact: true,
},
// {
// path: `${BASE_URL}/sms-message`,
// component: SMSIntegration,
// exact: true,
// },
{
path: `${BASE_URL}/billing`,
component: BillingPage,

View File

@@ -29,7 +29,7 @@
&__content {
width: 100%;
padding-bottom: 40px;
padding-bottom: 80px;
}
&__left-section {
@@ -72,10 +72,10 @@
&__text {
font-size: 18px;
opacity: 0.75;
opacity: 0.55;
margin-bottom: 10px;
font-weight: 200;
}
}
&__organization {
font-size: 16px;