Skip to content

Commit

Permalink
feat(scenario): update scenario name and description (#361)
Browse files Browse the repository at this point in the history
  • Loading branch information
balzdur authored Feb 7, 2024
1 parent 73bef91 commit 075d581
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 10 deletions.
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,10 @@
"typescript": "5.3.3",
"vite-tsconfig-paths": "^4.3.1",
"vitest": "1.2.1"
},
"pnpm": {
"patchedDependencies": {
"[email protected]": "patches/[email protected]"
}
}
}
1 change: 1 addition & 0 deletions packages/app-builder/public/locales/en/scenarios.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"create_scenario.trigger_object_placeholder": "Select a trigger object",
"create_scenario.no_trigger_object": "No trigger object. Edit your data model to add one.",
"create_scenario.trigger_object_title": "Trigger Object",
"update_scenario.title": "Update Scenario",
"scenario.create_rule.title": "New Rule",
"create_rule.title": "New Rule",
"create_rule.name_placeholder": "Add a name to your rule",
Expand Down
30 changes: 30 additions & 0 deletions packages/app-builder/src/repositories/ScenarioRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ import { type Scenario } from 'marble-api';

export interface ScenarioRepository {
listScenarios(): Promise<Scenario[]>;
createScenario(args: {
name: string;
description: string;
triggerObjectType: string;
}): Promise<Scenario>;
updateScenario(args: {
scenarioId: string;
name: string;
description: string | null;
}): Promise<Scenario>;
createScenarioIteration(args: {
scenarioId: string;
}): Promise<ScenarioIteration>;
getScenarioIterationRule(args: {
ruleId: string;
}): Promise<ScenarioIterationRule>;
Expand All @@ -39,6 +52,23 @@ export function getScenarioRepository() {
const scenarios = await marbleApiClient.listScenarios();
return scenarios;
},
createScenario: async (args) => {
const scenario = await marbleApiClient.createScenario(args);
return scenario;
},
updateScenario: async ({ scenarioId, name, description }) => {
const scenario = await marbleApiClient.updateScenario(scenarioId, {
name,
description: description ?? '',
});
return scenario;
},
createScenarioIteration: async ({ scenarioId }) => {
const scenarioIteration = await marbleApiClient.createScenarioIteration({
scenarioId,
});
return adaptScenarioIteration(scenarioIteration);
},
getScenarioIterationRule: async ({ ruleId }) => {
const { rule } = await marbleApiClient.getScenarioIterationRule(ruleId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { sortScenarioIterations } from '@app-builder/models/scenario-iteration';
import { useCurrentScenario } from '@app-builder/routes/_builder+/scenarios+/$scenarioId+/_layout';
import { CreateDraftIteration } from '@app-builder/routes/ressources+/scenarios+/$scenarioId+/$iterationId+/create_draft';
import { DeploymentActions } from '@app-builder/routes/ressources+/scenarios+/deployment';
import { UpdateScenario } from '@app-builder/routes/ressources+/scenarios+/update';
import { useEditorMode } from '@app-builder/services/editor';
import { serverServices } from '@app-builder/services/init.server';
import {
Expand Down Expand Up @@ -85,9 +86,17 @@ export default function ScenarioEditLayout() {
<Link to={getRoute('/scenarios/')}>
<Page.BackButton />
</Link>
<span className="line-clamp-2 text-ellipsis">
{currentScenario.name}
</span>
<UpdateScenario
defaultValue={{
name: currentScenario.name,
scenarioId: currentScenario.id,
description: currentScenario.description,
}}
>
<button className="line-clamp-2 text-ellipsis outline-none hover:text-purple-100 hover:underline focus:text-purple-100 focus:underline">
{currentScenario.name}
</button>
</UpdateScenario>
<VersionSelect
scenarioIterations={sortedScenarioIterations}
currentIteration={currentIteration}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const createScenarioFormSchema = z.object({

export async function action({ request }: ActionFunctionArgs) {
const { authService } = serverServices;
const { apiClient } = await authService.isAuthenticated(request, {
const { scenario } = await authService.isAuthenticated(request, {
failureRedirect: getRoute('/sign-in'),
});
const parsedForm = await parseFormSafe(request, createScenarioFormSchema);
Expand All @@ -56,17 +56,17 @@ export async function action({ request }: ActionFunctionArgs) {
const { name, description, triggerObjectType } = parsedForm.data;

try {
const scenario = await apiClient.createScenario({
const createdScenario = await scenario.createScenario({
name: name,
description: description,
triggerObjectType: triggerObjectType,
});
const scenarioIteration = await apiClient.createScenarioIteration({
scenarioId: scenario.id,
const scenarioIteration = await scenario.createScenarioIteration({
scenarioId: createdScenario.id,
});
return redirect(
getRoute('/scenarios/:scenarioId/i/:iterationId', {
scenarioId: fromUUID(scenario.id),
scenarioId: fromUUID(createdScenario.id),
iterationId: fromUUID(scenarioIteration.id),
}),
);
Expand Down
149 changes: 149 additions & 0 deletions packages/app-builder/src/routes/ressources+/scenarios+/update.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { FormError } from '@app-builder/components/Form/FormError';
import { FormField } from '@app-builder/components/Form/FormField';
import { FormInput } from '@app-builder/components/Form/FormInput';
import { FormLabel } from '@app-builder/components/Form/FormLabel';
import { setToastMessage } from '@app-builder/components/MarbleToaster';
import { serverServices } from '@app-builder/services/init.server';
import { getRoute } from '@app-builder/utils/routes';
import { fromUUID } from '@app-builder/utils/short-uuid';
import { conform, useForm } from '@conform-to/react';
import { getFieldsetConstraint, parse } from '@conform-to/zod';
import { type ActionFunctionArgs, json } from '@remix-run/node';
import { useFetcher, useNavigation } from '@remix-run/react';
import { useEffect, useId, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { redirectBack } from 'remix-utils/redirect-back';
import { Button, Modal } from 'ui-design-system';
import { z } from 'zod';

const updateScenarioFormSchema = z.object({
scenarioId: z.string().uuid(),
name: z.string().min(1),
description: z.string().nullable().default(null),
});

type UpdateScenarioForm = z.infer<typeof updateScenarioFormSchema>;

export async function action({ request }: ActionFunctionArgs) {
const {
authService,
toastSessionService: { getSession, commitSession },
} = serverServices;
const { scenario } = await authService.isAuthenticated(request, {
failureRedirect: getRoute('/sign-in'),
});
const formData = await request.formData();
const submission = parse(formData, { schema: updateScenarioFormSchema });

if (submission.intent !== 'submit' || !submission.value) {
return json(submission);
}

try {
await scenario.updateScenario(submission.value);
return redirectBack(request, {
fallback: getRoute('/scenarios/:scenarioId', {
scenarioId: fromUUID(submission.value.scenarioId),
}),
});
} catch (error) {
const session = await getSession(request);
setToastMessage(session, {
type: 'error',
messageKey: 'common:errors.unknown',
});
return json(submission, {
headers: { 'Set-Cookie': await commitSession(session) },
});
}
}

export function UpdateScenario({
children,
defaultValue,
}: {
children: React.ReactNode;
defaultValue: UpdateScenarioForm;
}) {
const [open, setOpen] = useState(false);

const navigation = useNavigation();
useEffect(() => {
if (navigation.state === 'loading') {
setOpen(false);
}
}, [navigation.state]);

return (
<Modal.Root open={open} onOpenChange={setOpen}>
<Modal.Trigger asChild>{children}</Modal.Trigger>
<Modal.Content>
<UpdateScenarioContent defaultValue={defaultValue} />
</Modal.Content>
</Modal.Root>
);
}

function UpdateScenarioContent({
defaultValue,
}: {
defaultValue: UpdateScenarioForm;
}) {
const { t } = useTranslation(['scenarios', 'common']);
const fetcher = useFetcher<typeof action>();
console.log(defaultValue);
const formId = useId();
const [form, { name, description, scenarioId }] = useForm({
id: formId,
defaultValue,
lastSubmission: fetcher.data,
constraint: getFieldsetConstraint(updateScenarioFormSchema),
// onValidate({ formData }) {
// return parse(formData, {
// schema: updateScenarioFormSchema,
// });
// },
});

return (
<fetcher.Form
method="PATCH"
action={getRoute('/ressources/scenarios/update')}
{...form.props}
>
<Modal.Title>{t('scenarios:create_scenario.title')}</Modal.Title>
<div className="flex flex-col gap-6 p-6">
<input {...conform.input(scenarioId, { type: 'hidden' })} />
<FormField config={name} className="group flex w-full flex-col gap-2">
<FormLabel>{t('scenarios:create_scenario.name')}</FormLabel>
<FormInput
type="text"
placeholder={t('scenarios:create_scenario.name_placeholder')}
/>
<FormError />
</FormField>
<FormField
config={description}
className="group flex w-full flex-col gap-2"
>
<FormLabel>{t('scenarios:create_scenario.description')}</FormLabel>
<FormInput
type="text"
placeholder={t('scenarios:create_scenario.description_placeholder')}
/>
<FormError />
</FormField>
<div className="flex flex-1 flex-row gap-2">
<Modal.Close asChild>
<Button className="flex-1" variant="secondary">
{t('common:cancel')}
</Button>
</Modal.Close>
<Button className="flex-1" variant="primary" type="submit">
{t('common:save')}
</Button>
</div>
</div>
</fetcher.Form>
);
}
5 changes: 5 additions & 0 deletions packages/app-builder/src/utils/routes/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,11 @@ export const routes = [
"path": "ressources/scenarios/deployment",
"file": "routes/ressources+/scenarios+/deployment.tsx"
},
{
"id": "routes/ressources+/scenarios+/update",
"path": "ressources/scenarios/update",
"file": "routes/ressources+/scenarios+/update.tsx"
},
{
"id": "routes/ressources+/settings+/inboxes+/create",
"path": "ressources/settings/inboxes/create",
Expand Down
2 changes: 2 additions & 0 deletions packages/app-builder/src/utils/routes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export type RoutePath =
| '/ressources/scenarios/:scenarioId/:iterationId/validate-with-given-trigger-or-rule'
| '/ressources/scenarios/create'
| '/ressources/scenarios/deployment'
| '/ressources/scenarios/update'
| '/ressources/settings/inboxes/create'
| '/ressources/settings/inboxes/delete'
| '/ressources/settings/inboxes/inbox-users/create'
Expand Down Expand Up @@ -139,6 +140,7 @@ export type RouteID =
| 'routes/ressources+/scenarios+/$scenarioId+/$iterationId+/validate-with-given-trigger-or-rule'
| 'routes/ressources+/scenarios+/create'
| 'routes/ressources+/scenarios+/deployment'
| 'routes/ressources+/scenarios+/update'
| 'routes/ressources+/settings+/inboxes+/create'
| 'routes/ressources+/settings+/inboxes+/delete'
| 'routes/ressources+/settings+/inboxes+/inbox-users.create'
Expand Down
15 changes: 15 additions & 0 deletions patches/[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
diff --git a/build/server/redirect-back.d.ts b/build/server/redirect-back.d.ts
index b96ca64aaf202b4103099dc4299e09310566f341..9b8b85276fe07283f8e2947ca752b67128a1e827 100644
--- a/build/server/redirect-back.d.ts
+++ b/build/server/redirect-back.d.ts
@@ -1,3 +1,4 @@
+import { TypedResponse } from "@remix-run/node";
/**
* Create a new Response with a redirect set to the URL the user was before.
* It uses the Referer header to detect the previous URL. It asks for a fallback
@@ -14,4 +15,4 @@
*/
export declare function redirectBack(request: Request, { fallback, ...init }: ResponseInit & {
fallback: string;
-}): Response;
+}): TypedResponse<never>;
10 changes: 8 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 075d581

Please sign in to comment.