move hq from app folder to pages folder, update react lib classNames

This commit is contained in:
Matthias Nannt
2022-12-07 16:38:09 +01:00
parent 3a5e297302
commit b0554757df
70 changed files with 439 additions and 338 deletions

View File

@@ -1,22 +1,23 @@
# Add lockfile and package.json's of isolated subworkspace
FROM --platform=linux/amd64 node:16-alpine AS installer
WORKDIR /app
FROM node:16-alpine AS installer
RUN apk update
RUN apk --no-cache add curl libc6-compat
RUN curl -fsSL "https://github.com/pnpm/pnpm/releases/latest/download/pnpm-linuxstatic-x64" -o /bin/pnpm; chmod +x /bin/pnpm;
WORKDIR /app
# First install the dependencies (as they change less often)
COPY . .
# Copy .env file because Docker don't follow symlinks
COPY .env /app/apps/hq/
RUN pnpm install
# Build the project
RUN pnpm dlx prisma generate
RUN pnpm turbo run build --filter=hq...
FROM --platform=linux/amd64 node:16-alpine AS runner
FROM node:16-alpine AS runner
RUN apk update
RUN apk --no-cache add curl libc6-compat
RUN curl -fsSL "https://github.com/pnpm/pnpm/releases/latest/download/pnpm-linuxstatic-x64" -o /bin/pnpm; chmod +x /bin/pnpm;
@@ -27,12 +28,14 @@ USER nextjs
WORKDIR /home/nextjs
COPY --from=installer /app/apps/hq .
COPY --from=installer /app/apps/hq/next.config.js .
COPY --from=installer /app/apps/hq/package.json .
COPY --from=installer --chown=nextjs:nodejs /app/packages/database/prisma/schema.prisma ./packages/database/prisma/schema.prisma
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=installer --chown=nextjs:nodejs /app/apps/hq/.next/standalone ./
COPY --from=installer --chown=nextjs:nodejs /app/apps/hq/.next/static ./apps/hq/.next/static
COPY --from=installer --chown=nextjs:nodejs /app/apps/hq/public ./apps/hq/public
COPY --from=installer --chown=nextjs:nodejs /app/packages/database/prisma ./packages/database/prisma
USER nextjs
EXPOSE 3000
CMD pnpm dlx prisma migrate deploy && pnpm start --filter=hq...
CMD pnpm dlx prisma migrate deploy && node apps/hq/server.js

View File

@@ -9,8 +9,7 @@ module.exports = {
output: "standalone",
experimental: {
outputFileTracingRoot: path.join(__dirname, "../../"),
appDir: true,
serverComponentsExternalPackages: ["@prisma/client"],
/* serverComponentsExternalPackages: ["@prisma/client"], */
},
images: {
remotePatterns: [

View File

@@ -14,6 +14,7 @@
"@formbricks/ui": "workspace:*",
"@headlessui/react": "^1.7.4",
"@heroicons/react": "^2.0.13",
"@vercel/analytics": "^0.1.5",
"bcryptjs": "^2.4.3",
"clsx": "^1.2.1",
"date-fns": "^2.29.3",

View File

@@ -1,7 +0,0 @@
"use client";
import { SessionProvider, SessionProviderProps } from "next-auth/react";
export default function ClientSessionProvider(props: SessionProviderProps) {
return <SessionProvider {...props} />;
}

View File

@@ -1,9 +0,0 @@
import { PasswordResetForm } from "./PasswordResetForm";
export default function ForgotPasswordPage() {
return (
<>
<PasswordResetForm />
</>
);
}

View File

@@ -1,29 +0,0 @@
"use client";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import { Logo } from "../Logo";
export default function AuthLayout({ children }: { children: React.ReactNode }) {
const { data: session, status } = useSession();
const router = useRouter();
if (session) {
router.push("/app");
}
return (
<div className="isolate bg-white">
<div className="bg-gradient-radial flex min-h-screen from-gray-200 to-gray-50">
<div className="mx-auto flex flex-1 flex-col justify-center px-4 py-12 sm:px-6 lg:flex-none lg:px-20 xl:px-24">
<div className="mx-auto w-full max-w-sm rounded-xl bg-white p-8 shadow-xl lg:w-96">
<div className="mb-8">
<Logo className="fill-zinc-900 px-16" />
</div>
{children}
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,114 +0,0 @@
import { XCircleIcon } from "@heroicons/react/24/solid";
import { signIn } from "next-auth/react";
import Link from "next/link";
import { useRouter } from "next/router";
import { Logo } from "../Logo";
export default function SignInPage() {
const router = useRouter();
const { error } = router.query;
const handleSubmit = async (e) => {
e.preventDefault();
await signIn("credentials", {
callbackUrl: router.query.callbackUrl?.toString() || "/projects",
email: e.target.elements.email.value,
password: e.target.elements.password.value,
});
};
return (
<>
<div className="flex min-h-screen bg-slate-100">
<div className="mx-auto flex flex-1 flex-col justify-center px-4 py-12 sm:px-6 lg:flex-none lg:px-20 xl:px-24">
{error && (
<div className="absolute top-10 rounded-md bg-red-50 p-4">
<div className="flex">
<div className="flex-shrink-0">
<XCircleIcon className="h-5 w-5 text-red-400" aria-hidden="true" />
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-red-800">An error occurred when logging you in</h3>
<div className="mt-2 text-sm text-red-700">
<p className="space-y-1 whitespace-pre-wrap">{error}</p>
</div>
</div>
</div>
</div>
)}
<div className="shadow-cont mx-auto w-full max-w-sm rounded-xl bg-white p-8 lg:w-96">
<div>
<Logo className="fill-zinc-900" />
</div>
<div className="mt-8">
<div className="mt-6">
<form
onSubmit={handleSubmit}
method="post"
action="/api/auth/callback/credentials"
className="space-y-6">
<div>
<label htmlFor="email" className="block text-sm font-medium text-slate-800">
Email address
</label>
<div className="mt-1">
<input
id="email"
name="email"
type="email"
autoComplete="email"
required
className="ph-no-capture block w-full appearance-none rounded-md border border-slate-300 px-3 py-2 placeholder-slate-300 shadow-sm focus:border-red-500 focus:outline-none focus:ring-red-500 sm:text-sm"
/>
</div>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-slate-800">
Password
</label>
<div className="mt-1">
<input
id="password"
name="password"
type="password"
autoComplete="current-password"
required
className="ph-no-capture block w-full appearance-none rounded-md border border-slate-300 px-3 py-2 placeholder-slate-300 shadow-sm focus:border-red-500 focus:outline-none focus:ring-red-500 sm:text-sm"
/>
</div>
</div>
<div>
<button
type="submit"
className="bg-red flex w-full justify-center rounded-md border border-transparent px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2">
Sign in
</button>
<div className="text-red mt-3 text-center text-xs hover:text-red-600">
{process.env.NEXT_PUBLIC_PASSWORD_RESET_DISABLED !== "1" && (
<div>
<Link href="/auth/forgot-password" id="forgot-password">
Forgot your password?
</Link>
</div>
)}
{process.env.NEXT_PUBLIC_SIGNUP_DISABLED !== "1" && (
<div>
<Link href="/auth/signup" id="create-account">
Create an account
</Link>
</div>
)}
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</>
);
}

View File

@@ -4,7 +4,7 @@ import { Button } from "@formbricks/ui";
import { XCircleIcon } from "@heroicons/react/20/solid";
import { signIn } from "next-auth/react";
import Link from "next/dist/client/link";
import { GithubButton } from "../GithubButton";
import { GithubButton } from "./GithubButton";
export const SigninForm = ({ callbackUrl, error }) => {
const handleSubmit = async (e) => {

View File

@@ -6,7 +6,7 @@ import { XCircleIcon } from "@heroicons/react/24/solid";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { GithubButton } from "../GithubButton";
import { GithubButton } from "./GithubButton";
export const SignupForm = () => {
const router = useRouter();

View File

@@ -1,16 +1,18 @@
"use client";
import LoadingSpinner from "@/app/LoadingSpinner";
import LoadingSpinner from "@/components/LoadingSpinner";
import EmptyPageFiller from "@/components/EmptyPageFiller";
import { useCustomers } from "@/lib/customers";
import { useTeam } from "@/lib/teams";
import { convertDateTimeString } from "@/lib/utils";
import { UsersIcon } from "@heroicons/react/24/outline";
import Link from "next/link";
import { useRouter } from "next/router";
export default function FormsPage({ params }) {
const { customers, isLoadingCustomers, isErrorCustomers } = useCustomers(params.teamId);
const { team, isLoadingTeam, isErrorTeam } = useTeam(params.teamId);
export default function FormsPage() {
const router = useRouter();
const { customers, isLoadingCustomers, isErrorCustomers } = useCustomers(router.query.teamId?.toString());
const { team, isLoadingTeam, isErrorTeam } = useTeam(router.query.teamId?.toString());
if (isLoadingCustomers || isLoadingTeam) {
return (
@@ -79,7 +81,7 @@ export default function FormsPage({ params }) {
</td>
<td className="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
<Link
href={`/app/teams/${params.teamId}/customers/${customer.id}`}
href={`/app/teams/${router.query.teamId}/customers/${customer.id}`}
className="text-brand-dark hover:text-brand-light">
View<span className="sr-only">, {customer.name}</span>
</Link>

View File

@@ -1,18 +1,22 @@
"use client";
import LoadingSpinner from "@/app/LoadingSpinner";
import AnalyticsCard from "@/components/AnalyticsCard";
import SubmissionDisplay from "@/components/forms/submissions/SubmissionDisplay";
import LoadingSpinner from "@/components/LoadingSpinner";
import { useCustomer } from "@/lib/customers";
import { useTeam } from "@/lib/teams";
import { onlyUnique } from "@/lib/utils";
import { ArrowRightIcon } from "@heroicons/react/20/solid";
import Link from "next/link";
import { useRouter } from "next/router";
import { useMemo } from "react";
import SubmissionDisplay from "../../forms/[formId]/submissions/SubmissionDisplay";
export default function FormsPage({ params }) {
const { team, isLoadingTeam, isErrorTeam } = useTeam(params.teamId);
const { customer, isLoadingCustomer, isErrorCustomer } = useCustomer(params.teamId, params.customerId);
export default function SingleCustomerPage() {
const router = useRouter();
const { team, isLoadingTeam, isErrorTeam } = useTeam(router.query.teamId?.toString());
const { customer, isLoadingCustomer, isErrorCustomer } = useCustomer(
router.query.teamId?.toString(),
router.query.customerId?.toString()
);
const formsParticipated = useMemo(() => {
if (customer && "submissions" in customer) {
@@ -60,7 +64,9 @@ export default function FormsPage({ params }) {
<SubmissionDisplay schema={{}} submission={submission} />
</div>
<div className="flex justify-end bg-gray-50 px-4 py-4 text-xs sm:px-6">
<Link href={`/app/teams/${params.teamId}/forms/${submission.formId}`}>To Form &rarr;</Link>
<Link href={`/app/teams/${router.query.teamId}/forms/${submission.formId}`}>
To Form &rarr;
</Link>
</div>
</div>
))}

View File

@@ -1,6 +1,6 @@
"use client";
import LoadingSpinner from "@/app/LoadingSpinner";
import LoadingSpinner from "@/components/LoadingSpinner";
import { useForm } from "@/lib/forms";
import { useTeam } from "@/lib/teams";
import { Button } from "@formbricks/ui";
@@ -11,6 +11,7 @@ import { useEffect, useMemo } from "react";
import { FaReact, FaVuejs } from "react-icons/fa";
import { AiFillHtml5 } from "react-icons/ai";
import { toast } from "react-toastify";
import { useRouter } from "next/router";
require("prismjs/components/prism-javascript");
@@ -49,9 +50,13 @@ const getLibs = (formId: string) => [
},
];
export default function FormsPage({ params }) {
const { form, isLoadingForm, isErrorForm } = useForm(params.formId, params.teamId);
const { team, isLoadingTeam, isErrorTeam } = useTeam(params.teamId);
export default function FormOverviewPage() {
const router = useRouter();
const { form, isLoadingForm, isErrorForm } = useForm(
router.query.formId?.toString(),
router.query.teamId?.toString()
);
const { team, isLoadingTeam, isErrorTeam } = useTeam(router.query.teamId?.toString());
const libs = useMemo(() => {
if (form) {
return getLibs(form.id);
@@ -165,7 +170,7 @@ import "@formbricks/react/styles.css";
export default function WaitlistForm() {
return (
<Form id="${form.id}" onSubmit={sendToHQ}>
<Form formId="${form.id}" hqUrl="${window.location.protocol}//${window.location.host}" onSubmit={sendToHQ}>
<Text name="name" label="What's your name?" validation="required" />
<Email
name="email"

View File

@@ -8,7 +8,7 @@ import { EllipsisHorizontalIcon, TrashIcon } from "@heroicons/react/24/solid";
import clsx from "clsx";
import Link from "next/link";
import { Fragment, useState } from "react";
import NewFormModal from "./NewFormModal";
import NewFormModal from "@/components/forms/NewFormModal";
export default function FormsList({ teamId }) {
const { forms, mutateForms, isLoadingForms } = useForms(teamId);

View File

@@ -1,13 +1,15 @@
"use client";
import LoadingSpinner from "@/app/LoadingSpinner";
import FormsList from "@/components/forms/FormsList";
import LoadingSpinner from "@/components/LoadingSpinner";
import { useForms } from "@/lib/forms";
import { useTeam } from "@/lib/teams";
import FormsList from "./FormsList";
import { useRouter } from "next/router";
export default function FormsPage({ params }) {
const { isLoadingForms, isErrorForms } = useForms(params.teamId);
const { team, isLoadingTeam, isErrorTeam } = useTeam(params.teamId);
export default function FormsPage({}) {
const router = useRouter();
const { isLoadingForms, isErrorForms } = useForms(router.query.teamId?.toString());
const { team, isLoadingTeam, isErrorTeam } = useTeam(router.query.teamId?.toString());
if (isLoadingForms || isLoadingTeam) {
return (
@@ -30,7 +32,7 @@ export default function FormsPage({ params }) {
</span>
</h1>
</header>
<FormsList teamId={params.teamId} />
<FormsList teamId={router.query.teamId} />
</div>
);
}

View File

@@ -24,7 +24,7 @@ export default function NewFormModal({ open, setOpen, teamId }: FormOnboardingMo
const form = await createForm(teamId, {
label,
});
router.push(`app/teams/${teamId}/forms/${form.id}/`);
router.push(`/app/teams/${teamId}/forms/${form.id}/`);
};
return (

View File

@@ -16,10 +16,14 @@ const getEmptyPipeline = () => {
return { label: "", type: null, events: [], config: {} };
};
export default function AddPipelineModal({ open, setOpen, params }) {
export default function AddPipelineModal({ open, setOpen }) {
const router = useRouter();
const [typeId, setTypeId] = useState(null);
const [pipeline, setPipeline] = useState(getEmptyPipeline());
const { pipelines, mutatePipelines } = usePipelines(params.formId, params.teamId);
const { pipelines, mutatePipelines } = usePipelines(
router.query.formId?.toString(),
router.query.teamId?.toString()
);
useEffect(() => {
if (typeId !== pipeline.type) {
@@ -36,7 +40,11 @@ export default function AddPipelineModal({ open, setOpen, params }) {
const handleSubmit = async (e) => {
e.preventDefault();
const newPipeline = await createPipeline(params.formId, params.teamId, pipeline);
const newPipeline = await createPipeline(
router.query.formId?.toString(),
router.query.teamId?.toString(),
pipeline
);
const newPipelines = JSON.parse(JSON.stringify(pipelines));
newPipelines.push(newPipeline);
mutatePipelines(newPipelines);

View File

@@ -1,6 +1,6 @@
"use client";
import LoadingSpinner from "@/app/LoadingSpinner";
import LoadingSpinner from "@/components/LoadingSpinner";
import EmptyPageFiller from "@/components/EmptyPageFiller";
import { useForm } from "@/lib/forms";
import { deletePipeline, persistPipeline, usePipelines } from "@/lib/pipelines";
@@ -15,6 +15,7 @@ import { AiOutlineMail } from "react-icons/ai";
import { SiAirtable, SiGoogle, SiNotion, SiSlack, SiZapier } from "react-icons/si";
import AddPipelineModal from "./AddPipelineModal";
import UpdatePipelineModal from "./UpdatePipelineModal";
import { useRouter } from "next/router";
const integrations = [
{
@@ -82,12 +83,16 @@ const integrations = [
},
];
export default function PipelinesPage({ params }) {
const { form, isLoadingForm, isErrorForm } = useForm(params.formId, params.teamId);
const { team, isLoadingTeam, isErrorTeam } = useTeam(params.teamId);
export default function PipelinesPage({}) {
const router = useRouter();
const { form, isLoadingForm, isErrorForm } = useForm(
router.query.formId?.toString(),
router.query.teamId?.toString()
);
const { team, isLoadingTeam, isErrorTeam } = useTeam(router.query.teamId?.toString());
const { pipelines, isLoadingPipelines, isErrorPipelines, mutatePipelines } = usePipelines(
params.formId,
params.teamId
router.query.formId?.toString(),
router.query.teamId?.toString()
);
const [openAddModal, setOpenAddModal] = useState(false);
@@ -97,7 +102,7 @@ export default function PipelinesPage({ params }) {
const toggleEnabled = async (pipeline) => {
const newPipeline = JSON.parse(JSON.stringify(pipeline));
newPipeline.enabled = !newPipeline.enabled;
await persistPipeline(params.formId, params.teamId, newPipeline);
await persistPipeline(router.query.formId, router.query.teamId, newPipeline);
const pipelineIdx = pipelines.findIndex((p) => p.id === pipeline.id);
if (pipelineIdx !== -1) {
const newPipelines = JSON.parse(JSON.stringify(pipelines));
@@ -112,7 +117,7 @@ export default function PipelinesPage({ params }) {
};
const deletePipelineAction = async (pipelineId) => {
await deletePipeline(params.formId, params.teamId, pipelineId);
await deletePipeline(router.query.formId?.toString(), router.query.teamId?.toString(), pipelineId);
const newPipelines = JSON.parse(JSON.stringify(pipelines));
const pipelineIdx = newPipelines.findIndex((p) => p.id === pipelineId);
if (pipelineIdx > -1) {
@@ -269,12 +274,11 @@ export default function PipelinesPage({ params }) {
<hr />
</EmptyPageFiller>
)}
<AddPipelineModal open={openAddModal} setOpen={setOpenAddModal} params={params} />
<AddPipelineModal open={openAddModal} setOpen={setOpenAddModal} />
{openUpdateModal && (
<UpdatePipelineModal
open={openUpdateModal}
setOpen={setOpenUpdateModal}
params={params}
pipelineId={updatePipelineId}
/>
)}

View File

@@ -1,19 +1,24 @@
import LoadingSpinner from "@/app/LoadingSpinner";
import LoadingSpinner from "@/components/LoadingSpinner";
import Modal from "@/components/Modal";
import { persistPipeline, usePipeline, usePipelines } from "@/lib/pipelines";
import { useRouter } from "next/router";
import PipelineSettings from "./PipelineSettings";
export default function UpdatePipelineModal({ open, setOpen, params, pipelineId }) {
export default function UpdatePipelineModal({ open, setOpen, pipelineId }) {
const router = useRouter();
const { pipeline, isLoadingPipeline, mutatePipeline } = usePipeline(
params.teamId,
params.formId,
router.query.teamId?.toString(),
router.query.formId?.toString(),
pipelineId
);
const { pipelines, mutatePipelines } = usePipelines(params.formId, params.teamId);
const { pipelines, mutatePipelines } = usePipelines(
router.query.formId?.toString(),
router.query.teamId?.toString()
);
const handleSubmit = async (e) => {
e.preventDefault();
await persistPipeline(params.formId, params.teamId, pipeline);
await persistPipeline(router.query.formId?.toString(), router.query.teamId?.toString(), pipeline);
const newPipelines = JSON.parse(JSON.stringify(pipelines));
const pipelineIdx = pipelines.findIndex((p) => p.id === pipelineId);
if (pipelineIdx > -1) {

View File

@@ -1,6 +1,6 @@
"use client";
import LoadingSpinner from "@/app/LoadingSpinner";
import LoadingSpinner from "@/components/LoadingSpinner";
import { useForm } from "@/lib/forms";
import clsx from "clsx";

View File

@@ -6,24 +6,29 @@ import { TrashIcon } from "@heroicons/react/24/outline";
import { useEffect, useState } from "react";
import { toast } from "react-toastify";
import { convertDateTimeString } from "@/lib/utils";
import LoadingSpinner from "@/app/LoadingSpinner";
import LoadingSpinner from "@/components/LoadingSpinner";
import type { Submission } from "@prisma/client";
import clsx from "clsx";
import SubmissionDisplay from "./SubmissionDisplay";
import SubmissionDisplay from "@/components/forms/submissions/SubmissionDisplay";
import { format } from "path";
import { useForm } from "@/lib/forms";
import { useRouter } from "next/router";
export default function ResultsResponses({ params }) {
export default function SubmissionsPage() {
const router = useRouter();
const { submissions, isLoadingSubmissions, mutateSubmissions, isErrorSubmissions } = useSubmissions(
params.teamId,
params.formId
router.query.teamId?.toString(),
router.query.formId?.toString()
);
const { form, isLoadingForm, isErrorForm } = useForm(
router.query.formId?.toString(),
router.query.teamId?.toString()
);
const { form, isLoadingForm, isErrorForm } = useForm(params.formId, params.teamId);
const [activeSubmission, setActiveSubmission] = useState<Submission | null>(null);
const handleDelete = async (submission: Submission) => {
try {
await deleteSubmission(params.teamId, params.formId, submission.id);
await deleteSubmission(router.query.teamId?.toString(), router.query.formId?.toString(), submission.id);
await mutateSubmissions();
setActiveSubmission(null);

View File

@@ -1,6 +1,6 @@
"use client";
import LoadingSpinner from "@/app/LoadingSpinner";
import LoadingSpinner from "@/components/LoadingSpinner";
import { useForm } from "@/lib/forms";
import { useTeam } from "@/lib/teams";
import { ExclamationTriangleIcon, InformationCircleIcon } from "@heroicons/react/20/solid";
@@ -8,13 +8,18 @@ import Link from "next/link";
import { Bar, Table } from "@formbricks/charts";
import { useSubmissions } from "@/lib/submissions";
import AnalyticsCard from "@/components/AnalyticsCard";
import { useRouter } from "next/router";
export default function PipelinesPage({ params }) {
const { form, isLoadingForm, isErrorForm } = useForm(params.formId, params.teamId);
const { team, isLoadingTeam, isErrorTeam } = useTeam(params.teamId);
export default function SummaryPage() {
const router = useRouter();
const { form, isLoadingForm, isErrorForm } = useForm(
router.query.formId?.toString(),
router.query.teamId?.toString()
);
const { team, isLoadingTeam, isErrorTeam } = useTeam(router.query.teamId?.toString());
const { submissions, isLoadingSubmissions, mutateSubmissions } = useSubmissions(
params.teamId,
params.formId
router.query.teamId?.toString(),
router.query.formId?.toString()
);
if (isLoadingForm || isLoadingTeam || isLoadingSubmissions) {
@@ -46,7 +51,7 @@ export default function PipelinesPage({ params }) {
<div className="w-full border-t border-gray-300" />
</div>
<div className="relative flex justify-center">
<span className="bg-gray-50 px-3 text-lg font-medium text-gray-900">Questions &amp; Answers</span>
<span className="bg-white px-3 text-lg font-medium text-gray-900">Questions &amp; Answers</span>
</div>
</div>
{Object.keys(form.schema).length === 0 ? (

View File

@@ -10,11 +10,10 @@ import Link from "next/link";
import { useRouter } from "next/navigation";
import { Fragment } from "react";
import { ToastContainer } from "react-toastify";
import LoadingSpinner from "../LoadingSpinner";
import LoadingSpinner from "@/components/LoadingSpinner";
import { Logo } from "../Logo";
export default function ProjectsLayout({ params, children }) {
const router = useRouter();
export default function LayoutApp({ children }) {
const userNavigation = [
{
name: "Settings",
@@ -24,6 +23,8 @@ export default function ProjectsLayout({ params, children }) {
},
{ name: "Sign out", onClick: () => signOut() },
];
const router = useRouter();
const { data: session, status } = useSession();
if (status === "loading") {
@@ -33,7 +34,9 @@ export default function ProjectsLayout({ params, children }) {
</div>
);
}
if (!session) {
console.log("no sessions");
router.push(`/auth/signin?callbackUrl=${encodeURIComponent(window.location.href)}`);
return <div></div>;
}

View File

@@ -0,0 +1,41 @@
import { useSession } from "next-auth/react";
import Head from "next/head";
import { useRouter } from "next/router";
import { Logo } from "@/components/Logo";
interface Props {
title?: string;
children: React.ReactNode;
}
export default function LayoutAuth({ title = "Formbricks HQ", children }: Props) {
const { data: session, status } = useSession();
const router = useRouter();
if (session) {
router.push("/app");
}
return (
<>
<Head>
<title>{title}</title>
</Head>
<div className="min-h-screen bg-gray-50">
{" "}
<div className="isolate bg-white">
<div className="bg-gradient-radial flex min-h-screen from-gray-200 to-gray-50">
<div className="mx-auto flex flex-1 flex-col justify-center px-4 py-12 sm:px-6 lg:flex-none lg:px-20 xl:px-24">
<div className="mx-auto w-full max-w-sm rounded-xl bg-white p-8 shadow-xl lg:w-96">
<div className="mb-8">
<Logo className="fill-zinc-900 px-16" />
</div>
{children}
</div>
</div>
</div>
</div>
</div>
</>
);
}

View File

@@ -3,34 +3,36 @@
import { Disclosure } from "@headlessui/react";
import clsx from "clsx";
import { usePathname } from "next/navigation";
import { useRouter } from "next/router";
import { useMemo } from "react";
export default function FormLayout({ params, children }) {
export default function LayoutWrapperForm({ children }) {
const router = useRouter();
const pathname = usePathname();
const navigation = useMemo(
() => [
{
name: "Form",
href: `/app/teams/${params.teamId}/forms/${params.formId}/`,
current: pathname.endsWith(params.formId),
href: `/app/teams/${router.query.teamId}/forms/${router.query.formId}/`,
current: pathname.endsWith(router.query.formId?.toString()),
},
{
name: "Pipelines",
href: `/app/teams/${params.teamId}/forms/${params.formId}/pipelines/`,
href: `/app/teams/${router.query.teamId}/forms/${router.query.formId}/pipelines/`,
current: pathname.includes("pipelines"),
},
{
name: "Summary",
href: `/app/teams/${params.teamId}/forms/${params.formId}/summary/`,
href: `/app/teams/${router.query.teamId}/forms/${router.query.formId}/summary/`,
current: pathname.includes("summary"),
},
{
name: "Submissions",
href: `/app/teams/${params.teamId}/forms/${params.formId}/submissions/`,
current: pathname.includes("results"),
href: `/app/teams/${router.query.teamId}/forms/${router.query.formId}/submissions/`,
current: pathname.includes("submissions"),
},
],
[params, pathname]
[router, pathname]
);
return (
<>

View File

@@ -1,38 +1,40 @@
"use client";
import { Logo } from "@/app/Logo";
import { Logo } from "@/components/Logo";
import { Dialog, Transition } from "@headlessui/react";
import { Cog8ToothIcon, RectangleStackIcon, UsersIcon, XMarkIcon } from "@heroicons/react/24/outline";
import clsx from "clsx";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useRouter } from "next/router";
import { Fragment, useMemo, useState } from "react";
export default function Example({ children, params }) {
export default function LayoutWrapperTeam({ children }) {
const router = useRouter();
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const pathname = usePathname();
const sidebarNavigation = useMemo(
() => [
{
name: "Forms",
href: `/app/teams/${params.teamId}/forms`,
href: `/app/teams/${router.query.teamId}/forms`,
icon: RectangleStackIcon,
current: pathname.includes("/form"),
},
{
name: "Customers",
href: `/app/teams/${params.teamId}/customers`,
href: `/app/teams/${router.query.teamId}/customers`,
icon: UsersIcon,
current: pathname.includes("/customers"),
},
{
name: "Settings",
href: `/app/teams/${params.teamId}/<settings>`,
href: `/app/teams/${router.query.teamId}/<settings>`,
icon: Cog8ToothIcon,
current: pathname.includes("/settings"),
},
],
[params, pathname]
[router.query, pathname]
);
return (

View File

@@ -1,14 +1,14 @@
"use client";
import LoadingSpinner from "@/app/LoadingSpinner";
import LoadingSpinner from "@/components/LoadingSpinner";
import Modal from "@/components/Modal";
import { createApiKey, deleteApiKey, useApiKeys } from "@/lib/apiKeys";
import { convertDateString, convertDateTimeString } from "@/lib/utils";
import { convertDateTimeString } from "@/lib/utils";
import { Form, Submit, Text } from "@formbricks/react";
import { Button } from "@formbricks/ui";
import { useState } from "react";
export default function MeSettingsPage() {
export default function ProfileSettingsPage() {
const { apiKeys, mutateApiKeys, isLoadingApiKeys } = useApiKeys();
const [openNewApiKeyModal, setOpenNewApiKeyModal] = useState(false);

View File

@@ -1,13 +1,15 @@
"use client";
import LoadingSpinner from "@/app/LoadingSpinner";
import LoadingSpinner from "@/components/LoadingSpinner";
import { useForms } from "@/lib/forms";
import { useTeam } from "@/lib/teams";
import { InformationCircleIcon } from "@heroicons/react/20/solid";
import { useRouter } from "next/router";
export default function FormsPage({ params }) {
const { forms, isLoadingForms, isErrorForms } = useForms(params.teamId);
const { team, isLoadingTeam, isErrorTeam } = useTeam(params.teamId);
export default function SettingsPage() {
const router = useRouter();
const { forms, isLoadingForms, isErrorForms } = useForms(router.query.teamId?.toString());
const { team, isLoadingTeam, isErrorTeam } = useTeam(router.query.teamId?.toString());
if (isLoadingForms || isLoadingTeam) {
return (

View File

@@ -1,7 +1,7 @@
import useSWR from "swr";
import { fetcher } from "./utils";
export const useCustomers = (teamId: number) => {
export const useCustomers = (teamId: string) => {
const { data, error, mutate } = useSWR(`/api/teams/${teamId}/customers`, fetcher);
return {
@@ -12,7 +12,7 @@ export const useCustomers = (teamId: number) => {
};
};
export const useCustomer = (teamId: number, customerId: string) => {
export const useCustomer = (teamId: string, customerId: string) => {
const { data, error, mutate } = useSWR(`/api/teams/${teamId}/customers/${customerId}`, fetcher);
return {
@@ -23,7 +23,7 @@ export const useCustomer = (teamId: number, customerId: string) => {
};
};
export const deleteCustomer = async (id: string, teamId: number) => {
export const deleteCustomer = async (id: string, teamId: string) => {
try {
await fetch(`/api/teams/${teamId}/customers/${id}`, {
method: "DELETE",

View File

@@ -0,0 +1,18 @@
import { Analytics } from "@vercel/analytics/react";
import { SessionProvider } from "next-auth/react";
import { ToastContainer } from "react-toastify";
import "@/styles/globals.css";
import "@/styles/toastify.css";
import "@/styles/prism.css";
function FormbricksApp({ Component, pageProps: { session, ...pageProps } }) {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
<ToastContainer />
<Analytics />
</SessionProvider>
);
}
export default FormbricksApp;

View File

@@ -1,15 +1,9 @@
import "@/styles/globals.css";
import "@/styles/toastify.css";
import "@/styles/prism.css";
// include styles from the ui package
import { Html, Head, Main, NextScript } from "next/document";
import SessionProvider from "./SessionProvider";
export default function RootLayout({ children }: { children: React.ReactNode }) {
export default function Document() {
return (
<html>
<head>
<title>Formbricks HQ</title>
<Html className="scroll-smooth">
<Head>
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png" />
@@ -19,10 +13,11 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<meta name="msapplication-TileColor" content="#002941" />
<meta name="msapplication-config" content="/favicon/browserconfig.xml" />
<meta name="theme-color" content="#ffffff" />
</head>
<body className="min-h-screen bg-gray-50">
<SessionProvider>{children} </SessionProvider>
</Head>
<body>
<Main />
<NextScript />
</body>
</html>
</Html>
);
}

View File

@@ -1,11 +1,12 @@
"use client";
import LayoutApp from "@/components/layout/LayoutApp";
import { useMemberships } from "@/lib/memberships";
import { Button } from "@formbricks/ui";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
import LoadingSpinner from "../LoadingSpinner";
import LoadingSpinner from "../../components/LoadingSpinner";
export default function ProjectsPage() {
const { data: session, status } = useSession();
@@ -17,19 +18,19 @@ export default function ProjectsPage() {
const teamId = memberships[0].teamId;
router.push(`/app/teams/${teamId}/forms`);
}
if (!session) {
router.push(`/auth/signin?callbackUrl=${encodeURIComponent(window.location.href)}`);
}
}, [memberships, router, session]);
if (!session) {
router.push(`/auth/signin?callbackUrl=${encodeURIComponent(window.location.href)}`);
return <div></div>;
}
if (isErrorMemberships) {
return <div>Something went wrong...</div>;
}
return (
<div className="flex h-full w-full items-center justify-center">
<LoadingSpinner />
</div>
<LayoutApp>
<div className="flex h-full w-full items-center justify-center">
<LoadingSpinner />
</div>
</LayoutApp>
);
}

View File

@@ -0,0 +1,12 @@
"use client";
import LayoutApp from "@/components/layout/LayoutApp";
import ProfileSettingsPage from "@/components/me/ProfileSettingsPage";
export default function TeamFormsPage({}) {
return (
<LayoutApp>
<ProfileSettingsPage />
</LayoutApp>
);
}

View File

@@ -0,0 +1,16 @@
"use client";
import CustomersPage from "@/components/customers/CustomersPage";
import SingleCustomerPage from "@/components/customers/SingleCustomerPage";
import LayoutApp from "@/components/layout/LayoutApp";
import LayoutWrapperTeam from "@/components/layout/LayoutWrapperTeam";
export default function Customers({}) {
return (
<LayoutApp>
<LayoutWrapperTeam>
<SingleCustomerPage />
</LayoutWrapperTeam>
</LayoutApp>
);
}

View File

@@ -0,0 +1,15 @@
"use client";
import CustomersPage from "@/components/customers/CustomersPage";
import LayoutApp from "@/components/layout/LayoutApp";
import LayoutWrapperTeam from "@/components/layout/LayoutWrapperTeam";
export default function Customers({}) {
return (
<LayoutApp>
<LayoutWrapperTeam>
<CustomersPage />
</LayoutWrapperTeam>
</LayoutApp>
);
}

View File

@@ -0,0 +1,16 @@
import FormOverviewPage from "@/components/forms/FormOverviewPage";
import LayoutApp from "@/components/layout/LayoutApp";
import LayoutWrapperForm from "@/components/layout/LayoutWrapperForm";
import LayoutWrapperTeam from "@/components/layout/LayoutWrapperTeam";
export default function FormOverview({}) {
return (
<LayoutApp>
<LayoutWrapperTeam>
<LayoutWrapperForm>
<FormOverviewPage />
</LayoutWrapperForm>
</LayoutWrapperTeam>
</LayoutApp>
);
}

View File

@@ -0,0 +1,16 @@
import PipelinesPage from "@/components/forms/pipelines/PipelinesPage";
import LayoutApp from "@/components/layout/LayoutApp";
import LayoutWrapperForm from "@/components/layout/LayoutWrapperForm";
import LayoutWrapperTeam from "@/components/layout/LayoutWrapperTeam";
export default function Pipeline({}) {
return (
<LayoutApp>
<LayoutWrapperTeam>
<LayoutWrapperForm>
<PipelinesPage />
</LayoutWrapperForm>
</LayoutWrapperTeam>
</LayoutApp>
);
}

View File

@@ -0,0 +1,16 @@
import SubmissionsPage from "@/components/forms/submissions/SubmissionsPage";
import LayoutApp from "@/components/layout/LayoutApp";
import LayoutWrapperForm from "@/components/layout/LayoutWrapperForm";
import LayoutWrapperTeam from "@/components/layout/LayoutWrapperTeam";
export default function Submissions({}) {
return (
<LayoutApp>
<LayoutWrapperTeam>
<LayoutWrapperForm>
<SubmissionsPage />
</LayoutWrapperForm>
</LayoutWrapperTeam>
</LayoutApp>
);
}

View File

@@ -0,0 +1,16 @@
import SummaryPage from "@/components/forms/summary/SummaryPage";
import LayoutApp from "@/components/layout/LayoutApp";
import LayoutWrapperForm from "@/components/layout/LayoutWrapperForm";
import LayoutWrapperTeam from "@/components/layout/LayoutWrapperTeam";
export default function Submissions({}) {
return (
<LayoutApp>
<LayoutWrapperTeam>
<LayoutWrapperForm>
<SummaryPage />
</LayoutWrapperForm>
</LayoutWrapperTeam>
</LayoutApp>
);
}

View File

@@ -0,0 +1,15 @@
"use client";
import FormsPage from "@/components/forms/FormsPage";
import LayoutApp from "@/components/layout/LayoutApp";
import LayoutWrapperTeam from "@/components/layout/LayoutWrapperTeam";
export default function TeamFormsPage({}) {
return (
<LayoutApp>
<LayoutWrapperTeam>
<FormsPage />
</LayoutWrapperTeam>
</LayoutApp>
);
}

View File

@@ -0,0 +1,15 @@
"use client";
import LayoutApp from "@/components/layout/LayoutApp";
import LayoutWrapperTeam from "@/components/layout/LayoutWrapperTeam";
import SettingsPage from "@/components/settings/SettingsPage";
export default function TeamFormsPage({}) {
return (
<LayoutApp>
<LayoutWrapperTeam>
<SettingsPage />
</LayoutWrapperTeam>
</LayoutApp>
);
}

View File

@@ -1,8 +1,9 @@
import LayoutAuth from "@/components/layout/LayoutAuth";
import { Button } from "@formbricks/ui";
export default function SignInPage() {
return (
<>
<LayoutAuth title="Password reset successful">
<h1 className="leading-2 mb-4 text-center font-bold">Password reset successfully requested</h1>
<p className="text-center">
Check your email for a link to reset your password. If it doesn&apos;t appear within a few minutes,
@@ -13,6 +14,6 @@ export default function SignInPage() {
Back to login
</Button>
</div>
</>
</LayoutAuth>
);
}

View File

@@ -0,0 +1,10 @@
import LayoutAuth from "@/components/layout/LayoutAuth";
import { PasswordResetForm } from "../../../components/auth/PasswordResetForm";
export default function ForgotPasswordPage() {
return (
<LayoutAuth title="Forgot password">
<PasswordResetForm />
</LayoutAuth>
);
}

View File

@@ -1,13 +1,14 @@
"use client";
import { useSearchParams } from "next/navigation";
import { ResetPasswordForm } from "./ResetPasswordForm";
import { ResetPasswordForm } from "@/components/auth/ResetPasswordForm";
import LayoutAuth from "@/components/layout/LayoutAuth";
export default function ResetPasswordPage() {
const searchParams = useSearchParams();
return (
<>
<LayoutAuth title="Reset password">
<ResetPasswordForm token={searchParams.get("token")} />
</>
</LayoutAuth>
);
}

View File

@@ -1,8 +1,9 @@
import LayoutAuth from "@/components/layout/LayoutAuth";
import { Button } from "@formbricks/ui";
export default function ResetPasswordSuccessPage() {
return (
<>
<LayoutAuth title="Password reset successful">
<h1 className="leading-2 mb-4 text-center font-bold">Password successfully reset</h1>
<p className="text-center">You can now log in with your new password</p>
<div className="mt-3 text-center">
@@ -10,6 +11,6 @@ export default function ResetPasswordSuccessPage() {
Go to login
</Button>
</div>
</>
</LayoutAuth>
);
}

View File

@@ -1,23 +1,24 @@
"use client";
import LayoutAuth from "@/components/layout/LayoutAuth";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { SigninForm } from "./SigninForm";
import { SigninForm } from "@/components/auth/SigninForm";
export default function SignInPage() {
const searchParams = useSearchParams();
return (
<>
<LayoutAuth title="Sign in">
<SigninForm callbackUrl={searchParams.get("callbackUrl")} error={searchParams.get("error")} />
{process.env.NEXT_PUBLIC_SIGNUP_DISABLED !== "1" && (
<div>
<Link
href="/auth/signup"
className="text-sky mt-3 grid grid-cols-1 space-y-2 text-center text-xs hover:text-sky-600">
className="text-sky mt-3 grid grid-cols-1 space-y-2 text-center text-xs hover:text-teal-600">
Create an account
</Link>
</div>
)}
</>
</LayoutAuth>
);
}

View File

@@ -1,8 +1,9 @@
import LayoutAuth from "@/components/layout/LayoutAuth";
import { Button } from "@formbricks/ui";
export default function SignupWithoutVerificationSuccess() {
return (
<>
<LayoutAuth title="Sign up successful">
<h1 className="leading-2 mb-4 text-center font-bold">User successfully created</h1>
<p className="text-center">
Your new user has been created successfully. Please click the button below and sign in to your
@@ -12,6 +13,6 @@ export default function SignupWithoutVerificationSuccess() {
<Button href="/" className="w-full justify-center">
Login
</Button>
</>
</LayoutAuth>
);
}

View File

@@ -1,9 +1,10 @@
import LayoutAuth from "@/components/layout/LayoutAuth";
import Link from "next/link";
import { SignupForm } from "./SignupForm";
import { SignupForm } from "../../../components/auth/SignupForm";
export default function SignUpPage() {
return (
<>
<LayoutAuth title="Create Account">
{process.env.NEXT_PUBLIC_SIGNUP_DISABLED === "1" ? (
<>
<h1 className="leading-2 mb-4 text-center font-bold">Sign up disabled</h1>
@@ -21,6 +22,6 @@ export default function SignUpPage() {
) : (
<SignupForm />
)}
</>
</LayoutAuth>
);
}

View File

@@ -1,18 +1,13 @@
"use client";
import LayoutAuth from "@/components/layout/LayoutAuth";
import { useSearchParams } from "next/navigation";
import { RequestVerificationEmail } from "./RequestVerificationEmail";
import { RequestVerificationEmail } from "@/components/auth/RequestVerificationEmail";
/* interface SignInProps {
searchParams?: {
email?: string;
};
} */
export default function SignIn() {
export default function VerficationPage() {
const searchParams = useSearchParams();
return (
<>
<LayoutAuth title="Email verification required">
{searchParams.get("email") ? (
<>
<h1 className="leading-2 mb-4 text-center font-bold">Please verify your email address</h1>
@@ -34,6 +29,6 @@ export default function SignIn() {
) : (
<p className="text-center">No E-Mail Address provided</p>
)}
</>
</LayoutAuth>
);
}

View File

@@ -1,14 +1,15 @@
"use client";
import { useSearchParams } from "next/navigation";
import { SignIn } from "./SignIn";
import { SignIn } from "@/components/auth/SignIn";
import LayoutAuth from "@/components/layout/LayoutAuth";
export default function Verify() {
const searchParams = useSearchParams();
return (
<>
<LayoutAuth title="Verify">
<p className="text-center">{!searchParams.get("token") ? "No Token provided" : "Verifying..."}</p>
<SignIn token={searchParams.get("token")} />
</>
</LayoutAuth>
);
}

View File

@@ -1,4 +1,3 @@
import clsx from "clsx";
import { useMemo } from "react";
import { useFormContext } from "react-hook-form";
import { getElementId } from "../../lib/element";
@@ -43,7 +42,7 @@ export function Radio(props: FormbricksProps) {
<Wrapper wrapperClassName={props.wrapperClassName}>
<Inner innerClassName={props.innerClassName}>
<input
className={clsx("formbricks-input", props.inputClassName)}
className={props.inputClassName || "formbricks-input"}
type="radio"
id={elemId}
{...register(props.name, {
@@ -70,7 +69,7 @@ export function Radio(props: FormbricksProps) {
<Wrapper wrapperClassName={props.wrapperClassName}>
<Inner innerClassName={props.innerClassName}>
<input
className={clsx("formbricks-input", props.inputClassName)}
className={props.inputClassName || "formbricks-input"}
type="radio"
id={`${props.name}-${option.value}`}
value={option.value}

View File

@@ -1,4 +1,3 @@
import clsx from "clsx";
import React from "react";
import { SVGComponent } from "../../types";
@@ -10,6 +9,8 @@ interface ButtonProps {
onClick?: React.MouseEventHandler<HTMLButtonElement> | undefined;
PrefixIcon?: SVGComponent;
SuffixIcon?: SVGComponent;
prefixIconClassName?: string;
suffixIconClassName?: string;
}
export default function ButtonComponent({
@@ -20,12 +21,14 @@ export default function ButtonComponent({
SuffixIcon,
type = "button",
elemId,
prefixIconClassName,
suffixIconClassName,
}: ButtonProps) {
return (
<button className={inputClassName || "formbricks-input"} type={type} id={elemId} onClick={onClick}>
{PrefixIcon && <PrefixIcon className={clsx("formbricks-prefix-icon")} />}
{PrefixIcon && <PrefixIcon className={prefixIconClassName || "formbricks-prefix-icon"} />}
{label}
{SuffixIcon && <SuffixIcon className={clsx("formbricks-suffix-icon")} />}
{SuffixIcon && <SuffixIcon className={suffixIconClassName || "formbricks-suffix-icon"} />}
</button>
);
}

View File

@@ -9,7 +9,7 @@ interface FieldsetProps {
export function Fieldset({ name, fieldsetClassName, children }: FieldsetProps) {
return (
<fieldset className={clsx("formbricks-fieldset", fieldsetClassName)} name={name}>
<fieldset className={fieldsetClassName || "formbricks-fieldset"} name={name}>
{children}
</fieldset>
);

View File

@@ -11,7 +11,7 @@ export function Help({ help, elemId, helpClassName }: HelpProps) {
return null;
}
return (
<div className={clsx("formbricks-help", helpClassName)} id={`help-${elemId}`}>
<div className={helpClassName || "formbricks-help"} id={`help-${elemId}`}>
{help}
</div>
);

View File

@@ -1,4 +1,3 @@
import clsx from "clsx";
import { useMemo } from "react";
import { useFormContext } from "react-hook-form";
import { getElementId } from "../../lib/element";
@@ -41,7 +40,7 @@ export function Input(props: FormbricksProps) {
<Label label={props.label} elemId={elemId} labelClassName={props.labelClassName} />
<Inner innerClassName={props.innerClassName}>
<input
className={clsx("formbricks-input", props.inputClassName)}
className={props.inputClassName || "formbricks-input"}
type={props.type.html}
id={elemId}
aria-invalid={errors[props.name] ? "true" : "false"}

View File

@@ -1,5 +1,4 @@
import { ErrorMessage } from "@hookform/error-message";
import clsx from "clsx";
import { useFormContext } from "react-hook-form";
interface HelpProps {
@@ -19,9 +18,9 @@ export function Messages({ messagesClassName, messageClassName, name }: HelpProp
render={({ messages }) =>
messages &&
Object.entries(messages).map(([type, message]) => (
<ul className={clsx("formbricks-messages", messagesClassName)}>
<ul className={messagesClassName || "formbricks-messages"}>
<li
className={clsx("formbricks-message", messageClassName)}
className={messageClassName || "formbricks-message"}
id={`${name}-${type}`}
data-message-type={type}
role="alert">

View File

@@ -1,10 +1,8 @@
import clsx from "clsx";
interface OptionProps {
optionClassName?: string;
children: React.ReactNode;
}
export function Option({ optionClassName, children }: OptionProps) {
return <div className={clsx("formbricks-option", optionClassName)}>{children}</div>;
return <div className={optionClassName || "formbricks-option"}>{children}</div>;
}

View File

@@ -1,10 +1,8 @@
import clsx from "clsx";
interface OptionsProps {
optionsClassName?: string;
children: React.ReactNode;
}
export function Options({ optionsClassName, children }: OptionsProps) {
return <div className={clsx("formbricks-options", optionsClassName)}>{children}</div>;
return <div className={optionsClassName || "formbricks-options"}>{children}</div>;
}

View File

@@ -1,5 +1,3 @@
import clsx from "clsx";
interface OuterProps {
inputType: string;
outerClassName?: string;
@@ -8,7 +6,7 @@ interface OuterProps {
export function Outer({ inputType, outerClassName, children }: OuterProps) {
return (
<div className={clsx("formbricks-outer", outerClassName)} data-type={inputType}>
<div className={outerClassName || "formbricks-outer"} data-type={inputType}>
{children}
</div>
);

View File

@@ -1,10 +1,8 @@
import clsx from "clsx";
interface WrapperProps {
wrapperClassName?: string;
children: React.ReactNode;
}
export function Wrapper({ wrapperClassName, children }: WrapperProps) {
return <div className={clsx("formbricks-wrapper", wrapperClassName)}>{children}</div>;
return <div className={wrapperClassName || "formbricks-wrapper"}>{children}</div>;
}

10
pnpm-lock.yaml generated
View File

@@ -90,6 +90,7 @@ importers:
'@types/node': ^18.11.9
'@types/react': ^18.0.25
'@types/react-dom': ^18.0.9
'@vercel/analytics': ^0.1.5
autoprefixer: ^10.4.13
bcryptjs: ^2.4.3
clsx: ^1.2.1
@@ -116,6 +117,7 @@ importers:
'@formbricks/ui': link:../../packages/ui
'@headlessui/react': 1.7.4_biqbaboplfbrettd7655fr4n2y
'@heroicons/react': 2.0.13_react@18.2.0
'@vercel/analytics': 0.1.5_react@18.2.0
bcryptjs: 2.4.3
clsx: 1.2.1
date-fns: 2.29.3
@@ -4833,6 +4835,14 @@ packages:
eslint-visitor-keys: 3.3.0
dev: false
/@vercel/analytics/0.1.5_react@18.2.0:
resolution: {integrity: sha512-/k9N8Ea3Yc5A52GlkjzUEbi2vE/izClrOf++ryBkxEfrZM/OZwtHkdNw/QExZ1h/B67RCZLK7bCOnKKrhG7gTg==}
peerDependencies:
react: ^16.8||^17||^18
dependencies:
react: 18.2.0
dev: false
/@vitejs/plugin-react/2.2.0_vite@3.2.4:
resolution: {integrity: sha512-FFpefhvExd1toVRlokZgxgy2JtnBOdp4ZDsq7ldCWaqGSGn9UhWMAVm/1lxPL14JfNS5yGz+s9yFrQY6shoStA==}
engines: {node: ^14.18.0 || >=16.0.0}