mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 01:18:34 +00:00
api manager works, persist login works
This commit is contained in:
1 parent
a52013fd0e
commit
9f35ceed04
8 files changed
+247
-18
No files matched your search
@@ -1,6 +1,6 @@
|
||||
BASE_API="https://api.storyteller.ai"
|
||||
GOOGLE_API="https://storage.googleapis.com"
|
||||
FUNNEL_API="https://style.storyteller.ai"
|
||||
CDN_API="https://cdn.storyteller.ai"
|
||||
GRAVATAR_API="https://www.gravatar.com"
|
||||
VITE_BASE_API="https://api.storyteller.ai"
|
||||
VITE_GOOGLE_API="https://storage.googleapis.com"
|
||||
VITE_FUNNEL_API="https://style.storyteller.ai"
|
||||
VITE_CDN_API="https://cdn.storyteller.ai"
|
||||
VITE_GRAVATAR_API="https://www.gravatar.com"
|
||||
|
||||
@@ -154,10 +154,6 @@ export class ApiManager {
|
||||
|
||||
return this.fetchMultipartFormData<T>(endpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
body: formData,
|
||||
});
|
||||
}
|
||||
|
||||
+20
-6
@@ -1,16 +1,30 @@
|
||||
import { StrictMode } from "react";
|
||||
import { StrictMode, useRef } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { useEffect } from "react";
|
||||
import EnvironmentVariables from "~/Classes/EnvironmentVariables";
|
||||
|
||||
import environmentVariables from "~/Classes/EnvironmentVariables";
|
||||
import { RouterProvider } from "react-router-dom";
|
||||
import { router } from "./router";
|
||||
|
||||
import "./global.css";
|
||||
|
||||
const App = () => {
|
||||
useEffect(() => {
|
||||
EnvironmentVariables.initialize(import.meta.env);
|
||||
}, []);
|
||||
const AppRerenderCount = useRef(0);
|
||||
AppRerenderCount.current++;
|
||||
if (AppRerenderCount.current === 1) {
|
||||
console.log(`App rerendered ${AppRerenderCount.current} times`);
|
||||
} else {
|
||||
console.warn(`App rerendered ${AppRerenderCount.current} times`);
|
||||
}
|
||||
|
||||
if (Object.keys(environmentVariables.values).length === 0) {
|
||||
environmentVariables.initialize({
|
||||
BASE_API: import.meta.env.VITE_BASE_API,
|
||||
GOOGLE_API: import.meta.env.VITE_GOOGLE_API,
|
||||
FUNNEL_API: import.meta.env.VITE_FUNNEL_API,
|
||||
CDN_API: import.meta.env.VITE_CDN_API,
|
||||
GRAVATAR_API: import.meta.env.VITE_GRAVATAR_API,
|
||||
});
|
||||
}
|
||||
|
||||
return <RouterProvider router={router} />;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import React from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { IconDefinition } from "@fortawesome/pro-thin-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
const kebabCase = (str: string) =>
|
||||
str
|
||||
.replace(/([a-z])([A-Z])/g, "$1-$2")
|
||||
.replace(/[\s_]+/g, "-")
|
||||
.toLowerCase();
|
||||
|
||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
inputClassName?: string;
|
||||
iconClassName?: string;
|
||||
label?: string;
|
||||
icon?: IconDefinition;
|
||||
isError?: boolean;
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export const Input = React.forwardRef(
|
||||
(
|
||||
{
|
||||
label,
|
||||
icon,
|
||||
inputClassName,
|
||||
iconClassName,
|
||||
className,
|
||||
id,
|
||||
isError,
|
||||
onBlur,
|
||||
onFocus,
|
||||
errorMessage,
|
||||
...rest
|
||||
}: InputProps,
|
||||
ref: React.ForwardedRef<HTMLInputElement>,
|
||||
) => {
|
||||
return (
|
||||
<div className={twMerge("flex flex-col", className)}>
|
||||
{label && <label htmlFor={id ? id : kebabCase(label)}>{label}</label>}
|
||||
|
||||
<div className="relative w-full">
|
||||
{icon && (
|
||||
<FontAwesomeIcon
|
||||
icon={icon}
|
||||
className={twMerge("text-md absolute pl-3 pt-3", iconClassName)}
|
||||
/>
|
||||
)}
|
||||
<input
|
||||
ref={ref}
|
||||
id={id ? id : label ? kebabCase(label) : undefined}
|
||||
className={twMerge(
|
||||
"bg-brand-secondary h-10 w-full rounded-lg px-3 py-2.5 text-white outline-none",
|
||||
"focus:outline-brand-primary outline-offset-0 transition-all duration-150 ease-in-out",
|
||||
icon && "pl-10",
|
||||
isError && "outline-red focus:outline-red",
|
||||
inputClassName,
|
||||
)}
|
||||
onFocus={(e: React.FocusEvent<HTMLInputElement>) => {
|
||||
if (onFocus) {
|
||||
onFocus(e);
|
||||
}
|
||||
}}
|
||||
onBlur={(e: React.FocusEvent<HTMLInputElement>) => {
|
||||
if (onBlur) {
|
||||
onBlur(e);
|
||||
}
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
{errorMessage && (
|
||||
<h6 className="text-red absolute z-10">{errorMessage}</h6>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Input.displayName = "Input";
|
||||
@@ -1,5 +1,5 @@
|
||||
import { faSpinnerThird } from "@fortawesome/pro-thin-svg-icons";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { faSpinnerThird } from "@fortawesome/pro-thin-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
export const Spinner = ({ className }: { className?: string }) => {
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./spinner";
|
||||
export * from "./Spinne";
|
||||
export * from "./Input";
|
||||
+135
-1
@@ -1,3 +1,137 @@
|
||||
import { useRef } from "react";
|
||||
import { useNavigate, useSearchParams, Link } from "react-router-dom";
|
||||
import { useSignalEffect } from "@preact/signals-react/runtime";
|
||||
|
||||
import { faKey, faUser } from "@fortawesome/pro-thin-svg-icons";
|
||||
|
||||
import { AUTH_STATUS } from "~/enums";
|
||||
import { authentication, login, logout } from "~/signals";
|
||||
|
||||
import {
|
||||
Input,
|
||||
// Spinner,
|
||||
} from "~/components/ui";
|
||||
|
||||
export const Login = () => {
|
||||
return <div>Login</div>;
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const formRef = useRef<HTMLFormElement | null>(null);
|
||||
const { status: authStatus } = authentication;
|
||||
// const authLoaderMessage = getAuthLoaderMessage();
|
||||
// const shouldShowLoader = checkShouldShowLoader();
|
||||
|
||||
// const showNoAccessModal = authStatus.value === AUTH_STATUS.NO_ACCESS;
|
||||
|
||||
const handleOnSumbit = (ev: React.FormEvent<HTMLFormElement>) => {
|
||||
ev.preventDefault();
|
||||
if (formRef.current) {
|
||||
const form = new FormData(formRef.current);
|
||||
const usernameOrEmail = form.get("usernameOrEmail")?.toString();
|
||||
const password = form.get("password")?.toString();
|
||||
if (usernameOrEmail && password && login) {
|
||||
login({
|
||||
usernameOrEmail,
|
||||
password,
|
||||
});
|
||||
}
|
||||
}
|
||||
}; // end handleOnSubmit
|
||||
|
||||
useSignalEffect(() => {
|
||||
const redirectPath = searchParams.get("redirect");
|
||||
if (authStatus.value === AUTH_STATUS.LOGGED_IN) {
|
||||
navigate(redirectPath ? redirectPath : "/");
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="fixed w-full">
|
||||
<div className="mx-auto my-6 w-10/12 max-w-2xl">
|
||||
<h1>Login to Storyteller</h1>
|
||||
</div>
|
||||
<div className="border-ui-border bg-ui-panel relative mx-auto my-6 w-10/12 max-w-2xl overflow-hidden rounded-lg border p-6">
|
||||
<form
|
||||
ref={formRef}
|
||||
onSubmit={handleOnSumbit}
|
||||
className="flex flex-col gap-4"
|
||||
>
|
||||
<Input
|
||||
label="Username or Email"
|
||||
icon={faUser}
|
||||
name="usernameOrEmail"
|
||||
placeholder="Username or Email"
|
||||
autoComplete="username"
|
||||
required
|
||||
/>
|
||||
<Input
|
||||
label="Password"
|
||||
icon={faKey}
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
/>
|
||||
<div className="align-items mb-3 flex">
|
||||
<a
|
||||
href="https://storyteller.ai/password-reset"
|
||||
className="text-brand-primary hover:text-brand-primary-400 grow text-sm transition-all duration-150"
|
||||
>
|
||||
Forgot your password?
|
||||
</a>
|
||||
<div className="flex justify-end gap-1 text-sm">
|
||||
<p>Don't have an account?</p>
|
||||
<Link to="/signup">Sign Up</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button className="h-11 w-full text-sm">Login</button>
|
||||
</form>
|
||||
{/* <LoadingDots
|
||||
className="absolute left-0 top-0 h-full w-full"
|
||||
isShowing={shouldShowLoader}
|
||||
message={authLoaderMessage}
|
||||
type="bricks"
|
||||
/>
|
||||
<ConfirmationModal
|
||||
text="We're in a closed beta and you'll need a beta key to use this app."
|
||||
title="Unauthorized"
|
||||
open={showNoAccessModal}
|
||||
onClose={handleClose}
|
||||
cancelText="Close"
|
||||
onCancel={handleClose}
|
||||
okText="Get Beta Key"
|
||||
onOk={() => {
|
||||
handleClose();
|
||||
if (window) {
|
||||
window.open("https://storyteller.ai/beta-key/redeem", "_blank");
|
||||
}
|
||||
}}
|
||||
/>*/}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// const handleClose = () => {
|
||||
// if (authentication.status.value !== AUTH_STATUS.LOGGED_OUT) {
|
||||
// logout();
|
||||
// }
|
||||
// };
|
||||
|
||||
// const checkShouldShowLoader = () => {
|
||||
// return (
|
||||
// authentication.status.value === AUTH_STATUS.LOGGING ||
|
||||
// authentication.status.value === AUTH_STATUS.LOGGED_IN
|
||||
// );
|
||||
// };
|
||||
// const getAuthLoaderMessage = () => {
|
||||
// if (authentication.status.value === AUTH_STATUS.LOGGED_IN) {
|
||||
// return "Authenticated, Redirecting...";
|
||||
// }
|
||||
// if (authentication.status.value === AUTH_STATUS.GET_USER_INFO) {
|
||||
// return "Getting User Info...";
|
||||
// }
|
||||
// return "Getting Session...";
|
||||
// };
|
||||
@@ -5,6 +5,10 @@ import tsconfigPaths from "vite-tsconfig-paths";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
server: {
|
||||
port: 5741,
|
||||
},
|
||||
|
||||
plugins: [
|
||||
tsconfigPaths(),
|
||||
react({
|
||||
|
||||
Reference in new issue
Block a user