Skip to content

Commit

Permalink
Merge pull request #418 from RBND-studio/dev
Browse files Browse the repository at this point in the history
Merge dev
  • Loading branch information
OPesicka committed Jun 23, 2024
2 parents 6850a13 + 74abed6 commit 55a7509
Show file tree
Hide file tree
Showing 186 changed files with 1,824 additions and 1,523 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<p align="center">
<img alt="Flows banner" src="./docs/github_banner.png">
<img alt="Flows banner" src="./docs/github-banner.png">
</p>
<p align="center">
<a href='http://makeapullrequest.com'><img alt='PRs Welcome' src='https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=shields'/></a>
Expand All @@ -11,10 +11,10 @@

Flows lets you build any onboarding you want. Guide users, increase feature adoption, and improve revenue.

- Create flows with a WYSIWYG editor or define them in your codebase
- Create flows with a WYSIWYG editor
- With Flows Cloud you can create and update flows on the fly without having to redeploy your app
- Automatically track the performance of your flows
- Use advanced step types like wait, conditional, call a function, and AI decision
- Use advanced step types like wait and conditional steps
- Customize anything with CSS and JS
- Connect to external analytics tools to get the full picture of your user's journey

Expand Down
8 changes: 4 additions & 4 deletions apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"prepare": "panda codegen"
},
"dependencies": {
"@flows/js": "0.0.52",
"@flows/js": "0.1.0",
"@hookform/resolvers": "^3.4.2",
"@marsidev/react-turnstile": "^0.6.1",
"@monaco-editor/react": "^4.6.0",
Expand All @@ -34,7 +34,7 @@
"dayjs": "^1.11.11",
"icons": "workspace:*",
"monaco-editor": "^0.49.0",
"next": "14.2.3",
"next": "14.2.4",
"posthog-js": "^1.139.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand All @@ -46,13 +46,13 @@
"zod": "^3.23.8"
},
"devDependencies": {
"@next/eslint-plugin-next": "14.2.3",
"@next/eslint-plugin-next": "14.2.4",
"@pandacss/dev": "0.39.2",
"@types/node": "^20.14.5",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"eslint-config-custom": "workspace:*",
"tsconfig": "workspace:*",
"typescript": "5.4.5"
"typescript": "5.5.2"
}
}
Binary file modified apps/app/public/favicon.ico
Binary file not shown.
22 changes: 0 additions & 22 deletions apps/app/src/app/(auth)/auth-wrapper.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export default async function ProjectTemplatePage({ params }: Props): Promise<JS
load(api["/css/template"]()),
]);

// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- or is intentional here
const cssVars = project.css_vars || defaultVars;
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- or is intentional here
const template = project.css_template || defaultTemplate;

return (
<>
<Flex flexDirection="column" gap="space8" mb="space16">
Expand All @@ -28,7 +33,7 @@ export default async function ProjectTemplatePage({ params }: Props): Promise<JS
template.
</Text>
</Flex>
<TemplateProvider defaultCssTemplate={defaultTemplate} defaultCssVars={defaultVars}>
<TemplateProvider cssTemplate={template} cssVars={cssVars}>
<CssVarsForm defaultVars={defaultVars} project={project} />
<CssTemplateForm defaultTemplate={defaultTemplate} project={project} />
<TemplatePreview />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ const TemplateContext = createContext<ITemplateContext>({

type Props = {
children?: ReactNode;
defaultCssVars?: string;
defaultCssTemplate?: string;
cssVars?: string;
cssTemplate?: string;
};

export const TemplateProvider: FC<Props> = ({ children, defaultCssTemplate, defaultCssVars }) => {
const [cssVars, setCssVars] = useState(defaultCssVars ?? "");
const [cssTemplate, setCssTemplate] = useState(defaultCssTemplate ?? "");
export const TemplateProvider: FC<Props> = (props) => {
const [cssVars, setCssVars] = useState(props.cssVars ?? "");
const [cssTemplate, setCssTemplate] = useState(props.cssTemplate ?? "");

const value = useMemo(
(): ITemplateContext => ({
Expand All @@ -37,7 +37,7 @@ export const TemplateProvider: FC<Props> = ({ children, defaultCssTemplate, defa
[cssTemplate, cssVars],
);

return <TemplateContext.Provider value={value}>{children}</TemplateContext.Provider>;
return <TemplateContext.Provider value={value}>{props.children}</TemplateContext.Provider>;
};

export const useTemplate = (): ITemplateContext => useContext(TemplateContext);
Original file line number Diff line number Diff line change
@@ -1,11 +1,36 @@
import type { FlowSteps, WaitStepOptions } from "@flows/js";
import type {
FlowModalStep,
FlowStep,
FlowSteps,
FlowTooltipStep,
FlowWaitStep,
FooterActionItem,
WaitStepOptions,
} from "@flows/js";
import { type FlowDetail, type UpdateFlow } from "lib/api";
import { useFormContext } from "react-hook-form";

import { type MatchGroup } from "./targeting";

export type FooterActionPlacement = "left" | "center" | "right";

export type WaitOptions = Omit<WaitStepOptions, "targetBranch"> & { targetBranch?: null | number };
type FooterItem = Omit<FooterActionItem, "targetBranch"> & { targetBranch?: null | number };
type IFooterActions = { left?: FooterItem[]; center?: FooterItem[]; right?: FooterItem[] };
type TooltipStep = Omit<FlowTooltipStep, "wait" | "footerActions"> & {
wait?: WaitOptions | WaitOptions[];
footerActions?: IFooterActions;
};
type ModalStep = Omit<FlowModalStep, "wait" | "footerActions"> & {
wait?: WaitOptions | WaitOptions[];
footerActions?: IFooterActions;
};
type IStep = TooltipStep | ModalStep | FlowWaitStep;
type Step = IStep | IStep[][];

export type IFlowEditForm = Pick<UpdateFlow, "frequency"> & {
steps: FlowSteps;
steps: Step[];

userProperties: MatchGroup[];
start: WaitStepOptions[];
};
Expand All @@ -31,13 +56,55 @@ export type SelectedItem =
| "frequency";

export const formToRequest = (data: IFlowEditForm): UpdateFlow => {
const fixedUserProperties = data.userProperties
.map((group) => group.filter((matcher) => !!matcher.key))
.filter((group) => !!group.length);
return {
...data,
start: data.start as unknown as UpdateFlow["start"],
steps: data.steps as unknown as UpdateFlow["steps"],
userProperties: fixedUserProperties,
};
};

export const fixFormData = (data: IFlowEditForm): IFlowEditForm => {
const fixedUserProperties = data.userProperties
.map((group) => group.filter((matcher) => !!matcher.key))
.filter((group) => !!group.length);

const fixedSteps = data.steps.map((step, i, arr) => {
if (Array.isArray(step)) return step;
const nextStep = arr.at(i + 1);
const nextStepBranchCount = Array.isArray(nextStep) ? nextStep.length : null;
const fixTargetBranch = (targetBranch: number | null | undefined): number | undefined => {
if (targetBranch === undefined || targetBranch === null) return;
if (nextStepBranchCount === null) return;
if (targetBranch >= nextStepBranchCount) return;
return targetBranch;
};

const waitArray = step.wait ? (Array.isArray(step.wait) ? step.wait : [step.wait]) : undefined;
const wait = waitArray?.map((w) => ({
...w,
targetBranch: fixTargetBranch(w.targetBranch),
}));

const footerActions =
"footerActions" in step && step.footerActions
? Object.entries(step.footerActions).reduce(
(acc, [key, actions]: [string, FooterActionItem[]]) => {
acc[key] = actions.map((action) => ({
...action,
targetBranch: fixTargetBranch(action.targetBranch),
}));
return acc;
},
{},
)
: undefined;

return {
...step,
wait,
footerActions,
} as FlowStep;
});

return { ...data, userProperties: fixedUserProperties, steps: fixedSteps };
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { FlowPublishChangesDialog } from "../(detail)/flow-publish-changes-dialo
import { Autosave } from "./autosave";
import {
createDefaultValues,
fixFormData,
formToRequest,
type IFlowEditForm,
type SelectedItem,
Expand Down Expand Up @@ -52,9 +53,10 @@ export const FlowEditForm: FC<Props> = ({ flow, organizationId }) => {
const { send } = useSend();
const onSubmit: SubmitHandler<IFlowEditForm> = useCallback(
async (data) => {
const res = await send(api["PATCH /flows/:flowId"](flow.id, formToRequest(data)), {
errorMessage: t.toasts.saveFlowFailed,
});
const res = await send(
api["PATCH /flows/:flowId"](flow.id, formToRequest(fixFormData(data))),
{ errorMessage: t.toasts.saveFlowFailed },
);
if (res.error) return;
reset(data, { keepValues: true });
router.refresh();
Expand All @@ -76,21 +78,25 @@ export const FlowEditForm: FC<Props> = ({ flow, organizationId }) => {
[handleSave],
);

const { isDirty } = formState;
const isDirty = useRef(formState.isDirty);
useEffect(() => {
isDirty.current = formState.isDirty;
}, [formState.isDirty]);

// Save on page unload (refresh, close, etc.)
useEffect(() => {
if (!isDirty) return;
if (!isDirty.current) return;
window.addEventListener("beforeunload", onUnload);
return () => window.removeEventListener("beforeunload", onUnload);
}, [isDirty, onUnload]);
}, [onUnload]);

// Save on SPA navigation to other page
useEffect(() => {
if (!isDirty) return;
return () => {
if (!isDirty.current) return;
void handleSubmit(onSubmit)();
};
}, [handleSubmit, isDirty, onSubmit]);
}, [handleSubmit, onSubmit]);

return (
<FormProvider {...methods}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const ModalStepForm: FC<Props> = ({ index }) => {
{...register(`${stepKey}.title`)}
defaultValue={initialValue.title}
description="HTML title of the modal"
className={css({ mb: "space16" })}
label="Title"
/>
<Input
Expand Down Expand Up @@ -65,17 +66,30 @@ export const ModalStepForm: FC<Props> = ({ index }) => {
onCheckedChange={field.onChange}
/>
{!field.value ? (
<Controller
control={control}
name={`${stepKey}.closeOnOverlayClick`}
render={({ field: closeField }) => (
<Checkbox
checked={closeField.value}
label="Close on overlay click"
onCheckedChange={closeField.onChange}
/>
)}
/>
<>
<Controller
control={control}
name={`${stepKey}.closeOnOverlayClick`}
render={({ field: closeField }) => (
<Checkbox
checked={closeField.value}
label="Close on overlay click"
onCheckedChange={closeField.onChange}
/>
)}
/>
<Controller
control={control}
name={`${stepKey}.disableOverlayClickLayer`}
render={({ field: closeField }) => (
<Checkbox
checked={closeField.value}
label="Disable overlay click layer"
onCheckedChange={closeField.onChange}
/>
)}
/>
</>
) : null}
</>
)}
Expand All @@ -90,11 +104,20 @@ export const ModalStepForm: FC<Props> = ({ index }) => {
<Accordion title="Advanced">
<Input
{...register(`${stepKey}.stepId`)}
className={css({ mb: "space16" })}
defaultValue={initialValue.stepId}
description={t.steps.stepIdDescription}
label={t.steps.stepIdLabel}
placeholder="my-step-id"
/>

<Input
{...register(`${stepKey}.zIndex`)}
defaultValue={initialValue.zIndex}
label="Modal z-index"
description="Z-index of the modal element"
placeholder="e.g. 1000"
/>
</Accordion>
</Flex>
</>
Expand Down
Loading

0 comments on commit 55a7509

Please sign in to comment.