Compare commits

..

14 Commits

Author SHA1 Message Date
github-actions[bot]
8a0f7fde3d Release formbricks-js 1.0.4 (#689)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-08-14 12:29:23 +02:00
Matti Nannt
01523393db Convert all attributes and userIds to string in formbricks-js (#688)
* convert any attribute input to string in formbricks-js

* add changeset, increase patch version of formbricks-js
2023-08-14 12:26:40 +02:00
Johannes
534dd5050d Make Survey Summary Page and Several Other Pages Responsive
Make Survey Summary Page and Several Other Pages Responsive
2023-08-14 11:12:48 +02:00
Johannes
a3e1e0498d fix restart UI 2023-08-14 11:11:50 +02:00
Johannes
beadbfa4b9 attributes and people page responsiveness 2023-08-14 10:53:35 +02:00
Johannes
a3162150a6 survey list and editor mobile tweaks 2023-08-14 10:43:30 +02:00
Johannes
1c6a5b2685 Add Loader in Product Delete button and tweak Weekly Summary UI
Add Loader in Product Delete button and tweak Weekly Summary UI
2023-08-14 10:33:56 +02:00
Johannes
9c8141abb2 Merge branch 'main' of github.com:formbricks/formbricks into shubham/for-1124-responsiveness-create-a-mobile-friendly-version-of-all-data 2023-08-14 10:23:16 +02:00
Johannes
88c17546b7 add font weight bold to weekly summary 2023-08-14 10:20:53 +02:00
Johannes
ccfc85f4fa Merge branch 'main' of github.com:formbricks/formbricks into shubham/for-1122-tweak-add-loading-state-to-delete-button-in-delete-product 2023-08-14 10:15:29 +02:00
Johannes
d9839aba24 Merge branch 'main' of github.com:formbricks/formbricks into shubham/for-1122-tweak-add-loading-state-to-delete-button-in-delete-product 2023-08-14 10:14:16 +02:00
Matti Nannt
d83c530012 Remove responses limit for link surveys on free plan (#686) 2023-08-14 09:50:58 +02:00
ShubhamPalriwala
8716367ec1 ui: data comps of survey summary are now responsive 2023-08-14 13:09:17 +05:30
ShubhamPalriwala
dcffb8106e feat: loader in product delete button 2023-08-13 09:54:10 +05:30
35 changed files with 160 additions and 141 deletions

View File

@@ -1,5 +1,12 @@
# @formbricks/web
## 1.0.3
### Patch Changes
- Updated dependencies [01523393]
- @formbricks/js@1.0.4
## 1.0.2
### Patch Changes

View File

@@ -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>

View File

@@ -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>
</>
);

View File

@@ -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>

View File

@@ -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"

View File

@@ -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>

View File

@@ -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",

View File

@@ -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,

View File

@@ -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" />

View File

@@ -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">

View File

@@ -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;

View File

@@ -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>

View File

@@ -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">

View File

@@ -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>

View File

@@ -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">

View File

@@ -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>
);
})}

View File

@@ -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">

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;

View File

@@ -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) => {

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>
`;
}

View File

@@ -1,6 +1,6 @@
{
"name": "@formbricks/web",
"version": "1.0.2",
"version": "1.0.3",
"private": true,
"scripts": {
"clean": "rimraf .turbo node_modules .next",

View File

@@ -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>
</>
);
}

View File

@@ -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

View File

@@ -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",

View File

@@ -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,

View File

@@ -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 });

View File

@@ -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();

View File

@@ -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>