mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-29 09:50:10 -06:00
Compare commits
12 Commits
shubham/fi
...
ReviewBot/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
096947a93a | ||
|
|
e563b3330a | ||
|
|
9271e375af | ||
|
|
35a9685b71 | ||
|
|
723ea558fa | ||
|
|
8a4a635ee3 | ||
|
|
1a30e9fd11 | ||
|
|
dc8e1c764b | ||
|
|
48e9148728 | ||
|
|
25525e0b03 | ||
|
|
9720c0ecba | ||
|
|
33cbe7cf22 |
@@ -4,8 +4,8 @@ on:
|
||||
# "Scheduled workflows run on the latest commit on the default or base branch."
|
||||
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
|
||||
schedule:
|
||||
# This will run the job at 23:00 UTC every day of every month.
|
||||
- cron: "0 21 * * *"
|
||||
# This will run the job at 22:00 UTC every day of every month.
|
||||
- cron: "0 22 * * *"
|
||||
jobs:
|
||||
cron-reportUsageToStripe:
|
||||
env:
|
||||
@@ -19,4 +19,5 @@ jobs:
|
||||
curl ${{ env.APP_URL }}/api/cron/report-usage \
|
||||
-X POST \
|
||||
-H 'x-api-key: ${{ env.CRON_SECRET }}' \
|
||||
-H 'Cache-Control: no-cache' \
|
||||
--fail
|
||||
|
||||
27
.github/workflows/playwright.yml
vendored
Normal file
27
.github/workflows/playwright.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Playwright Tests
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master ]
|
||||
pull_request:
|
||||
branches: [ main, master ]
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Install dependencies
|
||||
run: npm install -g pnpm && pnpm install
|
||||
- name: Install Playwright Browsers
|
||||
run: pnpm exec playwright install --with-deps
|
||||
- name: Run Playwright tests
|
||||
run: pnpm exec playwright test
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -44,4 +44,8 @@ packages/database/zod
|
||||
# nixos stuff
|
||||
.direnv
|
||||
|
||||
Zone.Identifier
|
||||
Zone.Identifier
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
|
||||
@@ -18,7 +18,6 @@ Formbricks v1.2 ships a lot of features targeting our Link Surveys. We have also
|
||||
| -------------------- | -------- | ------------------------------ | ----------------------------------------------------------- |
|
||||
| ENCRYPTION_KEY | true | `openssl rand -hex 32` | Needed for 2 Factor Authentication |
|
||||
| SHORT_URL_BASE | false | `<your-short-base-url>` | Needed if you want to enable shorter links for Link Surveys |
|
||||
| ASSET_PREFIX_URL | false | `<your-asset-hosted-base-url>` | Needed if you have a separate URL for hosted assets |
|
||||
|
||||
### Deprecated / Removed Environment Variables
|
||||
|
||||
|
||||
@@ -204,6 +204,7 @@ export async function copyToOtherEnvironmentAction(
|
||||
singleUse: existingSurvey.singleUse ?? prismaClient.JsonNull,
|
||||
productOverwrites: existingSurvey.productOverwrites ?? prismaClient.JsonNull,
|
||||
verifyEmail: existingSurvey.verifyEmail ?? prismaClient.JsonNull,
|
||||
styling: existingSurvey.styling ?? prismaClient.JsonNull,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ export default function SummaryMetadata({
|
||||
const ttc = useMemo(() => {
|
||||
let validTtcResponsesCountAcc = 0; //stores the count of responses that contains a _total value
|
||||
const ttc = responses.reduce((acc, response) => {
|
||||
if (response.ttc._total) {
|
||||
if (response.ttc?._total) {
|
||||
validTtcResponsesCountAcc++;
|
||||
return acc + response.ttc._total;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import { TSurvey } from "@formbricks/types/surveys";
|
||||
import { useState } from "react";
|
||||
|
||||
interface AnimatedSurveyBgProps {
|
||||
localSurvey?: TSurvey;
|
||||
handleBgChange: (bg: string, bgType: string) => void;
|
||||
}
|
||||
|
||||
export default function AnimatedSurveyBg({ localSurvey, handleBgChange }: AnimatedSurveyBgProps) {
|
||||
const [color, setColor] = useState(localSurvey?.styling?.background?.bg || "#ffff");
|
||||
const [hoveredVideo, setHoveredVideo] = useState<number | null>(null);
|
||||
|
||||
const animationFiles = {
|
||||
"/animated-bgs/Thumbnails/1_Thumb.mp4": "/animated-bgs/4K/1_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/2_Thumb.mp4": "/animated-bgs/4K/2_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/3_Thumb.mp4": "/animated-bgs/4K/3_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/4_Thumb.mp4": "/animated-bgs/4K/4_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/5_Thumb.mp4": "/animated-bgs/4K/5_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/6_Thumb.mp4": "/animated-bgs/4K/6_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/7_Thumb.mp4": "/animated-bgs/4K/7_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/8_Thumb.mp4": "/animated-bgs/4K/8_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/9_Thumb.mp4": "/animated-bgs/4K/9_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/10_Thumb.mp4": "/animated-bgs/4K/10_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/11_Thumb.mp4": "/animated-bgs/4K/11_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/12_Thumb.mp4": "/animated-bgs/4K/12_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/13_Thumb.mp4": "/animated-bgs/4K/13_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/14_Thumb.mp4": "/animated-bgs/4K/14_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/15_Thumb.mp4": "/animated-bgs/4K/15_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/16_Thumb.mp4": "/animated-bgs/4K/16_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/17_Thumb.mp4": "/animated-bgs/4K/17_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/18_Thumb.mp4": "/animated-bgs/4K/18_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/19_Thumb.mp4": "/animated-bgs/4K/19_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/20_Thumb.mp4": "/animated-bgs/4K/20_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/21_Thumb.mp4": "/animated-bgs/4K/21_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/22_Thumb.mp4": "/animated-bgs/4K/22_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/23_Thumb.mp4": "/animated-bgs/4K/23_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/24_Thumb.mp4": "/animated-bgs/4K/24_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/25_Thumb.mp4": "/animated-bgs/4K/25_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/26_Thumb.mp4": "/animated-bgs/4K/26_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/27_Thumb.mp4": "/animated-bgs/4K/27_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/28_Thumb.mp4": "/animated-bgs/4K/28_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/29_Thumb.mp4": "/animated-bgs/4K/29_4k.mp4",
|
||||
"/animated-bgs/Thumbnails/30_Thumb.mp4": "/animated-bgs/4K/30_4k.mp4",
|
||||
};
|
||||
|
||||
const handleMouseEnter = (index: number) => {
|
||||
setHoveredVideo(index);
|
||||
playVideo(index);
|
||||
};
|
||||
|
||||
const handleMouseLeave = (index: number) => {
|
||||
setHoveredVideo(null);
|
||||
pauseVideo(index);
|
||||
};
|
||||
|
||||
// Function to play the video
|
||||
const playVideo = (index: number) => {
|
||||
const video = document.getElementById(`video-${index}`) as HTMLVideoElement;
|
||||
if (video) {
|
||||
video.play();
|
||||
}
|
||||
};
|
||||
|
||||
// Function to pause the video
|
||||
const pauseVideo = (index: number) => {
|
||||
const video = document.getElementById(`video-${index}`) as HTMLVideoElement;
|
||||
if (video) {
|
||||
video.pause();
|
||||
}
|
||||
};
|
||||
|
||||
const handleBg = (x: string) => {
|
||||
setColor(x);
|
||||
handleBgChange(x, "animation");
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<div className="mt-4 grid grid-cols-6 gap-4">
|
||||
{Object.keys(animationFiles).map((key, index) => {
|
||||
const value = animationFiles[key];
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
onMouseEnter={() => handleMouseEnter(index)}
|
||||
onMouseLeave={() => handleMouseLeave(index)}
|
||||
onClick={() => handleBg(value)}
|
||||
className="relative cursor-pointer overflow-hidden rounded-lg">
|
||||
<video
|
||||
disablePictureInPicture
|
||||
id={`video-${index}`}
|
||||
autoPlay={hoveredVideo === index}
|
||||
className="h-46 w-96 origin-center scale-105 transform">
|
||||
<source src={`${key}`} type="video/mp4" />
|
||||
</video>
|
||||
<input
|
||||
className="absolute right-2 top-2 h-4 w-4 rounded-sm bg-white "
|
||||
type="checkbox"
|
||||
checked={color === value}
|
||||
onChange={() => handleBg(value)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { TSurvey } from "@formbricks/types/surveys";
|
||||
import { ColorPicker } from "@formbricks/ui/ColorPicker";
|
||||
import { useState } from "react";
|
||||
|
||||
interface ColorSurveyBgBgProps {
|
||||
localSurvey?: TSurvey;
|
||||
handleBgChange: (bg: string, bgType: string) => void;
|
||||
colours: string[];
|
||||
}
|
||||
|
||||
export default function ColorSurveyBg({ localSurvey, handleBgChange, colours }: ColorSurveyBgBgProps) {
|
||||
const [color, setColor] = useState(localSurvey?.styling?.background?.bg || "#ffff");
|
||||
|
||||
const handleBg = (x: string) => {
|
||||
setColor(x);
|
||||
handleBgChange(x, "color");
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<div className="w-full max-w-xs py-2">
|
||||
<ColorPicker color={color} onChange={handleBg} />
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-4 md:grid-cols-5 xl:grid-cols-8 2xl:grid-cols-10">
|
||||
{colours.map((x) => {
|
||||
return (
|
||||
<div
|
||||
className={`h-16 w-16 cursor-pointer rounded-lg ${
|
||||
color === x ? "border-4 border-slate-500" : ""
|
||||
}`}
|
||||
key={x}
|
||||
style={{ backgroundColor: `${x}` }}
|
||||
onClick={() => handleBg(x)}></div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import FileInput from "@formbricks/ui/FileInput";
|
||||
import { TSurvey } from "@formbricks/types/surveys";
|
||||
|
||||
interface ImageSurveyBgBgProps {
|
||||
localSurvey?: TSurvey;
|
||||
handleBgChange: (url: string, bgType: string) => void;
|
||||
}
|
||||
|
||||
export default function ImageSurveyBg({ localSurvey, handleBgChange }: ImageSurveyBgBgProps) {
|
||||
const isUrl = (str: string) => {
|
||||
try {
|
||||
new URL(str);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const fileUrl = isUrl(localSurvey?.styling?.background?.bg ?? "")
|
||||
? localSurvey?.styling?.background?.bg ?? ""
|
||||
: "";
|
||||
|
||||
return (
|
||||
<div className="mb-2 mt-4 w-full rounded-lg border bg-slate-50 p-4">
|
||||
<div className="flex w-full items-center justify-center">
|
||||
<FileInput
|
||||
id="survey-bg-file-input"
|
||||
allowedFileExtensions={["png", "jpeg", "jpg"]}
|
||||
environmentId={localSurvey?.environmentId}
|
||||
onFileUpload={(url: string[]) => {
|
||||
if (url.length > 0) {
|
||||
handleBgChange(url[0], "image");
|
||||
} else {
|
||||
handleBgChange("#ffff", "color");
|
||||
}
|
||||
}}
|
||||
fileUrl={fileUrl}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -18,6 +18,7 @@ interface SettingsViewProps {
|
||||
attributeClasses: TAttributeClass[];
|
||||
responseCount: number;
|
||||
membershipRole?: TMembershipRole;
|
||||
colours: string[];
|
||||
}
|
||||
|
||||
export default function SettingsView({
|
||||
@@ -28,6 +29,7 @@ export default function SettingsView({
|
||||
attributeClasses,
|
||||
responseCount,
|
||||
membershipRole,
|
||||
colours,
|
||||
}: SettingsViewProps) {
|
||||
return (
|
||||
<div className="mt-12 space-y-3 p-5">
|
||||
@@ -60,7 +62,7 @@ export default function SettingsView({
|
||||
environmentId={environment.id}
|
||||
/>
|
||||
|
||||
<StylingCard localSurvey={localSurvey} setLocalSurvey={setLocalSurvey} />
|
||||
<StylingCard localSurvey={localSurvey} setLocalSurvey={setLocalSurvey} colours={colours} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { TPlacement } from "@formbricks/types/common";
|
||||
import { TSurvey } from "@formbricks/types/surveys";
|
||||
import { TSurvey, TSurveyBackgroundBgType } from "@formbricks/types/surveys";
|
||||
import { ColorPicker } from "@formbricks/ui/ColorPicker";
|
||||
import { Label } from "@formbricks/ui/Label";
|
||||
import { Switch } from "@formbricks/ui/Switch";
|
||||
@@ -9,18 +9,28 @@ import { CheckCircleIcon } from "@heroicons/react/24/solid";
|
||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||
import { useState } from "react";
|
||||
import Placement from "./Placement";
|
||||
import SurveyBgSelectorTab from "./SurveyBgSelectorTab";
|
||||
|
||||
interface StylingCardProps {
|
||||
localSurvey: TSurvey;
|
||||
setLocalSurvey: React.Dispatch<React.SetStateAction<TSurvey>>;
|
||||
colours: string[];
|
||||
}
|
||||
|
||||
export default function StylingCard({ localSurvey, setLocalSurvey }: StylingCardProps) {
|
||||
export default function StylingCard({ localSurvey, setLocalSurvey, colours }: StylingCardProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { type, productOverwrites } = localSurvey;
|
||||
const { type, productOverwrites, styling } = localSurvey;
|
||||
const { brandColor, clickOutsideClose, darkOverlay, placement, highlightBorderColor } =
|
||||
productOverwrites ?? {};
|
||||
const { bg, bgType, brightness } = styling?.background ?? {};
|
||||
|
||||
const [inputValue, setInputValue] = useState(100);
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
setInputValue(e.target.value);
|
||||
handleBrightnessChange(parseInt(e.target.value));
|
||||
};
|
||||
|
||||
const togglePlacement = () => {
|
||||
setLocalSurvey({
|
||||
@@ -42,6 +52,34 @@ export default function StylingCard({ localSurvey, setLocalSurvey }: StylingCard
|
||||
});
|
||||
};
|
||||
|
||||
const toggleBackgroundColor = () => {
|
||||
setLocalSurvey({
|
||||
...localSurvey,
|
||||
styling: {
|
||||
...localSurvey.styling,
|
||||
background: {
|
||||
...localSurvey.styling?.background,
|
||||
bg: !!bg ? undefined : "#ffff",
|
||||
bgType: !!bg ? undefined : "color",
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const toggleBrightness = () => {
|
||||
setLocalSurvey({
|
||||
...localSurvey,
|
||||
styling: {
|
||||
...localSurvey.styling,
|
||||
background: {
|
||||
...localSurvey.styling?.background,
|
||||
brightness: !!brightness ? undefined : 100,
|
||||
},
|
||||
},
|
||||
});
|
||||
setInputValue(100);
|
||||
};
|
||||
|
||||
const toggleHighlightBorderColor = () => {
|
||||
setLocalSurvey({
|
||||
...localSurvey,
|
||||
@@ -62,6 +100,35 @@ export default function StylingCard({ localSurvey, setLocalSurvey }: StylingCard
|
||||
});
|
||||
};
|
||||
|
||||
const handleBgChange = (color: string, type: TSurveyBackgroundBgType) => {
|
||||
setInputValue(100);
|
||||
setLocalSurvey({
|
||||
...localSurvey,
|
||||
styling: {
|
||||
...localSurvey.styling,
|
||||
background: {
|
||||
...localSurvey.styling?.background,
|
||||
bg: color,
|
||||
bgType: type,
|
||||
brightness: undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleBrightnessChange = (percent: number) => {
|
||||
setLocalSurvey({
|
||||
...localSurvey,
|
||||
styling: {
|
||||
...(localSurvey.styling || {}),
|
||||
background: {
|
||||
...localSurvey.styling?.background,
|
||||
brightness: percent,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleBorderColorChange = (color: string) => {
|
||||
setLocalSurvey({
|
||||
...localSurvey,
|
||||
@@ -143,6 +210,66 @@ export default function StylingCard({ localSurvey, setLocalSurvey }: StylingCard
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{type == "link" && (
|
||||
<>
|
||||
{/* Background */}
|
||||
<div className="p-3">
|
||||
<div className="ml-2 flex items-center space-x-1">
|
||||
<Switch id="autoCompleteBg" checked={!!bg} onCheckedChange={toggleBackgroundColor} />
|
||||
<Label htmlFor="autoCompleteBg" className="cursor-pointer">
|
||||
<div className="ml-2">
|
||||
<h3 className="text-sm font-semibold text-slate-700">Change Background</h3>
|
||||
<p className="text-xs font-normal text-slate-500">
|
||||
Pick a background from our library or upload your own.
|
||||
</p>
|
||||
</div>
|
||||
</Label>
|
||||
</div>
|
||||
{bg && (
|
||||
<SurveyBgSelectorTab
|
||||
localSurvey={localSurvey}
|
||||
handleBgChange={handleBgChange}
|
||||
colours={colours}
|
||||
bgType={bgType}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{/* Overlay */}
|
||||
<div className="p-3">
|
||||
<div className="ml-2 flex items-center space-x-1">
|
||||
<Switch
|
||||
id="autoCompleteOverlay"
|
||||
checked={!!brightness}
|
||||
onCheckedChange={toggleBrightness}
|
||||
/>
|
||||
<Label htmlFor="autoCompleteOverlay" className="cursor-pointer">
|
||||
<div className="ml-2">
|
||||
<h3 className="text-sm font-semibold text-slate-700">Background Overlay</h3>
|
||||
<p className="text-xs font-normal text-slate-500">
|
||||
Darken or lighten background of your choice.
|
||||
</p>
|
||||
</div>
|
||||
</Label>
|
||||
</div>
|
||||
{brightness && (
|
||||
<div>
|
||||
<div className="mt-4 flex flex-col justify-center rounded-lg border bg-slate-50 p-4 px-8">
|
||||
<h3 className="mb-4 text-sm font-semibold text-slate-700">Transparency</h3>
|
||||
<input
|
||||
id="small-range"
|
||||
type="range"
|
||||
min="1"
|
||||
max="200"
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
className="range-sm mb-6 h-1 w-full cursor-pointer appearance-none rounded-lg bg-gray-200 dark:bg-gray-700"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{/* positioning */}
|
||||
{type !== "link" && (
|
||||
<div className="p-3 ">
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { TSurvey } from "@formbricks/types/surveys";
|
||||
import { useState } from "react";
|
||||
import AnimatedSurveyBg from "./AnimatedSurveyBg";
|
||||
import ColorSurveyBg from "./ColorSurveyBg";
|
||||
import ImageSurveyBg from "./ImageSurveyBg";
|
||||
|
||||
interface SurveyBgSelectorTabProps {
|
||||
localSurvey: TSurvey;
|
||||
handleBgChange: (bg: string, bgType: string) => void;
|
||||
colours: string[];
|
||||
bgType: string | null | undefined;
|
||||
}
|
||||
|
||||
const TabButton = ({ isActive, onClick, children }) => (
|
||||
<button
|
||||
className={`w-1/4 rounded-md p-2 text-sm font-medium leading-none text-slate-800 ${
|
||||
isActive ? "bg-white shadow-sm" : ""
|
||||
}`}
|
||||
onClick={onClick}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
||||
export default function SurveyBgSelectorTab({
|
||||
localSurvey,
|
||||
handleBgChange,
|
||||
colours,
|
||||
bgType,
|
||||
}: SurveyBgSelectorTabProps) {
|
||||
const [tab, setTab] = useState(bgType || "image");
|
||||
|
||||
const renderContent = () => {
|
||||
switch (tab) {
|
||||
case "image":
|
||||
return <ImageSurveyBg localSurvey={localSurvey} handleBgChange={handleBgChange} />;
|
||||
case "animation":
|
||||
return <AnimatedSurveyBg localSurvey={localSurvey} handleBgChange={handleBgChange} />;
|
||||
case "color":
|
||||
return <ColorSurveyBg localSurvey={localSurvey} handleBgChange={handleBgChange} colours={colours} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-4 flex flex-col items-center justify-center rounded-lg border bg-slate-50 p-4 px-8">
|
||||
<div className="flex w-full items-center justify-between rounded-lg border border-slate-300 bg-slate-50 px-6 py-1.5">
|
||||
<TabButton isActive={tab === "image"} onClick={() => setTab("image")}>
|
||||
Image
|
||||
</TabButton>
|
||||
<TabButton isActive={tab === "animation"} onClick={() => setTab("animation")}>
|
||||
Animation
|
||||
</TabButton>
|
||||
<TabButton isActive={tab === "color"} onClick={() => setTab("color")}>
|
||||
Color
|
||||
</TabButton>
|
||||
</div>
|
||||
{renderContent()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -23,6 +23,7 @@ interface SurveyEditorProps {
|
||||
attributeClasses: TAttributeClass[];
|
||||
responseCount: number;
|
||||
membershipRole?: TMembershipRole;
|
||||
colours: string[];
|
||||
}
|
||||
|
||||
export default function SurveyEditor({
|
||||
@@ -33,6 +34,7 @@ export default function SurveyEditor({
|
||||
attributeClasses,
|
||||
responseCount,
|
||||
membershipRole,
|
||||
colours,
|
||||
}: SurveyEditorProps): JSX.Element {
|
||||
const [activeView, setActiveView] = useState<"questions" | "settings">("questions");
|
||||
const [activeQuestionId, setActiveQuestionId] = useState<string | null>(null);
|
||||
@@ -99,6 +101,7 @@ export default function SurveyEditor({
|
||||
attributeClasses={attributeClasses}
|
||||
responseCount={responseCount}
|
||||
membershipRole={membershipRole}
|
||||
colours={colours}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
|
||||
@@ -222,6 +222,11 @@ export default function SurveyMenuBar({
|
||||
const { isDraft, ...rest } = question;
|
||||
return rest;
|
||||
}),
|
||||
attributeFilters: localSurvey.attributeFilters.filter((attributeFilter) => {
|
||||
if (attributeFilter.attributeClassId && attributeFilter.value) {
|
||||
return true;
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
if (!validateSurvey(localSurvey)) {
|
||||
@@ -252,6 +257,14 @@ export default function SurveyMenuBar({
|
||||
}
|
||||
};
|
||||
|
||||
function containsEmptyTriggers() {
|
||||
return (
|
||||
localSurvey.type === "web" &&
|
||||
localSurvey.triggers &&
|
||||
(localSurvey.triggers[0] === "" || localSurvey.triggers.length === 0)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{environment?.type === "development" && (
|
||||
@@ -298,7 +311,7 @@ export default function SurveyMenuBar({
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
disabled={isSurveyPublishing}
|
||||
disabled={isSurveyPublishing || containsEmptyTriggers()}
|
||||
variant={localSurvey.status === "draft" ? "secondary" : "darkCTA"}
|
||||
className="mr-3"
|
||||
loading={isSurveySaving}
|
||||
@@ -318,11 +331,7 @@ export default function SurveyMenuBar({
|
||||
)}
|
||||
{localSurvey.status === "draft" && !audiencePrompt && (
|
||||
<Button
|
||||
disabled={
|
||||
localSurvey.type === "web" &&
|
||||
localSurvey.triggers &&
|
||||
(localSurvey.triggers[0] === "" || localSurvey.triggers.length === 0 || isSurveySaving)
|
||||
}
|
||||
disabled={isSurveySaving || containsEmptyTriggers()}
|
||||
variant="darkCTA"
|
||||
loading={isSurveyPublishing}
|
||||
onClick={async () => {
|
||||
|
||||
@@ -12,6 +12,7 @@ import { getSurvey } from "@formbricks/lib/survey/service";
|
||||
import { getTeamByEnvironmentId } from "@formbricks/lib/team/service";
|
||||
import { ErrorComponent } from "@formbricks/ui/ErrorComponent";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { colours } from "@formbricks/lib/constants";
|
||||
import SurveyEditor from "./components/SurveyEditor";
|
||||
|
||||
export const generateMetadata = async ({ params }) => {
|
||||
@@ -67,6 +68,7 @@ export default async function SurveysEditPage({ params }) {
|
||||
attributeClasses={attributeClasses}
|
||||
responseCount={responseCount}
|
||||
membershipRole={currentUserMembership?.role}
|
||||
colours={colours}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -56,7 +56,7 @@ export default function Modal({
|
||||
ref={modalRef}
|
||||
style={highlightBorderColorStyle}
|
||||
className={cn(
|
||||
"pointer-events-auto absolute h-fit max-h-[90%] w-full max-w-sm 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-y-auto rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5 transition-all duration-500 ease-in-out ",
|
||||
previewMode === "desktop" ? getPlacementStyle(placement) : "max-w-full ",
|
||||
slidingAnimationClass
|
||||
)}>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import Modal from "@/app/(app)/environments/[environmentId]/surveys/components/Modal";
|
||||
import TabOption from "@/app/(app)/environments/[environmentId]/surveys/components/TabOption";
|
||||
|
||||
import { MediaBackground } from "@/app/s/[surveyId]/components/MediaBackground";
|
||||
import type { TEnvironment } from "@formbricks/types/environment";
|
||||
import type { TProduct } from "@formbricks/types/product";
|
||||
import { TUploadFileConfig } from "@formbricks/types/storage";
|
||||
@@ -155,6 +155,20 @@ export default function PreviewSurvey({
|
||||
setActiveQuestionId(survey.welcomeCard.enabled ? "start" : survey?.questions[0]?.id);
|
||||
}
|
||||
|
||||
function animationTrigger() {
|
||||
let storePreviewMode = previewMode;
|
||||
setPreviewMode("null");
|
||||
setTimeout(() => {
|
||||
setPreviewMode(storePreviewMode);
|
||||
}, 10);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (survey.styling?.background?.bgType === "animation") {
|
||||
animationTrigger();
|
||||
}
|
||||
}, [survey.styling?.background?.bg]);
|
||||
|
||||
useEffect(() => {
|
||||
if (environment && environment.widgetSetupCompleted) {
|
||||
setWidgetSetupCompleted(true);
|
||||
@@ -194,7 +208,7 @@ export default function PreviewSurvey({
|
||||
<div className="absolute right-0 top-0 m-2">
|
||||
<ResetProgressButton resetQuestionProgress={resetQuestionProgress} />
|
||||
</div>
|
||||
<div className="relative h-[90%] max-h-[40rem] w-80 overflow-hidden rounded-[3rem] border-8 border-slate-500 bg-slate-400">
|
||||
<MediaBackground survey={survey} ContentRef={ContentRef} isMobilePreview>
|
||||
{/* below element is use to create notch for the mobile device mockup */}
|
||||
<div className="absolute left-1/2 right-1/2 top-0 z-20 h-4 w-1/2 -translate-x-1/2 transform rounded-b-md bg-slate-500"></div>
|
||||
{previewType === "modal" ? (
|
||||
@@ -214,25 +228,19 @@ export default function PreviewSurvey({
|
||||
/>
|
||||
</Modal>
|
||||
) : (
|
||||
<div
|
||||
className="absolute top-0 z-10 flex h-full w-full flex-grow flex-col overflow-y-auto"
|
||||
ref={ContentRef}>
|
||||
<div className="flex w-full flex-grow flex-col items-center justify-center bg-white py-6">
|
||||
<div className="w-full max-w-md px-4">
|
||||
<SurveyInline
|
||||
survey={survey}
|
||||
brandColor={brandColor}
|
||||
activeQuestionId={activeQuestionId || undefined}
|
||||
isBrandingEnabled={product.linkSurveyBranding}
|
||||
onActiveQuestionChange={setActiveQuestionId}
|
||||
onFileUpload={onFileUpload}
|
||||
responseCount={42}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative z-10 w-full max-w-md px-4">
|
||||
<SurveyInline
|
||||
survey={survey}
|
||||
brandColor={brandColor}
|
||||
activeQuestionId={activeQuestionId || undefined}
|
||||
isBrandingEnabled={product.linkSurveyBranding}
|
||||
onActiveQuestionChange={setActiveQuestionId}
|
||||
onFileUpload={onFileUpload}
|
||||
responseCount={42}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</MediaBackground>
|
||||
</>
|
||||
)}
|
||||
{previewMode === "desktop" && (
|
||||
@@ -287,22 +295,20 @@ export default function PreviewSurvey({
|
||||
/>
|
||||
</Modal>
|
||||
) : (
|
||||
<div className="flex flex-grow flex-col overflow-y-auto rounded-b-lg" ref={ContentRef}>
|
||||
<div className="flex w-full flex-grow flex-col items-center justify-center bg-white p-4 py-6">
|
||||
<div className="w-full max-w-md">
|
||||
<SurveyInline
|
||||
survey={survey}
|
||||
brandColor={brandColor}
|
||||
activeQuestionId={activeQuestionId || undefined}
|
||||
isBrandingEnabled={product.linkSurveyBranding}
|
||||
onActiveQuestionChange={setActiveQuestionId}
|
||||
isRedirectDisabled={true}
|
||||
onFileUpload={onFileUpload}
|
||||
responseCount={42}
|
||||
/>
|
||||
</div>
|
||||
<MediaBackground survey={survey} ContentRef={ContentRef} isEditorView>
|
||||
<div className="z-0 w-full max-w-md rounded-lg p-4">
|
||||
<SurveyInline
|
||||
survey={survey}
|
||||
brandColor={brandColor}
|
||||
activeQuestionId={activeQuestionId || undefined}
|
||||
isBrandingEnabled={product.linkSurveyBranding}
|
||||
onActiveQuestionChange={setActiveQuestionId}
|
||||
isRedirectDisabled={true}
|
||||
onFileUpload={onFileUpload}
|
||||
responseCount={42}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</MediaBackground>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -2526,4 +2526,5 @@ export const minimalSurvey: TSurvey = {
|
||||
},
|
||||
productOverwrites: null,
|
||||
singleUse: null,
|
||||
styling: null,
|
||||
};
|
||||
|
||||
@@ -1,19 +1,28 @@
|
||||
import { IMPRINT_URL, PRIVACY_URL } from "@formbricks/lib/constants";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function LegalFooter() {
|
||||
interface LegalFooterProps {
|
||||
bgColor?: string | null;
|
||||
}
|
||||
|
||||
export default function LegalFooter({ bgColor }: LegalFooterProps) {
|
||||
if (!IMPRINT_URL && !PRIVACY_URL) return null;
|
||||
|
||||
return (
|
||||
<div className="h-10 w-full border-t border-slate-200">
|
||||
<div className="mx-auto max-w-lg p-3 text-center text-sm text-slate-400">
|
||||
<div
|
||||
className={`fixed bottom-0 h-12 w-full`}
|
||||
style={{
|
||||
backgroundColor: `${bgColor}`,
|
||||
}}>
|
||||
<div className="mx-auto max-w-lg p-3 text-center text-xs text-slate-400">
|
||||
{IMPRINT_URL && (
|
||||
<Link href={IMPRINT_URL} target="_blank">
|
||||
<Link href={IMPRINT_URL} target="_blank" className="hover:underline">
|
||||
Imprint
|
||||
</Link>
|
||||
)}
|
||||
{IMPRINT_URL && PRIVACY_URL && <span> | </span>}
|
||||
{IMPRINT_URL && PRIVACY_URL && <span className="px-2">|</span>}
|
||||
{PRIVACY_URL && (
|
||||
<Link href={PRIVACY_URL} target="_blank">
|
||||
<Link href={PRIVACY_URL} target="_blank" className="hover:underline">
|
||||
Privacy Policy
|
||||
</Link>
|
||||
)}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import ContentWrapper from "@formbricks/ui/ContentWrapper";
|
||||
import { SurveyInline } from "@formbricks/ui/Survey";
|
||||
import SurveyLinkUsed from "@/app/s/[surveyId]/components/SurveyLinkUsed";
|
||||
import VerifyEmail from "@/app/s/[surveyId]/components/VerifyEmail";
|
||||
import { getPrefillResponseData } from "@/app/s/[surveyId]/lib/prefilling";
|
||||
import { FormbricksAPI } from "@formbricks/api";
|
||||
import { ResponseQueue } from "@formbricks/lib/responseQueue";
|
||||
import { SurveyState } from "@formbricks/lib/surveyState";
|
||||
import { TProduct } from "@formbricks/types/product";
|
||||
import { TResponse, TResponseData, TResponseUpdate } from "@formbricks/types/responses";
|
||||
import { TUploadFileConfig } from "@formbricks/types/storage";
|
||||
import { TSurvey } from "@formbricks/types/surveys";
|
||||
import ContentWrapper from "@formbricks/ui/ContentWrapper";
|
||||
import { SurveyInline } from "@formbricks/ui/Survey";
|
||||
import { ArrowPathIcon } from "@heroicons/react/24/solid";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { FormbricksAPI } from "@formbricks/api";
|
||||
import { TUploadFileConfig } from "@formbricks/types/storage";
|
||||
|
||||
interface LinkSurveyProps {
|
||||
survey: TSurvey;
|
||||
@@ -119,7 +119,7 @@ export default function LinkSurvey({
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContentWrapper className="h-full w-full p-0 md:max-w-lg">
|
||||
<ContentWrapper className="h-full w-full p-0 md:max-w-md">
|
||||
{isPreview && (
|
||||
<div className="fixed left-0 top-0 flex w-full items-center justify-between bg-slate-600 p-2 px-4 text-center text-sm text-white shadow-sm">
|
||||
<div />
|
||||
|
||||
87
apps/web/app/s/[surveyId]/components/MediaBackground.tsx
Normal file
87
apps/web/app/s/[surveyId]/components/MediaBackground.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
"use client";
|
||||
|
||||
import { TSurvey } from "@formbricks/types/surveys";
|
||||
import React from "react";
|
||||
|
||||
interface MediaBackgroundProps {
|
||||
children: React.ReactNode;
|
||||
survey: TSurvey;
|
||||
isEditorView?: boolean;
|
||||
isMobilePreview?: boolean;
|
||||
ContentRef?: React.RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export const MediaBackground: React.FC<MediaBackgroundProps> = ({
|
||||
children,
|
||||
survey,
|
||||
isEditorView = false,
|
||||
isMobilePreview = false,
|
||||
ContentRef,
|
||||
}) => {
|
||||
const getFilterStyle = () => {
|
||||
return survey.styling?.background?.brightness
|
||||
? `brightness-${survey.styling?.background?.brightness}`
|
||||
: "";
|
||||
};
|
||||
|
||||
const renderBackground = () => {
|
||||
const filterStyle = getFilterStyle();
|
||||
const baseClasses = "absolute inset-0 h-full w-full";
|
||||
|
||||
switch (survey.styling?.background?.bgType) {
|
||||
case "color":
|
||||
return (
|
||||
<div
|
||||
className={`${baseClasses} ${filterStyle}`}
|
||||
style={{ backgroundColor: survey.styling?.background?.bg || "#ffff" }}
|
||||
/>
|
||||
);
|
||||
case "animation":
|
||||
return (
|
||||
<video muted loop autoPlay className={`${baseClasses} object-cover ${filterStyle}`}>
|
||||
<source src={survey.styling?.background?.bg || ""} type="video/mp4" />
|
||||
</video>
|
||||
);
|
||||
case "image":
|
||||
return (
|
||||
<div
|
||||
className={`${baseClasses} bg-cover bg-center ${filterStyle}`}
|
||||
style={{ backgroundImage: `url(${survey.styling?.background?.bg})` }}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <div className={`${baseClasses} bg-white`} />;
|
||||
}
|
||||
};
|
||||
|
||||
const renderContent = () => (
|
||||
<div className="absolute flex h-full w-full items-center justify-center overflow-y-auto">{children}</div>
|
||||
);
|
||||
|
||||
if (isMobilePreview) {
|
||||
return (
|
||||
<div
|
||||
ref={ContentRef}
|
||||
className={`relative h-[90%] max-h-[40rem] w-80 overflow-hidden rounded-3xl border-8 border-slate-500 ${getFilterStyle()}`}>
|
||||
{renderBackground()}
|
||||
{renderContent()}
|
||||
</div>
|
||||
);
|
||||
} else if (isEditorView) {
|
||||
return (
|
||||
<div ref={ContentRef} className="flex flex-grow flex-col overflow-y-auto rounded-b-lg">
|
||||
<div className="relative flex w-full flex-grow flex-col items-center justify-center p-4 py-6">
|
||||
{renderBackground()}
|
||||
<div className="flex h-full w-full items-center justify-center">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col items-center justify-center px-2">
|
||||
{renderBackground()}
|
||||
<div className="relative w-full">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1,15 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import type { NextPage } from "next";
|
||||
import { validateSurveyPinAction } from "@/app/s/[surveyId]/actions";
|
||||
import LinkSurvey from "@/app/s/[surveyId]/components/LinkSurvey";
|
||||
import { TSurveyPinValidationResponseError } from "@/app/s/[surveyId]/types";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { TProduct } from "@formbricks/types/product";
|
||||
import { TResponse } from "@formbricks/types/responses";
|
||||
import { OTPInput } from "@formbricks/ui/OTPInput";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { validateSurveyPinAction } from "@/app/s/[surveyId]/actions";
|
||||
import { TSurvey } from "@formbricks/types/surveys";
|
||||
import { TSurveyPinValidationResponseError } from "@/app/s/[surveyId]/types";
|
||||
import LinkSurvey from "@/app/s/[surveyId]/components/LinkSurvey";
|
||||
import { cn } from "@formbricks/lib/cn";
|
||||
import { OTPInput } from "@formbricks/ui/OTPInput";
|
||||
import type { NextPage } from "next";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
interface LinkSurveyPinScreenProps {
|
||||
surveyId: string;
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
import LegalFooter from "@/app/s/[surveyId]/components/LegalFooter";
|
||||
|
||||
export default async function SurveyLayout({ children }) {
|
||||
return (
|
||||
<div className="flex h-full flex-col justify-between bg-white">
|
||||
<div className="h-full overflow-y-auto">{children}</div>
|
||||
<LegalFooter />
|
||||
</div>
|
||||
);
|
||||
return <div>{children}</div>;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
export const revalidate = REVALIDATION_INTERVAL;
|
||||
|
||||
import { validateSurveySingleUseId } from "@/app/lib/singleUseSurveys";
|
||||
import LegalFooter from "@/app/s/[surveyId]/components/LegalFooter";
|
||||
import LinkSurvey from "@/app/s/[surveyId]/components/LinkSurvey";
|
||||
import { MediaBackground } from "@/app/s/[surveyId]/components/MediaBackground";
|
||||
import PinScreen from "@/app/s/[surveyId]/components/PinScreen";
|
||||
import SurveyInactive from "@/app/s/[surveyId]/components/SurveyInactive";
|
||||
import { checkValidity } from "@/app/s/[surveyId]/lib/prefilling";
|
||||
@@ -12,6 +14,7 @@ import { getResponseBySingleUseId } from "@formbricks/lib/response/service";
|
||||
import { getSurvey } from "@formbricks/lib/survey/service";
|
||||
import { TResponse } from "@formbricks/types/responses";
|
||||
import type { Metadata } from "next";
|
||||
|
||||
import { notFound } from "next/navigation";
|
||||
import { getEmailVerificationStatus } from "./lib/helpers";
|
||||
import { ZId } from "@formbricks/types/environment";
|
||||
@@ -183,17 +186,22 @@ export default async function LinkSurveyPage({ params, searchParams }: LinkSurve
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<LinkSurvey
|
||||
survey={survey}
|
||||
product={product}
|
||||
userId={userId}
|
||||
emailVerificationStatus={emailVerificationStatus}
|
||||
prefillAnswer={isPrefilledAnswerValid ? prefillAnswer : null}
|
||||
singleUseId={isSingleUseSurvey ? singleUseId : undefined}
|
||||
singleUseResponse={singleUseResponse ? singleUseResponse : undefined}
|
||||
webAppUrl={WEBAPP_URL}
|
||||
responseCount={survey.welcomeCard.showResponseCount ? responseCount : undefined}
|
||||
/>
|
||||
);
|
||||
return survey ? (
|
||||
<div>
|
||||
<MediaBackground survey={survey}>
|
||||
<LinkSurvey
|
||||
survey={survey}
|
||||
product={product}
|
||||
userId={userId}
|
||||
emailVerificationStatus={emailVerificationStatus}
|
||||
prefillAnswer={isPrefilledAnswerValid ? prefillAnswer : null}
|
||||
singleUseId={isSingleUseSurvey ? singleUseId : undefined}
|
||||
singleUseResponse={singleUseResponse ? singleUseResponse : undefined}
|
||||
webAppUrl={WEBAPP_URL}
|
||||
responseCount={survey.welcomeCard.showResponseCount ? responseCount : undefined}
|
||||
/>
|
||||
</MediaBackground>
|
||||
<LegalFooter bgColor={survey.styling?.background?.bg || "#ffff"} />
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
||||
@@ -59,24 +59,17 @@ x-github-auth-enabled: &github_auth_enabled 0
|
||||
x-github-id: &github_id
|
||||
x-github-secret: &github_secret # Configure Google Login
|
||||
|
||||
|
||||
x-google-auth-enabled: &google_auth_enabled 0
|
||||
x-google-client-id: &google_client_id
|
||||
x-google-client-secret: &google_client_secret # Disable Sentry warning
|
||||
|
||||
|
||||
x-sentry-ignore-api-resolution-error: &sentry_ignore_api_resolution_error # Enable Sentry Error Tracking
|
||||
|
||||
|
||||
x-next-public-sentry-dsn: &next_public_sentry_dsn # Cron Secret
|
||||
|
||||
# Set this to a random string to secure your cron endpoints
|
||||
x-cron-secret: &cron_secret YOUR_CRON_SECRET
|
||||
|
||||
|
||||
# Configure ASSET_PREFIX_URL when you want to ship JS & CSS files from a complete URL instead of the current domain
|
||||
x-asset-prefix-url: &asset_prefix_url
|
||||
|
||||
services:
|
||||
postgres:
|
||||
restart: always
|
||||
@@ -130,7 +123,6 @@ services:
|
||||
GOOGLE_CLIENT_ID: *google_client_id
|
||||
GOOGLE_CLIENT_SECRET: *google_client_secret
|
||||
CRON_SECRET: *cron_secret
|
||||
ASSET_PREFIX_URL: *asset_prefix_url
|
||||
|
||||
volumes:
|
||||
postgres:
|
||||
|
||||
@@ -65,9 +65,6 @@ x-environment: &environment
|
||||
# GOOGLE_CLIENT_ID:
|
||||
# GOOGLE_CLIENT_SECRET:
|
||||
|
||||
# Configure ASSET_PREFIX_URL when you want to ship JS & CSS files from a complete URL instead of the current domain
|
||||
# ASSET_PREFIX_URL: *asset_prefix_url
|
||||
|
||||
services:
|
||||
postgres:
|
||||
restart: always
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.26.2",
|
||||
"@playwright/test": "^1.40.1",
|
||||
"@types/node": "20.10.1",
|
||||
"eslint-config-formbricks": "workspace:*",
|
||||
"husky": "^8.0.3",
|
||||
"lint-staged": "^15.1.0",
|
||||
@@ -52,11 +54,14 @@
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"packageManager": "pnpm@8.1.1",
|
||||
"packageManager": "pnpm@8.11.0",
|
||||
"nextBundleAnalysis": {
|
||||
"budget": 358400,
|
||||
"budgetPercentIncreaseRed": 20,
|
||||
"minimumChangeThreshold": 0,
|
||||
"showDetails": true
|
||||
},
|
||||
"dependencies": {
|
||||
"playwright": "^1.40.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
TSurveyClosedMessage,
|
||||
TSurveyHiddenFields,
|
||||
TSurveyProductOverwrites,
|
||||
TSurveyStyling,
|
||||
TSurveyQuestions,
|
||||
TSurveySingleUse,
|
||||
TSurveyThankYouCard,
|
||||
@@ -27,6 +28,7 @@ declare global {
|
||||
export type SurveyThankYouCard = TSurveyThankYouCard;
|
||||
export type SurveyHiddenFields = TSurveyHiddenFields;
|
||||
export type SurveyProductOverwrites = TSurveyProductOverwrites;
|
||||
export type SurveyStyling = TSurveyStyling;
|
||||
export type SurveyClosedMessage = TSurveyClosedMessage;
|
||||
export type SurveySingleUse = TSurveySingleUse;
|
||||
export type SurveyVerifyEmail = TSurveyVerifyEmail;
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Survey" ADD COLUMN "styling" JSONB;
|
||||
@@ -289,6 +289,9 @@ model Survey {
|
||||
/// @zod.custom(imports.ZSurveyProductOverwrites)
|
||||
/// [SurveyProductOverwrites]
|
||||
productOverwrites Json?
|
||||
/// @zod.custom(imports.ZSurveyStyling)
|
||||
/// [SurveyStyling]
|
||||
styling Json?
|
||||
/// @zod.custom(imports.ZSurveySingleUse)
|
||||
/// [SurveySingleUse]
|
||||
singleUse Json? @default("{\"enabled\": false, \"isEncrypted\": true}")
|
||||
|
||||
@@ -18,6 +18,7 @@ export {
|
||||
ZSurveyHiddenFields,
|
||||
ZSurveyClosedMessage,
|
||||
ZSurveyProductOverwrites,
|
||||
ZSurveyStyling,
|
||||
ZSurveyVerifyEmail,
|
||||
ZSurveySingleUse,
|
||||
} from "@formbricks/types/surveys";
|
||||
|
||||
@@ -154,7 +154,7 @@ export const authOptions: NextAuthOptions = {
|
||||
async signIn({ user, account }: any) {
|
||||
if (account.provider === "credentials" || account.provider === "token") {
|
||||
if (!user.emailVerified && !EMAIL_VERIFICATION_DISABLED) {
|
||||
return `/auth/verification-requested?email=${encodeURIComponent(user.email)}`;
|
||||
throw new Error("Email Verification is Pending");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -197,7 +197,9 @@ export const authOptions: NextAuthOptions = {
|
||||
await updateProfile(existingUserWithAccount.id, { email: user.email });
|
||||
return true;
|
||||
}
|
||||
return "/auth/login?error=Looks%20like%20you%20updated%20your%20email%20somewhere%20else.%0AA%20user%20with%20this%20new%20email%20exists%20already.";
|
||||
throw new Error(
|
||||
"Looks like you updated your email somewhere else. A user with this new email exists already."
|
||||
);
|
||||
}
|
||||
|
||||
// There is no existing account for this identity provider / account id
|
||||
@@ -206,7 +208,7 @@ export const authOptions: NextAuthOptions = {
|
||||
const existingUserWithEmail = await getProfileByEmail(user.email);
|
||||
|
||||
if (existingUserWithEmail) {
|
||||
return "/auth/login?error=A%20user%20with%20this%20email%20exists%20already.";
|
||||
throw new Error("A user with this email exists already.");
|
||||
}
|
||||
|
||||
const userProfile = await createProfile({
|
||||
|
||||
@@ -71,6 +71,34 @@ export const IS_S3_CONFIGURED: boolean =
|
||||
export const PRICING_USERTARGETING_FREE_MTU = 2500;
|
||||
export const PRICING_APPSURVEYS_FREE_RESPONSES = 250;
|
||||
|
||||
// Colors for Survey Bg
|
||||
export const colours = [
|
||||
"#FFF2D8",
|
||||
"#EAD7BB",
|
||||
"#BCA37F",
|
||||
"#113946",
|
||||
"#04364A",
|
||||
"#176B87",
|
||||
"#64CCC5",
|
||||
"#DAFFFB",
|
||||
"#132043",
|
||||
"#1F4172",
|
||||
"#F1B4BB",
|
||||
"#FDF0F0",
|
||||
"#001524",
|
||||
"#445D48",
|
||||
"#D6CC99",
|
||||
"#FDE5D4",
|
||||
"#BEADFA",
|
||||
"#D0BFFF",
|
||||
"#DFCCFB",
|
||||
"#FFF8C9",
|
||||
"#FF8080",
|
||||
"#FFCF96",
|
||||
"#F6FDC3",
|
||||
"#CDFAD5",
|
||||
];
|
||||
|
||||
// Rate Limiting
|
||||
export const SIGNUP_RATE_LIMIT = {
|
||||
interval: 60 * 60 * 1000, // 60 minutes
|
||||
|
||||
@@ -270,14 +270,15 @@ export const createResponseLegacy = async (responseInput: TResponseLegacyInput):
|
||||
if (responseInput.personId) {
|
||||
person = await getPerson(responseInput.personId);
|
||||
}
|
||||
const ttcTemp = responseInput.ttc;
|
||||
const ttcTemp = responseInput.ttc ?? {};
|
||||
const questionId = Object.keys(ttcTemp)[0];
|
||||
const ttc = responseInput.finished
|
||||
? {
|
||||
...ttcTemp,
|
||||
_total: ttcTemp[questionId], // Add _total property with the same value
|
||||
}
|
||||
: ttcTemp;
|
||||
const ttc =
|
||||
responseInput.finished && responseInput.ttc
|
||||
? {
|
||||
...ttcTemp,
|
||||
_total: ttcTemp[questionId], // Add _total property with the same value
|
||||
}
|
||||
: ttcTemp;
|
||||
const responsePrisma = await prisma.response.create({
|
||||
data: {
|
||||
survey: {
|
||||
@@ -512,7 +513,11 @@ export const updateResponse = async (
|
||||
...currentResponse.data,
|
||||
...responseInput.data,
|
||||
};
|
||||
const ttc = responseInput.finished ? calculateTtcTotal(responseInput.ttc) : responseInput.ttc;
|
||||
const ttc = responseInput.ttc
|
||||
? responseInput.finished
|
||||
? calculateTtcTotal(responseInput.ttc)
|
||||
: responseInput.ttc
|
||||
: {};
|
||||
|
||||
const responsePrisma = await prisma.response.update({
|
||||
where: {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { getAttributeClasses } from "../attributeClass/service";
|
||||
import { ITEMS_PER_PAGE, SERVICES_REVALIDATION_INTERVAL } from "../constants";
|
||||
import { displayCache } from "../display/cache";
|
||||
import { getDisplaysByPersonId } from "../display/service";
|
||||
import { personCache } from "../person/cache";
|
||||
import { productCache } from "../product/cache";
|
||||
import { getProductByEnvironmentId } from "../product/service";
|
||||
import { responseCache } from "../response/cache";
|
||||
@@ -44,6 +45,7 @@ export const selectSurvey = {
|
||||
verifyEmail: true,
|
||||
redirectUrl: true,
|
||||
productOverwrites: true,
|
||||
styling: true,
|
||||
surveyClosedMessage: true,
|
||||
singleUse: true,
|
||||
pin: true,
|
||||
@@ -540,6 +542,7 @@ export const createSurvey = async (environmentId: string, surveyBody: TSurveyInp
|
||||
};
|
||||
|
||||
export const duplicateSurvey = async (environmentId: string, surveyId: string) => {
|
||||
validateInputs([environmentId, ZId], [surveyId, ZId]);
|
||||
const existingSurvey = await getSurvey(surveyId);
|
||||
|
||||
if (!existingSurvey) {
|
||||
@@ -585,6 +588,7 @@ export const duplicateSurvey = async (environmentId: string, surveyId: string) =
|
||||
productOverwrites: existingSurvey.productOverwrites
|
||||
? JSON.parse(JSON.stringify(existingSurvey.productOverwrites))
|
||||
: Prisma.JsonNull,
|
||||
styling: existingSurvey.styling ? JSON.parse(JSON.stringify(existingSurvey.styling)) : Prisma.JsonNull,
|
||||
verifyEmail: existingSurvey.verifyEmail
|
||||
? JSON.parse(JSON.stringify(existingSurvey.verifyEmail))
|
||||
: Prisma.JsonNull,
|
||||
@@ -605,8 +609,10 @@ export const duplicateSurvey = async (environmentId: string, surveyId: string) =
|
||||
return newSurvey;
|
||||
};
|
||||
|
||||
export const getSyncSurveys = (environmentId: string, person: TPerson): Promise<TSurvey[]> =>
|
||||
unstable_cache(
|
||||
export const getSyncSurveys = (environmentId: string, person: TPerson): Promise<TSurvey[]> => {
|
||||
validateInputs([environmentId, ZId]);
|
||||
|
||||
return unstable_cache(
|
||||
async () => {
|
||||
const product = await getProductByEnvironmentId(environmentId);
|
||||
|
||||
@@ -685,9 +691,10 @@ export const getSyncSurveys = (environmentId: string, person: TPerson): Promise<
|
||||
|
||||
return surveys;
|
||||
},
|
||||
[`getSyncSurveys-${environmentId}`],
|
||||
[`getSyncSurveys-${environmentId}-${person.userId}`],
|
||||
{
|
||||
tags: [
|
||||
personCache.tag.byEnvironmentIdAndUserId(environmentId, person.userId),
|
||||
displayCache.tag.byPersonId(person.id),
|
||||
surveyCache.tag.byEnvironmentId(environmentId),
|
||||
productCache.tag.byEnvironmentId(environmentId),
|
||||
@@ -695,3 +702,4 @@ export const getSyncSurveys = (environmentId: string, person: TPerson): Promise<
|
||||
revalidate: SERVICES_REVALIDATION_INTERVAL,
|
||||
}
|
||||
)();
|
||||
};
|
||||
|
||||
@@ -13,11 +13,10 @@ export default function Headline({
|
||||
}: HeadlineProps) {
|
||||
return (
|
||||
<label htmlFor={questionId} className="text-heading mb-1.5 block text-base font-semibold leading-6">
|
||||
<div
|
||||
className={`flex items-center ${alignTextCenter ? "justify-center" : "mr-[3ch] justify-between"}`}>
|
||||
<div className={`flex items-center ${alignTextCenter ? "justify-center" : "justify-between"}`}>
|
||||
{headline}
|
||||
{!required && (
|
||||
<span className="text-info-text self-start text-sm font-normal leading-7" tabIndex={-1}>
|
||||
<span className="text-info-text ml-2 self-start text-sm font-normal leading-7" tabIndex={-1}>
|
||||
Optional
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -174,7 +174,7 @@ export function Survey({
|
||||
return (
|
||||
<>
|
||||
<AutoCloseWrapper survey={survey} onClose={onClose}>
|
||||
<div className="flex h-full w-full flex-col justify-between bg-[--fb-survey-background-color] px-6 pb-3 pt-6">
|
||||
<div className="flex h-full w-full flex-col justify-between rounded-lg bg-[--fb-survey-background-color] px-6 pb-3 pt-6">
|
||||
<div ref={contentRef} className={cn(loadingElement ? "animate-pulse opacity-60" : "", "my-auto")}>
|
||||
{survey.questions.length === 0 && !survey.welcomeCard.enabled && !survey.thankYouCard.enabled ? (
|
||||
// Handle the case when there are no questions and both welcome and thank you cards are disabled
|
||||
|
||||
@@ -37,11 +37,13 @@ export type TResponseNote = z.infer<typeof ZResponseNote>;
|
||||
export const ZResponseMeta = z.object({
|
||||
source: z.string().optional(),
|
||||
url: z.string().optional(),
|
||||
userAgent: z.object({
|
||||
browser: z.string().optional(),
|
||||
os: z.string().optional(),
|
||||
device: z.string().optional(),
|
||||
}),
|
||||
userAgent: z
|
||||
.object({
|
||||
browser: z.string().optional(),
|
||||
os: z.string().optional(),
|
||||
device: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type TResponseMeta = z.infer<typeof ZResponseMeta>;
|
||||
@@ -55,7 +57,7 @@ export const ZResponse = z.object({
|
||||
personAttributes: ZResponsePersonAttributes,
|
||||
finished: z.boolean(),
|
||||
data: ZResponseData,
|
||||
ttc: ZResponseTtc,
|
||||
ttc: ZResponseTtc.optional(),
|
||||
notes: z.array(ZResponseNote),
|
||||
tags: z.array(ZTag),
|
||||
meta: ZResponseMeta.nullable(),
|
||||
@@ -77,7 +79,7 @@ export const ZResponseInput = z.object({
|
||||
singleUseId: z.string().nullable().optional(),
|
||||
finished: z.boolean(),
|
||||
data: ZResponseData,
|
||||
ttc: ZResponseTtc,
|
||||
ttc: ZResponseTtc.optional(),
|
||||
meta: z
|
||||
.object({
|
||||
source: z.string().optional(),
|
||||
@@ -104,7 +106,7 @@ export type TResponseLegacyInput = z.infer<typeof ZResponseLegacyInput>;
|
||||
export const ZResponseUpdateInput = z.object({
|
||||
finished: z.boolean(),
|
||||
data: ZResponseData,
|
||||
ttc: ZResponseTtc,
|
||||
ttc: ZResponseTtc.optional(),
|
||||
});
|
||||
|
||||
export type TResponseUpdateInput = z.infer<typeof ZResponseUpdateInput>;
|
||||
@@ -118,7 +120,7 @@ export type TResponseWithSurvey = z.infer<typeof ZResponseWithSurvey>;
|
||||
export const ZResponseUpdate = z.object({
|
||||
finished: z.boolean(),
|
||||
data: ZResponseData,
|
||||
ttc: ZResponseTtc,
|
||||
ttc: ZResponseTtc.optional(),
|
||||
meta: z
|
||||
.object({
|
||||
url: z.string().optional(),
|
||||
|
||||
@@ -45,6 +45,24 @@ export const ZSurveyProductOverwrites = z.object({
|
||||
|
||||
export type TSurveyProductOverwrites = z.infer<typeof ZSurveyProductOverwrites>;
|
||||
|
||||
export const ZSurveyBackgroundBgType = z.enum(["animation", "color", "image"]);
|
||||
|
||||
export type TSurveyBackgroundBgType = z.infer<typeof ZSurveyBackgroundBgType>;
|
||||
|
||||
export const ZSurveyStylingBackground = z.object({
|
||||
bg: z.string().nullish(),
|
||||
bgType: z.enum(["animation", "color", "image"]).nullish(),
|
||||
brightness: z.number().nullish(),
|
||||
});
|
||||
|
||||
export type TSurveyStylingBackground = z.infer<typeof ZSurveyStylingBackground>;
|
||||
|
||||
export const ZSurveyStyling = z.object({
|
||||
background: ZSurveyStylingBackground.nullish(),
|
||||
});
|
||||
|
||||
export type TSurveyStyling = z.infer<typeof ZSurveyStyling>;
|
||||
|
||||
export const ZSurveyClosedMessage = z
|
||||
.object({
|
||||
enabled: z.boolean().optional(),
|
||||
@@ -379,6 +397,7 @@ export const ZSurvey = z.object({
|
||||
autoComplete: z.number().nullable(),
|
||||
closeOnDate: z.date().nullable(),
|
||||
productOverwrites: ZSurveyProductOverwrites.nullable(),
|
||||
styling: ZSurveyStyling.nullable(),
|
||||
surveyClosedMessage: ZSurveyClosedMessage.nullable(),
|
||||
singleUse: ZSurveySingleUse.nullable(),
|
||||
verifyEmail: ZSurveyVerifyEmail.nullable(),
|
||||
|
||||
77
playwright.config.ts
Normal file
77
playwright.config.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: "./tests",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "html",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
// baseURL: 'http://127.0.0.1:3000',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
},
|
||||
|
||||
// {
|
||||
// name: "firefox",
|
||||
// use: { ...devices["Desktop Firefox"] },
|
||||
// },
|
||||
|
||||
// {
|
||||
// name: "webkit",
|
||||
// use: { ...devices["Desktop Safari"] },
|
||||
// },
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: { ...devices['Pixel 5'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: { ...devices['iPhone 12'] },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: {
|
||||
command: "pnpm go",
|
||||
url: "http://127.0.0.1:3000",
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
});
|
||||
147
pnpm-lock.yaml
generated
147
pnpm-lock.yaml
generated
@@ -7,10 +7,20 @@ settings:
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
playwright:
|
||||
specifier: ^1.40.1
|
||||
version: 1.40.1
|
||||
devDependencies:
|
||||
'@changesets/cli':
|
||||
specifier: ^2.26.2
|
||||
version: 2.26.2
|
||||
'@playwright/test':
|
||||
specifier: ^1.40.1
|
||||
version: 1.40.1
|
||||
'@types/node':
|
||||
specifier: 20.10.1
|
||||
version: 20.10.1
|
||||
eslint-config-formbricks:
|
||||
specifier: workspace:*
|
||||
version: link:packages/eslint-config-formbricks
|
||||
@@ -277,7 +287,7 @@ importers:
|
||||
version: 7.2.0(typescript@5.3.2)
|
||||
vite:
|
||||
specifier: ^4.4.11
|
||||
version: 4.5.0
|
||||
version: 4.5.0(@types/node@20.10.1)
|
||||
|
||||
apps/web:
|
||||
dependencies:
|
||||
@@ -443,16 +453,16 @@ importers:
|
||||
version: 9.0.0(eslint@8.54.0)
|
||||
eslint-config-turbo:
|
||||
specifier: latest
|
||||
version: 1.8.8(eslint@8.54.0)
|
||||
version: 1.10.16(eslint@8.54.0)
|
||||
terser:
|
||||
specifier: ^5.24.0
|
||||
version: 5.24.0
|
||||
vite:
|
||||
specifier: ^5.0.4
|
||||
version: 5.0.4(terser@5.24.0)
|
||||
version: 5.0.4(@types/node@20.10.1)(terser@5.24.0)
|
||||
vite-plugin-dts:
|
||||
specifier: ^3.6.4
|
||||
version: 3.6.4(typescript@5.3.2)(vite@5.0.4)
|
||||
version: 3.6.4(@types/node@20.10.1)(typescript@5.3.2)(vite@5.0.4)
|
||||
|
||||
packages/database:
|
||||
dependencies:
|
||||
@@ -526,7 +536,7 @@ importers:
|
||||
version: 9.0.0(eslint@8.54.0)
|
||||
eslint-config-turbo:
|
||||
specifier: latest
|
||||
version: 1.8.8(eslint@8.54.0)
|
||||
version: 1.10.16(eslint@8.54.0)
|
||||
eslint-plugin-react:
|
||||
specifier: 7.33.2
|
||||
version: 7.33.2(eslint@8.54.0)
|
||||
@@ -586,13 +596,13 @@ importers:
|
||||
version: 9.0.0(eslint@8.54.0)
|
||||
eslint-config-turbo:
|
||||
specifier: latest
|
||||
version: 1.8.8(eslint@8.54.0)
|
||||
version: 1.10.16(eslint@8.54.0)
|
||||
isomorphic-fetch:
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
jest:
|
||||
specifier: ^29.7.0
|
||||
version: 29.7.0
|
||||
version: 29.7.0(@types/node@20.10.1)
|
||||
jest-environment-jsdom:
|
||||
specifier: ^29.7.0
|
||||
version: 29.7.0
|
||||
@@ -604,10 +614,10 @@ importers:
|
||||
version: 5.24.0
|
||||
vite:
|
||||
specifier: ^5.0.4
|
||||
version: 5.0.4(terser@5.24.0)
|
||||
version: 5.0.4(@types/node@20.10.1)(terser@5.24.0)
|
||||
vite-plugin-dts:
|
||||
specifier: ^3.6.4
|
||||
version: 3.6.4(typescript@5.3.2)(vite@5.0.4)
|
||||
version: 3.6.4(@types/node@20.10.1)(typescript@5.3.2)(vite@5.0.4)
|
||||
|
||||
packages/lib:
|
||||
dependencies:
|
||||
@@ -713,7 +723,7 @@ importers:
|
||||
version: 9.0.0(eslint@8.54.0)
|
||||
eslint-config-turbo:
|
||||
specifier: latest
|
||||
version: 1.8.8(eslint@8.54.0)
|
||||
version: 1.10.16(eslint@8.54.0)
|
||||
postcss:
|
||||
specifier: ^8.4.31
|
||||
version: 8.4.31
|
||||
@@ -728,10 +738,10 @@ importers:
|
||||
version: 5.24.0
|
||||
vite:
|
||||
specifier: ^5.0.4
|
||||
version: 5.0.4(terser@5.24.0)
|
||||
version: 5.0.4(@types/node@20.10.1)(terser@5.24.0)
|
||||
vite-plugin-dts:
|
||||
specifier: ^3.6.4
|
||||
version: 3.6.4(typescript@5.3.2)(vite@5.0.4)
|
||||
version: 3.6.4(@types/node@20.10.1)(typescript@5.3.2)(vite@5.0.4)
|
||||
vite-tsconfig-paths:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1(typescript@5.3.2)(vite@5.0.4)
|
||||
@@ -4294,7 +4304,7 @@ packages:
|
||||
magic-string: 0.27.0
|
||||
react-docgen-typescript: 2.2.2(typescript@5.3.2)
|
||||
typescript: 5.3.2
|
||||
vite: 4.5.0
|
||||
vite: 4.5.0(@types/node@20.10.1)
|
||||
dev: true
|
||||
|
||||
/@jridgewell/gen-mapping@0.3.3:
|
||||
@@ -4668,24 +4678,24 @@ packages:
|
||||
'@types/react': 18.2.28
|
||||
react: 18.2.0
|
||||
|
||||
/@microsoft/api-extractor-model@7.28.2:
|
||||
/@microsoft/api-extractor-model@7.28.2(@types/node@20.10.1):
|
||||
resolution: {integrity: sha512-vkojrM2fo3q4n4oPh4uUZdjJ2DxQ2+RnDQL/xhTWSRUNPF6P4QyrvY357HBxbnltKcYu+nNNolVqc6TIGQ73Ig==}
|
||||
dependencies:
|
||||
'@microsoft/tsdoc': 0.14.2
|
||||
'@microsoft/tsdoc-config': 0.16.2
|
||||
'@rushstack/node-core-library': 3.61.0
|
||||
'@rushstack/node-core-library': 3.61.0(@types/node@20.10.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
dev: true
|
||||
|
||||
/@microsoft/api-extractor@7.38.0:
|
||||
/@microsoft/api-extractor@7.38.0(@types/node@20.10.1):
|
||||
resolution: {integrity: sha512-e1LhZYnfw+JEebuY2bzhw0imDCl1nwjSThTrQqBXl40hrVo6xm3j/1EpUr89QyzgjqmAwek2ZkIVZbrhaR+cqg==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@microsoft/api-extractor-model': 7.28.2
|
||||
'@microsoft/api-extractor-model': 7.28.2(@types/node@20.10.1)
|
||||
'@microsoft/tsdoc': 0.14.2
|
||||
'@microsoft/tsdoc-config': 0.16.2
|
||||
'@rushstack/node-core-library': 3.61.0
|
||||
'@rushstack/node-core-library': 3.61.0(@types/node@20.10.1)
|
||||
'@rushstack/rig-package': 0.5.1
|
||||
'@rushstack/ts-command-line': 4.16.1
|
||||
colors: 1.2.5
|
||||
@@ -5239,6 +5249,14 @@ packages:
|
||||
requiresBuild: true
|
||||
optional: true
|
||||
|
||||
/@playwright/test@1.40.1:
|
||||
resolution: {integrity: sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==}
|
||||
engines: {node: '>=16'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
playwright: 1.40.1
|
||||
dev: true
|
||||
|
||||
/@preact/preset-vite@2.7.0(@babel/core@7.23.5)(preact@10.19.2)(vite@5.0.4):
|
||||
resolution: {integrity: sha512-m5N0FVtxbCCDxNk55NGhsRpKJChYcupcuQHzMJc/Bll07IKZKn8amwYciyKFS9haU6AgzDAJ/ewvApr6Qg1DHw==}
|
||||
peerDependencies:
|
||||
@@ -5254,7 +5272,7 @@ packages:
|
||||
debug: 4.3.4
|
||||
kolorist: 1.8.0
|
||||
resolve: 1.22.8
|
||||
vite: 5.0.4(terser@5.24.0)
|
||||
vite: 5.0.4(@types/node@20.10.1)(terser@5.24.0)
|
||||
transitivePeerDependencies:
|
||||
- preact
|
||||
- supports-color
|
||||
@@ -5288,7 +5306,7 @@ packages:
|
||||
'@prefresh/utils': 1.2.0
|
||||
'@rollup/pluginutils': 4.2.1
|
||||
preact: 10.19.2
|
||||
vite: 5.0.4(terser@5.24.0)
|
||||
vite: 5.0.4(@types/node@20.10.1)(terser@5.24.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
@@ -7030,7 +7048,7 @@ packages:
|
||||
resolution: {integrity: sha512-6i/8UoL0P5y4leBIGzvkZdS85RDMG9y1ihZzmTZQ5LdHUYmZ7pKFoj8X0236s3lusPs1Fa5HTQUpwI+UfTcmeA==}
|
||||
dev: true
|
||||
|
||||
/@rushstack/node-core-library@3.61.0:
|
||||
/@rushstack/node-core-library@3.61.0(@types/node@20.10.1):
|
||||
resolution: {integrity: sha512-tdOjdErme+/YOu4gPed3sFS72GhtWCgNV9oDsHDnoLY5oDfwjKUc9Z+JOZZ37uAxcm/OCahDHfuu2ugqrfWAVQ==}
|
||||
peerDependencies:
|
||||
'@types/node': '*'
|
||||
@@ -7038,6 +7056,7 @@ packages:
|
||||
'@types/node':
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/node': 20.10.1
|
||||
colors: 1.2.5
|
||||
fs-extra: 7.0.1
|
||||
import-lazy: 4.0.0
|
||||
@@ -8150,7 +8169,7 @@ packages:
|
||||
magic-string: 0.30.5
|
||||
rollup: 3.29.4
|
||||
typescript: 5.3.2
|
||||
vite: 4.5.0
|
||||
vite: 4.5.0(@types/node@20.10.1)
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
@@ -8515,7 +8534,7 @@ packages:
|
||||
react: 18.2.0
|
||||
react-docgen: 6.0.4
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
vite: 4.5.0
|
||||
vite: 4.5.0(@types/node@20.10.1)
|
||||
transitivePeerDependencies:
|
||||
- '@preact/preset-vite'
|
||||
- encoding
|
||||
@@ -8968,7 +8987,7 @@ packages:
|
||||
/@types/jsonwebtoken@9.0.5:
|
||||
resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==}
|
||||
dependencies:
|
||||
'@types/node': 20.9.0
|
||||
'@types/node': 20.10.1
|
||||
dev: true
|
||||
|
||||
/@types/keyv@3.1.4:
|
||||
@@ -9052,18 +9071,6 @@ packages:
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
|
||||
/@types/node@20.8.6:
|
||||
resolution: {integrity: sha512-eWO4K2Ji70QzKUqRy6oyJWUeB7+g2cRagT3T/nxYibYcT4y2BDL8lqolRXjTHmkZCdJfIPaY73KbJAZmcryxTQ==}
|
||||
dependencies:
|
||||
undici-types: 5.25.3
|
||||
dev: true
|
||||
|
||||
/@types/node@20.9.0:
|
||||
resolution: {integrity: sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==}
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
dev: true
|
||||
|
||||
/@types/normalize-package-data@2.4.3:
|
||||
resolution: {integrity: sha512-ehPtgRgaULsFG8x0NeYJvmyH1hmlfsNLujHe9dQEia/7MAJYdzMSi19JtchUHjmBA6XC/75dK55mzZH+RyieSg==}
|
||||
|
||||
@@ -9080,7 +9087,7 @@ packages:
|
||||
/@types/qrcode@1.5.5:
|
||||
resolution: {integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==}
|
||||
dependencies:
|
||||
'@types/node': 20.8.6
|
||||
'@types/node': 20.10.1
|
||||
dev: true
|
||||
|
||||
/@types/qs@6.9.9:
|
||||
@@ -9647,7 +9654,7 @@ packages:
|
||||
'@babel/plugin-transform-react-jsx-source': 7.22.5(@babel/core@7.23.5)
|
||||
magic-string: 0.27.0
|
||||
react-refresh: 0.14.0
|
||||
vite: 4.5.0
|
||||
vite: 4.5.0(@types/node@20.10.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
@@ -9663,7 +9670,7 @@ packages:
|
||||
'@babel/plugin-transform-react-jsx-source': 7.22.5(@babel/core@7.23.2)
|
||||
'@types/babel__core': 7.20.3
|
||||
react-refresh: 0.14.0
|
||||
vite: 4.5.0
|
||||
vite: 4.5.0(@types/node@20.10.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
@@ -11417,7 +11424,7 @@ packages:
|
||||
capture-stack-trace: 1.0.2
|
||||
dev: false
|
||||
|
||||
/create-jest@29.7.0:
|
||||
/create-jest@29.7.0(@types/node@20.10.1):
|
||||
resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
hasBin: true
|
||||
@@ -12385,13 +12392,13 @@ packages:
|
||||
eslint: 8.54.0
|
||||
dev: true
|
||||
|
||||
/eslint-config-turbo@1.8.8(eslint@8.54.0):
|
||||
resolution: {integrity: sha512-+yT22sHOT5iC1sbBXfLIdXfbZuiv9bAyOXsxTxFCWelTeFFnANqmuKB3x274CFvf7WRuZ/vYP/VMjzU9xnFnxA==}
|
||||
/eslint-config-turbo@1.10.16(eslint@8.54.0):
|
||||
resolution: {integrity: sha512-O3NQI72bQHV7FvSC6lWj66EGx8drJJjuT1kuInn6nbMLOHdMBhSUX/8uhTAlHRQdlxZk2j9HtgFCIzSc93w42g==}
|
||||
peerDependencies:
|
||||
eslint: '>6.6.0'
|
||||
dependencies:
|
||||
eslint: 8.54.0
|
||||
eslint-plugin-turbo: 1.8.8(eslint@8.54.0)
|
||||
eslint-plugin-turbo: 1.10.16(eslint@8.54.0)
|
||||
dev: true
|
||||
|
||||
/eslint-import-resolver-node@0.3.9:
|
||||
@@ -12575,11 +12582,12 @@ packages:
|
||||
- typescript
|
||||
dev: true
|
||||
|
||||
/eslint-plugin-turbo@1.8.8(eslint@8.54.0):
|
||||
resolution: {integrity: sha512-zqyTIvveOY4YU5jviDWw9GXHd4RiKmfEgwsjBrV/a965w0PpDwJgEUoSMB/C/dU310Sv9mF3DSdEjxjJLaw6rA==}
|
||||
/eslint-plugin-turbo@1.10.16(eslint@8.54.0):
|
||||
resolution: {integrity: sha512-ZjrR88MTN64PNGufSEcM0tf+V1xFYVbeiMeuIqr0aiABGomxFLo4DBkQ7WI4WzkZtWQSIA2sP+yxqSboEfL9MQ==}
|
||||
peerDependencies:
|
||||
eslint: '>6.6.0'
|
||||
dependencies:
|
||||
dotenv: 16.0.3
|
||||
eslint: 8.54.0
|
||||
dev: true
|
||||
|
||||
@@ -13234,6 +13242,13 @@ packages:
|
||||
/fs.realpath@1.0.0:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
|
||||
/fsevents@2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
optional: true
|
||||
|
||||
/fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
@@ -14702,7 +14717,7 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/jest-cli@29.7.0:
|
||||
/jest-cli@29.7.0(@types/node@20.10.1):
|
||||
resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
hasBin: true
|
||||
@@ -14716,7 +14731,7 @@ packages:
|
||||
'@jest/test-result': 29.7.0
|
||||
'@jest/types': 29.6.3
|
||||
chalk: 4.1.2
|
||||
create-jest: 29.7.0
|
||||
create-jest: 29.7.0(@types/node@20.10.1)
|
||||
exit: 0.1.2
|
||||
import-local: 3.1.0
|
||||
jest-config: 29.7.0(@types/node@20.10.1)
|
||||
@@ -14811,7 +14826,7 @@ packages:
|
||||
'@jest/fake-timers': 29.7.0
|
||||
'@jest/types': 29.6.3
|
||||
'@types/jsdom': 20.0.1
|
||||
'@types/node': 20.8.6
|
||||
'@types/node': 20.10.1
|
||||
jest-mock: 29.7.0
|
||||
jest-util: 29.7.0
|
||||
jsdom: 20.0.3
|
||||
@@ -15102,7 +15117,7 @@ packages:
|
||||
supports-color: 8.1.1
|
||||
dev: true
|
||||
|
||||
/jest@29.7.0:
|
||||
/jest@29.7.0(@types/node@20.10.1):
|
||||
resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==}
|
||||
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
|
||||
hasBin: true
|
||||
@@ -15115,7 +15130,7 @@ packages:
|
||||
'@jest/core': 29.7.0
|
||||
'@jest/types': 29.6.3
|
||||
import-local: 3.1.0
|
||||
jest-cli: 29.7.0
|
||||
jest-cli: 29.7.0(@types/node@20.10.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- babel-plugin-macros
|
||||
@@ -17624,6 +17639,20 @@ packages:
|
||||
dependencies:
|
||||
find-up: 5.0.0
|
||||
|
||||
/playwright-core@1.40.1:
|
||||
resolution: {integrity: sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==}
|
||||
engines: {node: '>=16'}
|
||||
hasBin: true
|
||||
|
||||
/playwright@1.40.1:
|
||||
resolution: {integrity: sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==}
|
||||
engines: {node: '>=16'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
playwright-core: 1.40.1
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
/pngjs@5.0.0:
|
||||
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
@@ -20578,10 +20607,6 @@ packages:
|
||||
which-boxed-primitive: 1.0.2
|
||||
dev: true
|
||||
|
||||
/undici-types@5.25.3:
|
||||
resolution: {integrity: sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==}
|
||||
dev: true
|
||||
|
||||
/undici-types@5.26.5:
|
||||
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
||||
|
||||
@@ -21035,7 +21060,7 @@ packages:
|
||||
vfile-message: 3.1.4
|
||||
dev: false
|
||||
|
||||
/vite-plugin-dts@3.6.4(typescript@5.3.2)(vite@5.0.4):
|
||||
/vite-plugin-dts@3.6.4(@types/node@20.10.1)(typescript@5.3.2)(vite@5.0.4):
|
||||
resolution: {integrity: sha512-yOVhUI/kQhtS6lCXRYYLv2UUf9bftcwQK9ROxCX2ul17poLQs02ctWX7+vXB8GPRzH8VCK3jebEFtPqqijXx6w==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
peerDependencies:
|
||||
@@ -21045,13 +21070,13 @@ packages:
|
||||
vite:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@microsoft/api-extractor': 7.38.0
|
||||
'@microsoft/api-extractor': 7.38.0(@types/node@20.10.1)
|
||||
'@rollup/pluginutils': 5.0.5(rollup@2.78.0)
|
||||
'@vue/language-core': 1.8.22(typescript@5.3.2)
|
||||
debug: 4.3.4
|
||||
kolorist: 1.8.0
|
||||
typescript: 5.3.2
|
||||
vite: 5.0.4(terser@5.24.0)
|
||||
vite: 5.0.4(@types/node@20.10.1)(terser@5.24.0)
|
||||
vue-tsc: 1.8.22(typescript@5.3.2)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
@@ -21070,13 +21095,13 @@ packages:
|
||||
debug: 4.3.4
|
||||
globrex: 0.1.2
|
||||
tsconfck: 2.1.2(typescript@5.3.2)
|
||||
vite: 5.0.4(terser@5.24.0)
|
||||
vite: 5.0.4(@types/node@20.10.1)(terser@5.24.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
dev: true
|
||||
|
||||
/vite@4.5.0:
|
||||
/vite@4.5.0(@types/node@20.10.1):
|
||||
resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
@@ -21104,6 +21129,7 @@ packages:
|
||||
terser:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/node': 20.10.1
|
||||
esbuild: 0.18.20
|
||||
postcss: 8.4.31
|
||||
rollup: 3.29.4
|
||||
@@ -21111,7 +21137,7 @@ packages:
|
||||
fsevents: 2.3.3
|
||||
dev: true
|
||||
|
||||
/vite@5.0.4(terser@5.24.0):
|
||||
/vite@5.0.4(@types/node@20.10.1)(terser@5.24.0):
|
||||
resolution: {integrity: sha512-RzAr8LSvM8lmhB4tQ5OPcBhpjOZRZjuxv9zO5UcxeoY2bd3kP3Ticd40Qma9/BqZ8JS96Ll/jeBX9u+LJZrhVg==}
|
||||
engines: {node: ^18.0.0 || >=20.0.0}
|
||||
hasBin: true
|
||||
@@ -21139,6 +21165,7 @@ packages:
|
||||
terser:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/node': 20.10.1
|
||||
esbuild: 0.19.5
|
||||
postcss: 8.4.31
|
||||
rollup: 4.4.1
|
||||
|
||||
41
tests/initial.spec.ts
Normal file
41
tests/initial.spec.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
const email = "testp@gmail.com";
|
||||
const password = "Test@123";
|
||||
|
||||
async function createUser(page) {
|
||||
await page.goto("http://localhost:3000/auth/signup");
|
||||
await page.getByText("Continue with Email").click();
|
||||
await page.fill('input[name="name"]', "test");
|
||||
await page.press('input[name="name"]', "Tab");
|
||||
await page.fill('input[name="email"]', email);
|
||||
await page.press('input[name="email"]', "Tab");
|
||||
await page.fill('input[name="password"]', password);
|
||||
await page.press('input[name="password"]', "Enter");
|
||||
}
|
||||
|
||||
class LoginPage {
|
||||
async login(page, email, password) {
|
||||
await page.getByText("Login").click();
|
||||
await page.getByText("Login with Email").click();
|
||||
await page.fill('input[name="email"]', email);
|
||||
await page.press('input[name="email"]', "Tab");
|
||||
await page.fill('input[name="password"]', password);
|
||||
await page.press('input[name="password"]', "Enter");
|
||||
}
|
||||
}
|
||||
|
||||
test("create account, login, and complete onboarding", async ({ page }) => {
|
||||
const loginPage = new LoginPage();
|
||||
await createUser(page);
|
||||
await loginPage.login(page, email, password);
|
||||
await completeOnboarding(page);
|
||||
});
|
||||
await expect(page).toHaveTitle(/Your Surveys | Formbricks/);
|
||||
}
|
||||
|
||||
test("create account, login, and complete onboarding", async ({ page }) => {
|
||||
await createUser(page);
|
||||
await loginUser(page);
|
||||
await completeOnboarding(page);
|
||||
});
|
||||
Reference in New Issue
Block a user