diff --git a/.env.example b/.env.example index 8a2ff7b4aa..fbde111339 100644 --- a/.env.example +++ b/.env.example @@ -162,9 +162,6 @@ ENTERPRISE_LICENSE_KEY= # DEFAULT_ORGANIZATION_ID= # DEFAULT_ORGANIZATION_ROLE=admin -# set to 1 to skip onboarding for new users -# ONBOARDING_DISABLED=1 - # Send new users to customer.io # CUSTOMER_IO_API_KEY= # CUSTOMER_IO_SITE_ID= diff --git a/.github/workflows/kamal-deploy.yml b/.github/workflows/kamal-deploy.yml index c817507938..a60a1fd1d0 100644 --- a/.github/workflows/kamal-deploy.yml +++ b/.github/workflows/kamal-deploy.yml @@ -57,7 +57,6 @@ jobs: AIRTABLE_CLIENT_ID: ${{ secrets.AIRTABLE_CLIENT_ID }} ENTERPRISE_LICENSE_KEY: ${{ secrets.ENTERPRISE_LICENSE_KEY }} DEFAULT_ORGANIZATION_ID: ${{ vars.DEFAULT_ORGANIZATION_ID }} - ONBOARDING_DISABLED: ${{ vars.ONBOARDING_DISABLED }} CUSTOMER_IO_API_KEY: ${{ secrets.CUSTOMER_IO_API_KEY }} CUSTOMER_IO_SITE_ID: ${{ secrets.CUSTOMER_IO_SITE_ID }} NEXT_PUBLIC_POSTHOG_API_KEY: ${{ vars.NEXT_PUBLIC_POSTHOG_API_KEY }} diff --git a/.github/workflows/kamal-setup.yml b/.github/workflows/kamal-setup.yml index 5dc3d81ea8..8c40f3a575 100644 --- a/.github/workflows/kamal-setup.yml +++ b/.github/workflows/kamal-setup.yml @@ -54,7 +54,6 @@ jobs: AIRTABLE_CLIENT_ID: ${{ secrets.AIRTABLE_CLIENT_ID }} ENTERPRISE_LICENSE_KEY: ${{ secrets.ENTERPRISE_LICENSE_KEY }} DEFAULT_ORGANIZATION_ID: ${{ vars.DEFAULT_ORGANIZATION_ID }} - ONBOARDING_DISABLED: ${{ vars.ONBOARDING_DISABLED }} CUSTOMER_IO_API_KEY: ${{ secrets.CUSTOMER_IO_API_KEY }} CUSTOMER_IO_SITE_ID: ${{ secrets.CUSTOMER_IO_SITE_ID }} NEXT_PUBLIC_POSTHOG_API_KEY: ${{ vars.NEXT_PUBLIC_POSTHOG_API_KEY }} diff --git a/apps/docs/app/self-hosting/configuration/page.mdx b/apps/docs/app/self-hosting/configuration/page.mdx index 365172ded2..e43e2fbf9c 100644 --- a/apps/docs/app/self-hosting/configuration/page.mdx +++ b/apps/docs/app/self-hosting/configuration/page.mdx @@ -52,7 +52,6 @@ These variables are present inside your machineโ€™s docker-compose file. Restart | DEFAULT_BRAND_COLOR | Default brand color for your app (Can be overwritten from the UI as well). | optional | #64748b | | DEFAULT_ORGANIZATION_ID | Automatically assign new users to a specific organization when joining | optional | | | DEFAULT_ORGANIZATION_ROLE | Role of the user in the default organization. | optional | admin | -| ONBOARDING_DISABLED | Disables onboarding for new users if set toย 1 | optional | | | OIDC_DISPLAY_NAME | Display name for Custom OpenID Connect Provider | optional | | | OIDC_CLIENT_ID | Client ID for Custom OpenID Connect Provider | optional (required if OIDC auth is enabled) | | | OIDC_CLIENT_SECRET | Secret for Custom OpenID Connect Provider | optional (required if OIDC auth is enabled) | | diff --git a/apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/ConnectWithFormbricks.tsx b/apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/ConnectWithFormbricks.tsx new file mode 100644 index 0000000000..1937531229 --- /dev/null +++ b/apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/ConnectWithFormbricks.tsx @@ -0,0 +1,90 @@ +"use client"; + +import Dance from "@/images/onboarding-dance.gif"; +import Lost from "@/images/onboarding-lost.gif"; +import { ArrowRight } from "lucide-react"; +import Image from "next/image"; +import { useRouter } from "next/navigation"; +import { useEffect } from "react"; +import { cn } from "@formbricks/lib/cn"; +import { TEnvironment } from "@formbricks/types/environment"; +import { TProductConfigChannel } from "@formbricks/types/product"; +import { Button } from "@formbricks/ui/Button"; +import { OnboardingSetupInstructions } from "./OnboardingSetupInstructions"; + +interface ConnectWithFormbricksProps { + environment: TEnvironment; + webAppUrl: string; + widgetSetupCompleted: boolean; + channel: TProductConfigChannel; +} + +export const ConnectWithFormbricks = ({ + environment, + webAppUrl, + widgetSetupCompleted, + channel, +}: ConnectWithFormbricksProps) => { + const router = useRouter(); + + const handleFinishOnboarding = async () => { + if (!widgetSetupCompleted) { + router.push(`/environments/${environment.id}/connect/invite`); + return; + } + router.push(`/environments/${environment.id}/surveys`); + }; + + useEffect(() => { + const handleVisibilityChange = async () => { + if (document.visibilityState === "visible") { + router.refresh(); + } + }; + + document.addEventListener("visibilitychange", handleVisibilityChange); + return () => { + document.removeEventListener("visibilitychange", handleVisibilityChange); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( +
+
+
+ +
+
+ {widgetSetupCompleted ? ( +
+ lost +

Connection successful โœ…

+
+ ) : ( +
+ lost +

Waiting for your signal...

+
+ )} +
+
+ +
+ ); +}; diff --git a/apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/InviteOrganizationMember.tsx b/apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/InviteOrganizationMember.tsx new file mode 100644 index 0000000000..da39109056 --- /dev/null +++ b/apps/web/app/(app)/(onboarding)/environments/[environmentId]/connect/components/InviteOrganizationMember.tsx @@ -0,0 +1,121 @@ +"use client"; + +import { inviteOrganizationMemberAction } from "@/app/(app)/(onboarding)/organizations/actions"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useRouter } from "next/navigation"; +import { FormProvider, useForm } from "react-hook-form"; +import { toast } from "react-hot-toast"; +import { z } from "zod"; +import { TOrganization } from "@formbricks/types/organizations"; +import { Button } from "@formbricks/ui/Button"; +import { FormControl, FormError, FormField, FormItem, FormLabel } from "@formbricks/ui/Form"; +import { Input } from "@formbricks/ui/Input"; + +interface InviteOrganizationMemberProps { + organization: TOrganization; + environmentId: string; +} + +const ZInviteOrganizationMemberDetails = z.object({ + email: z.string().email(), + inviteMessage: z.string().trim().min(1), +}); +type TInviteOrganizationMemberDetails = z.infer; + +export const InviteOrganizationMember = ({ organization, environmentId }: InviteOrganizationMemberProps) => { + const router = useRouter(); + + const form = useForm({ + defaultValues: { + email: "", + inviteMessage: "I'm looking into Formbricks to run targeted surveys. Can you help me set it up? ๐Ÿ™", + }, + resolver: zodResolver(ZInviteOrganizationMemberDetails), + }); + const { isSubmitting } = form.formState; + + const handleInvite = async (data: TInviteOrganizationMemberDetails) => { + try { + await inviteOrganizationMemberAction(organization.id, data.email, "developer", data.inviteMessage); + toast.success("Invite sent successful"); + await finishOnboarding(); + } catch (error) { + toast.error("An unexpected error occurred"); + } + }; + + const finishOnboarding = async () => { + router.push(`/environments/${environmentId}/surveys`); + }; + + return ( +
+ +
+
+ ( + + Email + +
+ field.onChange(email)} + placeholder="engineering@acme.com" + className=" bg-white" + /> + {error?.message && {error.message}} +
+
+
+ )} + /> + ( + + Invite Message + +
+