mirror of
https://github.com/formbricks/formbricks.git
synced 2026-01-08 08:50:25 -06:00
@@ -88,4 +88,7 @@ NEXT_PUBLIC_SENTRY_DSN=
|
||||
# Configure Github Login
|
||||
NEXT_PUBLIC_GITHUB_AUTH_ENABLED=0
|
||||
GITHUB_ID=
|
||||
GITHUB_SECRET=
|
||||
GITHUB_SECRET=
|
||||
|
||||
# Configure Google Login
|
||||
NEXT_PUBLIC_GOOGLE_AUTH_ENABLED=0
|
||||
|
||||
@@ -90,6 +90,10 @@ NEXT_PUBLIC_GITHUB_AUTH_ENABLED=0
|
||||
GITHUB_ID=
|
||||
GITHUB_SECRET=
|
||||
|
||||
# Configure Google Login
|
||||
NEXT_PUBLIC_GOOGLE_AUTH_ENABLED=0
|
||||
|
||||
|
||||
# Stripe Billing Variables
|
||||
NEXT_PUBLIC_STRIPE_PRICING_TABLE_ID=
|
||||
NEXT_PUBLIC_STRIPE_PUBLIC_KEY=
|
||||
|
||||
@@ -68,7 +68,7 @@ export default function AppPage({}) {
|
||||
<button
|
||||
className="mr-2 flex max-w-xs items-center rounded-full bg-white text-sm font-medium text-slate-700 focus:outline-none focus:ring-2 focus:ring-cyan-500 focus:ring-offset-2 lg:rounded-md lg:p-2 lg:hover:bg-slate-50"
|
||||
onClick={() => {
|
||||
formbricks.track("Feedback Button Click");
|
||||
formbricks.track("Cancel Subscription");
|
||||
}}>
|
||||
Feedback
|
||||
</button>
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import BackToLoginButton from "@/components/auth/BackToLoginButton";
|
||||
import FormWrapper from "@/components/auth/FormWrapper";
|
||||
|
||||
const SignInPage: React.FC = () => {
|
||||
return (
|
||||
<div>
|
||||
<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,
|
||||
check your spam folder.
|
||||
</p>
|
||||
<div className="mt-5 text-center">
|
||||
<BackToLoginButton />
|
||||
<FormWrapper>
|
||||
<div>
|
||||
<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,
|
||||
check your spam folder.
|
||||
</p>
|
||||
<div className="mt-5 text-center">
|
||||
<BackToLoginButton />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FormWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { PasswordResetForm } from "../../../components/auth/PasswordResetForm";
|
||||
import FormWrapper from "@/components/auth/FormWrapper";
|
||||
|
||||
const ForgotPasswordPage: React.FC = () => {
|
||||
return <PasswordResetForm />;
|
||||
return (
|
||||
<FormWrapper>
|
||||
<PasswordResetForm />
|
||||
</FormWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default ForgotPasswordPage;
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { ResetPasswordForm } from "@/components/auth/ResetPasswordForm";
|
||||
import FormWrapper from "@/components/auth/FormWrapper";
|
||||
|
||||
const ResetPasswordPage: React.FC = () => {
|
||||
return <ResetPasswordForm />;
|
||||
return (
|
||||
<FormWrapper>
|
||||
<ResetPasswordForm />
|
||||
</FormWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResetPasswordPage;
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import BackToLoginButton from "@/components/auth/BackToLoginButton";
|
||||
import FormWrapper from "@/components/auth/FormWrapper";
|
||||
|
||||
export default function ResetPasswordSuccessPage() {
|
||||
return (
|
||||
<div>
|
||||
<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">
|
||||
<BackToLoginButton />
|
||||
<FormWrapper>
|
||||
<div>
|
||||
<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">
|
||||
<BackToLoginButton />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FormWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Logo } from "@/components/Logo";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { redirect } from "next/navigation";
|
||||
import { PosthogClientWrapper } from "../PosthogClientWrapper";
|
||||
@@ -12,16 +11,7 @@ export default async function AuthLayout({ children }: { children: React.ReactNo
|
||||
<PosthogClientWrapper>
|
||||
<div className="min-h-screen bg-slate-50">
|
||||
<div className="isolate bg-white">
|
||||
<div className="bg-gradient-radial flex min-h-screen from-slate-200 to-slate-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 text-center">
|
||||
<Logo className="mx-auto w-3/4" />
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gradient-radial flex min-h-screen from-slate-200 to-slate-50">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
</PosthogClientWrapper>
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import { SigninForm } from "@/components/auth/SigninForm";
|
||||
import Link from "next/link";
|
||||
import Testimonial from "@/components/auth/Testimonial";
|
||||
import FormWrapper from "@/components/auth/FormWrapper";
|
||||
|
||||
export default function SignInPage() {
|
||||
return (
|
||||
<div>
|
||||
<SigninForm />
|
||||
{process.env.NEXT_PUBLIC_SIGNUP_DISABLED !== "1" && (
|
||||
<div>
|
||||
<Link
|
||||
href="/auth/signup"
|
||||
className="hover:text-brand-dark mt-3 grid grid-cols-1 space-y-2 text-center text-xs text-slate-700">
|
||||
Create an account
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
<div className="grid min-h-screen w-full bg-gradient-to-tr from-slate-200 to-slate-50 lg:grid-cols-2">
|
||||
<div className="hidden lg:flex">
|
||||
<Testimonial />
|
||||
</div>
|
||||
<FormWrapper>
|
||||
<SigninForm />
|
||||
</FormWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import BackToLoginButton from "@/components/auth/BackToLoginButton";
|
||||
import FormWrapper from "@/components/auth/FormWrapper";
|
||||
|
||||
export default function SignupWithoutVerificationSuccess() {
|
||||
return (
|
||||
<div>
|
||||
<FormWrapper>
|
||||
<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
|
||||
@@ -10,6 +11,6 @@ export default function SignupWithoutVerificationSuccess() {
|
||||
</p>
|
||||
<hr className="my-4" />
|
||||
<BackToLoginButton />
|
||||
</div>
|
||||
</FormWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,26 +1,33 @@
|
||||
import Link from "next/link";
|
||||
import { SignupForm } from "@/components/auth/SignupForm";
|
||||
import FormWrapper from "@/components/auth/FormWrapper";
|
||||
import Testimonial from "@/components/auth/Testimonial";
|
||||
|
||||
export default function SignUpPage() {
|
||||
return (
|
||||
<div>
|
||||
{process.env.NEXT_PUBLIC_SIGNUP_DISABLED === "1" ? (
|
||||
<>
|
||||
<h1 className="leading-2 mb-4 text-center font-bold">Sign up disabled</h1>
|
||||
<p className="text-center">
|
||||
The account creation is disabled in this instance. Please contact the site administrator to create
|
||||
an account.
|
||||
</p>
|
||||
<hr className="my-4" />
|
||||
<Link
|
||||
href="/"
|
||||
className="mt-5 flex w-full justify-center rounded-md border border-slate-400 bg-white px-4 py-2 text-sm font-medium text-slate-600 shadow-sm hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2">
|
||||
Login
|
||||
</Link>
|
||||
</>
|
||||
) : (
|
||||
<SignupForm />
|
||||
)}
|
||||
<div className="grid min-h-screen w-full bg-gradient-to-tr from-slate-200 to-slate-50 lg:grid-cols-2">
|
||||
<div className="hidden lg:flex">
|
||||
<Testimonial />
|
||||
</div>
|
||||
<FormWrapper>
|
||||
{process.env.NEXT_PUBLIC_SIGNUP_DISABLED === "1" ? (
|
||||
<>
|
||||
<h1 className="leading-2 mb-4 text-center font-bold">Sign up disabled</h1>
|
||||
<p className="text-center">
|
||||
The account creation is disabled in this instance. Please contact the site administrator to
|
||||
create an account.
|
||||
</p>
|
||||
<hr className="my-4" />
|
||||
<Link
|
||||
href="/"
|
||||
className="mt-5 flex w-full justify-center rounded-md border border-slate-400 bg-white px-4 py-2 text-sm font-medium text-slate-600 shadow-sm hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2">
|
||||
Login
|
||||
</Link>
|
||||
</>
|
||||
) : (
|
||||
<SignupForm />
|
||||
)}
|
||||
</FormWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
import { RequestVerificationEmail } from "@/components/auth/RequestVerificationEmail";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import FormWrapper from "@/components/auth/FormWrapper";
|
||||
|
||||
export default function VerficationPage() {
|
||||
const searchParams = useSearchParams();
|
||||
return (
|
||||
<div>
|
||||
<FormWrapper>
|
||||
{searchParams && searchParams?.get("email") ? (
|
||||
<>
|
||||
<h1 className="leading-2 mb-4 text-center text-lg font-semibold text-slate-900">
|
||||
@@ -27,6 +28,6 @@ export default function VerficationPage() {
|
||||
) : (
|
||||
<p className="text-center">No E-Mail Address provided</p>
|
||||
)}
|
||||
</div>
|
||||
</FormWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
|
||||
import { SignIn } from "@/components/auth/SignIn";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import FormWrapper from "@/components/auth/FormWrapper";
|
||||
|
||||
export default function Verify() {
|
||||
const searchParams = useSearchParams();
|
||||
return searchParams && searchParams?.get("token") ? (
|
||||
<div>
|
||||
<FormWrapper>
|
||||
<p className="text-center">Verifying...</p>
|
||||
<SignIn token={searchParams.get("token")} />
|
||||
</div>
|
||||
</FormWrapper>
|
||||
) : (
|
||||
<p className="text-center">No Token provided</p>
|
||||
);
|
||||
|
||||
@@ -135,7 +135,7 @@ export default function WhenToSendCard({ environmentId, localSurvey, setLocalSur
|
||||
<Select
|
||||
value={triggerEventClassId}
|
||||
onValueChange={(eventClassId) => setTriggerEvent(idx, eventClassId)}>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
<SelectTrigger className="w-[240px]">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
||||
14
apps/web/components/auth/FormWrapper.tsx
Normal file
14
apps/web/components/auth/FormWrapper.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Logo } from "@/components/Logo";
|
||||
|
||||
export default function FormWrapper({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<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-6 text-center">
|
||||
<Logo className="mx-auto w-3/4" />
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { Button } from "@formbricks/ui";
|
||||
import { signIn } from "next-auth/react";
|
||||
import { FaGithub } from "react-icons/fa";
|
||||
|
||||
export const GithubButton = ({ text = "Login with Github" }) => {
|
||||
export const GithubButton = ({ text = "Continue with Github" }) => {
|
||||
const handleLogin = async () => {
|
||||
await signIn("github", {
|
||||
redirect: true,
|
||||
@@ -15,8 +15,8 @@ export const GithubButton = ({ text = "Login with Github" }) => {
|
||||
return (
|
||||
<Button
|
||||
type="button"
|
||||
StartIcon={FaGithub}
|
||||
startIconClassName="mr-2"
|
||||
EndIcon={FaGithub}
|
||||
startIconClassName="ml-2"
|
||||
onClick={handleLogin}
|
||||
variant="secondary"
|
||||
className="w-full justify-center">
|
||||
|
||||
26
apps/web/components/auth/GoogleButton.tsx
Normal file
26
apps/web/components/auth/GoogleButton.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@formbricks/ui";
|
||||
import { signIn } from "next-auth/react";
|
||||
import { FaGoogle } from "react-icons/fa";
|
||||
|
||||
export const GoogleButton = ({ text = "Continue with Google" }) => {
|
||||
const handleLogin = async () => {
|
||||
await signIn("google", {
|
||||
redirect: true,
|
||||
callbackUrl: "/", // redirect after login to /
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
type="button"
|
||||
EndIcon={FaGoogle}
|
||||
startIconClassName="ml-3"
|
||||
onClick={handleLogin}
|
||||
variant="secondary"
|
||||
className="w-full justify-center">
|
||||
{text}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
@@ -1,11 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { GoogleButton } from "@/components/auth/GoogleButton";
|
||||
import { Button } from "@formbricks/ui";
|
||||
import { XCircleIcon } from "@heroicons/react/24/solid";
|
||||
import { signIn } from "next-auth/react";
|
||||
import Link from "next/dist/client/link";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import { GithubButton } from "./GithubButton";
|
||||
|
||||
export const SigninForm = () => {
|
||||
@@ -22,9 +23,105 @@ export const SigninForm = () => {
|
||||
};
|
||||
|
||||
const [loggingIn, setLoggingIn] = useState(false);
|
||||
const [showLogin, setShowLogin] = useState(false);
|
||||
const [isButtonEnabled, setButtonEnabled] = useState(true);
|
||||
const [isPasswordFocused, setIsPasswordFocused] = useState(false);
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
|
||||
const checkFormValidity = () => {
|
||||
// If both fields are filled, enable the button
|
||||
if (formRef.current) {
|
||||
setButtonEnabled(formRef.current.checkValidity());
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="text-center">
|
||||
<p className="mb-8 text-lg text-slate-700">Log in to your account</p>
|
||||
<div className="space-y-2">
|
||||
<form onSubmit={handleSubmit} ref={formRef} className="space-y-2" onChange={checkFormValidity}>
|
||||
{showLogin && (
|
||||
<div>
|
||||
<div className="mb-2 transition-all duration-500 ease-in-out">
|
||||
<label htmlFor="email" className="sr-only">
|
||||
Email address
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
placeholder="work@email.com"
|
||||
defaultValue={searchParams?.get("email") || ""}
|
||||
className="focus:border-brand focus:ring-brand block w-full rounded-md border-slate-300 shadow-sm sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="transition-all duration-500 ease-in-out">
|
||||
<label htmlFor="password" className="sr-only">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
placeholder="*******"
|
||||
aria-placeholder="password"
|
||||
onFocus={() => setIsPasswordFocused(true)}
|
||||
required
|
||||
className="focus:border-brand focus:ring-brand block w-full rounded-md border-slate-300 shadow-sm sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
{process.env.NEXT_PUBLIC_PASSWORD_RESET_DISABLED !== "1" && isPasswordFocused && (
|
||||
<div className="ml-1 text-right transition-all duration-500 ease-in-out">
|
||||
<Link
|
||||
href="/auth/forgot-password"
|
||||
className="hover:text-brand-dark text-xs text-slate-500">
|
||||
Forgot your password?
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (!showLogin) {
|
||||
setShowLogin(true);
|
||||
setButtonEnabled(false);
|
||||
} else if (formRef.current) {
|
||||
formRef.current.requestSubmit();
|
||||
}
|
||||
}}
|
||||
variant="darkCTA"
|
||||
className="w-full justify-center"
|
||||
loading={loggingIn}
|
||||
disabled={!isButtonEnabled}>
|
||||
Continue with Email
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
{process.env.NEXT_PUBLIC_GOOGLE_AUTH_ENABLED === "1" && (
|
||||
<>
|
||||
<GoogleButton />
|
||||
</>
|
||||
)}
|
||||
{process.env.NEXT_PUBLIC_GITHUB_AUTH_ENABLED === "1" && (
|
||||
<>
|
||||
<GithubButton />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{process.env.NEXT_PUBLIC_SIGNUP_DISABLED !== "1" && (
|
||||
<div className="mt-3 text-center text-xs text-slate-600">
|
||||
Need an account?{" "}
|
||||
<Link href="/auth/signup" className="font-semibold underline">
|
||||
Register.
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{searchParams?.get("error") && (
|
||||
<div className="absolute top-10 rounded-md bg-red-50 p-4">
|
||||
<div className="flex">
|
||||
@@ -40,67 +137,6 @@ export const SigninForm = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<form onSubmit={handleSubmit} 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
|
||||
defaultValue={searchParams?.get("email") || ""}
|
||||
className="focus:border-brand focus:ring-brand block w-full rounded-md border-slate-300 shadow-sm 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="focus:border-brand focus:ring-brand block w-full rounded-md border-slate-300 shadow-sm sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button type="submit" className="w-full justify-center" loading={loggingIn}>
|
||||
Sign in
|
||||
</Button>
|
||||
</div>
|
||||
{process.env.NEXT_PUBLIC_PASSWORD_RESET_DISABLED !== "1" && (
|
||||
<div>
|
||||
<Link
|
||||
href="/auth/forgot-password"
|
||||
className="hover:text-brand-dark mt-3 grid grid-cols-1 space-y-2 text-center text-xs text-slate-700">
|
||||
Forgot your password?
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{process.env.NEXT_PUBLIC_GITHUB_AUTH_ENABLED === "1" && (
|
||||
<>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center" aria-hidden="true">
|
||||
<div className="w-full border-t border-slate-300" />
|
||||
</div>
|
||||
<div className="relative flex justify-center">
|
||||
<span className="bg-white px-2 text-sm text-slate-500">Sign in with</span>
|
||||
</div>
|
||||
</div>
|
||||
<GithubButton text="Sign in with GitHub" />{" "}
|
||||
</>
|
||||
)}
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,8 +5,9 @@ import { createUser } from "@/lib/users/users";
|
||||
import { XCircleIcon } from "@heroicons/react/24/solid";
|
||||
import Link from "next/link";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import { GithubButton } from "./GithubButton";
|
||||
import { GoogleButton } from "@/components/auth/GoogleButton";
|
||||
|
||||
export const SignupForm = () => {
|
||||
const searchParams = useSearchParams();
|
||||
@@ -36,6 +37,18 @@ export const SignupForm = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const [showLogin, setShowLogin] = useState(false);
|
||||
const [isButtonEnabled, setButtonEnabled] = useState(true);
|
||||
const [isPasswordFocused, setIsPasswordFocused] = useState(false);
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
|
||||
const checkFormValidity = () => {
|
||||
// If all fields are filled, enable the button
|
||||
if (formRef.current) {
|
||||
setButtonEnabled(formRef.current.checkValidity());
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{error && (
|
||||
@@ -53,106 +66,136 @@ export const SignupForm = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-slate-800">
|
||||
Full Name
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input
|
||||
id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
autoComplete="given-name"
|
||||
required
|
||||
className="focus:border-brand focus:ring-brand block w-full rounded-md border-slate-300 shadow-sm sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<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
|
||||
defaultValue={searchParams?.get("email") || ""}
|
||||
className="focus:border-brand focus:ring-brand block w-full rounded-md border-slate-300 shadow-sm 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"
|
||||
required
|
||||
className="focus:border-brand focus:ring-brand block w-full rounded-md border-slate-300 shadow-sm sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="mb-8 text-lg text-slate-700">Create your Formbricks account</p>
|
||||
<div className="space-y-2">
|
||||
<form onSubmit={handleSubmit} ref={formRef} className="space-y-2" onChange={checkFormValidity}>
|
||||
{showLogin && (
|
||||
<div>
|
||||
<div className="mb-2 transition-all duration-500 ease-in-out">
|
||||
<label htmlFor="name" className="sr-only">
|
||||
Full Name
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input
|
||||
id="name"
|
||||
name="name"
|
||||
type="text"
|
||||
autoComplete="given-name"
|
||||
placeholder="Full Name"
|
||||
aria-placeholder="Full Name"
|
||||
required
|
||||
className="focus:border-brand focus:ring-brand block w-full rounded-md border-slate-300 shadow-sm sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-2 transition-all duration-500 ease-in-out">
|
||||
<label htmlFor="email" className="sr-only">
|
||||
Email address
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
placeholder="work@email.com"
|
||||
defaultValue={searchParams?.get("email") || ""}
|
||||
className="focus:border-brand focus:ring-brand block w-full rounded-md border-slate-300 shadow-sm sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div className="transition-all duration-500 ease-in-out">
|
||||
<label htmlFor="password" className="sr-only">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
placeholder="*******"
|
||||
aria-placeholder="password"
|
||||
onFocus={() => setIsPasswordFocused(true)}
|
||||
required
|
||||
className="focus:border-brand focus:ring-brand block w-full rounded-md border-slate-300 shadow-sm sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
{process.env.NEXT_PUBLIC_PASSWORD_RESET_DISABLED !== "1" && isPasswordFocused && (
|
||||
<div className="ml-1 text-right transition-all duration-500 ease-in-out">
|
||||
<Link
|
||||
href="/auth/forgot-password"
|
||||
className="hover:text-brand-dark text-xs text-slate-500">
|
||||
Forgot your password?
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (!showLogin) {
|
||||
setShowLogin(true);
|
||||
setButtonEnabled(false);
|
||||
} else if (formRef.current) {
|
||||
formRef.current.requestSubmit();
|
||||
}
|
||||
}}
|
||||
variant="darkCTA"
|
||||
className="w-full justify-center"
|
||||
loading={signingUp}
|
||||
disabled={!isButtonEnabled}>
|
||||
Continue with Email
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<div>
|
||||
<Button type="submit" className="w-full justify-center" loading={signingUp}>
|
||||
Sign up
|
||||
</Button>
|
||||
|
||||
<div className="mt-3 text-center text-xs text-slate-600">
|
||||
Already have an account?{" "}
|
||||
<Link href="/auth/login" className="text-brand hover:text-brand-light">
|
||||
Log in.
|
||||
</Link>
|
||||
</div>
|
||||
{(process.env.NEXT_PUBLIC_TERMS_URL || process.env.NEXT_PUBLIC_PRIVACY_URL) && (
|
||||
<div className="mt-3 text-center text-xs text-slate-400">
|
||||
By clicking "Sign Up", you agree to our
|
||||
<br />
|
||||
{process.env.NEXT_PUBLIC_TERMS_URL && (
|
||||
<Link
|
||||
className="text-brand hover:text-brand-light underline"
|
||||
href={process.env.NEXT_PUBLIC_TERMS_URL}
|
||||
rel="noreferrer"
|
||||
target="_blank">
|
||||
terms of service
|
||||
</Link>
|
||||
)}
|
||||
{process.env.NEXT_PUBLIC_TERMS_URL && process.env.NEXT_PUBLIC_PRIVACY_URL && <span> and </span>}
|
||||
{process.env.NEXT_PUBLIC_PRIVACY_URL && (
|
||||
<Link
|
||||
className="text-brand hover:text-brand-light underline"
|
||||
href={process.env.NEXT_PUBLIC_PRIVACY_URL}
|
||||
rel="noreferrer"
|
||||
target="_blank">
|
||||
privacy policy
|
||||
</Link>
|
||||
)}
|
||||
.<br />
|
||||
We'll occasionally send you account related emails.
|
||||
</div>
|
||||
{process.env.NEXT_PUBLIC_GOOGLE_AUTH_ENABLED === "1" && (
|
||||
<>
|
||||
<GoogleButton />
|
||||
</>
|
||||
)}
|
||||
{process.env.NEXT_PUBLIC_GITHUB_AUTH_ENABLED === "1" && (
|
||||
<>
|
||||
<GithubButton />{" "}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{process.env.NEXT_PUBLIC_GITHUB_AUTH_ENABLED === "1" && (
|
||||
<>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center" aria-hidden="true">
|
||||
<div className="w-full border-t border-slate-300" />
|
||||
</div>
|
||||
<div className="relative flex justify-center">
|
||||
<span className="bg-white px-2 text-sm text-slate-500">Sign up with</span>
|
||||
</div>
|
||||
</div>
|
||||
<GithubButton text="Sign up with GitHub" />
|
||||
</>
|
||||
|
||||
{(process.env.NEXT_PUBLIC_TERMS_URL || process.env.NEXT_PUBLIC_PRIVACY_URL) && (
|
||||
<div className="mt-3 text-center text-xs text-slate-500">
|
||||
By signing up, you agree to our
|
||||
<br />
|
||||
{process.env.NEXT_PUBLIC_TERMS_URL && (
|
||||
<Link
|
||||
className="font-semibold"
|
||||
href="google.com" /* {process.env.NEXT_PUBLIC_TERMS_URL} */
|
||||
rel="noreferrer"
|
||||
target="_blank">
|
||||
Terms of Service
|
||||
</Link>
|
||||
)}
|
||||
{process.env.NEXT_PUBLIC_TERMS_URL && process.env.NEXT_PUBLIC_PRIVACY_URL && <span> and </span>}
|
||||
{process.env.NEXT_PUBLIC_PRIVACY_URL && (
|
||||
<Link
|
||||
className="font-semibold"
|
||||
href="google.com" /* {/* process.env.NEXT_PUBLIC_PRIVACY_URL }*/
|
||||
rel="noreferrer"
|
||||
target="_blank">
|
||||
Privacy Policy.
|
||||
</Link>
|
||||
)}
|
||||
{/* <br />
|
||||
We'll occasionally send you account related emails. */}
|
||||
<hr className="mx-6 mt-3"></hr>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
|
||||
<div className="mt-3 text-center text-xs text-slate-600">
|
||||
Have an account?{" "}
|
||||
<Link href="/auth/login" className="font-semibold underline">
|
||||
Log in.
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
51
apps/web/components/auth/Testimonial.tsx
Normal file
51
apps/web/components/auth/Testimonial.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { CheckCircleIcon } from "@heroicons/react/24/solid";
|
||||
import Image from "next/image";
|
||||
import Peer from "@/images/peer.webp";
|
||||
import CalComLogo from "@/images/cal-logo-light.svg";
|
||||
|
||||
export default function Testimonial() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<div className="mb-10 w-3/4 space-y-8 2xl:w-1/2">
|
||||
<div>
|
||||
<h2 className="text-3xl font-bold text-slate-600">Survey any segment</h2>
|
||||
<h3 className="text-3xl font-light text-slate-600">No coding required.</h3>
|
||||
</div>
|
||||
{/* <p className="text-slate-600">
|
||||
Make customer-centric decisions based on data.
|
||||
<br /> Keep 100% data ownership.
|
||||
</p> */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex space-x-2">
|
||||
<CheckCircleIcon className="text-brand-dark h-6 w-6" />
|
||||
<p className="inline text-lg text-slate-900">All features included</p>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<CheckCircleIcon className="text-brand-dark h-6 w-6" />
|
||||
<p className="inline text-lg text-slate-900">No Creditcard required</p>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<CheckCircleIcon className="text-brand-dark h-6 w-6" />
|
||||
<p className="inline text-lg text-slate-900">Trusted by 400+ product teams</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-1 relative flex rounded-xl border-slate-200 bg-slate-50 shadow-sm">
|
||||
<Image
|
||||
src={Peer}
|
||||
alt="Cal.com Co-CEO Peer Richelsen"
|
||||
className="absolute -bottom-9 right-10 h-28 w-28 rounded-full border border-slate-200 shadow-sm"
|
||||
/>
|
||||
<div className="space-y-2 p-6">
|
||||
<p className=" italic text-slate-700">
|
||||
We measure the clarity of our docs and learn from churn all on one platform. Great product, very
|
||||
responsive team!
|
||||
</p>
|
||||
<p className="text-sm text-slate-500">Peer Richelsen, Co-CEO Cal.com</p>
|
||||
<Image src={CalComLogo} alt="Cal.com Logo" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
apps/web/images/cal-logo-light.svg
Normal file
9
apps/web/images/cal-logo-light.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="101" height="22" viewBox="0 0 101 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.0582 20.817C4.32115 20.817 0 16.2763 0 10.6704C0 5.04589 4.1005 0.467773 10.0582 0.467773C13.2209 0.467773 15.409 1.43945 17.1191 3.66311L14.3609 5.96151C13.2025 4.72822 11.805 4.11158 10.0582 4.11158C6.17833 4.11158 4.04533 7.08268 4.04533 10.6704C4.04533 14.2582 6.38059 17.1732 10.0582 17.1732C11.7866 17.1732 13.2577 16.5566 14.4161 15.3233L17.1375 17.7151C15.501 19.8453 13.2577 20.817 10.0582 20.817Z" fill="#292929"/>
|
||||
<path d="M29.0161 5.88601H32.7304V20.4612H29.0161V18.331C28.2438 19.8446 26.9566 20.8536 24.4927 20.8536C20.5577 20.8536 17.4133 17.4341 17.4133 13.2297C17.4133 9.02528 20.5577 5.60571 24.4927 5.60571C26.9383 5.60571 28.2438 6.61477 29.0161 8.12835V5.88601ZM29.1264 13.2297C29.1264 10.95 27.5634 9.06266 25.0995 9.06266C22.7274 9.06266 21.1828 10.9686 21.1828 13.2297C21.1828 15.4346 22.7274 17.3967 25.0995 17.3967C27.5451 17.3967 29.1264 15.4907 29.1264 13.2297Z" fill="#292929"/>
|
||||
<path d="M35.3599 0H39.0742V20.4427H35.3599V0Z" fill="#292929"/>
|
||||
<path d="M40.7291 18.5182C40.7291 17.3223 41.6853 16.3132 42.9908 16.3132C44.2964 16.3132 45.2158 17.3223 45.2158 18.5182C45.2158 19.7515 44.278 20.7605 42.9908 20.7605C41.7037 20.7605 40.7291 19.7515 40.7291 18.5182Z" fill="#292929"/>
|
||||
<path d="M59.4296 18.1068C58.0505 19.7885 55.9543 20.8536 53.4719 20.8536C49.0404 20.8536 45.7858 17.4341 45.7858 13.2297C45.7858 9.02528 49.0404 5.60571 53.4719 5.60571C55.8623 5.60571 57.9402 6.61477 59.3193 8.20309L56.4508 10.6136C55.7336 9.71667 54.7958 9.04397 53.4719 9.04397C51.0999 9.04397 49.5553 10.95 49.5553 13.211C49.5553 15.472 51.0999 17.378 53.4719 17.378C54.9062 17.378 55.8991 16.6306 56.6346 15.6215L59.4296 18.1068Z" fill="#292929"/>
|
||||
<path d="M59.7422 13.2297C59.7422 9.02528 62.9968 5.60571 67.4283 5.60571C71.8598 5.60571 75.1144 9.02528 75.1144 13.2297C75.1144 17.4341 71.8598 20.8536 67.4283 20.8536C62.9968 20.8349 59.7422 17.4341 59.7422 13.2297ZM71.3449 13.2297C71.3449 10.95 69.8003 9.06266 67.4283 9.06266C65.0563 9.04397 63.5117 10.95 63.5117 13.2297C63.5117 15.4907 65.0563 17.3967 67.4283 17.3967C69.8003 17.3967 71.3449 15.4907 71.3449 13.2297Z" fill="#292929"/>
|
||||
<path d="M100.232 11.5482V20.4428H96.518V12.4638C96.518 9.94119 95.3412 8.85739 93.576 8.85739C91.921 8.85739 90.7442 9.67958 90.7442 12.4638V20.4428H87.0299V12.4638C87.0299 9.94119 85.8346 8.85739 84.0878 8.85739C82.4329 8.85739 80.9802 9.67958 80.9802 12.4638V20.4428H77.2659V5.8676H80.9802V7.88571C81.7525 6.31607 83.15 5.53125 85.3014 5.53125C87.3425 5.53125 89.0525 6.5403 89.9903 8.24074C90.9281 6.50293 92.3072 5.53125 94.8079 5.53125C97.8603 5.54994 100.232 7.86702 100.232 11.5482Z" fill="#292929"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
BIN
apps/web/images/peer.webp
Normal file
BIN
apps/web/images/peer.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
@@ -61,7 +61,7 @@ export const Button: React.ForwardRefExoticComponent<
|
||||
"inline-flex items-center appearance-none",
|
||||
// different styles depending on size
|
||||
size === "sm" && "px-3 py-2 text-sm leading-4 font-medium rounded-md",
|
||||
size === "base" && "px-6 py-2 text-sm font-medium rounded-md",
|
||||
size === "base" && "px-6 py-3 text-sm font-medium rounded-md",
|
||||
size === "lg" && "px-4 py-2 text-base font-medium rounded-md",
|
||||
size === "icon" &&
|
||||
"w-10 h-10 justify-center group p-2 border rounded-lg border-transparent text-neutral-400 hover:border-slate-200 transition",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"INTERNAL_SECRET",
|
||||
"MAIL_FROM",
|
||||
"NEXT_PUBLIC_EMAIL_VERIFICATION_DISABLED",
|
||||
"NEXT_PUBLIC_GOOGLE_AUTH_ENABLED",
|
||||
"NEXT_PUBLIC_GITHUB_AUTH_ENABLED",
|
||||
"NEXT_PUBLIC_IS_FORMBRICKS_CLOUD",
|
||||
"NEXT_PUBLIC_PASSWORD_RESET_DISABLED",
|
||||
|
||||
Reference in New Issue
Block a user