fix: one click demo

This commit is contained in:
Ahmed Bouhuolia
2024-08-19 21:21:39 +02:00
parent fed620505d
commit cca596b4a9
10 changed files with 270 additions and 23 deletions

View File

@@ -3,6 +3,8 @@ import { Service, Inject } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseController from '@/api/controllers/BaseController';
import { OneClickDemoApplication } from '@/services/OneClickDemo/OneClickDemoApplication';
import { reset } from 'colorette';
import { body } from 'express-validator';
@Service()
export class OneClickDemoController extends BaseController {
@@ -16,6 +18,14 @@ export class OneClickDemoController extends BaseController {
const router = Router();
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;
}
@@ -38,4 +48,26 @@ export class OneClickDemoController extends BaseController {
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);
}
}
}

View File

@@ -1,8 +1,11 @@
import { Inject, Service } from 'typedi';
import { faker } from '@faker-js/faker';
import uniqid from 'uniqid';
import AuthenticationApplication from '../Authentication/AuthApplication';
import OrganizationService from '../Organization/OrganizationService';
import { IAuthSignInPOJO } from '@/interfaces';
import { OneClickDemo } from '@/system/models/OneclickDemo';
import { SystemUser } from '@/system/models';
@Service()
export class CreateOneClickDemo {
@@ -16,17 +19,21 @@ export class CreateOneClickDemo {
* Creates one-click demo account.
* @returns {Promise<ICreateOneClickDemoPOJO>}
*/
async createOneClickDemo(): Promise<ICreateOneClickDemoPOJO> {
public async createOneClickDemo(): Promise<ICreateOneClickDemoPOJO> {
const firstName = faker.person.firstName();
const lastName = faker.person.lastName();
const email = faker.internet.email();
const password = '123123123';
const demoId = uniqid();
await this.authApp.signUp({ firstName, lastName, email, password });
//
const signedIn = await this.authApp.signIn(email, password);
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(
tenantId,
@@ -40,18 +47,33 @@ export class CreateOneClickDemo {
},
signedIn.user
);
return { email, signedIn, buildJob };
return { email, demoId, signedIn, buildJob };
}
/**
* Sign-in automicatlly using the demo id one creating an account finish.
* @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 {
email: string;
demoId: string;
signedIn: IAuthSignInPOJO;
buildJob: any;
}

View File

@@ -1,14 +1,25 @@
import { Inject, Service } from "typedi";
import { CreateOneClickDemo } from "./CreateOneClickDemo";
import { Inject, Service } from 'typedi';
import { CreateOneClickDemo } from './CreateOneClickDemo';
@Service()
export class OneClickDemoApplication {
@Inject()
private createOneClickDemoService: CreateOneClickDemo;
/**
*
* @returns
*/
public createOneClick() {
return this.createOneClickDemoService.createOneClickDemo();
}
}
/**
*
* @param oneClickDemoId
* @returns
*/
public autoSignIn(oneClickDemoId: string) {
return this.createOneClickDemoService.autoSignIn(oneClickDemoId);
}
}

View File

@@ -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');
};

View 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'];
}
}

View File

@@ -40,7 +40,9 @@ function AppInsider({ history }) {
<Router history={history}>
<Switch>
<Route path={'/one_click_demo'}>
<OneClickDemoPage />
<EnsureAuthNotAuthenticated>
<OneClickDemoPage />
</EnsureAuthNotAuthenticated>
</Route>
<Route path={'/auth/register/verify'}>
<EnsureAuthenticated>

View File

@@ -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;
}

View File

@@ -1,39 +1,85 @@
// @ts-nocheck
import { Button, Intent, ProgressBar, Spinner, Text } from '@blueprintjs/core';
import { useEffect, useState } from 'react';
import { Box } from '@/components';
import { useCreateOneClickDemo } from '@/hooks/query/oneclick-demo';
import { Button } from '@blueprintjs/core';
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 default function OneClickDemoPage() {
const { mutateAsync: createOneClickDemo } = useCreateOneClickDemo();
const {
mutateAsync: createOneClickDemo,
isLoading: isCreateOneClickLoading,
} = useCreateOneClickDemo();
const { mutateAsync: oneClickDemoSignIn } = useOneClickDemoSignin();
const [buildJobId, setBuildJobId] = useState<string>('');
const [demoId, setDemoId] = useState<string>('');
// Job done state.
const [isJobDone, setIsJobDone] = useState<boolean>(false);
const {
data: { running, queued, failed, completed },
isFetching: isJobFetching,
} = useJob(buildJobId, {
refetchInterval: 2000,
enabled: !!buildJobId,
enabled: !isJobDone && !!buildJobId,
onSuccess: (res) => {
if (res.completed) {
oneClickDemoSignIn({ demoId }).then((res) => {
debugger;
});
}
},
});
useEffect(() => {
if (completed) {
setIsJobDone(true);
}
}, [completed]);
}, [completed, setIsJobDone]);
const handleCreateAccountBtnClick = () => {
createOneClickDemo({})
.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(() => {});
};
const isLoading = running;
return (
<Box>
{running && (<h1>Building...</h1>)}
<Button onClick={handleCreateAccountBtnClick}>One-Click Create</Button>
<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} />
<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>
);
}

View File

@@ -16,7 +16,7 @@ import {
/**
* Saves the response data to cookies.
*/
function setAuthLoginCookies(data) {
export function setAuthLoginCookies(data) {
setCookie('token', data.token);
setCookie('authenticated_user_id', data.user.id);
setCookie('organization_id', data.tenant.organization_id);

View File

@@ -6,9 +6,22 @@ import {
useQueryClient,
} from 'react-query';
import useApiRequest from '../useRequest';
import {
useSetAuthToken,
useSetAuthUserId,
useSetLocale,
useSetOrganizationId,
useSetTenantId,
} from '../state';
import { setAuthLoginCookies } from './authentication';
import { batch } from 'react-redux';
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,
},
);
}