diff --git a/apps/backend/src/api/routes/billing.controller.ts b/apps/backend/src/api/routes/billing.controller.ts
index 5added0d..039513fb 100644
--- a/apps/backend/src/api/routes/billing.controller.ts
+++ b/apps/backend/src/api/routes/billing.controller.ts
@@ -30,6 +30,23 @@ export class BillingController {
};
}
+ @Post('/finish-trial')
+ async finishTrial(@GetOrgFromRequest() org: Organization) {
+ try {
+ await this._stripeService.finishTrial(org.paymentId);
+ } catch (err) {}
+ return {
+ finish: true,
+ };
+ }
+
+ @Get('/is-trial-finished')
+ async isTrialFinished(@GetOrgFromRequest() org: Organization) {
+ return {
+ finished: !org.isTrailing,
+ };
+ }
+
@Post('/subscribe')
subscribe(
@GetOrgFromRequest() org: Organization,
diff --git a/apps/backend/src/api/routes/media.controller.ts b/apps/backend/src/api/routes/media.controller.ts
index 480464ab..bb3a5c2f 100644
--- a/apps/backend/src/api/routes/media.controller.ts
+++ b/apps/backend/src/api/routes/media.controller.ts
@@ -180,6 +180,14 @@ export class MediaController {
return this._mediaService.videoFunction(identifier, functionName, body);
}
+ @Get('/generate-video/:type/allowed')
+ generateVideoAllowed(
+ @GetOrgFromRequest() org: Organization,
+ @Param('type') type: string
+ ) {
+ return this._mediaService.generateVideoAllowed(org, type);
+ }
+
@Post('/generate-video/:type')
generateVideo(
@GetOrgFromRequest() org: Organization,
diff --git a/apps/frontend/src/components/billing/finish.trial.tsx b/apps/frontend/src/components/billing/finish.trial.tsx
new file mode 100644
index 00000000..73ae1b74
--- /dev/null
+++ b/apps/frontend/src/components/billing/finish.trial.tsx
@@ -0,0 +1,84 @@
+import React, { FC, useCallback, useEffect, useState } from 'react';
+import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
+import { LoadingComponent } from '@gitroom/frontend/components/layout/loading';
+import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
+import { timer } from '@gitroom/helpers/utils/timer';
+import { Button } from '@gitroom/react/form/button';
+
+export const FinishTrial: FC<{ close: () => void }> = (props) => {
+ const [finished, setFinished] = useState(false);
+ const fetch = useFetch();
+
+ const finishSubscription = useCallback(async () => {
+ await fetch('/billing/finish-trial', {
+ method: 'POST',
+ });
+ checkFinished();
+ }, []);
+
+ const checkFinished = useCallback(async () => {
+ const {finished} = await (await fetch('/billing/is-trial-finished')).json();
+ if (!finished) {
+ await timer(2000);
+ return checkFinished();
+ }
+
+ setFinished(true);
+ }, []);
+
+ useEffect(() => {
+ finishSubscription();
+ }, []);
+
+ return (
+
+
+
+
+
+
+
+ {!finished &&
}
+ {finished && (
+
+
+ You trial has been successfully finished and you have been charged.
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+ );
+};
diff --git a/apps/frontend/src/components/billing/main.billing.component.tsx b/apps/frontend/src/components/billing/main.billing.component.tsx
index 3cca7aaf..6077fdbb 100644
--- a/apps/frontend/src/components/billing/main.billing.component.tsx
+++ b/apps/frontend/src/components/billing/main.billing.component.tsx
@@ -16,7 +16,7 @@ import { FAQComponent } from '@gitroom/frontend/components/billing/faq.component
import { useSWRConfig } from 'swr';
import { useUser } from '@gitroom/frontend/components/layout/user.context';
import interClass from '@gitroom/react/helpers/inter.font';
-import { useRouter } from 'next/navigation';
+import { useParams, useRouter, useSearchParams } from 'next/navigation';
import { useVariables } from '@gitroom/react/helpers/variable.context';
import { useModals } from '@mantine/modals';
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
@@ -28,6 +28,7 @@ import { useTrack } from '@gitroom/react/helpers/use.track';
import { TrackEnum } from '@gitroom/nestjs-libraries/user/track.enum';
import { PurchaseCrypto } from '@gitroom/frontend/components/billing/purchase.crypto';
import { useT } from '@gitroom/react/translation/get.transation.service.client';
+import { FinishTrial } from '@gitroom/frontend/components/billing/finish.trial';
export interface Tiers {
month: Array<{
name: 'Pro' | 'Standard';
@@ -224,6 +225,8 @@ export const MainBillingComponent: FC<{
const tolt = useTolt();
const track = useTrack();
const t = useT();
+ const queryParams = useSearchParams();
+ const [finishTrial, setFinishTrial] = useState(!!queryParams.get('finishTrial'));
const [subscription, setSubscription] = useState(
sub
@@ -399,6 +402,10 @@ export const MainBillingComponent: FC<{
{t('yearly', 'YEARLY')}
+
+ {finishTrial && (
+ setFinishTrial(false)} />
+ )}
{Object.entries(pricing)
.filter((f) => !isGeneral || f[0] !== 'FREE')
diff --git a/apps/frontend/src/components/launches/ai.video.tsx b/apps/frontend/src/components/launches/ai.video.tsx
index b2e757b8..de75b4a5 100644
--- a/apps/frontend/src/components/launches/ai.video.tsx
+++ b/apps/frontend/src/components/launches/ai.video.tsx
@@ -9,6 +9,9 @@ import useSWR from 'swr';
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
import { VideoWrapper } from '@gitroom/frontend/components/videos/video.render.component';
import { FormProvider, useForm } from 'react-hook-form';
+import { useUser } from '@gitroom/frontend/components/layout/user.context';
+import { VideoContextWrapper } from '@gitroom/frontend/components/videos/video.context.wrapper';
+import { useToaster } from '@gitroom/react/toaster/toaster';
export const Modal: FC<{
close: () => void;
@@ -22,6 +25,7 @@ export const Modal: FC<{
const setLocked = useLaunchStore((state) => state.setLocked);
const form = useForm();
const [position, setPosition] = useState('vertical');
+ const toaster = useToaster();
const loadCredits = useCallback(async () => {
return (
@@ -34,12 +38,16 @@ export const Modal: FC<{
const { data, mutate } = useSWR('copilot-credits', loadCredits);
const generate = useCallback(async () => {
+ await fetch(`/media/generate-video/${type.identifier}/allowed`);
setLoading(true);
close();
setLocked(true);
- console.log('lock');
const customParams = form.getValues();
+ if (!await form.trigger()) {
+ toaster.show('Please fill all required fields', 'warning');
+ return ;
+ }
try {
const image = await fetch(`/media/generate-video/${type.identifier}`, {
method: 'POST',
@@ -50,91 +58,90 @@ export const Modal: FC<{
}),
});
- console.log(image);
-
if (image.status == 200 || image.status == 201) {
onChange(await image.json());
}
} catch (e) {}
- console.log('remove lock');
setLocked(false);
setLoading(false);
}, [type, value, position]);
return (
-