mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-30 10:19:51 -06:00
move hq from app folder to pages folder, update react lib classNames
This commit is contained in:
@@ -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
|
||||
@@ -9,8 +9,7 @@ module.exports = {
|
||||
output: "standalone",
|
||||
experimental: {
|
||||
outputFileTracingRoot: path.join(__dirname, "../../"),
|
||||
appDir: true,
|
||||
serverComponentsExternalPackages: ["@prisma/client"],
|
||||
/* serverComponentsExternalPackages: ["@prisma/client"], */
|
||||
},
|
||||
images: {
|
||||
remotePatterns: [
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { SessionProvider, SessionProviderProps } from "next-auth/react";
|
||||
|
||||
export default function ClientSessionProvider(props: SessionProviderProps) {
|
||||
return <SessionProvider {...props} />;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { PasswordResetForm } from "./PasswordResetForm";
|
||||
|
||||
export default function ForgotPasswordPage() {
|
||||
return (
|
||||
<>
|
||||
<PasswordResetForm />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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) => {
|
||||
@@ -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();
|
||||
@@ -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>
|
||||
@@ -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 →</Link>
|
||||
<Link href={`/app/teams/${router.query.teamId}/forms/${submission.formId}`}>
|
||||
To Form →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
@@ -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);
|
||||
@@ -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}
|
||||
/>
|
||||
)}
|
||||
@@ -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) {
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
@@ -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 & Answers</span>
|
||||
<span className="bg-white px-3 text-lg font-medium text-gray-900">Questions & Answers</span>
|
||||
</div>
|
||||
</div>
|
||||
{Object.keys(form.schema).length === 0 ? (
|
||||
@@ -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>;
|
||||
}
|
||||
41
apps/hq/src/components/layout/LayoutAuth.tsx
Normal file
41
apps/hq/src/components/layout/LayoutAuth.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<>
|
||||
@@ -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 (
|
||||
@@ -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);
|
||||
|
||||
@@ -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 (
|
||||
@@ -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",
|
||||
|
||||
18
apps/hq/src/pages/_app.tsx
Normal file
18
apps/hq/src/pages/_app.tsx
Normal 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;
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
12
apps/hq/src/pages/app/me/settings/index.tsx
Normal file
12
apps/hq/src/pages/app/me/settings/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
15
apps/hq/src/pages/app/teams/[teamId]/customers/index.tsx
Normal file
15
apps/hq/src/pages/app/teams/[teamId]/customers/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
15
apps/hq/src/pages/app/teams/[teamId]/forms/index.tsx
Normal file
15
apps/hq/src/pages/app/teams/[teamId]/forms/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
15
apps/hq/src/pages/app/teams/[teamId]/settings/index.tsx
Normal file
15
apps/hq/src/pages/app/teams/[teamId]/settings/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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't appear within a few minutes,
|
||||
@@ -13,6 +14,6 @@ export default function SignInPage() {
|
||||
Back to login
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
</LayoutAuth>
|
||||
);
|
||||
}
|
||||
10
apps/hq/src/pages/auth/forgot-password/index.tsx
Normal file
10
apps/hq/src/pages/auth/forgot-password/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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
10
pnpm-lock.yaml
generated
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user