mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
fix: one click demo
This commit is contained in:
@@ -3,6 +3,8 @@ import { Service, Inject } from 'typedi';
|
|||||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
import BaseController from '@/api/controllers/BaseController';
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
import { OneClickDemoApplication } from '@/services/OneClickDemo/OneClickDemoApplication';
|
import { OneClickDemoApplication } from '@/services/OneClickDemo/OneClickDemoApplication';
|
||||||
|
import { reset } from 'colorette';
|
||||||
|
import { body } from 'express-validator';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class OneClickDemoController extends BaseController {
|
export class OneClickDemoController extends BaseController {
|
||||||
@@ -16,6 +18,14 @@ export class OneClickDemoController extends BaseController {
|
|||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post('/one_click', asyncMiddleware(this.oneClickDemo.bind(this)));
|
router.post('/one_click', asyncMiddleware(this.oneClickDemo.bind(this)));
|
||||||
|
router.post(
|
||||||
|
'/one_click_signin',
|
||||||
|
[
|
||||||
|
body('demo_id').exists(),
|
||||||
|
],
|
||||||
|
this.validationResult,
|
||||||
|
asyncMiddleware(this.oneClickSignIn.bind(this))
|
||||||
|
);
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
@@ -38,4 +48,26 @@ export class OneClickDemoController extends BaseController {
|
|||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Request} req
|
||||||
|
* @param {Response} res
|
||||||
|
* @param {NextFunction} next
|
||||||
|
*/
|
||||||
|
private async oneClickSignIn(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) {
|
||||||
|
const { demoId } = this.matchedBodyData(req);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.oneClickDemoApp.autoSignIn(demoId);
|
||||||
|
|
||||||
|
return res.status(200).send(data);
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
|
import uniqid from 'uniqid';
|
||||||
import AuthenticationApplication from '../Authentication/AuthApplication';
|
import AuthenticationApplication from '../Authentication/AuthApplication';
|
||||||
import OrganizationService from '../Organization/OrganizationService';
|
import OrganizationService from '../Organization/OrganizationService';
|
||||||
import { IAuthSignInPOJO } from '@/interfaces';
|
import { IAuthSignInPOJO } from '@/interfaces';
|
||||||
|
import { OneClickDemo } from '@/system/models/OneclickDemo';
|
||||||
|
import { SystemUser } from '@/system/models';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class CreateOneClickDemo {
|
export class CreateOneClickDemo {
|
||||||
@@ -16,17 +19,21 @@ export class CreateOneClickDemo {
|
|||||||
* Creates one-click demo account.
|
* Creates one-click demo account.
|
||||||
* @returns {Promise<ICreateOneClickDemoPOJO>}
|
* @returns {Promise<ICreateOneClickDemoPOJO>}
|
||||||
*/
|
*/
|
||||||
async createOneClickDemo(): Promise<ICreateOneClickDemoPOJO> {
|
public async createOneClickDemo(): Promise<ICreateOneClickDemoPOJO> {
|
||||||
const firstName = faker.person.firstName();
|
const firstName = faker.person.firstName();
|
||||||
const lastName = faker.person.lastName();
|
const lastName = faker.person.lastName();
|
||||||
const email = faker.internet.email();
|
const email = faker.internet.email();
|
||||||
const password = '123123123';
|
const password = '123123123';
|
||||||
|
const demoId = uniqid();
|
||||||
|
|
||||||
await this.authApp.signUp({ firstName, lastName, email, password });
|
await this.authApp.signUp({ firstName, lastName, email, password });
|
||||||
|
|
||||||
//
|
|
||||||
const signedIn = await this.authApp.signIn(email, password);
|
const signedIn = await this.authApp.signIn(email, password);
|
||||||
const tenantId = signedIn.tenant.id;
|
const tenantId = signedIn.tenant.id;
|
||||||
|
const userId = signedIn.user.id;
|
||||||
|
|
||||||
|
// Creates a new one-click demo.
|
||||||
|
await OneClickDemo.query().insert({ key: demoId, tenantId, userId });
|
||||||
|
|
||||||
const buildJob = await this.organizationService.buildRunJob(
|
const buildJob = await this.organizationService.buildRunJob(
|
||||||
tenantId,
|
tenantId,
|
||||||
@@ -40,18 +47,33 @@ export class CreateOneClickDemo {
|
|||||||
},
|
},
|
||||||
signedIn.user
|
signedIn.user
|
||||||
);
|
);
|
||||||
return { email, signedIn, buildJob };
|
return { email, demoId, signedIn, buildJob };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign-in automicatlly using the demo id one creating an account finish.
|
* Sign-in automicatlly using the demo id one creating an account finish.
|
||||||
* @param {string} oneClickDemoId -
|
* @param {string} oneClickDemoId -
|
||||||
*/
|
*/
|
||||||
async autoSignIn(oneClickDemoId: string) {}
|
async autoSignIn(oneClickDemoId: string) {
|
||||||
|
const foundOneclickDemo = await OneClickDemo.query()
|
||||||
|
.findOne('key', oneClickDemoId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
const userId = foundOneclickDemo.userId;
|
||||||
|
const user = await SystemUser.query().findById(userId);
|
||||||
|
|
||||||
|
const email = user.email;
|
||||||
|
const password = '123123123';
|
||||||
|
|
||||||
|
const signedIn = await this.authApp.signIn(email, password);
|
||||||
|
|
||||||
|
return signedIn;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ICreateOneClickDemoPOJO {
|
interface ICreateOneClickDemoPOJO {
|
||||||
email: string;
|
email: string;
|
||||||
|
demoId: string;
|
||||||
signedIn: IAuthSignInPOJO;
|
signedIn: IAuthSignInPOJO;
|
||||||
buildJob: any;
|
buildJob: any;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,25 @@
|
|||||||
import { Inject, Service } from "typedi";
|
import { Inject, Service } from 'typedi';
|
||||||
import { CreateOneClickDemo } from "./CreateOneClickDemo";
|
import { CreateOneClickDemo } from './CreateOneClickDemo';
|
||||||
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class OneClickDemoApplication {
|
export class OneClickDemoApplication {
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private createOneClickDemoService: CreateOneClickDemo;
|
private createOneClickDemoService: CreateOneClickDemo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
public createOneClick() {
|
public createOneClick() {
|
||||||
return this.createOneClickDemoService.createOneClickDemo();
|
return this.createOneClickDemoService.createOneClickDemo();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param oneClickDemoId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public autoSignIn(oneClickDemoId: string) {
|
||||||
|
return this.createOneClickDemoService.autoSignIn(oneClickDemoId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* @param { import("knex").Knex } knex
|
||||||
|
* @returns { Promise<void> }
|
||||||
|
*/
|
||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema.createTable('oneclick_demos', (table) => {
|
||||||
|
table.increments('id');
|
||||||
|
table.string('key');
|
||||||
|
table.integer('tenant_id').unsigned();
|
||||||
|
table.integer('user_id').unsigned();
|
||||||
|
table.timestamps();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param { import("knex").Knex } knex
|
||||||
|
* @returns { Promise<void> }
|
||||||
|
*/
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema.dropTableIfExists('oneclick_demos');
|
||||||
|
};
|
||||||
17
packages/server/src/system/models/OneclickDemo.ts
Normal file
17
packages/server/src/system/models/OneclickDemo.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import SystemModel from '@/system/models/SystemModel';
|
||||||
|
|
||||||
|
export class OneClickDemo extends SystemModel {
|
||||||
|
/**
|
||||||
|
* Table name
|
||||||
|
*/
|
||||||
|
static get tableName() {
|
||||||
|
return 'oneclick_demos';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamps columns.
|
||||||
|
*/
|
||||||
|
get timestamps() {
|
||||||
|
return ['createdAt'];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,7 +40,9 @@ function AppInsider({ history }) {
|
|||||||
<Router history={history}>
|
<Router history={history}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path={'/one_click_demo'}>
|
<Route path={'/one_click_demo'}>
|
||||||
<OneClickDemoPage />
|
<EnsureAuthNotAuthenticated>
|
||||||
|
<OneClickDemoPage />
|
||||||
|
</EnsureAuthNotAuthenticated>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path={'/auth/register/verify'}>
|
<Route path={'/auth/register/verify'}>
|
||||||
<EnsureAuthenticated>
|
<EnsureAuthenticated>
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
|
||||||
|
|
||||||
|
.root {
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner{
|
||||||
|
margin: auto;
|
||||||
|
max-width: 450px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progressBar{
|
||||||
|
height: 5px;
|
||||||
|
|
||||||
|
:global .bp4-progress-meter{
|
||||||
|
background-color: rgba(159, 171, 188, 0.8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.oneClickBtn {
|
||||||
|
width: 400px;
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waitingText{
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.54;
|
||||||
|
color: #5F6B7C;
|
||||||
|
}
|
||||||
@@ -1,39 +1,85 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
import { Button, Intent, ProgressBar, Spinner, Text } from '@blueprintjs/core';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Box } from '@/components';
|
import {
|
||||||
import { useCreateOneClickDemo } from '@/hooks/query/oneclick-demo';
|
useCreateOneClickDemo,
|
||||||
import { Button } from '@blueprintjs/core';
|
useOneClickDemoSignin,
|
||||||
|
} from '@/hooks/query/oneclick-demo';
|
||||||
|
import { Box, Icon, Stack } from '@/components';
|
||||||
import { useJob } from '@/hooks/query';
|
import { useJob } from '@/hooks/query';
|
||||||
|
import style from './OneClickDemoPage.module.scss';
|
||||||
|
|
||||||
export default function OneClickDemoPage() {
|
export default function OneClickDemoPage() {
|
||||||
const { mutateAsync: createOneClickDemo } = useCreateOneClickDemo();
|
const {
|
||||||
|
mutateAsync: createOneClickDemo,
|
||||||
|
isLoading: isCreateOneClickLoading,
|
||||||
|
} = useCreateOneClickDemo();
|
||||||
|
const { mutateAsync: oneClickDemoSignIn } = useOneClickDemoSignin();
|
||||||
const [buildJobId, setBuildJobId] = useState<string>('');
|
const [buildJobId, setBuildJobId] = useState<string>('');
|
||||||
|
const [demoId, setDemoId] = useState<string>('');
|
||||||
|
|
||||||
|
// Job done state.
|
||||||
|
const [isJobDone, setIsJobDone] = useState<boolean>(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: { running, queued, failed, completed },
|
data: { running, queued, failed, completed },
|
||||||
isFetching: isJobFetching,
|
isFetching: isJobFetching,
|
||||||
} = useJob(buildJobId, {
|
} = useJob(buildJobId, {
|
||||||
refetchInterval: 2000,
|
refetchInterval: 2000,
|
||||||
enabled: !!buildJobId,
|
enabled: !isJobDone && !!buildJobId,
|
||||||
|
onSuccess: (res) => {
|
||||||
|
if (res.completed) {
|
||||||
|
oneClickDemoSignIn({ demoId }).then((res) => {
|
||||||
|
debugger;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (completed) {
|
if (completed) {
|
||||||
|
setIsJobDone(true);
|
||||||
}
|
}
|
||||||
}, [completed]);
|
}, [completed, setIsJobDone]);
|
||||||
|
|
||||||
const handleCreateAccountBtnClick = () => {
|
const handleCreateAccountBtnClick = () => {
|
||||||
createOneClickDemo({})
|
createOneClickDemo({})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
setBuildJobId(res?.data?.data?.build_job?.job_id)
|
setBuildJobId(res?.data?.data?.build_job?.job_id);
|
||||||
|
setDemoId(res?.data?.data?.demo_id);
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
};
|
};
|
||||||
|
const isLoading = running;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box className={style.root}>
|
||||||
{running && (<h1>Building...</h1>)}
|
<Box className={style.inner}>
|
||||||
<Button onClick={handleCreateAccountBtnClick}>One-Click Create</Button>
|
<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} />
|
||||||
|
<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.PRIMARY}
|
||||||
|
onClick={handleCreateAccountBtnClick}
|
||||||
|
loading={isCreateOneClickLoading}
|
||||||
|
>
|
||||||
|
Create Demo Account
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
/**
|
/**
|
||||||
* Saves the response data to cookies.
|
* Saves the response data to cookies.
|
||||||
*/
|
*/
|
||||||
function setAuthLoginCookies(data) {
|
export function setAuthLoginCookies(data) {
|
||||||
setCookie('token', data.token);
|
setCookie('token', data.token);
|
||||||
setCookie('authenticated_user_id', data.user.id);
|
setCookie('authenticated_user_id', data.user.id);
|
||||||
setCookie('organization_id', data.tenant.organization_id);
|
setCookie('organization_id', data.tenant.organization_id);
|
||||||
|
|||||||
@@ -6,9 +6,22 @@ import {
|
|||||||
useQueryClient,
|
useQueryClient,
|
||||||
} from 'react-query';
|
} from 'react-query';
|
||||||
import useApiRequest from '../useRequest';
|
import useApiRequest from '../useRequest';
|
||||||
|
import {
|
||||||
|
useSetAuthToken,
|
||||||
|
useSetAuthUserId,
|
||||||
|
useSetLocale,
|
||||||
|
useSetOrganizationId,
|
||||||
|
useSetTenantId,
|
||||||
|
} from '../state';
|
||||||
|
import { setAuthLoginCookies } from './authentication';
|
||||||
|
import { batch } from 'react-redux';
|
||||||
|
|
||||||
interface CreateOneClickDemoValues {}
|
interface CreateOneClickDemoValues {}
|
||||||
interface CreateOneClickDemoRes {}
|
interface CreateOneClickDemoRes {
|
||||||
|
email: string;
|
||||||
|
signedIn: any;
|
||||||
|
buildJob: any;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -33,3 +46,54 @@ export function useCreateOneClickDemo(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface OneClickSigninDemoValues {
|
||||||
|
demoId: string;
|
||||||
|
}
|
||||||
|
interface OneClickSigninDemoRes {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {UseMutationOptions<OneClickSigninDemoRes, Error, OneClickSigninDemoValues>} props
|
||||||
|
* @returns {UseMutationResult<OneClickSigninDemoRes, Error, OneClickSigninDemoValues>}
|
||||||
|
*/
|
||||||
|
export function useOneClickDemoSignin(
|
||||||
|
props?: UseMutationOptions<
|
||||||
|
OneClickSigninDemoRes,
|
||||||
|
Error,
|
||||||
|
OneClickSigninDemoValues
|
||||||
|
>,
|
||||||
|
): UseMutationResult<OneClickSigninDemoRes, Error, OneClickSigninDemoValues> {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
const setAuthToken = useSetAuthToken();
|
||||||
|
const setOrganizationId = useSetOrganizationId();
|
||||||
|
const setUserId = useSetAuthUserId();
|
||||||
|
const setTenantId = useSetTenantId();
|
||||||
|
const setLocale = useSetLocale();
|
||||||
|
|
||||||
|
return useMutation<OneClickSigninDemoRes, Error, OneClickSigninDemoValues>(
|
||||||
|
({ demoId }) =>
|
||||||
|
apiRequest.post(`/demo/one_click_signin`, { demo_id: demoId }),
|
||||||
|
{
|
||||||
|
onSuccess: (res, id) => {
|
||||||
|
// Set authentication cookies.
|
||||||
|
setAuthLoginCookies(res.data);
|
||||||
|
|
||||||
|
batch(() => {
|
||||||
|
// Sets the auth metadata to global state.
|
||||||
|
setAuthToken(res.data.token);
|
||||||
|
setOrganizationId(res.data.tenant.organization_id);
|
||||||
|
setUserId(res.data.user.id);
|
||||||
|
setTenantId(res.data.tenant.id);
|
||||||
|
|
||||||
|
if (res.data?.tenant?.metadata?.language) {
|
||||||
|
setLocale(res.data?.tenant?.metadata?.language);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user