mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-20 10:31:14 -06:00
feat: product configuration pages loading skeletons (#3262)
Co-authored-by: Johannes <72809645+jobenjada@users.noreply.github.com>
This commit is contained in:
24
apps/web/app/(app)/components/LoadingCard.tsx
Normal file
24
apps/web/app/(app)/components/LoadingCard.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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} />;
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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 'Powered by Formbricks' 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 'Powered by Formbricks' Signature</Label>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsCard>
|
||||
</PageContentWrapper>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsCard>
|
||||
</PageContentWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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} />;
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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} />;
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
32
packages/ee/multi-language/components/language-labels.tsx
Normal file
32
packages/ee/multi-language/components/language-labels.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user