This commit is contained in:
knugget
2023-02-24 17:25:40 +01:00
22 changed files with 836 additions and 313 deletions
+7 -7
View File
@@ -14,10 +14,10 @@
"@formbricks/pmf": "workspace:*",
"@formbricks/react": "workspace:*",
"@formbricks/ui": "workspace:*",
"@heroicons/react": "^2.0.14",
"@heroicons/react": "^2.0.16",
"@tailwindcss/forms": "^0.5.3",
"clsx": "^1.2.1",
"next": "latest",
"next": "13.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-toastify": "^9.1.1"
@@ -25,13 +25,13 @@
"devDependencies": {
"@formbricks/tailwind-config": "workspace:*",
"@formbricks/tsconfig": "workspace:*",
"@types/node": "^18.11.18",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@types/node": "^18.14.1",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"autoprefixer": "^10.4.13",
"eslint-config-formbricks": "workspace:*",
"postcss": "^8.4.21",
"tailwindcss": "^3.2.4",
"typescript": "^4.9.4"
"tailwindcss": "^3.2.7",
"typescript": "^4.9.5"
}
}
@@ -41,7 +41,7 @@ function Header({ navigation }: any) {
className={clsx(
"sticky top-0 z-50 flex flex-wrap items-center justify-between bg-slate-100 px-4 py-5 shadow-md shadow-slate-900/5 transition duration-500 dark:shadow-none sm:px-6 lg:px-8",
isScrolled
? "[@supports(backdrop-filter:blur(0))]:bg-slate-100/75 dark:[@supports(backdrop-filter:blur(0))]:bg-slate-900/75 bg-slate-100/90 backdrop-blur dark:bg-slate-900/90"
? "bg-slate-100/90 backdrop-blur dark:bg-slate-900/90 [@supports(backdrop-filter:blur(0))]:bg-slate-100/75 dark:[@supports(backdrop-filter:blur(0))]:bg-slate-900/75"
: "dark:bg-transparent"
)}>
<div className="mr-6 flex lg:hidden">
+16 -16
View File
@@ -10,28 +10,28 @@
"lint": "next lint"
},
"dependencies": {
"@docsearch/react": "^3.3.2",
"@docsearch/react": "^3.3.3",
"@formbricks/engine-react": "workspace:*",
"@formbricks/feedback": "workspace:*",
"@formbricks/pmf": "workspace:*",
"@formbricks/react": "workspace:*",
"@formbricks/ui": "workspace:*",
"@headlessui/react": "^1.7.8",
"@heroicons/react": "^2.0.14",
"@headlessui/react": "^1.7.11",
"@heroicons/react": "^2.0.16",
"@mapbox/rehype-prism": "^0.8.0",
"@mdx-js/loader": "^2.2.1",
"@mdx-js/react": "^2.2.1",
"@next/mdx": "^13.1.6",
"@mdx-js/loader": "^2.3.0",
"@mdx-js/react": "^2.3.0",
"@next/mdx": "^13.2.1",
"add": "^2.0.6",
"clsx": "^1.2.1",
"lottie-web": "^5.10.2",
"next": "13.1.6",
"next-plausible": "^3.7.1",
"next-sitemap": "^3.1.48",
"next": "13.2.1",
"next-plausible": "^3.7.2",
"next-sitemap": "^3.1.52",
"prism-react-renderer": "^1.3.5",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.43.0",
"react-hook-form": "^7.43.2",
"react-responsive-embed": "^2.1.0",
"remark-gfm": "^3.0.1",
"sharp": "^0.31.3"
@@ -39,14 +39,14 @@
"devDependencies": {
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.9",
"@types/node": "18.11.18",
"@types/react": "18.0.27",
"@types/react-dom": "18.0.10",
"@types/node": "18.14.1",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.11",
"autoprefixer": "^10.4.13",
"eslint": "8.33.0",
"eslint": "8.34.0",
"eslint-config-formbricks": "workspace:*",
"postcss": "^8.4.21",
"tailwindcss": "^3.2.4",
"typescript": "4.9.4"
"tailwindcss": "^3.2.7",
"typescript": "4.9.5"
}
}
+13 -13
View File
@@ -14,40 +14,40 @@
"@formbricks/engine-react": "workspace:*",
"@formbricks/feedback": "workspace:*",
"@formbricks/ui": "workspace:*",
"@headlessui/react": "^1.7.8",
"@heroicons/react": "^2.0.14",
"@sentry/nextjs": "^7.34.0",
"@vercel/analytics": "^0.1.8",
"@headlessui/react": "^1.7.11",
"@heroicons/react": "^2.0.16",
"@sentry/nextjs": "^7.38.0",
"@vercel/analytics": "^0.1.10",
"bcryptjs": "^2.4.3",
"clsx": "^1.2.1",
"date-fns": "^2.29.3",
"jsonwebtoken": "^9.0.0",
"next": "^13.1.6",
"next-auth": "^4.19.0",
"next": "^13.2.1",
"next-auth": "^4.19.2",
"next-transpile-modules": "^10.0.0",
"nodemailer": "^6.9.1",
"platform": "^1.3.6",
"posthog-js": "^1.45.1",
"posthog-js": "^1.46.2",
"prismjs": "^1.29.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^4.7.1",
"react-loader-spinner": "^5.3.4",
"react-toastify": "^9.1.1",
"stripe": "^11.8.0",
"stripe": "^11.12.0",
"swr": "^2.0.3"
},
"devDependencies": {
"@formbricks/database": "workspace:*",
"@formbricks/tailwind-config": "workspace:*",
"@formbricks/tsconfig": "workspace:*",
"@types/node": "^18.11.18",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@types/node": "^18.14.1",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"autoprefixer": "^10.4.13",
"eslint": "^8.33.0",
"eslint": "^8.34.0",
"eslint-config-formbricks": "workspace:*",
"postcss": "^8.4.21",
"typescript": "^4.9.4"
"typescript": "^4.9.5"
}
}
@@ -46,7 +46,7 @@ export default function NewFormModal({ open, setOpen, organisationId }: FormOnbo
name: "Product-Market Fit Survey",
description: "Leverage the Superhuman PMF engine.",
icon: PMFIcon,
needsUpgrade: process.env.NEXT_PUBLIC_IS_FORMBRICKS_CLOUD && organisation?.plan === "free",
needsUpgrade: process.env.NEXT_PUBLIC_IS_FORMBRICKS_CLOUD === "1" && organisation?.plan === "free",
},
],
[organisation]
@@ -9,6 +9,7 @@ import clsx from "clsx";
import Link from "next/link";
import { useRouter } from "next/router";
import { toast } from "react-toastify";
import Tagging from "../shared/Tagging";
export default function PMFTimeline({ submissions }) {
const router = useRouter();
@@ -130,6 +131,7 @@ export default function PMFTimeline({ submissions }) {
</ul>
</div>
</div>
<Tagging submission={submission} />
<div className=" bg-gray-50 p-4 sm:p-6">
<div className="flex w-full justify-between gap-4">
<div>
@@ -7,6 +7,7 @@ import clsx from "clsx";
import Link from "next/link";
import { useRouter } from "next/router";
import { toast } from "react-toastify";
import Tagging from "../shared/Tagging";
export default function FeedbackTimeline({ submissions }) {
const router = useRouter();
@@ -103,6 +104,8 @@ export default function FeedbackTimeline({ submissions }) {
</p>
</div>
</div>
<Tagging submission={submission} />
<div className=" bg-slate-50 p-4 sm:p-6">
<div className="flex w-full justify-between gap-4">
<div>
@@ -9,6 +9,7 @@ import clsx from "clsx";
import Link from "next/link";
import { useRouter } from "next/router";
import { toast } from "react-toastify";
import Tagging from "../shared/Tagging";
export default function PMFTimeline({ submissions }) {
const router = useRouter();
@@ -103,7 +104,6 @@ export default function PMFTimeline({ submissions }) {
Somewhat disappointed
</span>
) : null}
<div className="text-sm text-slate-400">
<time dateTime={convertDateTimeString(submission.createdAt)}>
{new Date().getTime() - new Date(submission.createdAt).getTime() >
@@ -132,6 +132,8 @@ export default function PMFTimeline({ submissions }) {
</ul>
</div>
</div>
<Tagging submission={submission} />
<div className=" bg-slate-50 p-4 sm:p-6">
<div className="flex w-full justify-between gap-4">
<div>
@@ -154,10 +156,12 @@ export default function PMFTimeline({ submissions }) {
{parseUserAgent(submission.meta.userAgent)}
</p>
</div>
<div>
<p className="text-sm font-thin text-slate-500">Page</p>
<p className="text-sm text-slate-500">{submission.data.pageUrl}</p>
</div>
{submission.data.pageUrl && (
<div>
<p className="text-sm font-thin text-slate-500">Page</p>
<p className="text-sm text-slate-500">{submission.data.pageUrl}</p>
</div>
)}
</div>
<div className="mt-8 flex w-full justify-end">
@@ -5,7 +5,7 @@ import { camelToTitle, filterUniqueById } from "@/lib/utils";
import { RectangleGroupIcon } from "@heroicons/react/24/outline";
import clsx from "clsx";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { BsPin, BsPinFill } from "react-icons/bs";
interface Filter {
@@ -39,6 +39,19 @@ export default function FilterNavigation({
const { form, isLoadingForm, isErrorForm } = useForm(formId?.toString(), organisationId?.toString());
// get all the tags from the submissions
const tags = useMemo(() => {
const tags = [];
for (const submission of submissions) {
for (const tag of submission.tags) {
if (!tags.includes(tag)) {
tags.push(tag);
}
}
}
return tags;
}, [submissions]);
// filter submissions based on selected filters
useEffect(() => {
if (form) {
@@ -55,8 +68,32 @@ export default function FilterNavigation({
setNumTotalSubmissions([...submissions].filter((s) => !s.archived).length);
}
continue;
} else if (filter.type === "tags") {
const isAllActive = filter.options.find((option) => option.value === "all")?.active;
// no filter is all is selected, if not keep on filtering
if (!isAllActive) {
// filter for all other types
let listOfValidFilteredSubmissions = [];
for (const option of filter.options) {
if (option.active || option.pinned) {
listOfValidFilteredSubmissions.push(
newFilteredSubmissions.filter((submission) => {
if (submission.tags) {
return submission.tags.includes(option.value);
}
})
);
}
}
// add pinned submissions to the top
const flattenedListOfValidFilteredSubmissions = listOfValidFilteredSubmissions.flat();
newFilteredSubmissions = filterUniqueById(flattenedListOfValidFilteredSubmissions);
}
continue;
}
const isAllActive = filter.options.find((option) => option.value === "all")?.active;
// no filter is all is selected, if not keep on filtering
if (!isAllActive) {
@@ -178,9 +215,18 @@ export default function FilterNavigation({
{ value: "archived", label: "Archived", active: false },
],
});
// add tag selection to filters
filters.push({
name: "tags",
label: "Tags",
type: "tags",
options: [{ value: "all", label: "All", active: true, pinned: false }].concat([
...tags.map((tag) => ({ value: tag, label: tag, active: false, pinned: false })),
]),
});
setFilters(filters);
}
}, [form, limitFields]);
}, [form, limitFields, tags]);
if (isLoadingForm) {
return <LoadingSpinner />;
@@ -198,24 +244,24 @@ export default function FilterNavigation({
<h4 className="text-slate-600">{camelToTitle(filter.name)}</h4>
</div>
{filter.options.map((option) => (
<button
key={option.value}
type="button"
onClick={() => {
chooseOptionFilter(filter.name, option.value, option.active);
}}
className={clsx(
option.active || option.pinned
? "bg-slate-200 text-slate-900"
: "text-slate-600 hover:bg-slate-100 hover:text-slate-900",
"group my-1 flex w-full items-center rounded-md px-3 py-1.5 text-sm font-medium"
)}
aria-current={option.active ? "page" : undefined}>
<div className={clsx("-ml-1 mr-3 h-2 w-2 flex-shrink-0 rounded-full")} />
<span className="truncate">{option.label}</span>
<div key={option.value} className="relative">
<button
type="button"
onClick={() => {
chooseOptionFilter(filter.name, option.value, option.active);
}}
className={clsx(
option.active || option.pinned
? "bg-slate-200 text-slate-900"
: "text-slate-600 hover:bg-slate-100 hover:text-slate-900",
"group my-1 flex w-full items-center rounded-md px-3 py-1.5 text-sm font-medium transition-all duration-200"
)}
aria-current={option.active ? "page" : undefined}>
<span className="truncate">{option.label}</span>
</button>
{!["all", "inbox", "archived"].includes(option.value) && (option.active || option.pinned) && (
<button
className="ml-auto"
className="absolute right-2 top-1 rounded px-2 py-1 transition-all duration-100 hover:bg-slate-100"
onClick={(e) => {
e.stopPropagation();
pinOptionFilter(filter.name, option.value, !option.pinned);
@@ -227,7 +273,7 @@ export default function FilterNavigation({
)}
</button>
)}
</button>
</div>
))}
</div>
))}
@@ -3,7 +3,7 @@ import { RectangleStackIcon } from "@heroicons/react/24/solid";
export function SubmissionCounter({ numFilteredSubmissions, numTotalSubmissions }) {
return (
<div className="mb-4 rounded-lg border border-slate-200 px-4 py-2">
<div className="flex items-center text-base font-semibold text-slate-500">
<div className="flex flex-wrap items-center text-base font-semibold text-slate-500">
<RectangleStackIcon className="mr-2 h-5 w-5 text-slate-300" /> {numFilteredSubmissions} responses
{numFilteredSubmissions !== numTotalSubmissions && (
<div className="ml-2 text-sm font-medium text-slate-400">(out of {numTotalSubmissions})</div>
@@ -0,0 +1,169 @@
import { persistSubmission, useSubmissions } from "@/lib/submissions";
import { onlyUnique } from "@/lib/utils";
import { Combobox } from "@headlessui/react";
import { PlusIcon } from "@heroicons/react/24/outline";
import clsx from "clsx";
import { useRouter } from "next/router";
import { useMemo, useState } from "react";
// add interface props for tags
interface TaggingProps {
submission: any;
}
export default function Tagging({ submission }: TaggingProps) {
const router = useRouter();
const [isEditingTag, setIsEditingTag] = useState("");
const [currentTag, setCurrentTag] = useState("");
const { submissions, mutateSubmissions } = useSubmissions(
router.query.organisationId?.toString(),
router.query.formId?.toString()
);
// update submissions
const updateSubmissionsLocally = (updatedSubmission) => {
const updatedSubmissions = JSON.parse(JSON.stringify(submissions));
const submissionIdx = updatedSubmissions.findIndex((s) => s.id === updatedSubmission.id);
updatedSubmissions[submissionIdx] = updatedSubmission;
mutateSubmissions(updatedSubmissions, false);
};
// add tag to submission
const addTag = async (submission, tag) => {
if (!tag) return;
if (submission.tags.includes(tag)) {
setIsEditingTag("");
setCurrentTag("");
return;
}
const updatedSubmission = JSON.parse(JSON.stringify(submission));
updatedSubmission.tags = [...updatedSubmission.tags, tag];
updateSubmissionsLocally(updatedSubmission);
await persistSubmission(updatedSubmission, router.query.organisationId?.toString());
setIsEditingTag("");
setCurrentTag("");
};
//remove tag from submission
const removeTag = async (submission, tag) => {
const updatedSubmission = JSON.parse(JSON.stringify(submission));
updatedSubmission.tags = updatedSubmission.tags.filter((t) => t !== tag);
updateSubmissionsLocally(updatedSubmission);
await persistSubmission(updatedSubmission, router.query.organisationId?.toString());
};
// get all the tags from the submissions
const existingTags = useMemo(() => {
const tags = [];
for (const submission of submissions) {
for (const tag of submission.tags) {
if (!tags.includes(tag)) {
tags.push(tag);
}
}
}
return tags;
}, [submissions]);
const notYetAssignedTags = useMemo(
() => existingTags.filter((tag) => !submission.tags.includes(tag)),
[existingTags, submission.tags]
);
const filteredTags = useMemo(
() =>
currentTag === ""
? notYetAssignedTags
: notYetAssignedTags.filter((notYetAssignedTag) => {
return notYetAssignedTag.toLowerCase().includes(currentTag.toLowerCase());
}),
[currentTag, notYetAssignedTags]
);
return (
<div className="border-t border-slate-100 px-6 py-4">
<ul className="flex flex-wrap space-x-2">
{submission.tags.map((tag: string) => (
<li
key={tag}
className="my-1 inline-flex items-center rounded-full bg-slate-600 py-0.5 pl-2.5 pr-1 text-sm font-medium text-slate-100">
{tag}
<button
type="button"
onClick={() => removeTag(submission, tag)}
className="ml-0.5 inline-flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full text-slate-400 hover:bg-slate-500 hover:text-slate-200 focus:bg-slate-500 focus:text-white focus:outline-none">
<span className="sr-only">Remove large option</span>
<svg className="h-2 w-2" stroke="currentColor" fill="none" viewBox="0 0 8 8">
<path strokeLinecap="round" strokeWidth="1.5" d="M1 1l6 6m0-6L1 7" />
</svg>
</button>
</li>
))}
<li>
{isEditingTag && submission.id === isEditingTag ? (
<Combobox
as="div"
value={filteredTags.length > 0 && currentTag ? filteredTags[0] : currentTag}
onChange={(value) => {
addTag(submission, value);
}}>
<div className="relative">
<div className="relative">
<Combobox.Input
className="h-8 w-40 rounded-full border border-slate-300 bg-slate-50 text-sm text-slate-400 outline-none hover:text-slate-600 focus:border-2 focus:border-slate-300 focus:text-slate-600"
autoFocus={true}
value={currentTag}
onChange={(event) => {
setCurrentTag(event.target.value);
}}
/>
<button
type="button"
onClick={() => {
setIsEditingTag("");
setCurrentTag("");
}}
className="absolute top-1/2 right-1.5 inline-flex h-4 w-4 -translate-y-1/2 items-center justify-center rounded-full text-slate-400 hover:bg-slate-200 focus:bg-slate-500 focus:text-white focus:outline-none">
<span className="sr-only">Remove large option</span>
<svg className="h-2 w-2" stroke="currentColor" fill="none" viewBox="0 0 8 8">
<path strokeLinecap="round" strokeWidth="1.5" d="M1 1l6 6m0-6L1 7" />
</svg>
</button>
</div>
<Combobox.Options className="absolute z-10 mt-1 max-h-28 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
{[...filteredTags, currentTag].filter(onlyUnique).map((tag) => (
<Combobox.Option
key={tag}
value={tag}
className={({ active }) =>
clsx(
"relative cursor-default select-none py-2 pl-3 pr-9",
active ? "bg-slate-500 text-white" : "text-gray-900"
)
}>
{({ selected }) => (
<span className={clsx("block truncate", selected && "font-semibold")}>{tag}</span>
)}
</Combobox.Option>
))}
</Combobox.Options>
</div>
</Combobox>
) : (
<button
type="button"
className="rounded-full border border-dashed border-slate-300 py-1 px-3 text-sm text-slate-400 hover:text-slate-600"
onClick={() => {
setIsEditingTag(submission.id);
}}>
add tag <PlusIcon className="inline h-3 w-3" />
</button>
)}
</li>
</ul>
</div>
);
}
@@ -3,13 +3,12 @@
import { Disclosure } from "@headlessui/react";
import clsx from "clsx";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useRouter } from "next/router";
import { useMemo } from "react";
export default function LayoutWrapperCustomForm({ children }) {
const router = useRouter();
const pathname = usePathname();
const pathname = router.pathname;
const navigation = useMemo(
() => [
{
@@ -182,6 +182,8 @@ const benefitingUsers = [
"People who want to protect their finances and personal information from fraud and theft",
];
const tags = ["helpful", "useful", "interesting", "funny", "inspiring", "motivating", "thought-provoking"];
export const getPmfSubmissions = () => {
const submissions = [];
for (let i = 0; i < 142; i++) {
@@ -205,6 +207,7 @@ export const getPmfSubmissions = () => {
userAgent:
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
},
tags: [getRandomItem(tags)],
});
}
return submissions;
+1 -2
View File
@@ -1,6 +1,6 @@
import crypto from "crypto";
import { formatDistance } from "date-fns";
import intlFormat from "date-fns/intlFormat";
import { formatDistance, formatDistanceStrict, formatDistanceToNow } from "date-fns";
import platform from "platform";
import { demoEndpoints } from "./demo";
@@ -8,7 +8,6 @@ export const fetcher = async (url) => {
if (url in demoEndpoints) {
const { file } = demoEndpoints[url];
const { getData } = await import(`../demo-data/${file}`);
console.log(url, getData);
return getData(url);
}
const res = await fetch(url);
@@ -1,14 +1,14 @@
"use client";
import { useSearchParams } from "next/navigation";
import { ResetPasswordForm } from "@/components/auth/ResetPasswordForm";
import LayoutAuth from "@/components/layout/LayoutAuth";
import { useRouter } from "next/router";
export default function ResetPasswordPage() {
const searchParams = useSearchParams();
const router = useRouter();
return (
<LayoutAuth title="Reset password">
<ResetPasswordForm token={searchParams.get("token")} />
<ResetPasswordForm token={router.query.token} />
</LayoutAuth>
);
}
+3 -3
View File
@@ -2,14 +2,14 @@
import LayoutAuth from "@/components/layout/LayoutAuth";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { SigninForm } from "@/components/auth/SigninForm";
import { useRouter } from "next/router";
export default function SignInPage() {
const searchParams = useSearchParams();
const router = useRouter();
return (
<LayoutAuth title="Sign in">
<SigninForm callbackUrl={searchParams.get("callbackUrl")} error={searchParams.get("error")} />
<SigninForm callbackUrl={router.query.callbackUr} error={router.query.error} />
{process.env.NEXT_PUBLIC_SIGNUP_DISABLED !== "1" && (
<div>
<Link
@@ -1,20 +1,19 @@
"use client";
import LayoutAuth from "@/components/layout/LayoutAuth";
import { useSearchParams } from "next/navigation";
import { RequestVerificationEmail } from "@/components/auth/RequestVerificationEmail";
import { useRouter } from "next/router";
export default function VerficationPage() {
const searchParams = useSearchParams();
const router = useRouter();
return (
<LayoutAuth title="Email verification required">
{searchParams.get("email") ? (
{router.query.email ? (
<>
<h1 className="leading-2 mb-4 text-center font-bold">Please verify your email address</h1>
<p className="text-center">
We have sent you an email to the address{" "}
<span className="italic">{searchParams.get("email")}</span>. Please click the link in the email to
activate your account.
We have sent you an email to the address <span className="italic">{router.query.email}</span>.
Please click the link in the email to activate your account.
</p>
<hr className="my-4" />
<p className="text-center text-xs">
@@ -23,7 +22,7 @@ export default function VerficationPage() {
Click the button below to request a new email.
</p>
<div className="mt-5">
<RequestVerificationEmail email={searchParams.get("email")} />
<RequestVerificationEmail email={router.query.email.toString()} />
</div>
</>
) : (
+4 -4
View File
@@ -1,15 +1,15 @@
"use client";
import { useSearchParams } from "next/navigation";
import { SignIn } from "@/components/auth/SignIn";
import LayoutAuth from "@/components/layout/LayoutAuth";
import { useRouter } from "next/router";
export default function Verify() {
const searchParams = useSearchParams();
const router = useRouter();
return (
<LayoutAuth title="Verify">
<p className="text-center">{!searchParams.get("token") ? "No Token provided" : "Verifying..."}</p>
<SignIn token={searchParams.get("token")} />
<p className="text-center">{!router.query.token ? "No Token provided" : "Verifying..."}</p>
<SignIn token={router.query.token} />
</LayoutAuth>
);
}
+6
View File
@@ -27,3 +27,9 @@
background-color: #cbd5e1;
border: 3px solid #cbd5e1;
}
[type="text"]:focus {
--tw-ring-color: none;
--tw-ring-offset-color: none;
box-shadow: none;
}
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Submission" ADD COLUMN "tags" TEXT[];
+1
View File
@@ -80,6 +80,7 @@ model Submission {
customerOrganisationId String?
data Json @default("{}")
meta Json @default("{}")
tags String[]
}
enum Plan {
+519 -229
View File
File diff suppressed because it is too large Load Diff