mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-06 11:20:56 -05:00
Merge branch 'main' of https://github.com/formbricks/formbricks into feature/delete-team
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
name: Cron - weeklySummary
|
||||
name: Cron - closeOnDate
|
||||
|
||||
on:
|
||||
# "Scheduled workflows run on the latest commit on the default or base branch."
|
||||
@@ -10,14 +10,14 @@ jobs:
|
||||
cron-weeklySummary:
|
||||
env:
|
||||
APP_URL: ${{ secrets.APP_URL }}
|
||||
CRON_API_KEY: ${{ secrets.CRON_SECRET }}
|
||||
CRON_SECRET: ${{ secrets.CRON_SECRET }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: cURL request
|
||||
if: ${{ env.APP_URL && env.CRON_SECRET }}
|
||||
run: |
|
||||
curl ${{ secrets.APP_URL }}/api/cron/close_surveys \
|
||||
curl ${{ env.APP_URL }}/api/cron/close_surveys \
|
||||
-X POST \
|
||||
-H 'content-type: application/json' \
|
||||
-H 'authorization: ${{ secrets.CRON_SECRET }}' \
|
||||
-H 'x-api-key: ${{ env.CRON_SECRET }}' \
|
||||
--fail
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ COPY .env.docker /app/apps/web/.env
|
||||
RUN pnpm install
|
||||
|
||||
# Build the project
|
||||
RUN pnpm prebuild --filter=web...
|
||||
RUN pnpm post-install --filter=web...
|
||||
RUN pnpm turbo run build --filter=web...
|
||||
|
||||
FROM node:18-alpine AS runner
|
||||
|
||||
+106
@@ -18,6 +18,7 @@ import { useEffect, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { getPlacementStyle } from "@/lib/preview";
|
||||
import { PlacementType } from "@formbricks/types/js";
|
||||
import { DEFAULT_BRAND_COLOR } from "@formbricks/lib/constants";
|
||||
|
||||
export function EditBrandColor({ environmentId }) {
|
||||
const { product, isLoadingProduct, isErrorProduct } = useProduct(environmentId);
|
||||
@@ -180,6 +181,111 @@ export function EditPlacement({ environmentId }) {
|
||||
);
|
||||
}
|
||||
|
||||
export const EditHighlightBorder: React.FC<{ environmentId: string }> = ({ environmentId }) => {
|
||||
const { product, isLoadingProduct, isErrorProduct, mutateProduct } = useProduct(environmentId);
|
||||
const { triggerProductMutate, isMutatingProduct } = useProductMutation(environmentId);
|
||||
|
||||
const [showHighlightBorder, setShowHighlightBorder] = useState(false);
|
||||
const [color, setColor] = useState<string | null>(DEFAULT_BRAND_COLOR);
|
||||
|
||||
// Sync product state with local state
|
||||
// not a good pattern, we should find a better way to do this
|
||||
useEffect(() => {
|
||||
if (product) {
|
||||
setShowHighlightBorder(product.highlightBorderColor ? true : false);
|
||||
setColor(product.highlightBorderColor);
|
||||
}
|
||||
}, [product]);
|
||||
|
||||
const handleSave = () => {
|
||||
triggerProductMutate(
|
||||
{ highlightBorderColor: color },
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success("Settings updated successfully.");
|
||||
// refetch product to update data
|
||||
mutateProduct();
|
||||
},
|
||||
onError: () => {
|
||||
toast.error("Something went wrong!");
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const handleSwitch = (checked: boolean) => {
|
||||
if (checked) {
|
||||
if (!color) {
|
||||
setColor(DEFAULT_BRAND_COLOR);
|
||||
setShowHighlightBorder(true);
|
||||
} else {
|
||||
setShowHighlightBorder(true);
|
||||
}
|
||||
} else {
|
||||
setShowHighlightBorder(false);
|
||||
setColor(null);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoadingProduct) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
|
||||
if (isErrorProduct) {
|
||||
return <div>Error</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex min-h-full w-full">
|
||||
<div className="flex w-1/2 flex-col px-6 py-5">
|
||||
<div className="mb-6 flex items-center space-x-2">
|
||||
<Switch id="highlightBorder" checked={showHighlightBorder} onCheckedChange={handleSwitch} />
|
||||
<h2 className="text-sm font-medium text-slate-800">Show highlight border</h2>
|
||||
</div>
|
||||
|
||||
{showHighlightBorder && color ? (
|
||||
<>
|
||||
<Label htmlFor="brandcolor">Color (HEX)</Label>
|
||||
<ColorPicker color={color} onChange={setColor} />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
variant="darkCTA"
|
||||
className="mt-4 flex max-w-[80px] items-center justify-center"
|
||||
loading={isMutatingProduct}
|
||||
onClick={() => {
|
||||
handleSave();
|
||||
}}>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex w-1/2 flex-col items-center justify-center gap-4 bg-slate-200 px-6 py-5">
|
||||
<h3 className="text-slate-500">Preview</h3>
|
||||
<div
|
||||
className={cn("flex flex-col gap-4 rounded-lg border-2 bg-white p-5")}
|
||||
{...(showHighlightBorder &&
|
||||
color && {
|
||||
style: {
|
||||
borderColor: color,
|
||||
},
|
||||
})}>
|
||||
<h3 className="text-sm font-semibold text-slate-800">How easy was it for you to do this?</h3>
|
||||
<div className="flex rounded-2xl border border-slate-400">
|
||||
{[1, 2, 3, 4, 5].map((num) => (
|
||||
<div className="border-r border-slate-400 px-6 py-5 last:border-r-0">
|
||||
<span className="text-sm font-medium">{num}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export function EditFormbricksSignature({ environmentId }) {
|
||||
const { isLoadingEnvironment, isErrorEnvironment } = useEnvironment(environmentId);
|
||||
const { product, isLoadingProduct, isErrorProduct } = useProduct(environmentId);
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import SettingsCard from "../SettingsCard";
|
||||
import SettingsTitle from "../SettingsTitle";
|
||||
import { EditBrandColor, EditPlacement, EditFormbricksSignature } from "./editLookAndFeel";
|
||||
import {
|
||||
EditBrandColor,
|
||||
EditPlacement,
|
||||
EditFormbricksSignature,
|
||||
EditHighlightBorder,
|
||||
} from "./editLookAndFeel";
|
||||
|
||||
export default function ProfileSettingsPage({ params }: { params: { environmentId: string } }) {
|
||||
return (
|
||||
@@ -14,6 +19,12 @@ export default function ProfileSettingsPage({ params }: { params: { environmentI
|
||||
description="Change where surveys will be shown in your web app.">
|
||||
<EditPlacement environmentId={params.environmentId} />
|
||||
</SettingsCard>
|
||||
<SettingsCard
|
||||
noPadding
|
||||
title="Highlight Border"
|
||||
description="Make sure your users notice the survey you display">
|
||||
<EditHighlightBorder environmentId={params.environmentId} />
|
||||
</SettingsCard>
|
||||
<SettingsCard
|
||||
title="Formbricks Signature"
|
||||
description="We love your support but understand if you toggle it off.">
|
||||
|
||||
@@ -5,18 +5,27 @@ import { useTeamMutation } from "@/lib/teams/mutateTeams";
|
||||
import { useTeam } from "@/lib/teams/teams";
|
||||
import { Button, ErrorComponent, Input, Label } from "@formbricks/ui";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useForm, useWatch } from "react-hook-form";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
export default function EditTeamName({ environmentId }) {
|
||||
const { team, isLoadingTeam, isErrorTeam, mutateTeam } = useTeam(environmentId);
|
||||
const { register, handleSubmit } = useForm();
|
||||
const { register, control, handleSubmit, setValue } = useForm();
|
||||
const [teamId, setTeamId] = useState("");
|
||||
|
||||
const teamName = useWatch({
|
||||
control,
|
||||
name: "name",
|
||||
});
|
||||
const isTeamNameInputEmpty = !teamName?.trim();
|
||||
const currentTeamName = teamName?.trim().toLowerCase() ?? "";
|
||||
const previousTeamName = team?.name?.trim().toLowerCase() ?? "";
|
||||
|
||||
useEffect(() => {
|
||||
if (team && team.id !== "") {
|
||||
setTeamId(team.id);
|
||||
}
|
||||
setValue("name", team?.name ?? "");
|
||||
}, [team]);
|
||||
|
||||
const { isMutatingTeam, triggerTeamMutate } = useTeamMutation(teamId);
|
||||
@@ -42,9 +51,20 @@ export default function EditTeamName({ environmentId }) {
|
||||
});
|
||||
})}>
|
||||
<Label htmlFor="teamname">Team Name</Label>
|
||||
<Input type="text" id="teamname" defaultValue={team.name} {...register("name")} />
|
||||
<Input
|
||||
type="text"
|
||||
id="teamname"
|
||||
defaultValue={team?.name ?? ""}
|
||||
{...register("name")}
|
||||
className={isTeamNameInputEmpty ? "border-red-300 focus:border-red-300" : ""}
|
||||
/>
|
||||
|
||||
<Button type="submit" className="mt-4" variant="darkCTA" loading={isMutatingTeam}>
|
||||
<Button
|
||||
type="submit"
|
||||
className="mt-4"
|
||||
variant="darkCTA"
|
||||
loading={isMutatingTeam}
|
||||
disabled={isTeamNameInputEmpty || currentTeamName === previousTeamName}>
|
||||
Update
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm, useWatch } from "react-hook-form";
|
||||
import toast from "react-hot-toast";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
@@ -22,7 +22,19 @@ export function EditProductName({ environmentId }) {
|
||||
const { isMutatingProduct, triggerProductMutate } = useProductMutation(environmentId);
|
||||
const { mutateEnvironment } = useEnvironment(environmentId);
|
||||
|
||||
const { register, handleSubmit } = useForm();
|
||||
const { register, handleSubmit, control, setValue } = useForm();
|
||||
|
||||
const productName = useWatch({
|
||||
control,
|
||||
name: "name",
|
||||
});
|
||||
const isProductNameInputEmpty = !productName?.trim();
|
||||
const currentProductName = productName?.trim().toLowerCase() ?? "";
|
||||
const previousProductName = product?.name?.trim().toLowerCase() ?? "";
|
||||
|
||||
useEffect(() => {
|
||||
setValue("name", product?.name ?? "");
|
||||
}, [product?.name]);
|
||||
|
||||
if (isLoadingProduct) {
|
||||
return <LoadingSpinner />;
|
||||
@@ -45,9 +57,20 @@ export function EditProductName({ environmentId }) {
|
||||
});
|
||||
})}>
|
||||
<Label htmlFor="fullname">What's your product called?</Label>
|
||||
<Input type="text" id="fullname" defaultValue={product.name} {...register("name")} />
|
||||
<Input
|
||||
type="text"
|
||||
id="fullname"
|
||||
defaultValue={product.name}
|
||||
{...register("name")}
|
||||
className={isProductNameInputEmpty ? "border-red-300 focus:border-red-300" : ""}
|
||||
/>
|
||||
|
||||
<Button type="submit" variant="darkCTA" className="mt-4" loading={isMutatingProduct}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="darkCTA"
|
||||
className="mt-4"
|
||||
loading={isMutatingProduct}
|
||||
disabled={isProductNameInputEmpty || currentProductName === previousProductName}>
|
||||
Update
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
@@ -11,16 +11,28 @@ import { Button, ErrorComponent, Input, Label, ProfileAvatar } from "@formbricks
|
||||
import { Session } from "next-auth";
|
||||
import { signOut } from "next-auth/react";
|
||||
import Image from "next/image";
|
||||
import { Dispatch, SetStateAction, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Dispatch, SetStateAction, useEffect, useState } from "react";
|
||||
import { useForm, useWatch } from "react-hook-form";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
export function EditName() {
|
||||
const { register, handleSubmit } = useForm();
|
||||
const { register, handleSubmit, control, setValue } = useForm();
|
||||
const { profile, isLoadingProfile, isErrorProfile } = useProfile();
|
||||
|
||||
const { triggerProfileMutate, isMutatingProfile } = useProfileMutation();
|
||||
|
||||
const profileName = useWatch({
|
||||
control,
|
||||
name: "name",
|
||||
});
|
||||
const isProfileNameInputEmpty = !profileName?.trim();
|
||||
const currentProfileName = profileName?.trim().toLowerCase() ?? "";
|
||||
const previousProfileName = profile?.name?.trim().toLowerCase() ?? "";
|
||||
|
||||
useEffect(() => {
|
||||
setValue("name", profile?.name ?? "");
|
||||
}, [profile?.name]);
|
||||
|
||||
if (isLoadingProfile) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
@@ -41,13 +53,24 @@ export function EditName() {
|
||||
});
|
||||
})}>
|
||||
<Label htmlFor="fullname">Full Name</Label>
|
||||
<Input type="text" id="fullname" defaultValue={profile.name} {...register("name")} />
|
||||
<Input
|
||||
type="text"
|
||||
id="fullname"
|
||||
defaultValue={profile.name}
|
||||
{...register("name")}
|
||||
className={isProfileNameInputEmpty ? "border-red-300 focus:border-red-300" : ""}
|
||||
/>
|
||||
|
||||
<div className="mt-4">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input type="email" id="fullname" defaultValue={profile.email} disabled />
|
||||
</div>
|
||||
<Button type="submit" variant="darkCTA" className="mt-4" loading={isMutatingProfile}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="darkCTA"
|
||||
className="mt-4"
|
||||
loading={isMutatingProfile}
|
||||
disabled={isProfileNameInputEmpty || currentProfileName === previousProfileName}>
|
||||
Update
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
@@ -275,7 +275,10 @@ export default function PreviewSurvey({
|
||||
</div>
|
||||
|
||||
{previewType === "modal" ? (
|
||||
<Modal isOpen={isModalOpen} placement={product.placement}>
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
placement={product.placement}
|
||||
highlightBorderColor={product.highlightBorderColor}>
|
||||
{!countdownStop && autoClose !== null && autoClose > 0 && (
|
||||
<Progress progress={countdownProgress} brandColor={brandColor} />
|
||||
)}
|
||||
|
||||
+27
-7
@@ -5,6 +5,7 @@ import { DatePicker, Input, Label, Switch } from "@formbricks/ui";
|
||||
import { CheckCircleIcon } from "@heroicons/react/24/solid";
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
import { useEffect, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
interface ResponseOptionsCardProps {
|
||||
localSurvey: Survey;
|
||||
@@ -117,13 +118,27 @@ export default function ResponseOptionsCard({ localSurvey, setLocalSurvey }: Res
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputResponse = (e: any) => {
|
||||
let value = parseInt(e.target.value);
|
||||
if (value < 1) value = 1;
|
||||
const updatedSurvey: Survey = { ...localSurvey, autoComplete: value };
|
||||
const handleInputResponse = (e) => {
|
||||
const updatedSurvey: Survey = { ...localSurvey, autoComplete: parseInt(e.target.value) };
|
||||
setLocalSurvey(updatedSurvey);
|
||||
};
|
||||
|
||||
const handleInputResponseBlur = (e) => {
|
||||
if (parseInt(e.target.value) === 0) {
|
||||
toast.error("Response limit can't be set to 0");
|
||||
return;
|
||||
}
|
||||
|
||||
const inputResponses = localSurvey?._count?.responses || 0;
|
||||
|
||||
if (parseInt(e.target.value) <= inputResponses) {
|
||||
toast.error(
|
||||
`Response limit needs to exceed number of received responses (${localSurvey?._count?.responses}).`
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Collapsible.Root
|
||||
open={open}
|
||||
@@ -169,11 +184,16 @@ export default function ResponseOptionsCard({ localSurvey, setLocalSurvey }: Res
|
||||
<Input
|
||||
autoFocus
|
||||
type="number"
|
||||
min="1"
|
||||
min={
|
||||
localSurvey?._count?.responses
|
||||
? (localSurvey?._count?.responses + 1).toString()
|
||||
: "1"
|
||||
}
|
||||
id="autoCompleteResponses"
|
||||
value={localSurvey.autoComplete?.toString()}
|
||||
onChange={(e) => handleInputResponse(e)}
|
||||
className="ml-2 mr-2 inline w-16 bg-white text-center text-sm"
|
||||
onChange={handleInputResponse}
|
||||
onBlur={handleInputResponseBlur}
|
||||
className="ml-2 mr-2 inline w-20 bg-white text-center text-sm"
|
||||
/>
|
||||
completed responses.
|
||||
</p>
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ export default function SurveyEditor({ environmentId, surveyId }: SurveyEditorPr
|
||||
const [activeQuestionId, setActiveQuestionId] = useState<string | null>(null);
|
||||
const [localSurvey, setLocalSurvey] = useState<Survey | null>();
|
||||
const [invalidQuestions, setInvalidQuestions] = useState<String[] | null>(null);
|
||||
const { survey, isLoadingSurvey, isErrorSurvey } = useSurvey(environmentId, surveyId);
|
||||
const { survey, isLoadingSurvey, isErrorSurvey } = useSurvey(environmentId, surveyId, true);
|
||||
const { product, isLoadingProduct, isErrorProduct } = useProduct(environmentId);
|
||||
const { environment, isLoadingEnvironment, isErrorEnvironment } = useEnvironment(environmentId);
|
||||
|
||||
|
||||
+11
@@ -106,6 +106,17 @@ export default function SurveyMenuBar({
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
Check whether the count for autocomplete responses is not less
|
||||
than the current count of accepted response and also it is not set to 0
|
||||
*/
|
||||
if (
|
||||
(survey.autoComplete && survey._count?.responses && survey._count.responses >= survey.autoComplete) ||
|
||||
survey?.autoComplete === 0
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
+18
-2
@@ -9,6 +9,7 @@ import PreviewSurvey from "../PreviewSurvey";
|
||||
import TemplateList from "./TemplateList";
|
||||
import type { TProduct } from "@formbricks/types/v1/product";
|
||||
import type { TEnvironment } from "@formbricks/types/v1/environment";
|
||||
import { SearchBox } from "@formbricks/ui";
|
||||
|
||||
type TemplateContainerWithPreviewProps = {
|
||||
environmentId: string;
|
||||
@@ -23,7 +24,7 @@ export default function TemplateContainerWithPreview({
|
||||
}: TemplateContainerWithPreviewProps) {
|
||||
const [activeTemplate, setActiveTemplate] = useState<Template | null>(null);
|
||||
const [activeQuestionId, setActiveQuestionId] = useState<string | null>(null);
|
||||
|
||||
const [templateSearch, setTemplateSearch] = useState<string | null>(null);
|
||||
useEffect(() => {
|
||||
if (product && templates?.length) {
|
||||
const newTemplate = replacePresetPlaceholders(templates[0], product);
|
||||
@@ -36,11 +37,26 @@ export default function TemplateContainerWithPreview({
|
||||
<div className="flex h-full flex-col ">
|
||||
<div className="relative z-0 flex flex-1 overflow-hidden">
|
||||
<div className="flex-1 flex-col overflow-auto bg-slate-50">
|
||||
<h1 className="ml-6 mt-6 text-2xl font-bold text-slate-800">Create a new survey</h1>
|
||||
<div className="flex flex-col items-center justify-between md:flex-row md:items-start">
|
||||
<h1 className="ml-6 mt-6 text-2xl font-bold text-slate-800">Create a new survey</h1>
|
||||
<div className="ml-6 mt-6 px-6">
|
||||
<SearchBox
|
||||
autoFocus
|
||||
value={templateSearch ?? ""}
|
||||
onChange={(e) => setTemplateSearch(e.target.value)}
|
||||
placeholder={"Search..."}
|
||||
className="block rounded-md border border-slate-100 bg-white shadow-sm focus:border-slate-500 focus:outline-none focus:ring-0 sm:text-sm md:w-auto "
|
||||
type="search"
|
||||
name="search"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TemplateList
|
||||
environmentId={environmentId}
|
||||
environment={environment}
|
||||
product={product}
|
||||
templateSearch={templateSearch ?? ""}
|
||||
onTemplateClick={(template) => {
|
||||
setActiveQuestionId(template.preset.questions[0].id);
|
||||
setActiveTemplate(template);
|
||||
|
||||
+89
-70
@@ -28,11 +28,18 @@ type TemplateList = {
|
||||
onTemplateClick: (template: Template) => void;
|
||||
environment: TEnvironment;
|
||||
product: TProduct;
|
||||
templateSearch?: string;
|
||||
};
|
||||
|
||||
const ALL_CATEGORY_NAME = "All";
|
||||
const RECOMMENDED_CATEGORY_NAME = "For you";
|
||||
export default function TemplateList({ environmentId, onTemplateClick, product, environment }: TemplateList) {
|
||||
export default function TemplateList({
|
||||
environmentId,
|
||||
onTemplateClick,
|
||||
product,
|
||||
environment,
|
||||
templateSearch,
|
||||
}: TemplateList) {
|
||||
const router = useRouter();
|
||||
const [activeTemplate, setActiveTemplate] = useState<Template | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -49,15 +56,18 @@ export default function TemplateList({ environmentId, onTemplateClick, product,
|
||||
|
||||
const fullCategories =
|
||||
!!profile?.objective && profile.objective !== "other"
|
||||
? [RECOMMENDED_CATEGORY_NAME, ...defaultCategories]
|
||||
? [RECOMMENDED_CATEGORY_NAME, ALL_CATEGORY_NAME, ...defaultCategories]
|
||||
: [ALL_CATEGORY_NAME, ...defaultCategories];
|
||||
|
||||
setCategories(fullCategories);
|
||||
|
||||
const activeFilter =
|
||||
!!profile?.objective && profile.objective !== "other" ? RECOMMENDED_CATEGORY_NAME : ALL_CATEGORY_NAME;
|
||||
const activeFilter = templateSearch
|
||||
? ALL_CATEGORY_NAME
|
||||
: !!profile?.objective && profile.objective !== "other"
|
||||
? RECOMMENDED_CATEGORY_NAME
|
||||
: ALL_CATEGORY_NAME;
|
||||
setSelectedFilter(activeFilter);
|
||||
}, [profile]);
|
||||
}, [profile, templateSearch]);
|
||||
|
||||
const addSurvey = async (activeTemplate) => {
|
||||
setLoading(true);
|
||||
@@ -72,19 +82,38 @@ export default function TemplateList({ environmentId, onTemplateClick, product,
|
||||
if (isLoadingProfile) return <LoadingSpinner />;
|
||||
if (isErrorProfile) return <ErrorComponent />;
|
||||
|
||||
const filteredTemplates = templates.filter((template) => {
|
||||
const matchesCategory =
|
||||
selectedFilter === ALL_CATEGORY_NAME ||
|
||||
template.category === selectedFilter ||
|
||||
(selectedFilter === RECOMMENDED_CATEGORY_NAME && template.objectives?.includes(profile.objective));
|
||||
|
||||
const templateName = template.name?.toLowerCase();
|
||||
const templateDescription = template.description?.toLowerCase();
|
||||
const searchQuery = templateSearch?.toLowerCase() ?? "";
|
||||
const searchWords = searchQuery.split(" ");
|
||||
|
||||
const matchesSearch = searchWords.every(
|
||||
(word) => templateName?.includes(word) || templateDescription?.includes(word)
|
||||
);
|
||||
|
||||
return matchesCategory && matchesSearch;
|
||||
});
|
||||
|
||||
return (
|
||||
<main className="relative z-0 flex-1 overflow-y-auto px-6 pb-6 pt-3 focus:outline-none">
|
||||
<div className="mb-6 flex flex-wrap gap-2">
|
||||
<div className="mb-6 flex flex-wrap gap-1">
|
||||
{categories.map((category) => (
|
||||
<button
|
||||
key={category}
|
||||
type="button"
|
||||
onClick={() => setSelectedFilter(category)}
|
||||
disabled={templateSearch && templateSearch.length > 0 ? true : false}
|
||||
className={cn(
|
||||
selectedFilter === category
|
||||
? " bg-slate-800 font-semibold text-white"
|
||||
: " bg-white text-slate-700 hover:bg-slate-100",
|
||||
"mt-2 rounded border border-slate-800 px-2 py-1 text-sm transition-all duration-150 "
|
||||
"mt-2 rounded border border-slate-800 px-2 py-1 text-xs transition-all duration-150 "
|
||||
)}>
|
||||
{category}
|
||||
{category === RECOMMENDED_CATEGORY_NAME && <SparklesIcon className="ml-1 inline h-5 w-5" />}
|
||||
@@ -121,72 +150,62 @@ export default function TemplateList({ environmentId, onTemplateClick, product,
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
{templates
|
||||
.filter(
|
||||
(template) =>
|
||||
selectedFilter === ALL_CATEGORY_NAME ||
|
||||
template.category === selectedFilter ||
|
||||
(selectedFilter === RECOMMENDED_CATEGORY_NAME &&
|
||||
template.objectives?.includes(profile.objective))
|
||||
)
|
||||
.map((template: Template) => (
|
||||
<div
|
||||
onClick={() => {
|
||||
const newTemplate = replacePresetPlaceholders(template, product);
|
||||
onTemplateClick(newTemplate);
|
||||
setActiveTemplate(newTemplate);
|
||||
}}
|
||||
key={template.name}
|
||||
className={cn(
|
||||
activeTemplate?.name === template.name && "ring-2 ring-slate-400",
|
||||
"duration-120 group relative cursor-pointer rounded-lg bg-white p-6 shadow transition-all duration-150 hover:scale-105"
|
||||
)}>
|
||||
<div className="flex">
|
||||
<div
|
||||
className={`rounded border px-1.5 py-0.5 text-xs ${
|
||||
template.category === "Product Experience"
|
||||
? "border-blue-300 bg-blue-50 text-blue-500"
|
||||
: template.category === "Exploration"
|
||||
? "border-pink-300 bg-pink-50 text-pink-500"
|
||||
: template.category === "Growth"
|
||||
? "border-orange-300 bg-orange-50 text-orange-500"
|
||||
: template.category === "Increase Revenue"
|
||||
? "border-emerald-300 bg-emerald-50 text-emerald-500"
|
||||
: template.category === "Customer Success"
|
||||
? "border-violet-300 bg-violet-50 text-violet-500"
|
||||
: "border-slate-300 bg-slate-50 text-slate-500" // default color
|
||||
}`}>
|
||||
{template.category}
|
||||
</div>
|
||||
{template.preset.questions.some(
|
||||
(question) => question.logic && question.logic.length > 0
|
||||
) && (
|
||||
<TooltipProvider delayDuration={80}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<div>
|
||||
<SplitIcon className="ml-1.5 h-5 w-5 rounded border border-slate-300 bg-slate-50 p-0.5 text-slate-400" />
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>This survey uses branching logic.</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
{filteredTemplates.map((template: Template) => (
|
||||
<div
|
||||
onClick={() => {
|
||||
const newTemplate = replacePresetPlaceholders(template, product);
|
||||
onTemplateClick(newTemplate);
|
||||
setActiveTemplate(newTemplate);
|
||||
}}
|
||||
key={template.name}
|
||||
className={cn(
|
||||
activeTemplate?.name === template.name && "ring-2 ring-slate-400",
|
||||
"duration-120 group relative cursor-pointer rounded-lg bg-white p-6 shadow transition-all duration-150 hover:scale-105"
|
||||
)}>
|
||||
<div className="flex">
|
||||
<div
|
||||
className={`rounded border px-1.5 py-0.5 text-xs ${
|
||||
template.category === "Product Experience"
|
||||
? "border-blue-300 bg-blue-50 text-blue-500"
|
||||
: template.category === "Exploration"
|
||||
? "border-pink-300 bg-pink-50 text-pink-500"
|
||||
: template.category === "Growth"
|
||||
? "border-orange-300 bg-orange-50 text-orange-500"
|
||||
: template.category === "Increase Revenue"
|
||||
? "border-emerald-300 bg-emerald-50 text-emerald-500"
|
||||
: template.category === "Customer Success"
|
||||
? "border-violet-300 bg-violet-50 text-violet-500"
|
||||
: "border-slate-300 bg-slate-50 text-slate-500" // default color
|
||||
}`}>
|
||||
{template.category}
|
||||
</div>
|
||||
<h3 className="text-md mb-1 mt-3 text-left font-bold text-slate-700">{template.name}</h3>
|
||||
<p className="text-left text-xs text-slate-600">{template.description}</p>
|
||||
{activeTemplate?.name === template.name && (
|
||||
<Button
|
||||
variant="darkCTA"
|
||||
className="mt-6 px-6 py-3"
|
||||
disabled={activeTemplate === null}
|
||||
loading={loading}
|
||||
onClick={() => addSurvey(activeTemplate)}>
|
||||
Use this template
|
||||
</Button>
|
||||
{template.preset.questions.some((question) => question.logic && question.logic.length > 0) && (
|
||||
<TooltipProvider delayDuration={80}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<div>
|
||||
<SplitIcon className="ml-1.5 h-5 w-5 rounded border border-slate-300 bg-slate-50 p-0.5 text-slate-400" />
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>This survey uses branching logic.</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<h3 className="text-md mb-1 mt-3 text-left font-bold text-slate-700">{template.name}</h3>
|
||||
<p className="text-left text-xs text-slate-600">{template.description}</p>
|
||||
{activeTemplate?.name === template.name && (
|
||||
<Button
|
||||
variant="darkCTA"
|
||||
className="mt-6 px-6 py-3"
|
||||
disabled={activeTemplate === null}
|
||||
loading={loading}
|
||||
onClick={() => addSurvey(activeTemplate)}>
|
||||
Use this template
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -49,3 +49,16 @@ input:focus {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide the clear button for input type "search" */
|
||||
input[type="search"]::-webkit-search-cancel-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[type="search"]::-ms-clear {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[type="search"]::-ms-reveal {
|
||||
display: none;
|
||||
}
|
||||
@@ -1,19 +1,30 @@
|
||||
import { getPlacementStyle } from "@/lib/preview";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { PlacementType } from "@formbricks/types/js";
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
import { ReactNode, useEffect, useMemo, useState } from "react";
|
||||
|
||||
export default function Modal({
|
||||
children,
|
||||
isOpen,
|
||||
placement,
|
||||
highlightBorderColor,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
isOpen: boolean;
|
||||
placement: PlacementType;
|
||||
highlightBorderColor: string | null | undefined;
|
||||
}) {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
const highlightBorderColorStyle = useMemo(() => {
|
||||
if (!highlightBorderColor) return {};
|
||||
|
||||
return {
|
||||
border: `2px solid ${highlightBorderColor}`,
|
||||
overflow: "hidden",
|
||||
};
|
||||
}, [highlightBorderColor]);
|
||||
|
||||
useEffect(() => {
|
||||
setShow(isOpen);
|
||||
}, [isOpen]);
|
||||
@@ -23,9 +34,10 @@ export default function Modal({
|
||||
<div
|
||||
className={cn(
|
||||
show ? "translate-x-0 opacity-100" : "translate-x-32 opacity-0",
|
||||
"pointer-events-auto absolute max-h-[90%] w-full max-w-sm overflow-hidden overflow-y-auto rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5 transition-all duration-500 ease-in-out",
|
||||
"pointer-events-auto absolute h-fit max-h-[90%] w-full max-w-sm overflow-hidden overflow-y-auto rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5 transition-all duration-500 ease-in-out",
|
||||
getPlacementStyle(placement)
|
||||
)}>
|
||||
)}
|
||||
style={highlightBorderColorStyle}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -39,19 +39,21 @@ export default function MultipleChoiceMultiQuestion({
|
||||
.map((choice) => choice.label);
|
||||
|
||||
useEffect(() => {
|
||||
if(Array.isArray(storedResponseValue)){
|
||||
if (Array.isArray(storedResponseValue)) {
|
||||
const nonOtherSavedChoices = storedResponseValue?.filter((answer) =>
|
||||
nonOtherChoiceLabels.includes(answer)
|
||||
);
|
||||
const savedOtherSpecified = storedResponseValue?.find((answer) => !nonOtherChoiceLabels.includes(answer));
|
||||
nonOtherChoiceLabels.includes(answer)
|
||||
);
|
||||
const savedOtherSpecified = storedResponseValue?.find(
|
||||
(answer) => !nonOtherChoiceLabels.includes(answer)
|
||||
);
|
||||
|
||||
setSelectedChoices(nonOtherSavedChoices ?? []);
|
||||
setSelectedChoices(nonOtherSavedChoices ?? []);
|
||||
|
||||
if (savedOtherSpecified) {
|
||||
setOtherSpecified(savedOtherSpecified);
|
||||
setShowOther(true);
|
||||
if (savedOtherSpecified) {
|
||||
setOtherSpecified(savedOtherSpecified);
|
||||
setShowOther(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [storedResponseValue, question.id]);
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export default function AlertDialog({
|
||||
return (
|
||||
<Modal open={open} setOpen={setOpen} title={`Confirm ${confirmWhat}`}>
|
||||
<p>{text || "Are you sure? This action cannot be undone."}</p>
|
||||
<div className="my-4 space-x-2 text-right">
|
||||
<div className="space-x-2 text-right">
|
||||
<Button variant="warn" onClick={onDiscard}>
|
||||
Discard
|
||||
</Button>
|
||||
|
||||
@@ -32,7 +32,7 @@ export default function DeleteDialog({
|
||||
<Modal open={open} setOpen={setOpen} title={`Delete ${deleteWhat}`}>
|
||||
<p>{text || "Are you sure? This action cannot be undone."}</p>
|
||||
<div>{children}</div>
|
||||
<div className="my-4 space-x-2 text-right">
|
||||
<div className="space-x-2 text-right">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
|
||||
@@ -27,9 +27,9 @@ export const useSurveys = (environmentId: string) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useSurvey = (environmentId: string, id: string) => {
|
||||
export const useSurvey = (environmentId: string, id: string, analytics?: boolean) => {
|
||||
const { data, error, mutate, isLoading } = useSWR(
|
||||
`/api/v1/environments/${environmentId}/surveys/${id}`,
|
||||
`/api/v1/environments/${environmentId}/surveys/${id}${analytics ? "?analytics=true" : ""}`,
|
||||
fetcher
|
||||
);
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
|
||||
const surveyId = req.query.surveyId?.toString();
|
||||
|
||||
const analytics = req.query.analytics?.toString() === "true";
|
||||
|
||||
if (environmentId === undefined) {
|
||||
return res.status(400).json({ message: "Missing environmentId" });
|
||||
}
|
||||
@@ -31,6 +33,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
include: {
|
||||
triggers: true,
|
||||
attributeFilters: true,
|
||||
_count: analytics ? { select: { responses: { where: { finished: true } } } } : false,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -83,6 +86,8 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse)
|
||||
const body = { ...req.body };
|
||||
|
||||
delete body.updatedAt;
|
||||
// preventing issue with unknowingly updating analytics
|
||||
delete body._count;
|
||||
|
||||
// delete unused fields for link surveys
|
||||
if (body.type === "link") {
|
||||
|
||||
+2
-6
@@ -17,12 +17,8 @@ services:
|
||||
- postgres
|
||||
env_file:
|
||||
- .env
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.formbricks.rule=Host(`api.example.com`)" # TODO: Change with your own domain
|
||||
- "traefik.http.routers.formbricks.tls.certresolver=default"
|
||||
- "traefik.http.routers.formbricks.entrypoints=websecure"
|
||||
- "traefik.http.services.formbricks.loadbalancer.server.port=3000"
|
||||
ports:
|
||||
- 3000:3000
|
||||
|
||||
volumes:
|
||||
postgres:
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
"scripts": {
|
||||
"clean": "turbo run clean && rimraf node_modules",
|
||||
"build": "turbo run build",
|
||||
"prebuild": "turbo run prebuild",
|
||||
"post-install": "turbo run post-install",
|
||||
"db:migrate:dev": "turbo run db:migrate:dev",
|
||||
"db:migrate:deploy": "turbo run db:migrate:deploy",
|
||||
"db:migrate:vercel": "turbo run db:migrate:vercel",
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Product" ADD COLUMN "highlightBorderColor" TEXT;
|
||||
@@ -20,8 +20,8 @@
|
||||
"format": "prisma format",
|
||||
"generate": "prisma generate",
|
||||
"lint": "eslint ./src --fix",
|
||||
"prebuild": "npm run generate",
|
||||
"predev": "npm run generate"
|
||||
"post-install": "pnpm generate",
|
||||
"predev": "pnpm generate"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.0.0",
|
||||
|
||||
@@ -319,19 +319,20 @@ enum WidgetPlacement {
|
||||
}
|
||||
|
||||
model Product {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @updatedAt @map(name: "updated_at")
|
||||
name String
|
||||
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
||||
teamId String
|
||||
environments Environment[]
|
||||
brandColor String @default("#64748b")
|
||||
recontactDays Int @default(7)
|
||||
formbricksSignature Boolean @default(true)
|
||||
placement WidgetPlacement @default(bottomRight)
|
||||
clickOutsideClose Boolean @default(true)
|
||||
darkOverlay Boolean @default(false)
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now()) @map(name: "created_at")
|
||||
updatedAt DateTime @updatedAt @map(name: "updated_at")
|
||||
name String
|
||||
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
||||
teamId String
|
||||
environments Environment[]
|
||||
brandColor String @default("#64748b")
|
||||
highlightBorderColor String?
|
||||
recontactDays Int @default(7)
|
||||
formbricksSignature Boolean @default(true)
|
||||
placement WidgetPlacement @default(bottomRight)
|
||||
clickOutsideClose Boolean @default(true)
|
||||
darkOverlay Boolean @default(false)
|
||||
}
|
||||
|
||||
enum Plan {
|
||||
|
||||
@@ -32,6 +32,7 @@ export default function App({ config, survey, closeSurvey, errorHandler }: AppPr
|
||||
close={close}
|
||||
placement={config.state.product.placement}
|
||||
darkOverlay={config.state.product.darkOverlay}
|
||||
highlightBorderColor={config.state.product.highlightBorderColor}
|
||||
clickOutside={config.state.product.clickOutsideClose}>
|
||||
<SurveyView config={config} survey={survey} close={close} errorHandler={errorHandler} />
|
||||
</Modal>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { PlacementType } from "@formbricks/types/js";
|
||||
import { h, VNode } from "preact";
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { cn } from "../lib/utils";
|
||||
|
||||
export default function Modal({
|
||||
@@ -9,6 +9,7 @@ export default function Modal({
|
||||
placement,
|
||||
clickOutside,
|
||||
darkOverlay,
|
||||
highlightBorderColor,
|
||||
close,
|
||||
}: {
|
||||
children: VNode;
|
||||
@@ -16,6 +17,7 @@ export default function Modal({
|
||||
placement: PlacementType;
|
||||
clickOutside: boolean;
|
||||
darkOverlay: boolean;
|
||||
highlightBorderColor: string | null;
|
||||
close: () => void;
|
||||
}) {
|
||||
const [show, setShow] = useState(false);
|
||||
@@ -57,6 +59,17 @@ export default function Modal({
|
||||
}
|
||||
};
|
||||
|
||||
const highlightBorderColorStyle = useMemo(() => {
|
||||
if (!highlightBorderColor) return {};
|
||||
|
||||
return {
|
||||
borderRadius: "8px",
|
||||
border: "2px solid",
|
||||
overflow: "hidden",
|
||||
borderColor: highlightBorderColor,
|
||||
};
|
||||
}, [highlightBorderColor]);
|
||||
|
||||
return (
|
||||
<div
|
||||
aria-live="assertive"
|
||||
@@ -97,7 +110,7 @@ export default function Modal({
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className="">{children}</div>
|
||||
<div style={highlightBorderColorStyle}>{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,3 +18,4 @@ export const WEBAPP_URL =
|
||||
// Other
|
||||
export const INTERNAL_SECRET = process.env.INTERNAL_SECRET || "";
|
||||
export const CRON_SECRET = process.env.CRON_SECRET;
|
||||
export const DEFAULT_BRAND_COLOR = "#64748b";
|
||||
|
||||
@@ -8,6 +8,21 @@ import { ZProduct } from "@formbricks/types/v1/product";
|
||||
import type { TProduct } from "@formbricks/types/v1/product";
|
||||
import { cache } from "react";
|
||||
|
||||
const selectProduct = {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
name: true,
|
||||
teamId: true,
|
||||
brandColor: true,
|
||||
highlightBorderColor: true,
|
||||
recontactDays: true,
|
||||
formbricksSignature: true,
|
||||
placement: true,
|
||||
clickOutsideClose: true,
|
||||
darkOverlay: true,
|
||||
};
|
||||
|
||||
export const getProductByEnvironmentId = cache(async (environmentId: string): Promise<TProduct> => {
|
||||
let productPrisma;
|
||||
try {
|
||||
@@ -19,6 +34,7 @@ export const getProductByEnvironmentId = cache(async (environmentId: string): Pr
|
||||
},
|
||||
},
|
||||
},
|
||||
select: selectProduct,
|
||||
});
|
||||
|
||||
if (!productPrisma) {
|
||||
|
||||
@@ -33,6 +33,7 @@ export interface Survey {
|
||||
autoComplete: number | null;
|
||||
surveyClosedMessage: SurveyClosedMessage | null;
|
||||
closeOnDate: Date | null;
|
||||
_count: { responses: number | null } | null;
|
||||
}
|
||||
|
||||
export interface AttributeFilter {
|
||||
|
||||
@@ -7,6 +7,10 @@ export const ZProduct = z.object({
|
||||
name: z.string(),
|
||||
teamId: z.string(),
|
||||
brandColor: z.string().regex(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/),
|
||||
highlightBorderColor: z
|
||||
.string()
|
||||
.regex(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/)
|
||||
.nullish(),
|
||||
recontactDays: z.number().int(),
|
||||
formbricksSignature: z.boolean(),
|
||||
placement: z.enum(["bottomLeft", "bottomRight", "topLeft", "topRight", "center"]),
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import * as React from "react";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { Search } from "lucide-react";
|
||||
|
||||
export interface InputProps
|
||||
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "crossOrigin" | "dangerouslySetInnerHTML"> {
|
||||
crossOrigin?: "" | "anonymous" | "use-credentials" | undefined;
|
||||
dangerouslySetInnerHTML?: {
|
||||
__html: string;
|
||||
};
|
||||
}
|
||||
|
||||
const SearchBox = React.forwardRef<HTMLInputElement, InputProps>(({ className, ...props }, ref) => {
|
||||
return (
|
||||
<div className="relative">
|
||||
<input
|
||||
className={cn(
|
||||
"focus:border-brand flex h-10 w-full rounded-md border border-slate-300 bg-transparent px-5 pr-10 text-sm text-slate-800 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-500 dark:text-slate-300",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
|
||||
<Search className="h-5 w-5 text-gray-400" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export { SearchBox };
|
||||
@@ -59,6 +59,7 @@ export {
|
||||
} from "./components/Command";
|
||||
export { Calendar } from "./components/Calendar";
|
||||
export { DatePicker } from "./components/DatePicker";
|
||||
export { SearchBox } from "./components/SearchBox";
|
||||
|
||||
/* Icons */
|
||||
export { AngryBirdRageIcon } from "./components/icons/AngryBirdRageIcon";
|
||||
|
||||
Generated
+100
-45
@@ -19,7 +19,7 @@ importers:
|
||||
version: 3.12.7
|
||||
turbo:
|
||||
specifier: latest
|
||||
version: 1.10.3
|
||||
version: 1.10.7
|
||||
|
||||
apps/demo:
|
||||
dependencies:
|
||||
@@ -352,7 +352,7 @@ importers:
|
||||
version: 8.10.0(eslint@8.46.0)
|
||||
eslint-config-turbo:
|
||||
specifier: latest
|
||||
version: 1.10.7(eslint@8.46.0)
|
||||
version: 1.8.8(eslint@8.46.0)
|
||||
eslint-plugin-react:
|
||||
specifier: 7.33.1
|
||||
version: 7.33.1(eslint@8.46.0)
|
||||
@@ -1205,7 +1205,7 @@ packages:
|
||||
'@babel/core': ^7.0.0-0
|
||||
dependencies:
|
||||
'@babel/core': 7.22.9
|
||||
'@babel/helper-create-class-features-plugin': 7.22.6(@babel/core@7.22.9)
|
||||
'@babel/helper-create-class-features-plugin': 7.20.5(@babel/core@7.22.9)
|
||||
'@babel/helper-plugin-utils': 7.22.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -1962,7 +1962,7 @@ packages:
|
||||
'@babel/core': ^7.0.0-0
|
||||
dependencies:
|
||||
'@babel/core': 7.22.9
|
||||
'@babel/plugin-transform-react-jsx': 7.22.5(@babel/core@7.22.9)
|
||||
'@babel/plugin-transform-react-jsx': 7.19.0(@babel/core@7.22.9)
|
||||
dev: true
|
||||
|
||||
/@babel/plugin-transform-react-jsx@7.19.0(@babel/core@7.22.9):
|
||||
@@ -2271,7 +2271,7 @@ packages:
|
||||
'@babel/helper-plugin-utils': 7.22.5
|
||||
'@babel/helper-validator-option': 7.22.5
|
||||
'@babel/plugin-transform-react-display-name': 7.18.6(@babel/core@7.22.9)
|
||||
'@babel/plugin-transform-react-jsx': 7.22.5(@babel/core@7.22.9)
|
||||
'@babel/plugin-transform-react-jsx': 7.19.0(@babel/core@7.22.9)
|
||||
'@babel/plugin-transform-react-jsx-development': 7.18.6(@babel/core@7.22.9)
|
||||
'@babel/plugin-transform-react-pure-annotations': 7.18.6(@babel/core@7.22.9)
|
||||
dev: true
|
||||
@@ -3227,7 +3227,7 @@ packages:
|
||||
resolution: {integrity: sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==}
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.2
|
||||
'@jridgewell/trace-mapping': 0.3.18
|
||||
'@jridgewell/trace-mapping': 0.3.17
|
||||
|
||||
/@jridgewell/sourcemap-codec@1.4.14:
|
||||
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
|
||||
@@ -3237,7 +3237,6 @@ packages:
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.0
|
||||
'@jridgewell/sourcemap-codec': 1.4.14
|
||||
dev: true
|
||||
|
||||
/@jridgewell/trace-mapping@0.3.18:
|
||||
resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==}
|
||||
@@ -3583,6 +3582,10 @@ packages:
|
||||
resolution: {integrity: sha512-RmHanbV21saP/6OEPBJ7yJMuys68cIf8OBBWd7+uj40LdpmswVAwe1uzeuFyUsd6SfeITWT3XnQfn6wULeKwDQ==}
|
||||
dev: false
|
||||
|
||||
/@next/env@13.4.8:
|
||||
resolution: {integrity: sha512-twuSf1klb3k9wXI7IZhbZGtFCWvGD4wXTY2rmvzIgVhXhs7ISThrbNyutBx3jWIL8Y/Hk9+woytFz5QsgtcRKQ==}
|
||||
dev: false
|
||||
|
||||
/@next/eslint-plugin-next@13.4.12:
|
||||
resolution: {integrity: sha512-6rhK9CdxEgj/j1qvXIyLTWEaeFv7zOK8yJMulz3Owel0uek0U9MJCGzmKgYxM3aAUBo3gKeywCZKyQnJKto60A==}
|
||||
dependencies:
|
||||
@@ -6423,6 +6426,11 @@ packages:
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
/acorn@8.8.1:
|
||||
resolution: {integrity: sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
/agent-base@6.0.2:
|
||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
@@ -6863,8 +6871,8 @@ packages:
|
||||
peerDependencies:
|
||||
postcss: ^8.1.0
|
||||
dependencies:
|
||||
browserslist: 4.21.9
|
||||
caniuse-lite: 1.0.30001512
|
||||
browserslist: 4.21.5
|
||||
caniuse-lite: 1.0.30001466
|
||||
fraction.js: 4.2.0
|
||||
normalize-range: 0.1.2
|
||||
picocolors: 1.0.0
|
||||
@@ -6879,8 +6887,8 @@ packages:
|
||||
peerDependencies:
|
||||
postcss: ^8.1.0
|
||||
dependencies:
|
||||
browserslist: 4.21.9
|
||||
caniuse-lite: 1.0.30001512
|
||||
browserslist: 4.21.5
|
||||
caniuse-lite: 1.0.30001466
|
||||
fraction.js: 4.2.0
|
||||
normalize-range: 0.1.2
|
||||
picocolors: 1.0.0
|
||||
@@ -7370,6 +7378,17 @@ packages:
|
||||
pako: 1.0.11
|
||||
dev: true
|
||||
|
||||
/browserslist@4.21.5:
|
||||
resolution: {integrity: sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==}
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001512
|
||||
electron-to-chromium: 1.4.284
|
||||
node-releases: 2.0.10
|
||||
update-browserslist-db: 1.0.10(browserslist@4.21.5)
|
||||
dev: true
|
||||
|
||||
/browserslist@4.21.9:
|
||||
resolution: {integrity: sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==}
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
@@ -7611,6 +7630,10 @@ packages:
|
||||
lodash.uniq: 4.5.0
|
||||
dev: true
|
||||
|
||||
/caniuse-lite@1.0.30001466:
|
||||
resolution: {integrity: sha512-ewtFBSfWjEmxUgNBSZItFSmVtvk9zkwkl1OfRZlKA8slltRN+/C/tuGVrF9styXkN36Yu3+SeJ1qkXxDEyNZ5w==}
|
||||
dev: true
|
||||
|
||||
/caniuse-lite@1.0.30001512:
|
||||
resolution: {integrity: sha512-2S9nK0G/mE+jasCUsMPlARhRCts1ebcp2Ji8Y8PWi4NDE1iRdLCnEPHkEfeBrGC45L4isBx5ur3IQ6yTE2mRZw==}
|
||||
|
||||
@@ -8923,8 +8946,8 @@ packages:
|
||||
mimic-response: 3.1.0
|
||||
dev: false
|
||||
|
||||
/dedent@1.5.1:
|
||||
resolution: {integrity: sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==}
|
||||
/dedent@1.5.0:
|
||||
resolution: {integrity: sha512-3sSQTYoWKGcRHmHl6Y6opLpRJH55bxeGQ0Y1LCI5pZzUXvokVkj0FC4bi7uEwazxA9FQZ0Nv067Zt5kSUvXxEA==}
|
||||
peerDependencies:
|
||||
babel-plugin-macros: ^3.1.0
|
||||
peerDependenciesMeta:
|
||||
@@ -9293,6 +9316,10 @@ packages:
|
||||
jake: 10.8.5
|
||||
dev: true
|
||||
|
||||
/electron-to-chromium@1.4.284:
|
||||
resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==}
|
||||
dev: true
|
||||
|
||||
/electron-to-chromium@1.4.450:
|
||||
resolution: {integrity: sha512-BLG5HxSELlrMx7dJ2s+8SFlsCtJp37Zpk2VAxyC6CZtbc+9AJeZHfYHbrlSgdXp6saQ8StMqOTEDaBKgA7u1sw==}
|
||||
|
||||
@@ -9788,7 +9815,7 @@ packages:
|
||||
'@babel/eslint-parser': 7.19.1(@babel/core@7.22.9)(eslint@8.46.0)
|
||||
'@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.22.9)
|
||||
'@babel/plugin-syntax-decorators': 7.19.0(@babel/core@7.22.9)
|
||||
'@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.22.9)
|
||||
'@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.22.9)
|
||||
eslint: 8.46.0
|
||||
eslint-plugin-compat: 4.1.2(eslint@8.46.0)
|
||||
eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@6.2.1)(eslint@8.46.0)(jest@29.6.2)(typescript@5.1.6)
|
||||
@@ -9810,13 +9837,13 @@ packages:
|
||||
eslint: 8.46.0
|
||||
dev: true
|
||||
|
||||
/eslint-config-turbo@1.10.7(eslint@8.46.0):
|
||||
resolution: {integrity: sha512-0yHt5UlXVph8S4SOvP6gYehLvYjJj6XFKTYOG/WUQbjlcF0OU4pOT1a1juqmmBPWYlvJ0evt7v+RekY4tOopPQ==}
|
||||
/eslint-config-turbo@1.8.8(eslint@8.46.0):
|
||||
resolution: {integrity: sha512-+yT22sHOT5iC1sbBXfLIdXfbZuiv9bAyOXsxTxFCWelTeFFnANqmuKB3x274CFvf7WRuZ/vYP/VMjzU9xnFnxA==}
|
||||
peerDependencies:
|
||||
eslint: '>6.6.0'
|
||||
dependencies:
|
||||
eslint: 8.46.0
|
||||
eslint-plugin-turbo: 1.10.7(eslint@8.46.0)
|
||||
eslint-plugin-turbo: 1.8.8(eslint@8.46.0)
|
||||
dev: true
|
||||
|
||||
/eslint-import-resolver-node@0.3.6:
|
||||
@@ -10004,12 +10031,11 @@ packages:
|
||||
semver: 6.3.1
|
||||
string.prototype.matchall: 4.0.8
|
||||
|
||||
/eslint-plugin-turbo@1.10.7(eslint@8.46.0):
|
||||
resolution: {integrity: sha512-YikBHc75DY9VV1vAFUIBekHLQlxqVT5zTNibK8zBQInCUhF7PvyPJc0xXw5FSz8EYtt4uOV3r0Km3CmFRclS4Q==}
|
||||
/eslint-plugin-turbo@1.8.8(eslint@8.46.0):
|
||||
resolution: {integrity: sha512-zqyTIvveOY4YU5jviDWw9GXHd4RiKmfEgwsjBrV/a965w0PpDwJgEUoSMB/C/dU310Sv9mF3DSdEjxjJLaw6rA==}
|
||||
peerDependencies:
|
||||
eslint: '>6.6.0'
|
||||
dependencies:
|
||||
dotenv: 16.0.3
|
||||
eslint: 8.46.0
|
||||
dev: true
|
||||
|
||||
@@ -12301,7 +12327,7 @@ packages:
|
||||
'@types/node': 20.4.6
|
||||
chalk: 4.1.2
|
||||
co: 4.6.0
|
||||
dedent: 1.5.1
|
||||
dedent: 1.5.0
|
||||
is-generator-fn: 2.1.0
|
||||
jest-each: 29.6.2
|
||||
jest-matcher-utils: 29.6.2
|
||||
@@ -12395,7 +12421,7 @@ packages:
|
||||
chalk: 4.1.2
|
||||
diff-sequences: 29.4.3
|
||||
jest-get-type: 29.4.3
|
||||
pretty-format: 29.6.2
|
||||
pretty-format: 29.6.1
|
||||
dev: true
|
||||
|
||||
/jest-diff@29.6.2:
|
||||
@@ -12764,12 +12790,26 @@ packages:
|
||||
chalk: 5.3.0
|
||||
jest: 29.6.2
|
||||
jest-regex-util: 29.4.3
|
||||
jest-watcher: 29.6.2
|
||||
jest-watcher: 29.6.1
|
||||
slash: 5.1.0
|
||||
string-length: 5.0.1
|
||||
strip-ansi: 7.0.1
|
||||
dev: true
|
||||
|
||||
/jest-watcher@29.6.1:
|
||||
resolution: {integrity: sha512-d4wpjWTS7HEZPaaj8m36QiaP856JthRZkrgcIY/7ISoUWPIillrXM23WPboZVLbiwZBt4/qn2Jke84Sla6JhFA==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
dependencies:
|
||||
'@jest/test-result': 29.6.2
|
||||
'@jest/types': 29.6.1
|
||||
'@types/node': 20.4.6
|
||||
ansi-escapes: 4.3.2
|
||||
chalk: 4.1.2
|
||||
emittery: 0.13.1
|
||||
jest-util: 29.6.2
|
||||
string-length: 4.0.2
|
||||
dev: true
|
||||
|
||||
/jest-watcher@29.6.2:
|
||||
resolution: {integrity: sha512-GZitlqkMkhkefjfN/p3SJjrDaxPflqxEAv3/ik10OirZqJGYH5rPiIsgVcfof0Tdqg3shQGdEIxDBx+B4tuLzA==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
@@ -14616,7 +14656,7 @@ packages:
|
||||
next: '*'
|
||||
dependencies:
|
||||
'@corex/deepmerge': 4.0.43
|
||||
'@next/env': 13.4.12
|
||||
'@next/env': 13.4.8
|
||||
fast-glob: 3.2.12
|
||||
minimist: 1.2.8
|
||||
next: 13.4.12(react-dom@18.2.0)(react@18.2.0)
|
||||
@@ -14785,6 +14825,10 @@ packages:
|
||||
vm-browserify: 1.1.2
|
||||
dev: true
|
||||
|
||||
/node-releases@2.0.10:
|
||||
resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==}
|
||||
dev: true
|
||||
|
||||
/node-releases@2.0.12:
|
||||
resolution: {integrity: sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==}
|
||||
|
||||
@@ -19425,7 +19469,7 @@ packages:
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@jridgewell/source-map': 0.3.2
|
||||
acorn: 8.10.0
|
||||
acorn: 8.8.1
|
||||
commander: 2.20.3
|
||||
source-map-support: 0.5.21
|
||||
|
||||
@@ -19766,65 +19810,65 @@ packages:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
|
||||
/turbo-darwin-64@1.10.3:
|
||||
resolution: {integrity: sha512-IIB9IomJGyD3EdpSscm7Ip1xVWtYb7D0x7oH3vad3gjFcjHJzDz9xZ/iw/qItFEW+wGFcLSRPd+1BNnuLM8AsA==}
|
||||
/turbo-darwin-64@1.10.7:
|
||||
resolution: {integrity: sha512-N2MNuhwrl6g7vGuz4y3fFG2aR1oCs0UZ5HKl8KSTn/VC2y2YIuLGedQ3OVbo0TfEvygAlF3QGAAKKtOCmGPNKA==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-darwin-arm64@1.10.3:
|
||||
resolution: {integrity: sha512-SBNmOZU9YEB0eyNIxeeQ+Wi0Ufd+nprEVp41rgUSRXEIpXjsDjyBnKnF+sQQj3+FLb4yyi/yZQckB+55qXWEsw==}
|
||||
/turbo-darwin-arm64@1.10.7:
|
||||
resolution: {integrity: sha512-WbJkvjU+6qkngp7K4EsswOriO3xrNQag7YEGRtfLoDdMTk4O4QTeU6sfg2dKfDsBpTidTvEDwgIYJhYVGzrz9Q==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-linux-64@1.10.3:
|
||||
resolution: {integrity: sha512-kvAisGKE7xHJdyMxZLvg53zvHxjqPK1UVj4757PQqtx9dnjYHSc8epmivE6niPgDHon5YqImzArCjVZJYpIGHQ==}
|
||||
/turbo-linux-64@1.10.7:
|
||||
resolution: {integrity: sha512-x1CF2CDP1pDz/J8/B2T0hnmmOQI2+y11JGIzNP0KtwxDM7rmeg3DDTtDM/9PwGqfPotN9iVGgMiMvBuMFbsLhg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-linux-arm64@1.10.3:
|
||||
resolution: {integrity: sha512-Qgaqln0IYRgyL0SowJOi+PNxejv1I2xhzXOI+D+z4YHbgSx87ox1IsALYBlK8VRVYY8VCXl+PN12r1ioV09j7A==}
|
||||
/turbo-linux-arm64@1.10.7:
|
||||
resolution: {integrity: sha512-JtnBmaBSYbs7peJPkXzXxsRGSGBmBEIb6/kC8RRmyvPAMyqF8wIex0pttsI+9plghREiGPtRWv/lfQEPRlXnNQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-windows-64@1.10.3:
|
||||
resolution: {integrity: sha512-rbH9wManURNN8mBnN/ZdkpUuTvyVVEMiUwFUX4GVE5qmV15iHtZfDLUSGGCP2UFBazHcpNHG1OJzgc55GFFrUw==}
|
||||
/turbo-windows-64@1.10.7:
|
||||
resolution: {integrity: sha512-7A/4CByoHdolWS8dg3DPm99owfu1aY/W0V0+KxFd0o2JQMTQtoBgIMSvZesXaWM57z3OLsietFivDLQPuzE75w==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo-windows-arm64@1.10.3:
|
||||
resolution: {integrity: sha512-ThlkqxhcGZX39CaTjsHqJnqVe+WImjX13pmjnpChz6q5HHbeRxaJSFzgrHIOt0sUUVx90W/WrNRyoIt/aafniw==}
|
||||
/turbo-windows-arm64@1.10.7:
|
||||
resolution: {integrity: sha512-D36K/3b6+hqm9IBAymnuVgyePktwQ+F0lSXr2B9JfAdFPBktSqGmp50JNC7pahxhnuCLj0Vdpe9RqfnJw5zATA==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/turbo@1.10.3:
|
||||
resolution: {integrity: sha512-U4gKCWcKgLcCjQd4Pl8KJdfEKumpyWbzRu75A6FCj6Ctea1PIm58W6Ltw1QXKqHrl2pF9e1raAskf/h6dlrPCA==}
|
||||
/turbo@1.10.7:
|
||||
resolution: {integrity: sha512-xm0MPM28TWx1e6TNC3wokfE5eaDqlfi0G24kmeHupDUZt5Wd0OzHFENEHMPqEaNKJ0I+AMObL6nbSZonZBV2HA==}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
optionalDependencies:
|
||||
turbo-darwin-64: 1.10.3
|
||||
turbo-darwin-arm64: 1.10.3
|
||||
turbo-linux-64: 1.10.3
|
||||
turbo-linux-arm64: 1.10.3
|
||||
turbo-windows-64: 1.10.3
|
||||
turbo-windows-arm64: 1.10.3
|
||||
turbo-darwin-64: 1.10.7
|
||||
turbo-darwin-arm64: 1.10.7
|
||||
turbo-linux-64: 1.10.7
|
||||
turbo-linux-arm64: 1.10.7
|
||||
turbo-windows-64: 1.10.7
|
||||
turbo-windows-arm64: 1.10.7
|
||||
dev: true
|
||||
|
||||
/tween-functions@1.2.0:
|
||||
@@ -20158,6 +20202,17 @@ packages:
|
||||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/update-browserslist-db@1.0.10(browserslist@4.21.5):
|
||||
resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
browserslist: '>= 4.21.0'
|
||||
dependencies:
|
||||
browserslist: 4.21.5
|
||||
escalade: 3.1.1
|
||||
picocolors: 1.0.0
|
||||
dev: true
|
||||
|
||||
/update-browserslist-db@1.0.11(browserslist@4.21.9):
|
||||
resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==}
|
||||
hasBin: true
|
||||
|
||||
+11
-3
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"$schema": "https://turborepo.org/schema.json",
|
||||
"pipeline": {
|
||||
"@formbricks/database#build": {
|
||||
"cache": false,
|
||||
"dependsOn": ["post-install"]
|
||||
},
|
||||
"@formbricks/web#go": {
|
||||
"cache": false,
|
||||
"persistent": true,
|
||||
@@ -77,6 +81,13 @@
|
||||
"VERCEL_URL"
|
||||
]
|
||||
},
|
||||
"post-install": {
|
||||
"cache": false,
|
||||
"dependsOn": [],
|
||||
"outputs": [],
|
||||
"inputs": ["./schema.prisma"],
|
||||
"env": ["PRISMA_GENERATE_DATAPROXY"]
|
||||
},
|
||||
"db:setup": {
|
||||
"cache": false,
|
||||
"outputs": []
|
||||
@@ -85,9 +96,6 @@
|
||||
"persistent": true,
|
||||
"cache": false
|
||||
},
|
||||
"prebuild": {
|
||||
"outputs": []
|
||||
},
|
||||
"db:migrate:dev": {
|
||||
"outputs": []
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user