From e9299ef97dc32b8e9aed7e4d4a59d385b343a153 Mon Sep 17 00:00:00 2001 From: Michael Chung Date: Tue, 22 Apr 2025 23:51:58 -0400 Subject: [PATCH] ui components --- .../shared_authentication/authentication.tsx | 41 ++++ .../shared_authentication/profiledropdown.tsx | 188 ++++++++++++++++++ .../shared_authentication/signup.tsx | 188 ++++++++++++++++++ 3 files changed, 417 insertions(+) create mode 100644 frontend/apps/editor2d/src/components/shared_authentication/authentication.tsx create mode 100644 frontend/apps/editor2d/src/components/shared_authentication/profiledropdown.tsx create mode 100644 frontend/apps/editor2d/src/components/shared_authentication/signup.tsx diff --git a/frontend/apps/editor2d/src/components/shared_authentication/authentication.tsx b/frontend/apps/editor2d/src/components/shared_authentication/authentication.tsx new file mode 100644 index 0000000000..aa1d2d906b --- /dev/null +++ b/frontend/apps/editor2d/src/components/shared_authentication/authentication.tsx @@ -0,0 +1,41 @@ +import { useSignals } from "@preact/signals-react/runtime"; + +import { AUTH_STATUS } from "~/signals/authentication/enums"; +import { authentication } from "~/signals"; + +import { ButtonLink } from "~/components/ui/ButtonLink"; +import ProfileDropdown from "~/src/components/ProfileDropdown"; + +// TODO MAKE THIS A SHARED COMPONENT ART CRAFT +export const AuthButtons = () => { + useSignals(); + + const { status } = authentication; + + if (status.value === AUTH_STATUS.LOGGED_IN) { + return ; + } else { + return ( +
+ | +
+ + Login + + + Sign Up + +
+
+ ); + } +}; diff --git a/frontend/apps/editor2d/src/components/shared_authentication/profiledropdown.tsx b/frontend/apps/editor2d/src/components/shared_authentication/profiledropdown.tsx new file mode 100644 index 0000000000..673c40d38d --- /dev/null +++ b/frontend/apps/editor2d/src/components/shared_authentication/profiledropdown.tsx @@ -0,0 +1,188 @@ +import { faEnvelope, faKey, faUser } from "@fortawesome/pro-solid-svg-icons"; +import { GlobalSettingsManager } from "./pages/PageEnigma/GlobalSettingsManager"; +import { Button, Input } from "~/shared/components"; +import { addToast, authentication, signUp } from "~/signals"; +import { FormEvent, useRef, useState, StrictMode } from "react"; +import { AUTH_STATUS, ToastTypes } from "~/enums"; +import { useSignalEffect, useSignals } from "@preact/signals-react/runtime"; +import { useNavigate, useSearchParams, BrowserRouter } from "react-router-dom"; +import { createRoot } from "react-dom/client"; + +import "./styles/normalize.css"; +import "./styles/tailwind.css"; +import "./styles/base.css"; +import "@fortawesome/fontawesome-svg-core/styles.css"; + +import { config } from "@fortawesome/fontawesome-svg-core"; + +config.autoAddCss = false; /* eslint-disable import/first */ +// TODO MAKE THIS A SHARED COMPONENT ART CRAFT +const regEx = + /^(([^<>()[\].,;:\s@"]+(.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i; + +// TODO(bt,2025-04-19): Make these configurable +const ENV = { + BASE_API: "https://api.storyteller.ai", + GOOGLE_API: "https://studio.storyteller.ai", + FUNNEL_API: "https://studio.storyteller.ai", + CDN_API: "https://cdn-2.fakeyou.com", + GRAVATAR_API: "https://studio.storyteller.ai", + DEPLOY_PRIME_URL: "https://studio.storyteller.ai", +}; + +export default function SignUpScreen() { + useSignals(); + const navigate = useNavigate(); + const formRef = useRef(null); + const [usernameError, setUsernameError] = useState(""); + const [emailError, setEmailError] = useState(""); + const [passwordError, setPasswordError] = useState(""); + const [passwordConfirmationError, setPasswordConfirmationError] = + useState(""); + const [searchParams] = useSearchParams(); + const { status: authStatus } = authentication; + + const handleOnSubmit = (ev: FormEvent) => { + ev.preventDefault(); + if (formRef.current) { + const form = new FormData(formRef.current); + const email = form.get("email")?.toString(); + const password = form.get("password")?.toString(); + const passwordConfirmation = form + .get("password-confirmation") + ?.toString(); + const username = form.get("username")?.toString(); + setUsernameError(""); + if (!username || username.length < 6) { + setUsernameError("Username must be at least 6 characters long"); + } + setEmailError(""); + if (!email || !email.match(regEx)) { + setEmailError("Email must be a valid email address"); + } + setPasswordError(""); + if (!password || password.length < 8) { + setPasswordError("Password must be at least 8 characters long"); + } + setPasswordConfirmationError(""); + if (!passwordError && passwordConfirmation !== password) { + setPasswordConfirmationError("Passwords do not match"); + } + + if ( + !usernameError && + !passwordError && + !emailError && + !passwordConfirmationError && + signUp + ) { + signUp({ + username: username!, + email: email!, + password: password!, + passwordConfirmation: passwordConfirmation!, + }).then((error) => { + console.log("Error occured", error); + if (error) { + addToast(ToastTypes.ERROR, error); + } + }); + } + } + }; // end handleOnSubmit + + useSignalEffect(() => { + const redirectPath = searchParams.get("redirect"); + if (authStatus.value === AUTH_STATUS.LOGGED_IN) { + navigate(redirectPath ? redirectPath : "/"); + return; + } + if (authStatus.value === AUTH_STATUS.NO_ACCESS) { + window.open("https://storyteller.ai/", "_self"); + return; + } + }); + + return ( +
+
+ ArtCraft Logo +
+
+
+

Sign up

+
+ + + + + +
+
+
+
+

Already have an account?

+ + Log in + +
+
+
+
+ ); +} + +// TODO: Replace environment variables from `root.tsx` +createRoot(document.getElementById("root")!).render( + <> + + + + + + + , +); diff --git a/frontend/apps/editor2d/src/components/shared_authentication/signup.tsx b/frontend/apps/editor2d/src/components/shared_authentication/signup.tsx new file mode 100644 index 0000000000..37a4aae073 --- /dev/null +++ b/frontend/apps/editor2d/src/components/shared_authentication/signup.tsx @@ -0,0 +1,188 @@ +import { faEnvelope, faKey, faUser } from "@fortawesome/pro-solid-svg-icons"; +import { GlobalSettingsManager } from "./pages/PageEnigma/GlobalSettingsManager"; +import { Button, Input } from "~/components"; +import { addToast, authentication, signUp } from "~/signals"; +import { FormEvent, useRef, useState, StrictMode } from "react"; +import { AUTH_STATUS, ToastTypes } from "~/enums"; +import { useSignalEffect, useSignals } from "@preact/signals-react/runtime"; +import { useNavigate, useSearchParams, BrowserRouter } from "react-router-dom"; +import { createRoot } from "react-dom/client"; + +import "./styles/normalize.css"; +import "./styles/tailwind.css"; +import "./styles/base.css"; +import "@fortawesome/fontawesome-svg-core/styles.css"; + +import { config } from "@fortawesome/fontawesome-svg-core"; + +config.autoAddCss = false; /* eslint-disable import/first */ +// TODO MAKE THIS A SHARED COMPONENT ART CRAFT +const regEx = + /^(([^<>()[\].,;:\s@"]+(.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i; + +// TODO(bt,2025-04-19): Make these configurable +const ENV = { + BASE_API: "https://api.storyteller.ai", + GOOGLE_API: "https://studio.storyteller.ai", + FUNNEL_API: "https://studio.storyteller.ai", + CDN_API: "https://cdn-2.fakeyou.com", + GRAVATAR_API: "https://studio.storyteller.ai", + DEPLOY_PRIME_URL: "https://studio.storyteller.ai", +}; + +export default function SignUpScreen() { + useSignals(); + const navigate = useNavigate(); + const formRef = useRef(null); + const [usernameError, setUsernameError] = useState(""); + const [emailError, setEmailError] = useState(""); + const [passwordError, setPasswordError] = useState(""); + const [passwordConfirmationError, setPasswordConfirmationError] = + useState(""); + const [searchParams] = useSearchParams(); + const { status: authStatus } = authentication; + + const handleOnSubmit = (ev: FormEvent) => { + ev.preventDefault(); + if (formRef.current) { + const form = new FormData(formRef.current); + const email = form.get("email")?.toString(); + const password = form.get("password")?.toString(); + const passwordConfirmation = form + .get("password-confirmation") + ?.toString(); + const username = form.get("username")?.toString(); + setUsernameError(""); + if (!username || username.length < 6) { + setUsernameError("Username must be at least 6 characters long"); + } + setEmailError(""); + if (!email || !email.match(regEx)) { + setEmailError("Email must be a valid email address"); + } + setPasswordError(""); + if (!password || password.length < 8) { + setPasswordError("Password must be at least 8 characters long"); + } + setPasswordConfirmationError(""); + if (!passwordError && passwordConfirmation !== password) { + setPasswordConfirmationError("Passwords do not match"); + } + + if ( + !usernameError && + !passwordError && + !emailError && + !passwordConfirmationError && + signUp + ) { + signUp({ + username: username!, + email: email!, + password: password!, + passwordConfirmation: passwordConfirmation!, + }).then((error) => { + console.log("Error occured", error); + if (error) { + addToast(ToastTypes.ERROR, error); + } + }); + } + } + }; // end handleOnSubmit + + useSignalEffect(() => { + const redirectPath = searchParams.get("redirect"); + if (authStatus.value === AUTH_STATUS.LOGGED_IN) { + navigate(redirectPath ? redirectPath : "/"); + return; + } + if (authStatus.value === AUTH_STATUS.NO_ACCESS) { + window.open("https://storyteller.ai/", "_self"); + return; + } + }); + + return ( +
+
+ ArtCraft Logo +
+
+
+

Sign up

+
+ + + + + +
+
+
+
+

Already have an account?

+ + Log in + +
+
+
+
+ ); +} + +// TODO: Replace environment variables from `root.tsx` +createRoot(document.getElementById("root")!).render( + <> + + + + + + + , +);