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 # @formbricks/web
## 1.0.3
### Patch Changes
- Updated dependencies [01523393]
- @formbricks/js@1.0.4
## 1.0.2 ## 1.0.2
### Patch Changes ### Patch Changes

View File

@@ -5,7 +5,7 @@ import { TagIcon } from "@heroicons/react/24/solid";
export default function AttributeClassDataRow({ attributeClass }) { export default function AttributeClassDataRow({ attributeClass }) {
return ( return (
<div className="m-2 grid h-16 grid-cols-5 content-center rounded-lg hover:bg-slate-100"> <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="flex items-center">
<div className="h-10 w-10 flex-shrink-0"> <div className="h-10 w-10 flex-shrink-0">
<TagIcon className="h-8 w-8 flex-shrink-0 text-slate-500" /> <TagIcon className="h-8 w-8 flex-shrink-0 text-slate-500" />
@@ -22,10 +22,10 @@ export default function AttributeClassDataRow({ attributeClass }) {
</div> </div>
</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 className="text-slate-900">{timeSinceConditionally(attributeClass.createdAt.toString())}</div>
</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 className="text-slate-900">{timeSinceConditionally(attributeClass.updatedAt.toString())}</div>
</div> </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="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="col-span-3 pl-6 ">Name</div>
<div className="text-center">Created</div> <div className="text-center hidden sm:block">Created</div>
<div className="text-center">Last Updated</div> <div className="text-center hidden sm:block">Last Updated</div>
</div> </div>
</> </>
); );

View File

@@ -16,7 +16,6 @@ import {
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/shared/DropdownMenu"; } from "@/components/shared/DropdownMenu";
import { Popover, PopoverContent, PopoverTrigger } from "@formbricks/ui";
import LoadingSpinner from "@/components/shared/LoadingSpinner"; import LoadingSpinner from "@/components/shared/LoadingSpinner";
import CreateTeamModal from "@/components/team/CreateTeamModal"; import CreateTeamModal from "@/components/team/CreateTeamModal";
import { import {
@@ -25,25 +24,29 @@ import {
changeEnvironmentByTeam, changeEnvironmentByTeam,
} from "@/lib/environments/changeEnvironments"; } from "@/lib/environments/changeEnvironments";
import { useEnvironment } from "@/lib/environments/environments"; import { useEnvironment } from "@/lib/environments/environments";
import { formbricksLogout } from "@/lib/formbricks";
import { useMemberships } from "@/lib/memberships"; import { useMemberships } from "@/lib/memberships";
import { useTeam } from "@/lib/teams/teams"; import { useTeam } from "@/lib/teams/teams";
import { capitalizeFirstLetter, truncate } from "@/lib/utils"; 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 { import {
CustomersIcon, CustomersIcon,
DashboardIcon, DashboardIcon,
ErrorComponent, ErrorComponent,
FilterIcon, FilterIcon,
FormIcon, FormIcon, Popover, PopoverContent, PopoverTrigger, ProfileAvatar,
ProfileAvatar,
SettingsIcon, SettingsIcon,
Tooltip, Tooltip,
TooltipContent, TooltipContent,
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger
} from "@formbricks/ui"; } from "@formbricks/ui";
import { import {
AdjustmentsVerticalIcon, AdjustmentsVerticalIcon,
ArrowRightOnRectangleIcon, ArrowRightOnRectangleIcon,
ChatBubbleBottomCenterTextIcon,
ChevronDownIcon, ChevronDownIcon,
CodeBracketIcon, CodeBracketIcon,
CreditCardIcon, CreditCardIcon,
@@ -53,9 +56,9 @@ import {
PlusIcon, PlusIcon,
UserCircleIcon, UserCircleIcon,
UsersIcon, UsersIcon,
ChatBubbleBottomCenterTextIcon,
} from "@heroicons/react/24/solid"; } from "@heroicons/react/24/solid";
import clsx from "clsx"; import clsx from "clsx";
import { MenuIcon } from "lucide-react";
import type { Session } from "next-auth"; import type { Session } from "next-auth";
import { signOut } from "next-auth/react"; import { signOut } from "next-auth/react";
import Image from "next/image"; import Image from "next/image";
@@ -63,11 +66,6 @@ import Link from "next/link";
import { usePathname, useRouter } from "next/navigation"; import { usePathname, useRouter } from "next/navigation";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import AddProductModal from "./AddProductModal"; 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 { interface EnvironmentsNavbarProps {
environmentId: string; 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" /> <MenuIcon className="h-6 w-6 rounded-md bg-slate-200 p-1 text-slate-600" />
</span> </span>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="mr-4 bg-slate-200"> <PopoverContent className="mr-4 bg-slate-100 shadow">
<div className="flex flex-col"> <div className="flex flex-col">
{navigation.map((navItem) => ( {navigation.map((navItem) => (
<Link key={navItem.name} href={navItem.href}> <Link key={navItem.name} href={navItem.href}>
@@ -276,7 +274,7 @@ export default function EnvironmentsNavbar({ environmentId, session }: Environme
onClick={() => setMobileNavMenuOpen(false)} onClick={() => setMobileNavMenuOpen(false)}
className={cn( className={cn(
"flex items-center space-x-2 rounded-md p-2", "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" /> <navItem.icon className="h-5 w-5" />
<span className="font-medium text-slate-600">{navItem.name}</span> <span className="font-medium text-slate-600">{navItem.name}</span>

View File

@@ -9,7 +9,7 @@ export default function IntegrationsPage({ params }) {
<div> <div>
<h1 className="my-2 text-3xl font-bold text-slate-800">Integrations</h1> <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> <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 <Card
docsHref="https://formbricks.com/docs/getting-started/nextjs-app" docsHref="https://formbricks.com/docs/getting-started/nextjs-app"
docsText="Docs" docsText="Docs"

View File

@@ -26,8 +26,8 @@ export default async function PeoplePage({ params }) {
<div className="rounded-lg border border-slate-200"> <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="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-3 pl-6 ">User</div>
<div className="col-span-2 text-center">User ID</div> <div className="col-span-2 text-center hidden sm:block">User ID</div>
<div className="col-span-2 text-center">Email</div> <div className="col-span-2 text-center hidden sm:block">Email</div>
</div> </div>
{people.map((person) => ( {people.map((person) => (
<Link <Link
@@ -51,12 +51,12 @@ export default async function PeoplePage({ params }) {
</div> </div>
</div> </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"> <div className="ph-no-capture text-slate-900">
{truncateMiddle(getAttributeValue(person, "userId"), 24)} {truncateMiddle(getAttributeValue(person, "userId"), 24)}
</div> </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">{getAttributeValue(person, "email")}</div> <div className="ph-no-capture text-slate-900">{getAttributeValue(person, "email")}</div>
</div> </div>
</div> </div>

View File

@@ -51,7 +51,8 @@ export default function PricingTable({ environmentId, session }: PricingTablePro
"Unlimited surveys", "Unlimited surveys",
"Unlimited team members", "Unlimited team members",
"Remove branding", "Remove branding",
"100 responses per survey", "Unlimited link survey responses",
"100 responses per web-app survey",
"Granular targeting", "Granular targeting",
"In-product surveys", "In-product surveys",
"Link surveys", "Link surveys",

View File

@@ -125,6 +125,7 @@ export function DeleteProduct({ environmentId }) {
const router = useRouter(); const router = useRouter();
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const [deletingProduct, setDeletingProduct] = useState(false);
const { profile } = useProfile(); const { profile } = useProfile();
const { team } = useMembers(environmentId); const { team } = useMembers(environmentId);
@@ -149,7 +150,9 @@ export function DeleteProduct({ environmentId }) {
setIsDeleteDialogOpen(false); setIsDeleteDialogOpen(false);
return; return;
} }
setDeletingProduct(true);
const deleteProductRes = await deleteProduct(environmentId); const deleteProductRes = await deleteProduct(environmentId);
setDeletingProduct(false);
if (deleteProductRes?.id?.length > 0) { if (deleteProductRes?.id?.length > 0) {
toast.success("Product deleted successfully."); toast.success("Product deleted successfully.");
@@ -191,6 +194,7 @@ export function DeleteProduct({ environmentId }) {
deleteWhat="Product" deleteWhat="Product"
open={isDeleteDialogOpen} open={isDeleteDialogOpen}
setOpen={setIsDeleteDialogOpen} setOpen={setIsDeleteDialogOpen}
isDeleting={deletingProduct}
onDelete={handleDeleteProduct} onDelete={handleDeleteProduct}
text={`Are you sure you want to delete "${truncate( text={`Are you sure you want to delete "${truncate(
product?.name, product?.name,

View File

@@ -498,7 +498,7 @@ function ResetProgressButton({ resetQuestionProgress }) {
return ( return (
<Button <Button
variant="minimal" 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}> onClick={resetQuestionProgress}>
Restart Restart
<ArrowPathRoundedSquareIcon className="ml-2 h-4 w-4" /> <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 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 { Badge } from "@formbricks/ui";
import { ComputerDesktopIcon, LinkIcon, PlusIcon } from "@heroicons/react/24/solid"; import { ComputerDesktopIcon, LinkIcon, PlusIcon } from "@heroicons/react/24/solid";
import Link from "next/link"; 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 }) { export default async function SurveysList({ environmentId }: { environmentId: string }) {
const product = await getProductByEnvironmentId(environmentId); const product = await getProductByEnvironmentId(environmentId);
@@ -23,7 +23,7 @@ export default async function SurveysList({ environmentId }: { environmentId: st
return ( 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`}> <Link href={`/environments/${environmentId}/surveys/templates`}>
<li className="col-span-1 h-56"> <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"> <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 (!team) throw new Error(`Team not found for environment: ${environmentId}`);
if (survey.environmentId !== environmentId) throw new Error(`Survey not found: ${surveyId}`); if (survey.environmentId !== environmentId) throw new Error(`Survey not found: ${surveyId}`);
const limitReached = 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 responses = limitReached ? allResponses.slice(0, RESPONSES_LIMIT_FREE) : allResponses;
const responsesCount = allResponses.length; const responsesCount = allResponses.length;

View File

@@ -26,19 +26,21 @@ export default function CTASummary({ questionSummary }: CTASummaryProps) {
return ( return (
<div className=" rounded-lg border border-slate-200 bg-slate-50 shadow-sm"> <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> <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>
<div className="flex space-x-2 font-semibold text-slate-600"> <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 text-sm">Call-to-Action</div> <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 text-sm"> <div className=" flex items-center rounded-lg bg-slate-100 p-2">
<InboxStackIcon className="mr-2 h-4 w-4 " /> <InboxStackIcon className="mr-2 h-4 w-4 " />
{ctr.count} responses {ctr.count} responses
</div> </div>
</div> </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="text flex justify-between px-2 pb-2">
<div className="mr-8 flex space-x-1"> <div className="mr-8 flex space-x-1">
<p className="font-semibold text-slate-700">Clickthrough Rate (CTR)</p> <p className="font-semibold text-slate-700">Clickthrough Rate (CTR)</p>

View File

@@ -34,19 +34,21 @@ export default function ConsentSummary({ questionSummary }: ConsentSummaryProps)
return ( return (
<div className=" rounded-lg border border-slate-200 bg-slate-50 shadow-sm"> <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> <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>
<div className="flex space-x-2 font-semibold text-slate-600"> <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 text-sm">Consent</div> <div className="rounded-lg bg-slate-100 p-2">Consent</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 " /> <InboxStackIcon className="mr-2 h-4 w-4 " />
{ctr.count} responses {ctr.count} responses
</div> </div>
</div> </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>
<div className="text flex justify-between px-2 pb-2"> <div className="text flex justify-between px-2 pb-2">
<div className="mr-8 flex space-x-1"> <div className="mr-8 flex space-x-1">

View File

@@ -124,17 +124,19 @@ export default function MultipleChoiceSummary({
return ( return (
<div className=" rounded-lg border border-slate-200 bg-slate-50 shadow-sm"> <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> <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>
<div className="flex space-x-2 font-semibold text-slate-600"> <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 text-sm"> <div className="rounded-lg bg-slate-100 p-2">
{isSingleChoice {isSingleChoice
? "Multiple-Choice Single Select Question" ? "Multiple-Choice Single Select Question"
: "Multiple-Choice Multi Select Question"} : "Multiple-Choice Multi Select Question"}
</div> </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 " /> <InboxStackIcon className="mr-2 h-4 w-4 " />
{totalResponses} responses {totalResponses} responses
</div> </div>
@@ -144,11 +146,11 @@ export default function MultipleChoiceSummary({
</div> */} </div> */}
</div> </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) => ( {results.map((result: any, resultsIdx) => (
<div key={result.label}> <div key={result.label}>
<div className="text flex justify-between px-2 pb-2"> <div className="text flex flex-col justify-between px-2 pb-2 sm:flex-row">
<div className="mr-8 flex space-x-1"> <div className="mr-8 flex w-full justify-between space-x-1 sm:justify-normal">
<p className="font-semibold text-slate-700"> <p className="font-semibold text-slate-700">
{results.length - resultsIdx} - {result.label} {results.length - resultsIdx} - {result.label}
</p> </p>
@@ -158,7 +160,7 @@ export default function MultipleChoiceSummary({
</p> </p>
</div> </div>
</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"} {result.count} {result.count === 1 ? "response" : "responses"}
</p> </p>
</div> </div>

View File

@@ -74,19 +74,21 @@ export default function NPSSummary({ questionSummary }: NPSSummaryProps) {
return ( return (
<div className=" rounded-lg border border-slate-200 bg-slate-50 shadow-sm"> <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> <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>
<div className="flex space-x-2 font-semibold text-slate-600"> <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 text-sm">Net Promoter Score (NPS)</div> <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 text-sm"> <div className=" flex items-center rounded-lg bg-slate-100 p-2">
<InboxStackIcon className="mr-2 h-4 w-4 " /> <InboxStackIcon className="mr-2 h-4 w-4 " />
{result.total} responses {result.total} responses
</div> </div>
</div> </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) => ( {["promoters", "passives", "detractors"].map((group) => (
<div key={group}> <div key={group}>
<div className="mb-2 flex justify-between"> <div className="mb-2 flex justify-between">
@@ -107,7 +109,7 @@ export default function NPSSummary({ questionSummary }: NPSSummaryProps) {
))} ))}
</div> </div>
{dismissed.count > 0 && ( {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 key={dismissed.label}>
<div className="text flex justify-between px-2 pb-2"> <div className="text flex justify-between px-2 pb-2">
<div className="mr-8 flex space-x-1"> <div className="mr-8 flex space-x-1">

View File

@@ -18,13 +18,15 @@ function findEmail(person) {
export default function OpenTextSummary({ questionSummary, environmentId }: OpenTextSummaryProps) { export default function OpenTextSummary({ questionSummary, environmentId }: OpenTextSummaryProps) {
return ( return (
<div className="rounded-lg border border-slate-200 bg-slate-50 shadow-sm"> <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> <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>
<div className="flex space-x-2 font-semibold text-slate-600"> <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 text-sm">Open Text Question</div> <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 text-sm"> <div className=" flex items-center rounded-lg bg-slate-100 p-2">
<InboxStackIcon className="mr-2 h-4 w-4" /> <InboxStackIcon className="mr-2 h-4 w-4" />
{questionSummary.responses.length} Responses {questionSummary.responses.length} Responses
</div> </div>
@@ -32,9 +34,9 @@ export default function OpenTextSummary({ questionSummary, environmentId }: Open
</div> </div>
<div className="rounded-b-lg bg-white "> <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="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="pl-4 md:pl-6">User</div>
<div className="col-span-2 pl-6">Response</div> <div className="col-span-2 pl-4 md:pl-6">Response</div>
<div className="px-6">Time</div> <div className="px-4 md:px-6">Time</div>
</div> </div>
{questionSummary.responses.map((response) => { {questionSummary.responses.map((response) => {
const email = response.person && findEmail(response.person); const email = response.person && findEmail(response.person);
@@ -42,29 +44,32 @@ export default function OpenTextSummary({ questionSummary, environmentId }: Open
return ( return (
<div <div
key={response.id} key={response.id}
className="grid grid-cols-4 items-center border-b border-slate-100 py-2 text-slate-800"> 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-6"> <div className="pl-4 md:pl-6">
{response.person ? ( {response.person ? (
<Link <Link
className="ph-no-capture group flex items-center" className="ph-no-capture group flex items-center"
href={`/environments/${environmentId}/people/${response.person.id}`}> href={`/environments/${environmentId}/people/${response.person.id}`}>
<PersonAvatar personId={response.person.id} /> <div className="hidden md:flex">
<PersonAvatar personId={response.person.id} />
<p className="ph-no-capture ml-2 text-slate-600 group-hover:underline"> </div>
<p className="ph-no-capture break-all text-slate-600 group-hover:underline md:ml-2">
{displayIdentifier} {displayIdentifier}
</p> </p>
</Link> </Link>
) : ( ) : (
<div className="group flex items-center"> <div className="group flex items-center">
<PersonAvatar personId="anonymous" /> <div className="hidden md:flex">
<p className="ml-2 text-slate-600">Anonymous</p> <PersonAvatar personId="anonymous" />
</div>
<p className="break-all text-slate-600 md:ml-2">Anonymous</p>
</div> </div>
)} )}
</div> </div>
<div className="ph-no-capture col-span-2 whitespace-pre-wrap pl-6 font-semibold"> <div className="ph-no-capture col-span-2 whitespace-pre-wrap pl-6 font-semibold">
{response.value} {response.value}
</div> </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> </div>
); );
})} })}

View File

@@ -75,19 +75,21 @@ export default function RatingSummary({ questionSummary }: RatingSummaryProps) {
return ( return (
<div className=" rounded-lg border border-slate-200 bg-slate-50 shadow-sm"> <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> <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>
<div className="flex space-x-2 font-semibold text-slate-600"> <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 text-sm">Rating Question</div> <div className="rounded-lg bg-slate-100 p-2">Rating 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 " /> <InboxStackIcon className="mr-2 h-4 w-4 " />
{totalResponses} responses {totalResponses} responses
</div> </div>
</div> </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) => ( {results.map((result: any) => (
<div key={result.label}> <div key={result.label}>
<div className="text flex justify-between px-2 pb-2"> <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>
<div className="px-4 py-3"> <div className="px-4 py-3">
<p className="font-semibold">Add Question</p> <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>
</div> </div>
</Collapsible.CollapsibleTrigger> </Collapsible.CollapsibleTrigger>

View File

@@ -94,7 +94,7 @@ export default function HowToSendCard({ localSurvey, setLocalSurvey, environment
</div> </div>
<div> <div>
<p className="font-semibold text-slate-800">How to ask</p> <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. In-app survey, link survey or email survey.
</p> </p>
</div> </div>

View File

@@ -1,5 +1,5 @@
import { cn } from "@formbricks/lib/cn"; import { cn } from "@formbricks/lib/cn";
import { QueueListIcon, Cog8ToothIcon } from "@heroicons/react/24/solid"; import { Cog8ToothIcon, QueueListIcon } from "@heroicons/react/24/solid";
interface Tab { interface Tab {
id: "questions" | "settings"; id: "questions" | "settings";
@@ -27,7 +27,7 @@ interface QuestionsAudienceTabsProps {
export default function QuestionsAudienceTabs({ activeId, setActiveId }: QuestionsAudienceTabsProps) { export default function QuestionsAudienceTabs({ activeId, setActiveId }: QuestionsAudienceTabsProps) {
return ( 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"> <nav className="flex h-full items-center space-x-4" aria-label="Tabs">
{tabs.map((tab) => ( {tabs.map((tab) => (
<button <button

View File

@@ -100,7 +100,7 @@ export default function RecontactOptionsCard({
</div> </div>
<div> <div>
<p className="font-semibold text-slate-800">Recontact Options</p> <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. Decide how often people can answer this survey.
</p> </p>
</div> </div>

View File

@@ -151,7 +151,7 @@ export default function ResponseOptionsCard({ localSurvey, setLocalSurvey }: Res
</div> </div>
<div> <div>
<p className="font-semibold text-slate-800">Response Options</p> <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. Decide how and how long people can respond.
</p> </p>
</div> </div>

View File

@@ -1,17 +1,17 @@
"use client"; "use client";
import LoadingSpinner from "@/components/shared/LoadingSpinner"; import LoadingSpinner from "@/components/shared/LoadingSpinner";
import { useEnvironment } from "@/lib/environments/environments";
import { useProduct } from "@/lib/products/products"; import { useProduct } from "@/lib/products/products";
import { useSurvey } from "@/lib/surveys/surveys"; import { useSurvey } from "@/lib/surveys/surveys";
import type { Survey } from "@formbricks/types/surveys"; import type { Survey } from "@formbricks/types/surveys";
import { ErrorComponent } from "@formbricks/ui"; import { ErrorComponent } from "@formbricks/ui";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import PreviewSurvey from "../../PreviewSurvey"; import PreviewSurvey from "../../PreviewSurvey";
import SettingsView from "./SettingsView"; import QuestionsAudienceTabs from "./QuestionsSettingsTabs";
import QuestionsAudienceTabs from "./QuestionsAudienceTabs";
import QuestionsView from "./QuestionsView"; import QuestionsView from "./QuestionsView";
import SettingsView from "./SettingsView";
import SurveyMenuBar from "./SurveyMenuBar"; import SurveyMenuBar from "./SurveyMenuBar";
import { useEnvironment } from "@/lib/environments/environments";
interface SurveyEditorProps { interface SurveyEditorProps {
environmentId: string; environmentId: string;

View File

@@ -9,10 +9,10 @@ import { deleteSurvey } from "@/lib/surveys/surveys";
import type { Survey } from "@formbricks/types/surveys"; import type { Survey } from "@formbricks/types/surveys";
import { Button, Input } from "@formbricks/ui"; import { Button, Input } from "@formbricks/ui";
import { ArrowLeftIcon, Cog8ToothIcon, ExclamationTriangleIcon } from "@heroicons/react/24/solid"; import { ArrowLeftIcon, Cog8ToothIcon, ExclamationTriangleIcon } from "@heroicons/react/24/solid";
import { isEqual } from "lodash";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { isEqual } from "lodash";
import { validateQuestion } from "./Validation"; import { validateQuestion } from "./Validation";
interface SurveyMenuBarProps { interface SurveyMenuBarProps {
@@ -169,7 +169,7 @@ export default function SurveyMenuBar({
}}> }}>
Back Back
</Button> </Button>
<p className="pl-4 font-semibold">{product.name} / </p> <p className="pl-4 font-semibold hidden md:block">{product.name} / </p>
<Input <Input
defaultValue={localSurvey.name} defaultValue={localSurvey.name}
onChange={(e) => { onChange={(e) => {

View File

@@ -142,7 +142,7 @@ export default function WhenToSendCard({ environmentId, localSurvey, setLocalSur
<div> <div>
<p className="font-semibold text-slate-800">Survey Trigger</p> <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. Choose the actions which trigger the survey.
</p> </p>
</div> </div>

View File

@@ -14,10 +14,9 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@formbricks/ui"; } 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 * as Collapsible from "@radix-ui/react-collapsible";
import { useEffect, useState } from "react"; /* */ import { useEffect, useState } from "react"; /* */
import { UserGroupIcon, FunnelIcon } from "@heroicons/react/24/solid";
const filterConditions = [ const filterConditions = [
{ id: "equals", name: "equals" }, { id: "equals", name: "equals" },
@@ -107,7 +106,7 @@ export default function WhoToSendCard({ environmentId, localSurvey, setLocalSurv
</div> </div>
<div> <div>
<p className="font-semibold text-slate-800">Target Audience</p> <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. Pre-segment your users with attributes filters.
</p> </p>
</div> </div>

View File

@@ -144,7 +144,7 @@ const createSurveyFields = (surveryResponses: SurveyResponse[]) => {
surveyFields += ` surveyFields += `
<div style="margin-top:1em;"> <div style="margin-top:1em;">
<p style="margin:0px;">${headline}</p> <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> </div>
`; `;
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@formbricks/web", "name": "@formbricks/web",
"version": "1.0.2", "version": "1.0.3",
"private": true, "private": true,
"scripts": { "scripts": {
"clean": "rimraf .turbo node_modules .next", "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 # @formbricks/js
## 1.0.4
### Patch Changes
- 01523393: Convert all attributes and userIds to string in formbricks-js
## 1.0.3 ## 1.0.3
### Patch Changes ### Patch Changes

View File

@@ -1,7 +1,7 @@
{ {
"name": "@formbricks/js", "name": "@formbricks/js",
"license": "MIT", "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.", "description": "Formbricks-js allows you to connect your app to Formbricks, display surveys and trigger events.",
"keywords": [ "keywords": [
"Formbricks", "Formbricks",

View File

@@ -6,7 +6,7 @@ import { trackAction } from "./lib/actions";
import { initialize } from "./lib/init"; import { initialize } from "./lib/init";
import { Logger } from "./lib/logger"; import { Logger } from "./lib/logger";
import { checkPageUrl } from "./lib/noCodeEvents"; 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"; export type { EnvironmentId, KeyValueData, PersonId, ResponseId, SurveyId } from "@formbricks/api";
@@ -21,7 +21,7 @@ const init = async (initConfig: InitConfig) => {
await queue.wait(); await queue.wait();
}; };
const setUserId = async (userId: string): Promise<void> => { const setUserId = async (userId: string | number): Promise<void> => {
queue.add(true, setPersonUserId, userId); queue.add(true, setPersonUserId, userId);
await queue.wait(); await queue.wait();
}; };
@@ -31,12 +31,17 @@ const setEmail = async (email: string): Promise<void> => {
await queue.wait(); 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); queue.add(true, setPersonAttribute, key, value);
await queue.wait(); await queue.wait();
}; };
const logout = async (): Promise<void> => { const logout = async (): Promise<void> => {
queue.add(true, logoutPerson);
await queue.wait();
};
const reset = async (): Promise<void> => {
queue.add(true, resetPerson); queue.add(true, resetPerson);
await queue.wait(); await queue.wait();
}; };
@@ -58,6 +63,7 @@ const formbricks = {
setAttribute, setAttribute,
track, track,
logout, logout,
reset,
registerRouteChange, registerRouteChange,
getApi, getApi,
getPerson, getPerson,

View File

@@ -3,7 +3,7 @@ import { checkInitialized } from "./init";
export class CommandQueue { export class CommandQueue {
private queue: { 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; checkInitialized: boolean;
commandArgs: any[]; commandArgs: any[];
}[] = []; }[] = [];
@@ -13,7 +13,7 @@ export class CommandQueue {
public add<A>( public add<A>(
checkInitialized: boolean = true, 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[] ...args: A[]
) { ) {
this.queue.push({ command, checkInitialized, commandArgs: args }); this.queue.push({ command, checkInitialized, commandArgs: args });

View File

@@ -118,11 +118,11 @@ export const hasAttributeKey = (key: string): boolean => {
}; };
export const setPersonUserId = async ( export const setPersonUserId = async (
userId: string userId: string | number
): Promise<Result<void, NetworkError | MissingPersonError | AttributeAlreadyExistsError>> => { ): Promise<Result<void, NetworkError | MissingPersonError | AttributeAlreadyExistsError>> => {
logger.debug("setting userId: " + userId); logger.debug("setting userId: " + userId);
// check if attribute already exists with this value // 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."); logger.debug("userId already set to this value. Skipping update.");
return okVoid(); 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", 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); if (result.ok !== true) return err(result.error);
@@ -145,16 +145,16 @@ export const setPersonUserId = async (
export const setPersonAttribute = async ( export const setPersonAttribute = async (
key: string, key: string,
value: string value: any
): Promise<Result<void, NetworkError | MissingPersonError>> => { ): 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 // check if attribute already exists with this value
if (hasAttributeValue(key, value)) { if (hasAttributeValue(key, value.toString())) {
logger.debug("attribute already set to this value. Skipping update."); logger.debug("Attribute already set to this value. Skipping update.");
return okVoid(); return okVoid();
} }
const result = await updatePersonAttribute(key, value); const result = await updatePersonAttribute(key, value.toString());
let error: NetworkError | MissingPersonError; let error: NetworkError | MissingPersonError;
@@ -176,9 +176,14 @@ export const setPersonAttribute = async (
return okVoid(); return okVoid();
}; };
export const logoutPerson = async (): Promise<void> => {
logger.debug("Resetting state");
config.update({ state: undefined });
};
export const resetPerson = async (): Promise<Result<void, NetworkError>> => { export const resetPerson = async (): Promise<Result<void, NetworkError>> => {
logger.debug("Resetting state & getting new state from backend"); logger.debug("Resetting state & getting new state from backend");
config.update({ state: undefined }); await logoutPerson();
try { try {
await sync(); await sync();
return okVoid(); return okVoid();

View File

@@ -37,7 +37,7 @@ export const HalfCircle: React.FC<HalfCircleProps> = ({ value }: { value: number
</div> </div>
<div className="flex justify-between text-sm leading-10 text-slate-600"> <div className="flex justify-between text-sm leading-10 text-slate-600">
<p>-100</p> <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> <p>100</p>
</div> </div>
</div> </div>