mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-22 14:10:45 -06:00
Compare commits
14 Commits
v1.0.2
...
@formbrick
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a0f7fde3d | ||
|
|
01523393db | ||
|
|
534dd5050d | ||
|
|
a3e1e0498d | ||
|
|
beadbfa4b9 | ||
|
|
a3162150a6 | ||
|
|
1c6a5b2685 | ||
|
|
9c8141abb2 | ||
|
|
88c17546b7 | ||
|
|
ccfc85f4fa | ||
|
|
d9839aba24 | ||
|
|
d83c530012 | ||
|
|
8716367ec1 | ||
|
|
dcffb8106e |
@@ -1,5 +1,12 @@
|
||||
# @formbricks/web
|
||||
|
||||
## 1.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [01523393]
|
||||
- @formbricks/js@1.0.4
|
||||
|
||||
## 1.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -5,7 +5,7 @@ import { TagIcon } from "@heroicons/react/24/solid";
|
||||
export default function AttributeClassDataRow({ attributeClass }) {
|
||||
return (
|
||||
<div className="m-2 grid h-16 grid-cols-5 content-center rounded-lg hover:bg-slate-100">
|
||||
<div className="col-span-3 flex items-center pl-6 text-sm">
|
||||
<div className="sm:col-span-3 col-span-5 flex items-center pl-6 text-sm">
|
||||
<div className="flex items-center">
|
||||
<div className="h-10 w-10 flex-shrink-0">
|
||||
<TagIcon className="h-8 w-8 flex-shrink-0 text-slate-500" />
|
||||
@@ -22,10 +22,10 @@ export default function AttributeClassDataRow({ attributeClass }) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="my-auto whitespace-nowrap text-center text-sm text-slate-500">
|
||||
<div className="my-auto whitespace-nowrap text-center text-sm text-slate-500 hidden md:block">
|
||||
<div className="text-slate-900">{timeSinceConditionally(attributeClass.createdAt.toString())}</div>
|
||||
</div>
|
||||
<div className="my-auto whitespace-nowrap text-center text-sm text-slate-500">
|
||||
<div className="my-auto whitespace-nowrap text-center text-sm text-slate-500 hidden md:block">
|
||||
<div className="text-slate-900">{timeSinceConditionally(attributeClass.updatedAt.toString())}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,8 +3,8 @@ export default function AttributeTableHeading() {
|
||||
<>
|
||||
<div className="grid h-12 grid-cols-5 content-center rounded-lg bg-slate-100 text-left text-sm font-semibold text-slate-900">
|
||||
<div className="col-span-3 pl-6 ">Name</div>
|
||||
<div className="text-center">Created</div>
|
||||
<div className="text-center">Last Updated</div>
|
||||
<div className="text-center hidden sm:block">Created</div>
|
||||
<div className="text-center hidden sm:block">Last Updated</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/shared/DropdownMenu";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@formbricks/ui";
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
import CreateTeamModal from "@/components/team/CreateTeamModal";
|
||||
import {
|
||||
@@ -25,25 +24,29 @@ import {
|
||||
changeEnvironmentByTeam,
|
||||
} from "@/lib/environments/changeEnvironments";
|
||||
import { useEnvironment } from "@/lib/environments/environments";
|
||||
import { formbricksLogout } from "@/lib/formbricks";
|
||||
import { useMemberships } from "@/lib/memberships";
|
||||
import { useTeam } from "@/lib/teams/teams";
|
||||
import { capitalizeFirstLetter, truncate } from "@/lib/utils";
|
||||
import formbricks from "@formbricks/js";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
|
||||
import {
|
||||
CustomersIcon,
|
||||
DashboardIcon,
|
||||
ErrorComponent,
|
||||
FilterIcon,
|
||||
FormIcon,
|
||||
ProfileAvatar,
|
||||
FormIcon, Popover, PopoverContent, PopoverTrigger, ProfileAvatar,
|
||||
SettingsIcon,
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
TooltipTrigger
|
||||
} from "@formbricks/ui";
|
||||
import {
|
||||
AdjustmentsVerticalIcon,
|
||||
ArrowRightOnRectangleIcon,
|
||||
ChatBubbleBottomCenterTextIcon,
|
||||
ChevronDownIcon,
|
||||
CodeBracketIcon,
|
||||
CreditCardIcon,
|
||||
@@ -53,9 +56,9 @@ import {
|
||||
PlusIcon,
|
||||
UserCircleIcon,
|
||||
UsersIcon,
|
||||
ChatBubbleBottomCenterTextIcon,
|
||||
} from "@heroicons/react/24/solid";
|
||||
import clsx from "clsx";
|
||||
import { MenuIcon } from "lucide-react";
|
||||
import type { Session } from "next-auth";
|
||||
import { signOut } from "next-auth/react";
|
||||
import Image from "next/image";
|
||||
@@ -63,11 +66,6 @@ import Link from "next/link";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import AddProductModal from "./AddProductModal";
|
||||
import { formbricksLogout } from "@/lib/formbricks";
|
||||
import formbricks from "@formbricks/js";
|
||||
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
|
||||
import { MenuIcon } from "lucide-react";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
|
||||
interface EnvironmentsNavbarProps {
|
||||
environmentId: string;
|
||||
@@ -268,7 +266,7 @@ export default function EnvironmentsNavbar({ environmentId, session }: Environme
|
||||
<MenuIcon className="h-6 w-6 rounded-md bg-slate-200 p-1 text-slate-600" />
|
||||
</span>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="mr-4 bg-slate-200">
|
||||
<PopoverContent className="mr-4 bg-slate-100 shadow">
|
||||
<div className="flex flex-col">
|
||||
{navigation.map((navItem) => (
|
||||
<Link key={navItem.name} href={navItem.href}>
|
||||
@@ -276,7 +274,7 @@ export default function EnvironmentsNavbar({ environmentId, session }: Environme
|
||||
onClick={() => setMobileNavMenuOpen(false)}
|
||||
className={cn(
|
||||
"flex items-center space-x-2 rounded-md p-2",
|
||||
navItem.current && "bg-slate-300"
|
||||
navItem.current && "bg-slate-200"
|
||||
)}>
|
||||
<navItem.icon className="h-5 w-5" />
|
||||
<span className="font-medium text-slate-600">{navItem.name}</span>
|
||||
|
||||
@@ -9,7 +9,7 @@ export default function IntegrationsPage({ params }) {
|
||||
<div>
|
||||
<h1 className="my-2 text-3xl font-bold text-slate-800">Integrations</h1>
|
||||
<p className="mb-6 text-slate-500">Connect Formbricks with your favorite tools.</p>
|
||||
<div className="grid grid-cols-3 gap-6">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<Card
|
||||
docsHref="https://formbricks.com/docs/getting-started/nextjs-app"
|
||||
docsText="Docs"
|
||||
|
||||
@@ -26,8 +26,8 @@ export default async function PeoplePage({ params }) {
|
||||
<div className="rounded-lg border border-slate-200">
|
||||
<div className="grid h-12 grid-cols-7 content-center rounded-lg bg-slate-100 text-left text-sm font-semibold text-slate-900">
|
||||
<div className="col-span-3 pl-6 ">User</div>
|
||||
<div className="col-span-2 text-center">User ID</div>
|
||||
<div className="col-span-2 text-center">Email</div>
|
||||
<div className="col-span-2 text-center hidden sm:block">User ID</div>
|
||||
<div className="col-span-2 text-center hidden sm:block">Email</div>
|
||||
</div>
|
||||
{people.map((person) => (
|
||||
<Link
|
||||
@@ -51,12 +51,12 @@ export default async function PeoplePage({ params }) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-2 my-auto whitespace-nowrap text-center text-sm text-slate-500">
|
||||
<div className="col-span-2 my-auto whitespace-nowrap text-center text-sm text-slate-500 hidden sm:block">
|
||||
<div className="ph-no-capture text-slate-900">
|
||||
{truncateMiddle(getAttributeValue(person, "userId"), 24)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-2 my-auto whitespace-nowrap text-center text-sm text-slate-500">
|
||||
<div className="col-span-2 my-auto whitespace-nowrap text-center text-sm text-slate-500 hidden sm:block">
|
||||
<div className="ph-no-capture text-slate-900">{getAttributeValue(person, "email")}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -51,7 +51,8 @@ export default function PricingTable({ environmentId, session }: PricingTablePro
|
||||
"Unlimited surveys",
|
||||
"Unlimited team members",
|
||||
"Remove branding",
|
||||
"100 responses per survey",
|
||||
"Unlimited link survey responses",
|
||||
"100 responses per web-app survey",
|
||||
"Granular targeting",
|
||||
"In-product surveys",
|
||||
"Link surveys",
|
||||
|
||||
@@ -125,6 +125,7 @@ export function DeleteProduct({ environmentId }) {
|
||||
const router = useRouter();
|
||||
|
||||
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
|
||||
const [deletingProduct, setDeletingProduct] = useState(false);
|
||||
|
||||
const { profile } = useProfile();
|
||||
const { team } = useMembers(environmentId);
|
||||
@@ -149,7 +150,9 @@ export function DeleteProduct({ environmentId }) {
|
||||
setIsDeleteDialogOpen(false);
|
||||
return;
|
||||
}
|
||||
setDeletingProduct(true);
|
||||
const deleteProductRes = await deleteProduct(environmentId);
|
||||
setDeletingProduct(false);
|
||||
|
||||
if (deleteProductRes?.id?.length > 0) {
|
||||
toast.success("Product deleted successfully.");
|
||||
@@ -191,6 +194,7 @@ export function DeleteProduct({ environmentId }) {
|
||||
deleteWhat="Product"
|
||||
open={isDeleteDialogOpen}
|
||||
setOpen={setIsDeleteDialogOpen}
|
||||
isDeleting={deletingProduct}
|
||||
onDelete={handleDeleteProduct}
|
||||
text={`Are you sure you want to delete "${truncate(
|
||||
product?.name,
|
||||
|
||||
@@ -498,7 +498,7 @@ function ResetProgressButton({ resetQuestionProgress }) {
|
||||
return (
|
||||
<Button
|
||||
variant="minimal"
|
||||
className="py-0.2 bg-white px-2 text-sm text-slate-500"
|
||||
className="py-0.2 bg-white mr-2 px-2 text-sm text-slate-500 font-sans"
|
||||
onClick={resetQuestionProgress}>
|
||||
Restart
|
||||
<ArrowPathRoundedSquareIcon className="ml-2 h-4 w-4" />
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import SurveyDropDownMenu from "@/app/(app)/environments/[environmentId]/surveys/SurveyDropDownMenu";
|
||||
import SurveyStarter from "@/app/(app)/environments/[environmentId]/surveys/SurveyStarter";
|
||||
import SurveyStatusIndicator from "@/components/shared/SurveyStatusIndicator";
|
||||
import { getEnvironment, getEnvironments } from "@formbricks/lib/services/environment";
|
||||
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
|
||||
import { getSurveysWithAnalytics } from "@formbricks/lib/services/survey";
|
||||
import type { TEnvironment } from "@formbricks/types/v1/environment";
|
||||
import type { TSurveyWithAnalytics } from "@formbricks/types/v1/surveys";
|
||||
import { Badge } from "@formbricks/ui";
|
||||
import { ComputerDesktopIcon, LinkIcon, PlusIcon } from "@heroicons/react/24/solid";
|
||||
import Link from "next/link";
|
||||
import SurveyDropDownMenu from "@/app/(app)/environments/[environmentId]/surveys/SurveyDropDownMenu";
|
||||
import SurveyStarter from "@/app/(app)/environments/[environmentId]/surveys/SurveyStarter";
|
||||
import { getProductByEnvironmentId } from "@formbricks/lib/services/product";
|
||||
import { getEnvironment, getEnvironments } from "@formbricks/lib/services/environment";
|
||||
import { getSurveysWithAnalytics } from "@formbricks/lib/services/survey";
|
||||
import type { TSurveyWithAnalytics } from "@formbricks/types/v1/surveys";
|
||||
import type { TEnvironment } from "@formbricks/types/v1/environment";
|
||||
|
||||
export default async function SurveysList({ environmentId }: { environmentId: string }) {
|
||||
const product = await getProductByEnvironmentId(environmentId);
|
||||
@@ -23,7 +23,7 @@ export default async function SurveysList({ environmentId }: { environmentId: st
|
||||
|
||||
return (
|
||||
<>
|
||||
<ul className="grid grid-cols-2 place-content-stretch gap-6 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-5 ">
|
||||
<ul className="grid place-content-stretch gap-6 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-5 ">
|
||||
<Link href={`/environments/${environmentId}/surveys/templates`}>
|
||||
<li className="col-span-1 h-56">
|
||||
<div className="delay-50 flex h-full items-center justify-center overflow-hidden rounded-md bg-gradient-to-br from-slate-900 to-slate-800 font-light text-white shadow transition ease-in-out hover:scale-105 hover:from-slate-800 hover:to-slate-700">
|
||||
|
||||
@@ -13,7 +13,10 @@ export const getAnalysisData = async (surveyId: string, environmentId: string) =
|
||||
if (!team) throw new Error(`Team not found for environment: ${environmentId}`);
|
||||
if (survey.environmentId !== environmentId) throw new Error(`Survey not found: ${surveyId}`);
|
||||
const limitReached =
|
||||
IS_FORMBRICKS_CLOUD && team.plan === "free" && allResponses.length >= RESPONSES_LIMIT_FREE;
|
||||
IS_FORMBRICKS_CLOUD &&
|
||||
team.plan === "free" &&
|
||||
survey.type === "web" &&
|
||||
allResponses.length >= RESPONSES_LIMIT_FREE;
|
||||
const responses = limitReached ? allResponses.slice(0, RESPONSES_LIMIT_FREE) : allResponses;
|
||||
const responsesCount = allResponses.length;
|
||||
|
||||
|
||||
@@ -26,19 +26,21 @@ export default function CTASummary({ questionSummary }: CTASummaryProps) {
|
||||
|
||||
return (
|
||||
<div className=" rounded-lg border border-slate-200 bg-slate-50 shadow-sm">
|
||||
<div className="space-y-2 px-6 pb-5 pt-6">
|
||||
<div className="space-y-2 px-4 pb-5 pt-6 md:px-6">
|
||||
<div>
|
||||
<h3 className="pb-1 text-xl font-semibold text-slate-900">{questionSummary.question.headline}</h3>
|
||||
<h3 className="text-lg pb-1 font-semibold text-slate-900 md:text-xl">
|
||||
{questionSummary.question.headline}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex space-x-2 font-semibold text-slate-600">
|
||||
<div className="rounded-lg bg-slate-100 p-2 text-sm">Call-to-Action</div>
|
||||
<div className=" flex items-center rounded-lg bg-slate-100 p-2 text-sm">
|
||||
<div className="flex space-x-2 text-xs font-semibold text-slate-600 md:text-sm">
|
||||
<div className="rounded-lg bg-slate-100 p-2 ">Call-to-Action</div>
|
||||
<div className=" flex items-center rounded-lg bg-slate-100 p-2">
|
||||
<InboxStackIcon className="mr-2 h-4 w-4 " />
|
||||
{ctr.count} responses
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-5 rounded-b-lg bg-white px-6 pb-6 pt-4">
|
||||
<div className="space-y-5 rounded-b-lg bg-white px-4 pb-6 pt-4 text-sm md:px-6 md:text-base">
|
||||
<div className="text flex justify-between px-2 pb-2">
|
||||
<div className="mr-8 flex space-x-1">
|
||||
<p className="font-semibold text-slate-700">Clickthrough Rate (CTR)</p>
|
||||
|
||||
@@ -34,19 +34,21 @@ export default function ConsentSummary({ questionSummary }: ConsentSummaryProps)
|
||||
|
||||
return (
|
||||
<div className=" rounded-lg border border-slate-200 bg-slate-50 shadow-sm">
|
||||
<div className="space-y-2 px-6 pb-5 pt-6">
|
||||
<div className="space-y-2 px-4 pb-5 pt-6 md:px-6">
|
||||
<div>
|
||||
<h3 className="pb-1 text-xl font-semibold text-slate-900">{questionSummary.question.headline}</h3>
|
||||
<h3 className="pb-1 text-lg font-semibold text-slate-900 md:text-xl">
|
||||
{questionSummary.question.headline}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex space-x-2 font-semibold text-slate-600">
|
||||
<div className="rounded-lg bg-slate-100 p-2 text-sm">Consent</div>
|
||||
<div className=" flex items-center rounded-lg bg-slate-100 p-2 text-sm">
|
||||
<div className="flex space-x-2 text-xs font-semibold text-slate-600 md:text-sm">
|
||||
<div className="rounded-lg bg-slate-100 p-2">Consent</div>
|
||||
<div className=" flex items-center rounded-lg bg-slate-100 p-2">
|
||||
<InboxStackIcon className="mr-2 h-4 w-4 " />
|
||||
{ctr.count} responses
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-5 rounded-b-lg bg-white px-6 pb-6 pt-4">
|
||||
<div className="space-y-5 rounded-b-lg bg-white px-4 pb-6 pt-4 text-sm md:px-6 md:text-base">
|
||||
<div>
|
||||
<div className="text flex justify-between px-2 pb-2">
|
||||
<div className="mr-8 flex space-x-1">
|
||||
|
||||
@@ -124,17 +124,19 @@ export default function MultipleChoiceSummary({
|
||||
|
||||
return (
|
||||
<div className=" rounded-lg border border-slate-200 bg-slate-50 shadow-sm">
|
||||
<div className="space-y-2 px-6 pb-5 pt-6">
|
||||
<div className="space-y-2 px-4 pb-5 pt-6 md:px-6">
|
||||
<div>
|
||||
<h3 className="pb-1 text-xl font-semibold text-slate-900">{questionSummary.question.headline}</h3>
|
||||
<h3 className="pb-1 text-lg font-semibold text-slate-900 md:text-xl">
|
||||
{questionSummary.question.headline}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex space-x-2 font-semibold text-slate-600">
|
||||
<div className="rounded-lg bg-slate-100 p-2 text-sm">
|
||||
<div className="flex space-x-2 text-xs font-semibold text-slate-600 md:text-sm">
|
||||
<div className="rounded-lg bg-slate-100 p-2">
|
||||
{isSingleChoice
|
||||
? "Multiple-Choice Single Select Question"
|
||||
: "Multiple-Choice Multi Select Question"}
|
||||
</div>
|
||||
<div className=" flex items-center rounded-lg bg-slate-100 p-2 text-sm">
|
||||
<div className="flex items-center rounded-lg bg-slate-100 p-2">
|
||||
<InboxStackIcon className="mr-2 h-4 w-4 " />
|
||||
{totalResponses} responses
|
||||
</div>
|
||||
@@ -144,11 +146,11 @@ export default function MultipleChoiceSummary({
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-5 rounded-b-lg bg-white px-6 pb-6 pt-4">
|
||||
<div className="space-y-5 rounded-b-lg bg-white px-4 pb-6 pt-4 text-sm md:px-6 md:text-base">
|
||||
{results.map((result: any, resultsIdx) => (
|
||||
<div key={result.label}>
|
||||
<div className="text flex justify-between px-2 pb-2">
|
||||
<div className="mr-8 flex space-x-1">
|
||||
<div className="text flex flex-col justify-between px-2 pb-2 sm:flex-row">
|
||||
<div className="mr-8 flex w-full justify-between space-x-1 sm:justify-normal">
|
||||
<p className="font-semibold text-slate-700">
|
||||
{results.length - resultsIdx} - {result.label}
|
||||
</p>
|
||||
@@ -158,7 +160,7 @@ export default function MultipleChoiceSummary({
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="flex w-32 items-end justify-end text-slate-600">
|
||||
<p className="flex w-full pt-1 text-slate-600 sm:items-end sm:justify-end sm:pt-0">
|
||||
{result.count} {result.count === 1 ? "response" : "responses"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -74,19 +74,21 @@ export default function NPSSummary({ questionSummary }: NPSSummaryProps) {
|
||||
|
||||
return (
|
||||
<div className=" rounded-lg border border-slate-200 bg-slate-50 shadow-sm">
|
||||
<div className="space-y-2 px-6 pb-5 pt-6">
|
||||
<div className="space-y-2 px-4 pb-5 pt-6 md:px-6">
|
||||
<div>
|
||||
<h3 className="pb-1 text-xl font-semibold text-slate-900">{questionSummary.question.headline}</h3>
|
||||
<h3 className="text-lg pb-1 font-semibold text-slate-900 md:text-xl">
|
||||
{questionSummary.question.headline}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex space-x-2 font-semibold text-slate-600">
|
||||
<div className="rounded-lg bg-slate-100 p-2 text-sm">Net Promoter Score (NPS)</div>
|
||||
<div className=" flex items-center rounded-lg bg-slate-100 p-2 text-sm">
|
||||
<div className="flex space-x-2 text-xs font-semibold text-slate-600 md:text-sm">
|
||||
<div className="rounded-lg bg-slate-100 p-2">Net Promoter Score (NPS)</div>
|
||||
<div className=" flex items-center rounded-lg bg-slate-100 p-2">
|
||||
<InboxStackIcon className="mr-2 h-4 w-4 " />
|
||||
{result.total} responses
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-5 bg-white px-6 pb-6 pt-4">
|
||||
<div className="space-y-5 rounded-b-lg bg-white px-4 pb-6 pt-4 text-sm md:px-6 md:text-base">
|
||||
{["promoters", "passives", "detractors"].map((group) => (
|
||||
<div key={group}>
|
||||
<div className="mb-2 flex justify-between">
|
||||
@@ -107,7 +109,7 @@ export default function NPSSummary({ questionSummary }: NPSSummaryProps) {
|
||||
))}
|
||||
</div>
|
||||
{dismissed.count > 0 && (
|
||||
<div className="border-t bg-white px-6 pb-6 pt-4">
|
||||
<div className="border-t bg-white px-4 pb-6 pt-4 text-sm md:px-6 md:text-base">
|
||||
<div key={dismissed.label}>
|
||||
<div className="text flex justify-between px-2 pb-2">
|
||||
<div className="mr-8 flex space-x-1">
|
||||
|
||||
@@ -18,13 +18,15 @@ function findEmail(person) {
|
||||
export default function OpenTextSummary({ questionSummary, environmentId }: OpenTextSummaryProps) {
|
||||
return (
|
||||
<div className="rounded-lg border border-slate-200 bg-slate-50 shadow-sm">
|
||||
<div className="space-y-2 px-6 pb-5 pt-6">
|
||||
<div className="space-y-2 px-4 pb-5 pt-6 md:px-6">
|
||||
<div>
|
||||
<h3 className="pb-1 text-xl font-semibold text-slate-900">{questionSummary.question.headline}</h3>
|
||||
<h3 className="text-lg pb-1 font-semibold text-slate-900 md:text-xl">
|
||||
{questionSummary.question.headline}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex space-x-2 font-semibold text-slate-600">
|
||||
<div className="rounded-lg bg-slate-100 p-2 text-sm">Open Text Question</div>
|
||||
<div className=" flex items-center rounded-lg bg-slate-100 p-2 text-sm">
|
||||
<div className="flex space-x-2 text-xs font-semibold text-slate-600 md:text-sm">
|
||||
<div className="rounded-lg bg-slate-100 p-2 ">Open Text Question</div>
|
||||
<div className=" flex items-center rounded-lg bg-slate-100 p-2">
|
||||
<InboxStackIcon className="mr-2 h-4 w-4" />
|
||||
{questionSummary.responses.length} Responses
|
||||
</div>
|
||||
@@ -32,9 +34,9 @@ export default function OpenTextSummary({ questionSummary, environmentId }: Open
|
||||
</div>
|
||||
<div className="rounded-b-lg bg-white ">
|
||||
<div className="grid h-10 grid-cols-4 items-center border-y border-slate-200 bg-slate-100 text-sm font-bold text-slate-600">
|
||||
<div className="pl-6">User</div>
|
||||
<div className="col-span-2 pl-6">Response</div>
|
||||
<div className="px-6">Time</div>
|
||||
<div className="pl-4 md:pl-6">User</div>
|
||||
<div className="col-span-2 pl-4 md:pl-6">Response</div>
|
||||
<div className="px-4 md:px-6">Time</div>
|
||||
</div>
|
||||
{questionSummary.responses.map((response) => {
|
||||
const email = response.person && findEmail(response.person);
|
||||
@@ -42,29 +44,32 @@ export default function OpenTextSummary({ questionSummary, environmentId }: Open
|
||||
return (
|
||||
<div
|
||||
key={response.id}
|
||||
className="grid grid-cols-4 items-center border-b border-slate-100 py-2 text-slate-800">
|
||||
<div className="pl-6">
|
||||
className="grid grid-cols-4 items-center border-b border-slate-100 py-2 text-sm text-slate-800 md:text-base">
|
||||
<div className="pl-4 md:pl-6">
|
||||
{response.person ? (
|
||||
<Link
|
||||
className="ph-no-capture group flex items-center"
|
||||
href={`/environments/${environmentId}/people/${response.person.id}`}>
|
||||
<PersonAvatar personId={response.person.id} />
|
||||
|
||||
<p className="ph-no-capture ml-2 text-slate-600 group-hover:underline">
|
||||
<div className="hidden md:flex">
|
||||
<PersonAvatar personId={response.person.id} />
|
||||
</div>
|
||||
<p className="ph-no-capture break-all text-slate-600 group-hover:underline md:ml-2">
|
||||
{displayIdentifier}
|
||||
</p>
|
||||
</Link>
|
||||
) : (
|
||||
<div className="group flex items-center">
|
||||
<PersonAvatar personId="anonymous" />
|
||||
<p className="ml-2 text-slate-600">Anonymous</p>
|
||||
<div className="hidden md:flex">
|
||||
<PersonAvatar personId="anonymous" />
|
||||
</div>
|
||||
<p className="break-all text-slate-600 md:ml-2">Anonymous</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="ph-no-capture col-span-2 whitespace-pre-wrap pl-6 font-semibold">
|
||||
{response.value}
|
||||
</div>
|
||||
<div className="px-6 text-slate-500">{timeSince(response.updatedAt.toISOString())}</div>
|
||||
<div className="px-4 text-slate-500 md:px-6">{timeSince(response.updatedAt.toISOString())}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -75,19 +75,21 @@ export default function RatingSummary({ questionSummary }: RatingSummaryProps) {
|
||||
|
||||
return (
|
||||
<div className=" rounded-lg border border-slate-200 bg-slate-50 shadow-sm">
|
||||
<div className="space-y-2 px-6 pb-5 pt-6">
|
||||
<div className="space-y-2 px-4 pb-5 pt-6 md:px-6">
|
||||
<div>
|
||||
<h3 className="pb-1 text-xl font-semibold text-slate-900">{questionSummary.question.headline}</h3>
|
||||
<h3 className="text-lg pb-1 font-semibold text-slate-900 md:text-xl">
|
||||
{questionSummary.question.headline}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="flex space-x-2 font-semibold text-slate-600">
|
||||
<div className="rounded-lg bg-slate-100 p-2 text-sm">Rating Question</div>
|
||||
<div className=" flex items-center rounded-lg bg-slate-100 p-2 text-sm">
|
||||
<div className="flex space-x-2 text-xs font-semibold text-slate-600 md:text-sm">
|
||||
<div className="rounded-lg bg-slate-100 p-2">Rating Question</div>
|
||||
<div className="flex items-center rounded-lg bg-slate-100 p-2">
|
||||
<InboxStackIcon className="mr-2 h-4 w-4 " />
|
||||
{totalResponses} responses
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-5 bg-white px-6 pb-6 pt-4">
|
||||
<div className="space-y-5 bg-white px-4 pb-6 pt-4 text-sm md:px-6 md:text-base">
|
||||
{results.map((result: any) => (
|
||||
<div key={result.label}>
|
||||
<div className="text flex justify-between px-2 pb-2">
|
||||
|
||||
@@ -38,7 +38,7 @@ export default function AddQuestionButton({ addQuestion, environmentId }: AddQue
|
||||
</div>
|
||||
<div className="px-4 py-3">
|
||||
<p className="font-semibold">Add Question</p>
|
||||
<p className="mt-1 truncate text-sm text-slate-500">Add a new question to your survey</p>
|
||||
<p className="mt-1 text-sm text-slate-500">Add a new question to your survey</p>
|
||||
</div>
|
||||
</div>
|
||||
</Collapsible.CollapsibleTrigger>
|
||||
|
||||
@@ -94,7 +94,7 @@ export default function HowToSendCard({ localSurvey, setLocalSurvey, environment
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-slate-800">How to ask</p>
|
||||
<p className="mt-1 truncate text-sm text-slate-500">
|
||||
<p className="mt-1 text-sm text-slate-500">
|
||||
In-app survey, link survey or email survey.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { QueueListIcon, Cog8ToothIcon } from "@heroicons/react/24/solid";
|
||||
import { Cog8ToothIcon, QueueListIcon } from "@heroicons/react/24/solid";
|
||||
|
||||
interface Tab {
|
||||
id: "questions" | "settings";
|
||||
@@ -27,7 +27,7 @@ interface QuestionsAudienceTabsProps {
|
||||
|
||||
export default function QuestionsAudienceTabs({ activeId, setActiveId }: QuestionsAudienceTabsProps) {
|
||||
return (
|
||||
<div className="fixed z-10 flex h-14 w-1/2 items-center justify-center border bg-white">
|
||||
<div className="fixed z-10 flex h-14 md:w-1/2 w-full items-center justify-center border bg-white">
|
||||
<nav className="flex h-full items-center space-x-4" aria-label="Tabs">
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
@@ -100,7 +100,7 @@ export default function RecontactOptionsCard({
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-slate-800">Recontact Options</p>
|
||||
<p className="mt-1 truncate text-sm text-slate-500">
|
||||
<p className="mt-1 text-sm text-slate-500">
|
||||
Decide how often people can answer this survey.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -151,7 +151,7 @@ export default function ResponseOptionsCard({ localSurvey, setLocalSurvey }: Res
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-slate-800">Response Options</p>
|
||||
<p className="mt-1 truncate text-sm text-slate-500">
|
||||
<p className="mt-1 text-sm text-slate-500">
|
||||
Decide how and how long people can respond.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import LoadingSpinner from "@/components/shared/LoadingSpinner";
|
||||
import { useEnvironment } from "@/lib/environments/environments";
|
||||
import { useProduct } from "@/lib/products/products";
|
||||
import { useSurvey } from "@/lib/surveys/surveys";
|
||||
import type { Survey } from "@formbricks/types/surveys";
|
||||
import { ErrorComponent } from "@formbricks/ui";
|
||||
import { useEffect, useState } from "react";
|
||||
import PreviewSurvey from "../../PreviewSurvey";
|
||||
import SettingsView from "./SettingsView";
|
||||
import QuestionsAudienceTabs from "./QuestionsAudienceTabs";
|
||||
import QuestionsAudienceTabs from "./QuestionsSettingsTabs";
|
||||
import QuestionsView from "./QuestionsView";
|
||||
import SettingsView from "./SettingsView";
|
||||
import SurveyMenuBar from "./SurveyMenuBar";
|
||||
import { useEnvironment } from "@/lib/environments/environments";
|
||||
|
||||
interface SurveyEditorProps {
|
||||
environmentId: string;
|
||||
|
||||
@@ -9,10 +9,10 @@ import { deleteSurvey } from "@/lib/surveys/surveys";
|
||||
import type { Survey } from "@formbricks/types/surveys";
|
||||
import { Button, Input } from "@formbricks/ui";
|
||||
import { ArrowLeftIcon, Cog8ToothIcon, ExclamationTriangleIcon } from "@heroicons/react/24/solid";
|
||||
import { isEqual } from "lodash";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { isEqual } from "lodash";
|
||||
import { validateQuestion } from "./Validation";
|
||||
|
||||
interface SurveyMenuBarProps {
|
||||
@@ -169,7 +169,7 @@ export default function SurveyMenuBar({
|
||||
}}>
|
||||
Back
|
||||
</Button>
|
||||
<p className="pl-4 font-semibold">{product.name} / </p>
|
||||
<p className="pl-4 font-semibold hidden md:block">{product.name} / </p>
|
||||
<Input
|
||||
defaultValue={localSurvey.name}
|
||||
onChange={(e) => {
|
||||
|
||||
@@ -142,7 +142,7 @@ export default function WhenToSendCard({ environmentId, localSurvey, setLocalSur
|
||||
|
||||
<div>
|
||||
<p className="font-semibold text-slate-800">Survey Trigger</p>
|
||||
<p className="mt-1 truncate text-sm text-slate-500">
|
||||
<p className="mt-1 text-sm text-slate-500">
|
||||
Choose the actions which trigger the survey.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -14,10 +14,9 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@formbricks/ui";
|
||||
import { CheckCircleIcon, PlusIcon, TrashIcon } from "@heroicons/react/24/solid";
|
||||
import { CheckCircleIcon, FunnelIcon, PlusIcon, TrashIcon, UserGroupIcon } from "@heroicons/react/24/solid";
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
import { useEffect, useState } from "react"; /* */
|
||||
import { UserGroupIcon, FunnelIcon } from "@heroicons/react/24/solid";
|
||||
|
||||
const filterConditions = [
|
||||
{ id: "equals", name: "equals" },
|
||||
@@ -107,7 +106,7 @@ export default function WhoToSendCard({ environmentId, localSurvey, setLocalSurv
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-slate-800">Target Audience</p>
|
||||
<p className="mt-1 truncate text-sm text-slate-500">
|
||||
<p className="mt-1 text-sm text-slate-500">
|
||||
Pre-segment your users with attributes filters.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -144,7 +144,7 @@ const createSurveyFields = (surveryResponses: SurveyResponse[]) => {
|
||||
surveyFields += `
|
||||
<div style="margin-top:1em;">
|
||||
<p style="margin:0px;">${headline}</p>
|
||||
<p style="font-weight: 500; margin:0px;">${answer}</p>
|
||||
<p style="font-weight: bold; margin:0px;">${answer}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@formbricks/web",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"clean": "rimraf .turbo node_modules .next",
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import Script from "next/script";
|
||||
|
||||
declare global {
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
["stripe-pricing-table"]: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function BillingPage({ organisationId }: { organisationId: string }) {
|
||||
if (!process.env.NEXT_PUBLIC_STRIPE_PRICING_TABLE_ID || !process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY) {
|
||||
return <div>Stripe environment variables not set</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Script async src="https://js.stripe.com/v3/pricing-table.js" />
|
||||
<stripe-pricing-table
|
||||
pricing-table-id={process.env.NEXT_PUBLIC_STRIPE_PRICING_TABLE_ID}
|
||||
publishable-key={process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY}
|
||||
client-reference-id={organisationId}></stripe-pricing-table>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
# @formbricks/js
|
||||
|
||||
## 1.0.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 01523393: Convert all attributes and userIds to string in formbricks-js
|
||||
|
||||
## 1.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@formbricks/js",
|
||||
"license": "MIT",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.4",
|
||||
"description": "Formbricks-js allows you to connect your app to Formbricks, display surveys and trigger events.",
|
||||
"keywords": [
|
||||
"Formbricks",
|
||||
|
||||
@@ -6,7 +6,7 @@ import { trackAction } from "./lib/actions";
|
||||
import { initialize } from "./lib/init";
|
||||
import { Logger } from "./lib/logger";
|
||||
import { checkPageUrl } from "./lib/noCodeEvents";
|
||||
import { resetPerson, setPersonAttribute, setPersonUserId, getPerson } from "./lib/person";
|
||||
import { resetPerson, setPersonAttribute, setPersonUserId, getPerson, logoutPerson } from "./lib/person";
|
||||
|
||||
export type { EnvironmentId, KeyValueData, PersonId, ResponseId, SurveyId } from "@formbricks/api";
|
||||
|
||||
@@ -21,7 +21,7 @@ const init = async (initConfig: InitConfig) => {
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const setUserId = async (userId: string): Promise<void> => {
|
||||
const setUserId = async (userId: string | number): Promise<void> => {
|
||||
queue.add(true, setPersonUserId, userId);
|
||||
await queue.wait();
|
||||
};
|
||||
@@ -31,12 +31,17 @@ const setEmail = async (email: string): Promise<void> => {
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const setAttribute = async (key: string, value: string): Promise<void> => {
|
||||
const setAttribute = async (key: string, value: any): Promise<void> => {
|
||||
queue.add(true, setPersonAttribute, key, value);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const logout = async (): Promise<void> => {
|
||||
queue.add(true, logoutPerson);
|
||||
await queue.wait();
|
||||
};
|
||||
|
||||
const reset = async (): Promise<void> => {
|
||||
queue.add(true, resetPerson);
|
||||
await queue.wait();
|
||||
};
|
||||
@@ -58,6 +63,7 @@ const formbricks = {
|
||||
setAttribute,
|
||||
track,
|
||||
logout,
|
||||
reset,
|
||||
registerRouteChange,
|
||||
getApi,
|
||||
getPerson,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { checkInitialized } from "./init";
|
||||
|
||||
export class CommandQueue {
|
||||
private queue: {
|
||||
command: (args: any) => Promise<Result<void, any>> | Result<void, any>;
|
||||
command: (args: any) => Promise<Result<void, any>> | Result<void, any> | Promise<void>;
|
||||
checkInitialized: boolean;
|
||||
commandArgs: any[];
|
||||
}[] = [];
|
||||
@@ -13,7 +13,7 @@ export class CommandQueue {
|
||||
|
||||
public add<A>(
|
||||
checkInitialized: boolean = true,
|
||||
command: (...args: A[]) => Promise<Result<void, any>> | Result<void, any>,
|
||||
command: (...args: A[]) => Promise<Result<void, any>> | Result<void, any> | Promise<void>,
|
||||
...args: A[]
|
||||
) {
|
||||
this.queue.push({ command, checkInitialized, commandArgs: args });
|
||||
|
||||
@@ -118,11 +118,11 @@ export const hasAttributeKey = (key: string): boolean => {
|
||||
};
|
||||
|
||||
export const setPersonUserId = async (
|
||||
userId: string
|
||||
userId: string | number
|
||||
): Promise<Result<void, NetworkError | MissingPersonError | AttributeAlreadyExistsError>> => {
|
||||
logger.debug("setting userId: " + userId);
|
||||
// check if attribute already exists with this value
|
||||
if (hasAttributeValue("userId", userId)) {
|
||||
if (hasAttributeValue("userId", userId.toString())) {
|
||||
logger.debug("userId already set to this value. Skipping update.");
|
||||
return okVoid();
|
||||
}
|
||||
@@ -132,7 +132,7 @@ export const setPersonUserId = async (
|
||||
message: "userId cannot be changed after it has been set. You need to reset first",
|
||||
});
|
||||
}
|
||||
const result = await updatePersonUserId(userId);
|
||||
const result = await updatePersonUserId(userId.toString());
|
||||
|
||||
if (result.ok !== true) return err(result.error);
|
||||
|
||||
@@ -145,16 +145,16 @@ export const setPersonUserId = async (
|
||||
|
||||
export const setPersonAttribute = async (
|
||||
key: string,
|
||||
value: string
|
||||
value: any
|
||||
): Promise<Result<void, NetworkError | MissingPersonError>> => {
|
||||
logger.debug("setting attribute: " + key + " to value: " + value);
|
||||
logger.debug("Setting attribute: " + key + " to value: " + value);
|
||||
// check if attribute already exists with this value
|
||||
if (hasAttributeValue(key, value)) {
|
||||
logger.debug("attribute already set to this value. Skipping update.");
|
||||
if (hasAttributeValue(key, value.toString())) {
|
||||
logger.debug("Attribute already set to this value. Skipping update.");
|
||||
return okVoid();
|
||||
}
|
||||
|
||||
const result = await updatePersonAttribute(key, value);
|
||||
const result = await updatePersonAttribute(key, value.toString());
|
||||
|
||||
let error: NetworkError | MissingPersonError;
|
||||
|
||||
@@ -176,9 +176,14 @@ export const setPersonAttribute = async (
|
||||
return okVoid();
|
||||
};
|
||||
|
||||
export const logoutPerson = async (): Promise<void> => {
|
||||
logger.debug("Resetting state");
|
||||
config.update({ state: undefined });
|
||||
};
|
||||
|
||||
export const resetPerson = async (): Promise<Result<void, NetworkError>> => {
|
||||
logger.debug("Resetting state & getting new state from backend");
|
||||
config.update({ state: undefined });
|
||||
await logoutPerson();
|
||||
try {
|
||||
await sync();
|
||||
return okVoid();
|
||||
|
||||
@@ -37,7 +37,7 @@ export const HalfCircle: React.FC<HalfCircleProps> = ({ value }: { value: number
|
||||
</div>
|
||||
<div className="flex justify-between text-sm leading-10 text-slate-600">
|
||||
<p>-100</p>
|
||||
<p className="text-4xl text-black">{Math.round(value)}</p>
|
||||
<p className="text-2xl text-black md:text-4xl">{Math.round(value)}</p>
|
||||
<p>100</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user