feat: product configuration pages loading skeletons (#3262)

Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com>
This commit is contained in:
Pratik
2024-10-11 04:46:16 +05:30
committed by GitHub
parent 4c8be95737
commit fe8c1fbc47
18 changed files with 372 additions and 712 deletions

View File

@@ -0,0 +1,24 @@
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
import { cn } from "@formbricks/lib/cn";
export const LoadingCard = ({
title,
description,
skeletonLines,
}: {
title: string;
description: string;
skeletonLines: Array<{ classes: string }>;
}) => {
return (
<SettingsCard title={title} description={description}>
<div className="w-full space-y-4">
{skeletonLines.map((line, index) => (
<div key={index}>
<div className={cn("animate-pulse rounded-full bg-slate-200", line.classes)}></div>
</div>
))}
</div>
</SettingsCard>
);
};

View File

@@ -1,138 +1,46 @@
"use client";
import { BrushIcon, KeyIcon, LanguagesIcon, ListChecksIcon, TagIcon, UsersIcon } from "lucide-react";
import { usePathname } from "next/navigation";
import { cn } from "@formbricks/lib/cn";
import { LoadingCard } from "@/app/(app)/components/LoadingCard";
import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId]/product/components/ProductConfigNavigation";
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
import { PageHeader } from "@formbricks/ui/components/PageHeader";
const LoadingCard = ({ title, description, skeletonLines }) => {
return (
<div className="w-full max-w-4xl rounded-xl border border-slate-200 bg-white py-4 text-left shadow-sm">
<div className="grid content-center border-b border-slate-200 px-4 pb-4 text-left text-slate-900">
<h3 className="text-lg font-medium leading-6">{title}</h3>
<p className="mt-1 text-sm text-slate-500">{description}</p>
</div>
<div className="w-full">
<div className="rounded-lg px-4">
{skeletonLines.map((line, index) => (
<div key={index} className="mt-4">
<div
className={`flex animate-pulse flex-col items-center justify-center space-y-2 rounded-lg bg-slate-200 py-6 text-center ${line.classes}`}></div>
</div>
))}
</div>
</div>
</div>
);
};
const Loading = () => {
const pathname = usePathname();
const cards = [
{
title: "Widget Status",
description: "Check if the Formbricks widget is alive and kicking.",
skeletonLines: [{ classes: " h-44 max-w-full rounded-md" }],
title: "Website & App Connection Status",
description: "Check if your app is successfully connected with Formbricks. Reload page to recheck.",
skeletonLines: [{ classes: " h-44 max-w-full rounded-lg" }],
},
{
title: "How to setup",
description: "Follow these steps to setup the Formbricks widget within your app.",
skeletonLines: [
{ classes: "h-12 w-24 rounded-lg" },
{ classes: "h-10 w-60 rounded-lg" },
{ classes: "h-10 w-60 rounded-lg" },
{ classes: "h-12 w-24 rounded-lg" },
{ classes: "h-10 w-60 rounded-lg" },
{ classes: "h-10 w-60 rounded-lg" },
],
},
{
title: "Your EnvironmentId",
description: "This id uniquely identifies this Formbricks environment.",
skeletonLines: [{ classes: "h-6 w-4/6 rounded-full" }],
},
{
title: "How To Setup",
description: "Follow these steps to setup the Formbricks widget within your app",
skeletonLines: [
{ classes: "h-6 w-24 rounded-full" },
{ classes: "h-4 w-60 rounded-full" },
{ classes: "h-4 w-60 rounded-full" },
{ classes: "h-6 w-24 rounded-full" },
{ classes: "h-4 w-60 rounded-full" },
{ classes: "h-4 w-60 rounded-full" },
],
},
];
let navigation = [
{
id: "general",
label: "General",
icon: <UsersIcon className="h-5 w-5" />,
current: pathname?.includes("/general"),
},
{
id: "look",
label: "Look & Feel",
icon: <BrushIcon className="h-5 w-5" />,
current: pathname?.includes("/look"),
},
{
id: "languages",
label: "Survey Languages",
icon: <LanguagesIcon className="h-5 w-5" />,
hidden: true,
current: pathname?.includes("/languages"),
},
{
id: "tags",
label: "Tags",
icon: <TagIcon className="h-5 w-5" />,
current: pathname?.includes("/tags"),
},
{
id: "api-keys",
label: "API Keys",
icon: <KeyIcon className="h-5 w-5" />,
current: pathname?.includes("/api-keys"),
},
{
id: "website-connection",
label: "Website Connection",
icon: <ListChecksIcon className="h-5 w-5" />,
current: pathname?.includes("/website-connection"),
hidden: true,
},
{
id: "app-connection",
label: "App Connection",
icon: <ListChecksIcon className="h-5 w-5" />,
current: pathname?.includes("/app-connection"),
hidden: true,
skeletonLines: [{ classes: "h-12 w-4/6 rounded-lg" }],
},
];
return (
<div>
<PageContentWrapper>
<PageHeader pageTitle="Configuration">
<div className="grid h-10 w-full grid-cols-[auto,1fr]">
<nav className="flex h-full min-w-full items-center space-x-4" aria-label="Tabs">
{navigation.map((navElem) => (
<div
key={navElem.id}
className={cn(
navElem.id === "app-connection"
? "border-brand-dark border-b-2 font-semibold text-slate-900"
: "border-transparent text-slate-500 transition-all duration-150 ease-in-out hover:border-slate-300 hover:text-slate-700",
"flex h-full items-center border-b-2 px-3 text-sm font-medium",
navElem.hidden && "hidden"
)}
aria-current={navElem.id === "app-connection" ? "page" : undefined}>
{navElem.label}
</div>
))}
</nav>
<div className="justify-self-end"></div>
</div>
</PageHeader>
<div className="mt-4 flex max-w-4xl animate-pulse items-center space-y-4 rounded-lg border bg-blue-50 p-6 text-sm text-blue-900 shadow-sm md:space-y-0 md:text-base"></div>
{cards.map((card, index) => (
<LoadingCard key={index} {...card} />
))}
</PageContentWrapper>
</div>
<PageContentWrapper>
<PageHeader pageTitle="Configuration">
<ProductConfigNavigation activeId="app-connection" loading />
</PageHeader>
<div className="mt-4 flex max-w-4xl animate-pulse items-center space-y-4 rounded-lg border bg-blue-50 p-6 text-sm text-blue-900 shadow-sm md:space-y-0 md:text-base"></div>
{cards.map((card, index) => (
<LoadingCard key={index} {...card} />
))}
</PageContentWrapper>
);
};

View File

@@ -43,17 +43,17 @@ const Page = async ({ params }) => {
description="Check if your app is successfully connected with Formbricks. Reload page to recheck.">
{environment && <WidgetStatusIndicator environment={environment} />}
</SettingsCard>
<SettingsCard
title="How to setup"
description="Follow these steps to setup the Formbricks widget within your app."
noPadding>
<SetupInstructions environmentId={params.environmentId} webAppUrl={WEBAPP_URL} />
</SettingsCard>
<SettingsCard
title="Your EnvironmentId"
description="This id uniquely identifies this Formbricks environment.">
<EnvironmentIdField environmentId={params.environmentId} />
</SettingsCard>
<SettingsCard
title="How To Setup"
description="Follow these steps to setup the Formbricks widget within your app"
noPadding>
<SetupInstructions environmentId={params.environmentId} webAppUrl={WEBAPP_URL} />
</SettingsCard>
</div>
</PageContentWrapper>
);

View File

@@ -1,8 +1,6 @@
"use client";
import { BrushIcon, KeyIcon, LanguagesIcon, ListChecksIcon, TagIcon, UsersIcon } from "lucide-react";
import { usePathname } from "next/navigation";
import { cn } from "@formbricks/lib/cn";
import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId]/product/components/ProductConfigNavigation";
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
import { PageHeader } from "@formbricks/ui/components/PageHeader";
@@ -26,7 +24,7 @@ const LoadingCard = () => {
</div>
</div>
<div className="flex justify-start">
<div className="mt-4 flex h-7 w-44 animate-pulse flex-col items-center justify-center rounded-md bg-black text-sm text-white">
<div className="mt-4 flex h-8 w-44 animate-pulse flex-col items-center justify-center rounded-md bg-black text-sm text-white">
Loading
</div>
</div>
@@ -37,85 +35,14 @@ const LoadingCard = () => {
};
const Loading = () => {
const pathname = usePathname();
let navigation = [
{
id: "general",
label: "General",
icon: <UsersIcon className="h-5 w-5" />,
current: pathname?.includes("/general"),
},
{
id: "look",
label: "Look & Feel",
icon: <BrushIcon className="h-5 w-5" />,
current: pathname?.includes("/look"),
},
{
id: "languages",
label: "Survey Languages",
icon: <LanguagesIcon className="h-5 w-5" />,
hidden: true,
current: pathname?.includes("/languages"),
},
{
id: "tags",
label: "Tags",
icon: <TagIcon className="h-5 w-5" />,
current: pathname?.includes("/tags"),
},
{
id: "api-keys",
label: "API Keys",
icon: <KeyIcon className="h-5 w-5" />,
current: pathname?.includes("/api-keys"),
},
{
id: "website-connection",
label: "Website Connection",
icon: <ListChecksIcon className="h-5 w-5" />,
current: pathname?.includes("/website-connection"),
hidden: true,
},
{
id: "app-connection",
label: "App Connection",
icon: <ListChecksIcon className="h-5 w-5" />,
current: pathname?.includes("/app-connection"),
hidden: true,
},
];
return (
<div>
<PageContentWrapper>
<PageHeader pageTitle="Configuration">
<div className="grid h-10 w-full grid-cols-[auto,1fr]">
<nav className="flex h-full min-w-full items-center space-x-4" aria-label="Tabs">
{navigation.map((navElem) => (
<div
key={navElem.id}
className={cn(
navElem.id === "api-keys"
? "border-brand-dark border-b-2 font-semibold text-slate-900"
: "border-transparent text-slate-500 transition-all duration-150 ease-in-out hover:border-slate-300 hover:text-slate-700",
"flex h-full items-center border-b-2 px-3 text-sm font-medium",
navElem.hidden && "hidden"
)}
aria-current={navElem.id === "api-keys" ? "page" : undefined}>
{navElem.label}
</div>
))}
</nav>
<div className="justify-self-end"></div>
</div>
</PageHeader>
<div className="mt-4 flex max-w-4xl animate-pulse items-center space-y-4 rounded-lg border bg-blue-50 p-6 text-sm text-blue-900 shadow-sm md:space-y-0 md:text-base"></div>
<LoadingCard />
</PageContentWrapper>
</div>
<PageContentWrapper>
<PageHeader pageTitle="Configuration">
<ProductConfigNavigation activeId="api-keys" loading />
</PageHeader>
<div className="mt-4 flex max-w-4xl animate-pulse items-center space-y-4 rounded-lg border bg-blue-50 p-6 text-sm text-blue-900 shadow-sm md:space-y-0 md:text-base"></div>
<LoadingCard />
</PageContentWrapper>
);
};

View File

@@ -5,15 +5,17 @@ import { usePathname } from "next/navigation";
import { SecondaryNavigation } from "@formbricks/ui/components/SecondaryNavigation";
interface ProductConfigNavigationProps {
environmentId: string;
activeId: string;
isMultiLanguageAllowed: boolean;
environmentId?: string;
isMultiLanguageAllowed?: boolean;
loading?: boolean;
}
export const ProductConfigNavigation = ({
environmentId,
activeId,
environmentId,
isMultiLanguageAllowed,
loading,
}: ProductConfigNavigationProps) => {
const pathname = usePathname();
let navigation = [
@@ -62,5 +64,5 @@ export const ProductConfigNavigation = ({
},
];
return <SecondaryNavigation navigation={navigation} activeId={activeId} />;
return <SecondaryNavigation navigation={navigation} activeId={activeId} loading={loading} />;
};

View File

@@ -1,34 +1,11 @@
"use client";
import { BrushIcon, KeyIcon, LanguagesIcon, ListChecksIcon, TagIcon, UsersIcon } from "lucide-react";
import { usePathname } from "next/navigation";
import { cn } from "@formbricks/lib/cn";
import { LoadingCard } from "@/app/(app)/components/LoadingCard";
import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId]/product/components/ProductConfigNavigation";
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
import { PageHeader } from "@formbricks/ui/components/PageHeader";
const LoadingCard = ({ title, description, skeletonLines }) => {
return (
<div className="w-full max-w-4xl rounded-xl border border-slate-200 bg-white py-4 shadow-sm">
<div className="grid content-center border-b border-slate-200 px-4 pb-4 text-left text-slate-900">
<h3 className="text-lg font-medium leading-6">{title}</h3>
<p className="mt-1 text-sm text-slate-500">{description}</p>
</div>
<div className="w-full">
<div className="rounded-lg px-4 py-4 pb-0 pt-2">
{skeletonLines.map((line, index) => (
<div key={index} className="mt-4">
<div className={`animate-pulse rounded-full bg-slate-200 ${line.classes}`}></div>
</div>
))}
</div>
</div>
</div>
);
};
const Loading = () => {
const pathname = usePathname();
const cards = [
{
title: "Product Name",
@@ -48,83 +25,15 @@ const Loading = () => {
},
];
let navigation = [
{
id: "general",
label: "General",
icon: <UsersIcon className="h-5 w-5" />,
current: pathname?.includes("/general"),
},
{
id: "look",
label: "Look & Feel",
icon: <BrushIcon className="h-5 w-5" />,
current: pathname?.includes("/look"),
},
{
id: "languages",
label: "Survey Languages",
icon: <LanguagesIcon className="h-5 w-5" />,
hidden: true,
current: pathname?.includes("/languages"),
},
{
id: "tags",
label: "Tags",
icon: <TagIcon className="h-5 w-5" />,
current: pathname?.includes("/tags"),
},
{
id: "api-keys",
label: "API Keys",
icon: <KeyIcon className="h-5 w-5" />,
current: pathname?.includes("/api-keys"),
},
{
id: "website-connection",
label: "Website Connection",
icon: <ListChecksIcon className="h-5 w-5" />,
current: pathname?.includes("/website-connection"),
hidden: true,
},
{
id: "app-connection",
label: "App Connection",
icon: <ListChecksIcon className="h-5 w-5" />,
current: pathname?.includes("/app-connection"),
hidden: true,
},
];
return (
<div>
<PageContentWrapper>
<PageHeader pageTitle="Configuration">
<div className="grid h-10 w-full grid-cols-[auto,1fr]">
<nav className="flex h-full min-w-full items-center space-x-4" aria-label="Tabs">
{navigation.map((navElem) => (
<div
key={navElem.id}
className={cn(
navElem.id === "general"
? "border-brand-dark border-b-2 font-semibold text-slate-900"
: "border-transparent text-slate-500 transition-all duration-150 ease-in-out hover:border-slate-300 hover:text-slate-700",
"flex h-full items-center border-b-2 px-3 text-sm font-medium",
navElem.hidden && "hidden"
)}
aria-current={navElem.id === "general" ? "page" : undefined}>
{navElem.label}
</div>
))}
</nav>
<div className="justify-self-end"></div>
</div>
</PageHeader>
{cards.map((card, index) => (
<LoadingCard key={index} {...card} />
))}
</PageContentWrapper>
</div>
<PageContentWrapper>
<PageHeader pageTitle="Configuration">
<ProductConfigNavigation activeId="general" loading />
</PageHeader>
{cards.map((card, index) => (
<LoadingCard key={index} {...card} />
))}
</PageContentWrapper>
);
};

View File

@@ -0,0 +1,33 @@
"use client";
import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId]/product/components/ProductConfigNavigation";
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
import { LanguageLabels } from "@formbricks/ee/multi-language/components/language-labels";
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
import { PageHeader } from "@formbricks/ui/components/PageHeader";
const Loading = () => {
return (
<PageContentWrapper>
<PageHeader pageTitle="Configuration">
<ProductConfigNavigation activeId="languages" loading />
</PageHeader>
<SettingsCard
title="Multi-language surveys"
description="Add languages to create multi-language surveys.">
<div className="flex flex-col space-y-4">
<LanguageLabels />
{[...Array(3)].map((_, idx) => (
<div key={idx} className="my-3 grid h-10 grid-cols-4 gap-4">
<div className="h-full animate-pulse rounded-md bg-slate-200" />
<div className="h-full animate-pulse rounded-md bg-slate-200" />
<div className="h-full animate-pulse rounded-md bg-slate-200" />
</div>
))}
</div>
</SettingsCard>
</PageContentWrapper>
);
};
export default Loading;

View File

@@ -1,8 +1,7 @@
"use client";
import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId]/product/components/ProductConfigNavigation";
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
import { BrushIcon, KeyIcon, LanguagesIcon, ListChecksIcon, TagIcon, UsersIcon } from "lucide-react";
import { usePathname } from "next/navigation";
import { cn } from "@formbricks/lib/cn";
import { Badge } from "@formbricks/ui/components/Badge";
import { Button } from "@formbricks/ui/components/Button";
@@ -21,202 +20,140 @@ const placements = [
];
const Loading = () => {
const pathname = usePathname();
let navigation = [
{
id: "general",
label: "General",
icon: <UsersIcon className="h-5 w-5" />,
current: pathname?.includes("/general"),
},
{
id: "look",
label: "Look & Feel",
icon: <BrushIcon className="h-5 w-5" />,
current: pathname?.includes("/look"),
},
{
id: "languages",
label: "Survey Languages",
icon: <LanguagesIcon className="h-5 w-5" />,
hidden: true,
current: pathname?.includes("/languages"),
},
{
id: "tags",
label: "Tags",
icon: <TagIcon className="h-5 w-5" />,
current: pathname?.includes("/tags"),
},
{
id: "api-keys",
label: "API Keys",
icon: <KeyIcon className="h-5 w-5" />,
current: pathname?.includes("/api-keys"),
},
{
id: "website-connection",
label: "Website Connection",
icon: <ListChecksIcon className="h-5 w-5" />,
current: pathname?.includes("/website-connection"),
hidden: true,
},
{
id: "app-connection",
label: "App Connection",
icon: <ListChecksIcon className="h-5 w-5" />,
current: pathname?.includes("/app-connection"),
hidden: true,
},
];
return (
<div>
<PageContentWrapper>
<PageHeader pageTitle="Configuration">
<div className="grid h-10 w-full grid-cols-[auto,1fr]">
<nav className="flex h-full min-w-full items-center space-x-4" aria-label="Tabs">
{navigation.map((navElem) => (
<div
key={navElem.id}
className={cn(
navElem.id === "look"
? "border-brand-dark border-b-2 font-semibold text-slate-900"
: "border-transparent text-slate-500 transition-all duration-150 ease-in-out hover:border-slate-300 hover:text-slate-700",
"flex h-full items-center border-b-2 px-3 text-sm font-medium",
navElem.hidden && "hidden"
)}
aria-current={navElem.id === "look" ? "page" : undefined}>
{navElem.label}
<PageContentWrapper>
<PageHeader pageTitle="Configuration">
<ProductConfigNavigation activeId="look" loading />
</PageHeader>
<SettingsCard
title="Theme"
className="max-w-7xl"
description="Create a style theme for all surveys. You can enable custom styling for each survey.">
<div className="flex animate-pulse">
<div className="w-1/2">
<div className="flex flex-col gap-4 pr-6">
<div className="flex flex-col gap-4 rounded-lg bg-slate-50 p-4">
<div className="flex items-center gap-6">
<Switch />
<div className="flex flex-col">
<h3 className="text-sm font-semibold text-slate-700">Enable custom styling</h3>
<p className="text-xs text-slate-500">
Allow users to override this theme in the editor.
</p>
</div>
</div>
</div>
<div className="flex flex-col gap-3 bg-slate-50 p-4">
<div className="w-full rounded-lg border border-slate-300 bg-white">
<div className="flex flex-col p-4">
<h2 className="text-sm font-semibold text-slate-700">Form Styling</h2>
<p className="mt-1 text-xs text-slate-500">
Style the question texts, descriptions and input fields.
</p>
</div>
</div>
<div className="w-full rounded-lg border border-slate-300 bg-white">
<div className="flex flex-col p-4">
<h2 className="text-sm font-semibold text-slate-700">Card Styling</h2>
<p className="mt-1 text-xs text-slate-500">Style the survey card.</p>
</div>
</div>
<div className="w-full rounded-lg border border-slate-300 bg-white">
<div className="flex flex-col p-4">
<div className="flex items-center gap-2">
<h2 className="text-sm font-semibold text-slate-700">Background Styling</h2>
<Badge text="Link Surveys" type="gray" size="normal" />
</div>
<p className="mt-1 text-xs text-slate-500">
Change the background to a color, image or animation.
</p>
</div>
</div>
</div>
</div>
</div>
<div className="relative flex w-1/2 flex-row items-center justify-center rounded-lg bg-slate-100 pt-4">
<div className="relative mb-3 flex h-fit w-5/6 items-center justify-center rounded-lg border border-slate-300 bg-slate-200">
<div className="flex h-[90%] max-h-[90%] w-4/6 flex-1 flex-col">
<div className="flex h-8 w-full items-center rounded-t-lg bg-slate-100">
<div className="ml-6 flex space-x-2">
<div className="h-3 w-3 rounded-full bg-red-500"></div>
<div className="h-3 w-3 rounded-full bg-amber-500"></div>
<div className="h-3 w-3 rounded-full bg-emerald-500"></div>
</div>
<div className="ml-4 flex w-full justify-between font-mono text-sm text-slate-400">
<p>Preview</p>
<div className="flex items-center pr-6">Restart</div>
</div>
</div>
<div className="grid h-[500px] place-items-center bg-white">
<h1 className="text-xl font-semibold text-slate-700">Loading preview...</h1>
</div>
</div>
</div>
</div>
</div>
</SettingsCard>
<SettingsCard title="Logo" description="Upload your company logo to brand surveys and link previews.">
<div className="w-full animate-pulse items-center">
<div className="relative flex h-52 w-full cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed border-slate-300 bg-slate-50 hover:bg-slate-100 dark:border-slate-600 dark:bg-slate-700 dark:hover:border-slate-500 dark:hover:bg-slate-800">
<p className="text-xl font-semibold text-slate-700">Loading...</p>
</div>
</div>
</SettingsCard>
<SettingsCard
title="In-app Survey Placement"
description="Change where surveys will be shown in your web app.">
<div className="w-full items-center">
<div className="flex cursor-not-allowed select-none">
<RadioGroup>
{placements.map((placement) => (
<div key={placement.value} className="flex items-center space-x-2 whitespace-nowrap">
<RadioGroupItem
className="cursor-not-allowed select-none"
id={placement.value}
value={placement.value}
disabled={placement.disabled}
/>
<Label
htmlFor={placement.value}
className={cn(
placement.disabled ? "cursor-not-allowed text-slate-500" : "text-slate-900"
)}>
{placement.name}
</Label>
</div>
))}
</nav>
<div className="justify-self-end"></div>
</div>
</PageHeader>
<SettingsCard
title="Theme"
className="max-w-7xl"
description="Create a style theme for all surveys. You can enable custom styling for each survey.">
<div className="flex animate-pulse">
<div className="w-1/2">
<div className="flex flex-col gap-4 pr-6">
<div className="flex flex-col gap-4 rounded-lg bg-slate-50 p-4">
<div className="flex items-center gap-6">
<Switch />
<div className="flex flex-col">
<h3 className="text-sm font-semibold text-slate-700">Enable custom styling</h3>
<p className="text-xs text-slate-500">
Allow users to override this theme in the editor.
</p>
</div>
</div>
</div>
<div className="flex flex-col gap-3 bg-slate-50 p-4">
<div className="w-full rounded-lg border border-slate-300 bg-white">
<div className="flex flex-col p-4">
<h2 className="text-sm font-semibold text-slate-700">Form Styling</h2>
<p className="mt-1 text-xs text-slate-500">
Style the question texts, descriptions and input fields.
</p>
</div>
</div>
<div className="w-full rounded-lg border border-slate-300 bg-white">
<div className="flex flex-col p-4">
<h2 className="text-sm font-semibold text-slate-700">Card Styling</h2>
<p className="mt-1 text-xs text-slate-500">Style the survey card.</p>
</div>
</div>
<div className="w-full rounded-lg border border-slate-300 bg-white">
<div className="flex flex-col p-4">
<div className="flex items-center gap-2">
<h2 className="text-sm font-semibold text-slate-700">Background Styling</h2>
<Badge text="Link Surveys" type="gray" size="normal" />
</div>
<p className="mt-1 text-xs text-slate-500">
Change the background to a color, image or animation.
</p>
</div>
</div>
</div>
</div>
</div>
<div className="relative flex w-1/2 flex-row items-center justify-center rounded-lg bg-slate-100 pt-4">
<div className="relative mb-3 flex h-fit w-5/6 items-center justify-center rounded-lg border border-slate-300 bg-slate-200">
<div className="flex h-[95] max-h-[90%] w-4/6 flex-1 flex-col">
<div className="flex h-8 w-full items-center rounded-t-lg bg-slate-100">
<div className="ml-6 flex space-x-2">
<div className="h-3 w-3 rounded-full bg-red-500"></div>
<div className="h-3 w-3 rounded-full bg-amber-500"></div>
<div className="h-3 w-3 rounded-full bg-emerald-500"></div>
</div>
<div className="ml-4 flex w-full justify-between font-mono text-sm text-slate-400">
<p>Preview</p>
<div className="flex items-center pr-6">Restart</div>
</div>
</div>
<div className="grid h-[500px] place-items-center bg-white">
<h1 className="text-xl font-semibold text-slate-700">Loading preview...</h1>
</div>
</div>
</div>
</RadioGroup>
<div className="relative ml-8 h-40 w-full rounded bg-slate-200">
<div className={cn("absolute bottom-3 h-16 w-16 rounded bg-slate-700 sm:right-3")}></div>
</div>
</div>
</SettingsCard>
<Button className="pointer-events-none mt-4 animate-pulse cursor-not-allowed select-none bg-slate-200">
Loading
</Button>
</div>
</SettingsCard>
<SettingsCard
title="In-app Survey Placement"
description="Change where surveys will be shown in your web app.">
<div className="w-full items-center">
<div className="flex cursor-not-allowed select-none">
<RadioGroup>
{placements.map((placement) => (
<div key={placement.value} className="flex items-center space-x-2 whitespace-nowrap">
<RadioGroupItem
className="cursor-not-allowed select-none"
id={placement.value}
value={placement.value}
disabled={placement.disabled}
/>
<Label
htmlFor={placement.value}
className={cn(
placement.disabled ? "cursor-not-allowed text-slate-500" : "text-slate-900"
)}>
{placement.name}
</Label>
</div>
))}
</RadioGroup>
<div className="relative ml-8 h-40 w-full rounded bg-slate-200">
<div className={cn("absolute bottom-3 h-16 w-16 rounded bg-slate-700 sm:right-3")}></div>
</div>
</div>
<Button className="pointer-events-none mt-4 animate-pulse cursor-not-allowed select-none bg-slate-200">
Loading
</Button>
<SettingsCard
title="Formbricks Signature"
description="We love your support but understand if you toggle it off.">
<div className="w-full items-center">
<div className="pointer-events-none flex cursor-not-allowed select-none items-center space-x-2">
<Switch id="signature" checked={false} />
<Label htmlFor="signature">Show &apos;Powered by Formbricks&apos; Signature</Label>
</div>
</SettingsCard>
<SettingsCard
title="Formbricks Signature"
description="We love your support but understand if you toggle it off.">
<div className="w-full items-center">
<div className="pointer-events-none flex cursor-not-allowed select-none items-center space-x-2">
<Switch id="signature" checked={false} />
<Label htmlFor="signature">Show &apos;Powered by Formbricks&apos; Signature</Label>
</div>
</div>
</SettingsCard>
</PageContentWrapper>
</div>
</div>
</SettingsCard>
</PageContentWrapper>
);
};

View File

@@ -0,0 +1,42 @@
"use client";
import { ProductConfigNavigation } from "@/app/(app)/environments/[environmentId]/product/components/ProductConfigNavigation";
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
import { PageHeader } from "@formbricks/ui/components/PageHeader";
const Loading = () => {
return (
<PageContentWrapper>
<PageHeader pageTitle="Configuration">
<ProductConfigNavigation activeId="tags" />
</PageHeader>
<SettingsCard title="Manage Tags" description="Merge and remove response tags.">
<div className="w-full">
<div className="grid grid-cols-4 content-center rounded-lg bg-white text-left text-sm font-semibold text-slate-900">
<div className="col-span-2">Tag</div>
<div className="col-span-1 text-center">Count</div>
<div className="col-span-1 flex justify-center text-center">Actions</div>
</div>
<div className="w-full">
{[...Array(3)].map((_, idx) => (
<div key={idx} className="grid h-16 w-full grid-cols-4 content-center">
<div className="col-span-2 h-10 animate-pulse rounded-md bg-slate-200" />
<div className="flex items-center justify-center">
<div className="h-5 w-5 animate-pulse rounded-md bg-slate-200" />
</div>
<div className="flex items-center gap-2">
<div className="h-8 w-1/2 animate-pulse rounded-md bg-slate-200" />
<div className="h-8 w-1/2 animate-pulse rounded-md bg-slate-200" />
</div>
</div>
))}
</div>
</div>
</SettingsCard>
</PageContentWrapper>
);
};
export default Loading;

View File

@@ -7,9 +7,11 @@ import { SecondaryNavigation } from "@formbricks/ui/components/SecondaryNavigati
export const AccountSettingsNavbar = ({
environmentId,
activeId,
loading,
}: {
environmentId: string;
activeId: string;
environmentId?: string;
loading?: boolean;
}) => {
const pathname = usePathname();
const navigation = [
@@ -29,5 +31,5 @@ export const AccountSettingsNavbar = ({
},
];
return <SecondaryNavigation navigation={navigation} activeId={activeId} />;
return <SecondaryNavigation navigation={navigation} activeId={activeId} loading={loading} />;
};

View File

@@ -1,22 +1,7 @@
const LoadingCard = ({ title, description, skeletonLines }) => {
return (
<div className="my-4 w-full max-w-4xl rounded-xl border border-slate-200 bg-white py-4 shadow-sm">
<div className="grid content-center border-b border-slate-200 px-4 pb-4 text-left text-slate-900">
<h3 className="text-lg font-medium leading-6">{title}</h3>
<p className="mt-1 text-sm text-slate-500">{description}</p>
</div>
<div className="w-full">
<div className="rounded-lg px-6 py-5">
{skeletonLines.map((line, index) => (
<div key={index} className="mt-4">
<div className={`animate-pulse rounded-full bg-slate-200 ${line.classes}`}></div>
</div>
))}
</div>
</div>
</div>
);
};
import { LoadingCard } from "@/app/(app)/components/LoadingCard";
import { AccountSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(account)/components/AccountSettingsNavbar";
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
import { PageHeader } from "@formbricks/ui/components/PageHeader";
const Loading = () => {
const cards = [
@@ -32,33 +17,15 @@ const Loading = () => {
},
];
const pages = ["Profile", "Notifications"];
return (
<div className="p-6">
<div>
<div className="flex items-center justify-between space-x-4 pb-4">
<h1 className="text-3xl font-bold text-slate-800">Account Settings</h1>
</div>
</div>
<div className="mb-6 border-b border-slate-200">
<div className="grid h-10 w-full grid-cols-[auto,1fr]">
<nav className="flex h-full min-w-full items-center space-x-4" aria-label="Tabs">
{pages.map((navElem) => (
<div
key={navElem}
className="flex h-full items-center border-b-2 border-transparent px-3 text-sm font-medium text-slate-500 transition-all duration-150 ease-in-out hover:border-slate-300 hover:text-slate-700">
{navElem}
</div>
))}
</nav>
<div className="justify-self-end"></div>
</div>
</div>
<PageContentWrapper>
<PageHeader pageTitle="Account Settings">
<AccountSettingsNavbar activeId="notifications" loading />
</PageHeader>
{cards.map((card, index) => (
<LoadingCard key={index} {...card} />
))}
</div>
</PageContentWrapper>
);
};

View File

@@ -1,22 +1,7 @@
const LoadingCard = ({ title, description, skeletonLines }) => {
return (
<div className="my-4 w-full max-w-4xl rounded-xl border border-slate-200 bg-white py-4 shadow-sm">
<div className="grid content-center border-b border-slate-200 px-4 pb-4 text-left text-slate-900">
<h3 className="text-lg font-medium leading-6">{title}</h3>
<p className="mt-1 text-sm text-slate-500">{description}</p>
</div>
<div className="w-full">
<div className="rounded-lg px-6 py-5">
{skeletonLines.map((line, index) => (
<div key={index} className="mt-4">
<div className={`animate-pulse rounded-full bg-slate-200 ${line.classes}`}></div>
</div>
))}
</div>
</div>
</div>
);
};
import { LoadingCard } from "@/app/(app)/components/LoadingCard";
import { AccountSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(account)/components/AccountSettingsNavbar";
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
import { PageHeader } from "@formbricks/ui/components/PageHeader";
const Loading = () => {
const cards = [
@@ -47,33 +32,15 @@ const Loading = () => {
},
];
const pages = ["Profile", "Notifications"];
return (
<div className="p-6">
<div>
<div className="flex items-center justify-between space-x-4 pb-4">
<h1 className="text-3xl font-bold text-slate-800">Account Settings</h1>
</div>
</div>
<div className="mb-6 border-b border-slate-200">
<div className="grid h-10 w-full grid-cols-[auto,1fr]">
<nav className="flex h-full min-w-full items-center space-x-4" aria-label="Tabs">
{pages.map((navElem) => (
<div
key={navElem}
className="flex h-full items-center border-b-2 border-transparent px-3 text-sm font-medium text-slate-500 transition-all duration-150 ease-in-out hover:border-slate-300 hover:text-slate-700">
{navElem}
</div>
))}
</nav>
<div className="justify-self-end"></div>
</div>
</div>
<PageContentWrapper>
<PageHeader pageTitle="Account Settings">
<AccountSettingsNavbar activeId="profile" loading />
</PageHeader>
{cards.map((card, index) => (
<LoadingCard key={index} {...card} />
))}
</div>
</PageContentWrapper>
);
};

View File

@@ -11,11 +11,13 @@ export const OrganizationSettingsNavbar = ({
isFormbricksCloud,
membershipRole,
activeId,
loading,
}: {
environmentId: string;
environmentId?: string;
isFormbricksCloud: boolean;
membershipRole?: TMembershipRole;
activeId: string;
loading?: boolean;
}) => {
const pathname = usePathname();
const { isAdmin, isOwner } = getAccessFlags(membershipRole);
@@ -48,5 +50,5 @@ export const OrganizationSettingsNavbar = ({
},
];
return <SecondaryNavigation navigation={navigation} activeId={activeId} />;
return <SecondaryNavigation navigation={navigation} activeId={activeId} loading={loading} />;
};

View File

@@ -1,30 +1,17 @@
const pages = ["Members", "Enterprise License"];
import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar";
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
import { PageHeader } from "@formbricks/ui/components/PageHeader";
const Loading = () => {
return (
<div className="p-6">
<div>
<div className="flex items-center justify-between space-x-4 pb-4">
<h1 className="text-3xl font-bold text-slate-800">Organization Settings</h1>
</div>
</div>
<div className="mb-6 border-b border-slate-200">
<div className="grid h-10 w-full grid-cols-[auto,1fr]">
<nav className="flex h-full min-w-full items-center space-x-4" aria-label="Tabs">
{pages.map((navElem) => (
<div
key={navElem}
className="flex h-full items-center border-b-2 border-transparent px-3 text-sm font-medium text-slate-500 transition-all duration-150 ease-in-out hover:border-slate-300 hover:text-slate-700">
{navElem}
</div>
))}
</nav>
<div className="justify-self-end"></div>
</div>
</div>
<PageContentWrapper>
<PageHeader pageTitle="Organization Settings">
<OrganizationSettingsNavbar isFormbricksCloud={IS_FORMBRICKS_CLOUD} activeId="enterprise" loading />
</PageHeader>
<div className="my-8 h-64 animate-pulse rounded-xl bg-slate-200"></div>
<div className="my-8 h-96 animate-pulse rounded-md bg-slate-200"></div>
</div>
</PageContentWrapper>
);
};

View File

@@ -1,26 +1,8 @@
import { LoadingCard } from "@/app/(app)/components/LoadingCard";
import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar";
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
const LoadingCard = ({ title, description, skeletonLines }) => {
return (
<div
data-testid="members-loading-card"
className="my-4 w-full max-w-4xl rounded-xl border border-slate-200 bg-white py-4 shadow-sm">
<div className="grid content-center border-b border-slate-200 px-4 pb-4 text-left text-slate-900">
<h3 className="text-lg font-medium leading-6">{title}</h3>
<p className="mt-1 text-sm text-slate-500">{description}</p>
</div>
<div className="w-full">
<div className="rounded-lg px-6">
{skeletonLines.map((line, index) => (
<div key={index} className="mt-4">
<div className={`animate-pulse rounded-full bg-slate-200 ${line.classes}`}></div>
</div>
))}
</div>
</div>
</div>
);
};
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
import { PageHeader } from "@formbricks/ui/components/PageHeader";
const cards = [
{
@@ -41,34 +23,16 @@ const cards = [
},
];
const pages = ["Members", IS_FORMBRICKS_CLOUD ? "Billing & Plan" : "Enterprise License"];
const Loading = () => {
return (
<div className="p-6">
<div>
<div className="flex items-center justify-between space-x-4 pb-4">
<h1 className="text-3xl font-bold text-slate-800">Organization Settings</h1>
</div>
</div>
<div className="mb-6 border-b border-slate-200">
<div className="grid h-10 w-full grid-cols-[auto,1fr]">
<nav className="flex h-full min-w-full items-center space-x-4" aria-label="Tabs">
{pages.map((navElem) => (
<div
key={navElem}
className="flex h-full items-center border-b-2 border-transparent px-3 text-sm font-medium text-slate-500 transition-all duration-150 ease-in-out hover:border-slate-300 hover:text-slate-700">
{navElem}
</div>
))}
</nav>
<div className="justify-self-end"></div>
</div>
</div>
<PageContentWrapper>
<PageHeader pageTitle="Organization Settings">
<OrganizationSettingsNavbar isFormbricksCloud={IS_FORMBRICKS_CLOUD} activeId="members" loading />
</PageHeader>
{cards.map((card, index) => (
<LoadingCard key={index} {...card} />
))}
</div>
</PageContentWrapper>
);
};

View File

@@ -1,30 +1,17 @@
const pages = ["Members", "Billing & Plan"];
import { OrganizationSettingsNavbar } from "@/app/(app)/environments/[environmentId]/settings/(organization)/components/OrganizationSettingsNavbar";
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
import { PageContentWrapper } from "@formbricks/ui/components/PageContentWrapper";
import { PageHeader } from "@formbricks/ui/components/PageHeader";
const Loading = () => {
return (
<div className="p-6">
<div>
<div className="flex items-center justify-between space-x-4 pb-4">
<h1 className="text-3xl font-bold text-slate-800">Organization Settings</h1>
</div>
</div>
<div className="mb-6 border-b border-slate-200">
<div className="grid h-10 w-full grid-cols-[auto,1fr]">
<nav className="flex h-full min-w-full items-center space-x-4" aria-label="Tabs">
{pages.map((navElem) => (
<div
key={navElem}
className="flex h-full items-center border-b-2 border-transparent px-3 text-sm font-medium text-slate-500 transition-all duration-150 ease-in-out hover:border-slate-300 hover:text-slate-700">
{navElem}
</div>
))}
</nav>
<div className="justify-self-end"></div>
</div>
</div>
<PageContentWrapper>
<PageHeader pageTitle="Organization Settings">
<OrganizationSettingsNavbar isFormbricksCloud={IS_FORMBRICKS_CLOUD} activeId="billing" loading />
</PageHeader>
<div className="my-8 h-64 animate-pulse rounded-xl bg-slate-200"></div>
<div className="my-8 h-96 animate-pulse rounded-md bg-slate-200"></div>
</div>
</PageContentWrapper>
);
};

View File

@@ -1,6 +1,6 @@
"use client";
import { InfoIcon, PlusIcon } from "lucide-react";
import { PlusIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { getFormattedErrorMessage } from "@formbricks/lib/actionClient/helper";
@@ -8,14 +8,13 @@ import { iso639Languages } from "@formbricks/lib/i18n/utils";
import type { TLanguage, TProduct } from "@formbricks/types/product";
import { Button } from "@formbricks/ui/components/Button";
import { ConfirmationModal } from "@formbricks/ui/components/ConfirmationModal";
import { Label } from "@formbricks/ui/components/Label";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@formbricks/ui/components/Tooltip";
import {
createLanguageAction,
deleteLanguageAction,
getSurveysUsingGivenLanguageAction,
updateLanguageAction,
} from "../lib/actions";
import { LanguageLabels } from "./language-labels";
import { LanguageRow } from "./language-row";
interface EditLanguageProps {
@@ -214,35 +213,6 @@ export function EditLanguage({ product }: EditLanguageProps) {
);
}
function AliasTooltip() {
return (
<TooltipProvider delayDuration={80}>
<Tooltip>
<TooltipTrigger tabIndex={-1}>
<div>
<InfoIcon className="h-4 w-4 text-slate-400" />
</div>
</TooltipTrigger>
<TooltipContent>
The alias is an alternate name to identify the language in link surveys and the SDK (optional)
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
function LanguageLabels() {
return (
<div className="mb-2 grid w-full grid-cols-4 gap-4">
<Label htmlFor="languagesId">Language</Label>
<Label htmlFor="languagesId">Identifier (ISO)</Label>
<Label className="flex items-center space-x-2" htmlFor="Alias">
<span>Alias</span> <AliasTooltip />
</Label>
</div>
);
}
const EditSaveButtons: React.FC<{
isEditing: boolean;
onSave: () => void;

View File

@@ -0,0 +1,32 @@
import { InfoIcon } from "lucide-react";
import { Label } from "@formbricks/ui/components/Label";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@formbricks/ui/components/Tooltip";
export function LanguageLabels() {
return (
<div className="mb-2 grid w-full grid-cols-4 gap-4">
<Label htmlFor="languagesId">Language</Label>
<Label htmlFor="languagesId">Identifier (ISO)</Label>
<Label className="flex items-center space-x-2" htmlFor="Alias">
<span>Alias</span> <AliasTooltip />
</Label>
</div>
);
}
function AliasTooltip() {
return (
<TooltipProvider delayDuration={80}>
<Tooltip>
<TooltipTrigger tabIndex={-1}>
<div>
<InfoIcon className="h-4 w-4 text-slate-400" />
</div>
</TooltipTrigger>
<TooltipContent>
The alias is an alternate name to identify the language in link surveys and the SDK (optional)
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}