diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/components/UnifiedStyling.tsx b/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/components/UnifiedStyling.tsx index d4fc88ce08..741470a654 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/components/UnifiedStyling.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/settings/lookandfeel/components/UnifiedStyling.tsx @@ -1,18 +1,139 @@ "use client"; -import React, { useState } from "react"; +import { RotateCcwIcon } from "lucide-react"; +import { useRouter } from "next/navigation"; +import React, { useEffect, useState } from "react"; +import toast from "react-hot-toast"; import { TProduct } from "@formbricks/types/product"; +import { Button } from "@formbricks/ui/Button"; import { ColorPicker } from "@formbricks/ui/ColorPicker"; +import { Slider } from "@formbricks/ui/Slider"; +import CardArrangement from "@formbricks/ui/Styling/CardArrangement"; +import DarkModeColors from "@formbricks/ui/Styling/DarkModeColors"; import { Switch } from "@formbricks/ui/Switch"; +import { updateProductAction } from "../actions"; + type UnifiedStylingProps = { product: TProduct; }; +const colorDefaults = { + brandColor: "#64748b", + questionColor: "#2b2524", + inputColor: "#efefef", + inputBorderColor: "#c0c0c0", + cardBackgroundColor: "#c0c0c0", + highlighBorderColor: "#64748b", +}; + const UnifiedStyling = ({ product }: UnifiedStylingProps) => { - // const [color, setColor] = useState("#333"); - const [color, setColor] = useState(product.styling?.brandColor?.light); + const router = useRouter(); + const [unifiedStyling, setUnifiedStyling] = useState(product.styling?.unifiedStyling ?? false); + const [allowStyleOverwrite, setAllowStyleOverwrite] = useState( + product.styling?.allowStyleOverwrite ?? false + ); + const [brandColor, setBrandColor] = useState( + product.styling?.brandColor?.light ?? colorDefaults.brandColor + ); + const [questionColor, setQuestionColor] = useState( + product.styling?.questionColor?.light ?? colorDefaults.questionColor + ); + const [inputColor, setInputColor] = useState( + product.styling?.inputColor?.light ?? colorDefaults.inputColor + ); + const [inputBorderColor, setInputBorderColor] = useState( + product.styling?.inputBorderColor?.light ?? colorDefaults.inputBorderColor + ); + const [cardBackgroundColor, setCardBackgroundColor] = useState( + product.styling?.cardBackgroundColor?.light ?? colorDefaults.cardBackgroundColor + ); + + // highlight border + const [allowHighlightBorder, setAllowHighlightBorder] = useState( + !!product.styling?.highlightBorderColor?.light ?? false + ); + const [highlightBorderColor, setHighlightBorderColor] = useState( + product.styling?.highlightBorderColor?.light ?? colorDefaults.highlighBorderColor + ); + + const [isDarkMode, setIsDarkMode] = useState(product.styling?.isDarkModeEnabled ?? false); + + const [brandColorDark, setBrandColorDark] = useState(product.styling?.brandColor?.dark); + + const [questionColorDark, setQuestionColorDark] = useState(product.styling?.questionColor?.dark); + + const [inputColorDark, setInputColorDark] = useState(product.styling?.inputColor?.dark); + + const [inputBorderColorDark, setInputBorderColorDark] = useState(product.styling?.inputBorderColor?.dark); + + const [cardBackgroundColorDark, setCardBackgroundColorDark] = useState( + product.styling?.cardBackgroundColor?.dark + ); + + const [highlightBorderColorDark, setHighlightBorderColorDark] = useState( + product.styling?.highlightBorderColor?.dark + ); + + const [roundness, setRoundness] = useState(product.styling?.roundness ?? 8); + + const [linkSurveysCardArrangement, setLinkSurveysCardArrangement] = useState( + product.styling?.cardArrangement?.linkSurveys ?? "casual" + ); + const [inAppSurveysCardArrangement, setInAppSurveysCardArrangement] = useState( + product.styling?.cardArrangement?.inAppSurveys ?? "casual" + ); + + useEffect(() => { + if (!unifiedStyling) { + setAllowStyleOverwrite(false); + } + }, [unifiedStyling]); + + const onSave = async () => { + await updateProductAction(product.id, { + styling: { + unifiedStyling, + allowStyleOverwrite, + brandColor: { + light: brandColor, + dark: brandColorDark, + }, + questionColor: { + light: questionColor, + dark: questionColorDark, + }, + inputColor: { + light: inputColor, + dark: inputColorDark, + }, + inputBorderColor: { + light: inputBorderColor, + dark: inputBorderColorDark, + }, + cardBackgroundColor: { + light: cardBackgroundColor, + dark: cardBackgroundColorDark, + }, + highlightBorderColor: allowHighlightBorder + ? { + light: highlightBorderColor, + dark: highlightBorderColorDark, + } + : undefined, + isDarkModeEnabled: isDarkMode, + roundness, + cardArrangement: { + linkSurveys: linkSurveysCardArrangement, + inAppSurveys: inAppSurveysCardArrangement, + }, + }, + }); + + toast.success("Styling updated successfully."); + router.refresh(); + }; return (
@@ -21,18 +142,29 @@ const UnifiedStyling = ({ product }: UnifiedStylingProps) => {
- + { + setUnifiedStyling(value); + }} + />

Enable unified styling

-

Set base styles for all surveys below

+

Set base styles for all surveys below

- + { + setAllowStyleOverwrite(value); + }} + disabled={!unifiedStyling} + />

Allow overwriting styles

-

+

Activate if you want some surveys to be styled differently

@@ -42,47 +174,129 @@ const UnifiedStyling = ({ product }: UnifiedStylingProps) => {

Brand color

-

Change the text color of the survey questions.

+

Change the text color of the survey questions.

- +

Question color

-

Change the text color of the survey questions.

+

Change the text color of the survey questions.

- +

Input color

-

Change the text color of the survey questions.

+

Change the text color of the survey questions.

- +

Input border color

-

Change the text color of the survey questions.

+

Change the text color of the survey questions.

- +

Card background color

-

Change the text color of the survey questions.

+

Change the text color of the survey questions.

- +
+ +
+
+ { + setAllowHighlightBorder(value); + }} + disabled={!unifiedStyling} + /> +
+

Add highlight border

+

Add on outer border to your survey card

+
+
+ + {allowHighlightBorder && ( + + )} +
+ + + +
+
+

Roundness

+

Change the border radius of the card and the inputs.

+
+ + setRoundness(value[0])} + disabled={!unifiedStyling} + /> +
+ + + + +
+ +
+ + +
diff --git a/packages/types/product.ts b/packages/types/product.ts index 67abcaa503..3463486fc6 100644 --- a/packages/types/product.ts +++ b/packages/types/product.ts @@ -36,6 +36,7 @@ export const ZProductUpdateInput = z.object({ clickOutsideClose: z.boolean().optional(), darkOverlay: z.boolean().optional(), environments: z.array(ZEnvironment).optional(), + styling: ZStyling.optional(), }); export type TProductUpdateInput = z.infer; diff --git a/packages/types/styling.ts b/packages/types/styling.ts index 4f2e41abd6..53c46574b8 100644 --- a/packages/types/styling.ts +++ b/packages/types/styling.ts @@ -8,6 +8,7 @@ export const ZStylingColor = z.object({ }); export const ZCardArrangementOptions = z.enum(["casual", "straight", "simple"]); +export type TCardArrangementOptions = z.infer; export const ZCardArrangement = z.object({ linkSurveys: ZCardArrangementOptions, diff --git a/packages/ui/Slider/index.tsx b/packages/ui/Slider/index.tsx new file mode 100644 index 0000000000..834303ea2e --- /dev/null +++ b/packages/ui/Slider/index.tsx @@ -0,0 +1,24 @@ +"use client"; + +import * as SliderPrimitive from "@radix-ui/react-slider"; +import * as React from "react"; + +import { cn } from "@formbricks/lib/cn"; + +const Slider = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + + +)); +Slider.displayName = SliderPrimitive.Root.displayName; + +export { Slider }; diff --git a/packages/ui/Styling/CardArrangement.tsx b/packages/ui/Styling/CardArrangement.tsx new file mode 100644 index 0000000000..afab3ba6eb --- /dev/null +++ b/packages/ui/Styling/CardArrangement.tsx @@ -0,0 +1,72 @@ +import { useMemo } from "react"; + +import { cn } from "@formbricks/lib/cn"; +import { TCardArrangementOptions } from "@formbricks/types/styling"; + +import { Button } from "../Button"; + +type CardArrangementProps = { + surveyType: "link" | "web"; + activeCardArrangement: TCardArrangementOptions; + setActiveCardArrangement: (arrangement: TCardArrangementOptions) => void; +}; + +const CardArrangement = ({ + activeCardArrangement, + surveyType, + setActiveCardArrangement, +}: CardArrangementProps) => { + const surveyTypeDerived = useMemo(() => { + return surveyType == "link" ? "Link" : "In App"; + }, [surveyType]); + + return ( +
+
+

+ Card Arrangement for {surveyTypeDerived} Surveys +

+

+ How funky do you want your cards in {surveyTypeDerived} Surveys +

+
+ +
+ + + + + +
+
+ ); +}; + +export default CardArrangement; diff --git a/packages/ui/Styling/DarkModeColors.tsx b/packages/ui/Styling/DarkModeColors.tsx new file mode 100644 index 0000000000..9f59a41fc3 --- /dev/null +++ b/packages/ui/Styling/DarkModeColors.tsx @@ -0,0 +1,117 @@ +import { ColorPicker } from "../ColorPicker"; +import { Switch } from "../Switch"; + +const colorDefaults = { + brandColor: "#64748b", + questionColor: "#2b2524", + inputColor: "#efefef", + inputBorderColor: "#c0c0c0", + cardBackgroundColor: "#c0c0c0", + highlightBorderColor: "#64748b", +}; + +const ColorSelectorWithLabel = ({ + label, + color, + setColor, +}: { + label: string; + color: string; + setColor: React.Dispatch>; +}) => { + return ( +
+

{label}

+ +
+ ); +}; + +type DarModeColorProps = { + isDarkMode: boolean; + setIsDarkMode: React.Dispatch>; + brandColor?: string; + setBrandColor: React.Dispatch>; + questionColor?: string; + setQuestionColor: React.Dispatch>; + inputColor?: string; + setInputColor: React.Dispatch>; + inputBorderColor?: string; + setInputBorderColor: React.Dispatch>; + cardBackgroundColor?: string; + setCardBackgroundColor: React.Dispatch>; + highlightBorderColor?: string; + setHighlighBorderColor: React.Dispatch>; +}; + +const DarkModeColors = ({ + isDarkMode, + setIsDarkMode, + brandColor, + cardBackgroundColor, + highlightBorderColor, + inputBorderColor, + inputColor, + questionColor, + setBrandColor, + setCardBackgroundColor, + setHighlighBorderColor, + setInputBorderColor, + setInputColor, + setQuestionColor, +}: DarModeColorProps) => { + return ( +
+
+ { + setIsDarkMode(value); + }} + /> + +
+

Add "Dark Mode" Colors

+

Your app has a dark mode? Set a different set of colors.

+
+
+ + {isDarkMode && ( +
+ + + + + + +
+ )} +
+ ); +}; + +export default DarkModeColors;