UNPKG

@syngrisi/syngrisi

Version:
674 lines (673 loc) 21.2 kB
import { c as createStyles, u as useQuery, k as ky, a as config, l as log, j as jsxs, C as Center, A as Anchor, b as jsx, N as NR, T as Text, d as useMantineTheme, e as Container, P as Paper, B as Box, f as useDocumentTitle, r as react, L as LoadingOverlay, z as zB, w as wN, g as Title, h as Button, i as Progress, G as Group, m as dj, n as lAe, o as useSearchParams, p as useForm, q as TextInput, s as Checkbox, S as Switch, t as GCe, v as Nie, Q as QueryClient, x as useRoutes, y as useLocalStorage, D as useHotkeys, E as QueryClientProvider, F as ColorSchemeProvider, M as MantineProvider, H as createRoot, I as BrowserRouter } from "./use-form.2c921e76.js"; import { P as PasswordInput } from "./PasswordInput.85496f1d.js"; const index = ""; const useStyle = createStyles((theme) => ({ footer: { marginTop: 120, borderTop: `1px solid ${theme.colorScheme === "dark" ? theme.colors.dark[5] : theme.colors.gray[2]}` }, footerText: { color: "white", fontSize: "1rem" }, footerLink: { margin: "8px", color: "white", fontSize: "1rem", ":hover": { textDecoration: "underline", filter: "brightness(120%)" } } })); function AuthFooter() { const { classes } = useStyle(); const { isLoading, isError, data, error } = useQuery(["version"], () => ky(`${config.baseUri}/v1/app/info`).then((res) => res.json())); if (isError) { log.error(error); return null; } if (isLoading) return null; const tagUrl = `https://github.com/syngrisi/syngrisi/releases/tag/v${data.version}`; return /* @__PURE__ */ jsxs(Center, { children: [/* @__PURE__ */ jsxs(Anchor, { href: "https://github.com/syngrisi/syngrisi/tree/main/packages/syngrisi", target: "_blank", className: classes.footerLink, children: [/* @__PURE__ */ jsx(NR, { size: "1rem", stroke: 1 }), "GitHub"] }), /* @__PURE__ */ jsx(Text, { component: "span", className: classes.footerText, children: "|" }), /* @__PURE__ */ jsx(Anchor, { className: classes.footerLink, href: tagUrl, children: `v${data.version}` })] }); } function AuthLogo() { const theme = useMantineTheme(); const { colorScheme } = theme; return /* @__PURE__ */ jsxs(Container, { style: { paddingTop: "50px", display: "flex", alignItems: "center" }, children: [/* @__PURE__ */ jsx(Paper, { style: { display: "flex", alignItems: "center", padding: "5px 8px", borderRadius: "2px 20px 2px 20px" }, children: /* @__PURE__ */ jsxs("svg", { width: "54", viewBox: "0 0 64 67", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [/* @__PURE__ */ jsx("path", { d: "M62.245 64.6094L7.36118 36.1968C6.98858 36.0039 6.98859 35.471 7.36118 35.2781L62.245 6.86548C62.5893 6.68725 63 6.93713 63 7.32482V64.1501C63 64.5378 62.5893 64.7876 62.245 64.6094Z", fill: "#2B8A3E", fillOpacity: "0.38", stroke: "#2B8A3E", strokeOpacity: "0.6", strokeWidth: "1.63692" }), /* @__PURE__ */ jsx("path", { d: "M1.74625 59.1348L56.63 30.7222C57.0026 30.5293 57.0026 29.9964 56.63 29.8035L1.74625 1.39087C1.40196 1.21264 0.991211 1.46252 0.991211 1.85021V58.6755C0.991211 59.0631 1.40196 59.313 1.74625 59.1348Z", fill: "#1C7ED6", fillOpacity: "0.4", stroke: "#1C7ED6", strokeOpacity: "0.6", strokeWidth: "1.63692" })] }) }), /* @__PURE__ */ jsx(Box, { style: { color: "white", fontSize: "3rem", paddingLeft: "8px", fontFamily: "Roboto, sans-serif", fontWeight: 500 }, children: /* @__PURE__ */ jsx(Paper, { style: { backgroundColor: "rgba(0, 0, 0, 0)" }, children: /* @__PURE__ */ jsx(Text, { color: colorScheme === "dark" ? "gray.4" : "white", children: "Syngrisi" }) }) })] }); } function LogoutForm() { useDocumentTitle("Logout page"); react.exports.useEffect(() => { ky(`${config.baseUri}/v1/auth/logout`); }, []); const logoutInfo = useQuery(["logout"], () => ky(`${config.baseUri}/v1/auth/logout`).then((res) => res.json()), { refetchOnWindowFocus: false }); const userInfo = useQuery(["current_user", logoutInfo], () => ky(`${config.baseUri}/v1/users/current`).then((res) => res.json()), { refetchOnWindowFocus: false }); if (userInfo.isError) { log.error(userInfo.error); } const success = !userInfo.isLoading && Object.keys(userInfo.data).length === 0; return /* @__PURE__ */ jsxs(Container, { size: 420, my: 40, children: [/* @__PURE__ */ jsx(LoadingOverlay, { visible: userInfo.isLoading, transitionDuration: 300, overlayBlur: 1, loaderProps: { color: "green" } }), /* @__PURE__ */ jsxs(Paper, { withBorder: true, shadow: "md", p: 30, mt: 30, radius: "md", hidden: userInfo.isLoading, children: [success ? /* @__PURE__ */ jsx(Text, { align: "center", color: "green", children: /* @__PURE__ */ jsx(zB, { size: "6rem" }) }) : /* @__PURE__ */ jsx(Text, { align: "center", color: "red", children: /* @__PURE__ */ jsx(wN, { size: "6rem" }) }), /* @__PURE__ */ jsx(Title, { align: "center", children: success ? "Success!" : "Failed" }), /* @__PURE__ */ jsx(Text, { align: "center", size: 16, mt: "md", children: success ? "You have been successfully logged out. Click Sign In to login again." : "Something went wrong" }), /* @__PURE__ */ jsx(Button, { fullWidth: true, id: "submit", mt: "xl", color: "green", type: "submit", component: "a", href: "/auth/", children: "Sign In" })] })] }); } const requirements = [{ re: /[0-9]/, label: "Includes number", id: "include-numbers" }, { re: /[a-z]/, label: "Includes lowercase letter", id: "include-lowercase" }, { re: /[A-Z]/, label: "Includes uppercase letter", id: "include-uppercase" }, { re: /[$&+,:;=?@#|'<>.^*()%!-]/, label: "Includes special symbol", id: "include-special" }]; function Bars({ value }) { function getStrength(password) { let multiplier = password.length > 5 ? 0 : 1; requirements.forEach((requirement) => { if (!requirement.re.test(password)) { multiplier += 1; } }); return Math.max(100 - 100 / (requirements.length + 1) * multiplier, 0); } const strength = getStrength(value); const bars = Array(4).fill(0).map((_, index2) => /* @__PURE__ */ jsx(Progress, { styles: { bar: { transitionDuration: "0ms" } }, value: value.length > 0 && index2 === 0 ? 100 : strength >= (index2 + 1) / 4 * 100 ? 100 : 0, color: strength > 80 ? "teal" : strength > 50 ? "yellow" : "red", size: 4 }, index2)); return /* @__PURE__ */ jsx(Group, { spacing: 5, grow: true, mt: "xs", mb: "md", children: bars }); } function PasswordRequirement({ meets, label, id }) { return /* @__PURE__ */ jsx(Text, { color: meets ? "teal" : "red", mt: 5, size: "sm", id, children: /* @__PURE__ */ jsxs(Center, { inline: true, children: [meets ? /* @__PURE__ */ jsx(dj, { size: 14, stroke: 1.5 }) : /* @__PURE__ */ jsx(lAe, { size: 14, stroke: 1.5 }), /* @__PURE__ */ jsx(Box, { ml: 7, children: label })] }) }); } function ChangePasswordForm() { useDocumentTitle("Change Password Page"); const [searchParams] = useSearchParams(); const isFirstRun = !!searchParams.get("first_run"); let validatedRequirements; const form = useForm({ initialValues: { currentPassword: "", newPassword: "", newPasswordConfirmation: "" }, validate: { currentPassword: (val) => { if (isFirstRun) return null; return val !== "" ? null : "Old password is Empty"; }, newPassword: (val) => { if (validatedRequirements.some((x) => !x.meets)) { return "The password doesn't meet the requirements"; } return null; }, newPasswordConfirmation: (val) => val === form.values.newPassword ? null : "New password and password confirmation must be match" } }); const [errorMessage, setErrorMessage] = react.exports.useState(""); const [loader, setLoader] = react.exports.useState(false); validatedRequirements = requirements.map((requirement) => ({ key: requirement.id, id: requirement.id, label: requirement.label, meets: requirement.re.test(form.values.newPassword) })); const checks = validatedRequirements.map((requirement) => /* @__PURE__ */ jsx(PasswordRequirement, { id: requirement.id, label: requirement.label, meets: requirement.meets }, requirement.id)); function handleNewPasswordFields(event, path) { form.setFieldValue(path, event.currentTarget.value); } async function handleFormSubmissions(values) { try { setErrorMessage(""); setLoader(true); const url = isFirstRun ? `${config.baseUri}/v1/auth/change_first_run` : `${config.baseUri}/v1/auth/change`; const resp = await ky(url, { throwHttpErrors: false, method: "POST", credentials: "include", body: JSON.stringify({ currentPassword: isFirstRun ? "" : values.currentPassword, newPassword: values.newPassword, isFirstRun }), headers: { "content-type": "application/json" } }); const result = await resp.json(); setLoader(false); if (result.message === "success") { return window.location.assign("/auth/changeSuccess"); } if (result.message) { log.error(typeof result === "object" ? JSON.stringify(result) : result.toString()); return setErrorMessage(result.message); } log.error(typeof result === "object" ? JSON.stringify(result) : result.toString()); return setErrorMessage(result.message); } catch (e) { log.error(e.stack || e.toString()); setErrorMessage("Connection error"); } finally { setLoader(false); } } return /* @__PURE__ */ jsx(Container, { size: 420, my: 40, children: /* @__PURE__ */ jsx(Paper, { withBorder: true, shadow: "md", p: 30, mt: 30, radius: "md", children: /* @__PURE__ */ jsxs("form", { onSubmit: form.onSubmit((values) => handleFormSubmissions(values)), children: [/* @__PURE__ */ jsx(Title, { order: isFirstRun ? 3 : 2, id: "title", children: isFirstRun ? "Change Password for default Administrator" : "Change Password" }), /* @__PURE__ */ jsx(PasswordInput, { label: "Current Password", placeholder: "Enter current password", id: "current-password", value: isFirstRun ? "---------------" : form.values.currentPassword, onChange: (event) => form.setFieldValue("currentPassword", event.currentTarget.value), error: form.errors.currentPassword && "Invalid password", disabled: isFirstRun || false, required: true }), /* @__PURE__ */ jsx( PasswordInput, { value: form.values.newPassword, onChange: (event) => handleNewPasswordFields(event, "newPassword"), placeholder: "New Password", id: "new-password", label: "New Password", error: form.errors.newPassword, required: true } ), /* @__PURE__ */ jsx(PasswordInput, { value: form.values.newPasswordConfirmation, onChange: (event) => handleNewPasswordFields(event, "newPasswordConfirmation"), placeholder: "New Password", id: "new-password-confirmation", label: "New Password Confirmation", error: form.errors.newPasswordConfirmation, required: true }), /* @__PURE__ */ jsx(Bars, { value: form.values.newPassword }), /* @__PURE__ */ jsx(PasswordRequirement, { label: "Has at least 6 characters", id: "include-six-chars", meets: form.values.newPassword.length > 5 }), checks, errorMessage && /* @__PURE__ */ jsx(Text, { size: "sm", color: "red", mt: "md", id: "error-message", hidden: false, children: errorMessage }), /* @__PURE__ */ jsx(Button, { fullWidth: true, id: "change-password", mt: "xl", color: "green", type: "submit", children: "Update password" }), /* @__PURE__ */ jsx(LoadingOverlay, { visible: loader, transitionDuration: 300, overlayBlur: 1, loaderProps: { color: "green" } })] }) }) }); } function LoginForm() { useDocumentTitle("Login Page"); const form = useForm({ initialValues: { email: "", password: "", rememberMe: true }, validate: { email: (val) => { if (val === "Test" || val === "Administrator") return null; return /^\S+@\S+$/.test(val) ? null : "Invalid email"; } } }); const [errorMessage, setErrorMessage] = react.exports.useState(""); const [searchParams, setSearchParams] = useSearchParams(); const [loader, setLoader] = react.exports.useState(false); const successRedirectUrl = searchParams.get("origin") || "/"; async function handleFormSubmissions(values) { try { setErrorMessage(""); setLoader(true); const resp = await ky(`${config.baseUri}/v1/auth/login`, { throwHttpErrors: false, method: "POST", credentials: "include", body: JSON.stringify({ username: values.email, password: values.password, rememberMe: values.rememberMe }), headers: { "content-type": "application/json" } }); const result = await resp.json(); setLoader(false); if (result.message === "success") { return window.location.assign(successRedirectUrl); } if (result.message) { log.error(typeof result === "object" ? JSON.stringify(result) : result.toString()); return setErrorMessage(result.message); } log.error(typeof result === "object" ? JSON.stringify(result) : result.toString()); setErrorMessage("Connection error"); } catch (e) { log.error(e.stack || e.toString()); setErrorMessage("Connection error"); } finally { setLoader(false); } } return /* @__PURE__ */ jsx(Container, { size: 420, my: 40, children: /* @__PURE__ */ jsx(Paper, { withBorder: true, shadow: "md", p: 30, mt: 30, radius: "md", children: /* @__PURE__ */ jsxs("form", { onSubmit: form.onSubmit((values) => handleFormSubmissions(values)), children: [/* @__PURE__ */ jsx(Title, { children: "Sign in" }), /* @__PURE__ */ jsx(TextInput, { label: "Email", id: "email", placeholder: "username@domain.com", value: form.values.email, onChange: (event) => form.setFieldValue("email", event.currentTarget.value), error: form.errors.email && "Invalid email", required: true }), /* @__PURE__ */ jsx(PasswordInput, { label: "Password", id: "password", placeholder: "Your password", value: form.values.password, onChange: (event) => form.setFieldValue("password", event.currentTarget.value), required: true, mt: "md" }), /* @__PURE__ */ jsx(Group, { position: "apart", mt: "md", children: /* @__PURE__ */ jsx(Checkbox, { label: "Remember me", onChange: (event) => form.setFieldValue("rememberMe", event.currentTarget.checked) }) }), errorMessage && /* @__PURE__ */ jsx(Text, { size: "sm", color: "red", mt: "md", id: "error-message", hidden: false, children: errorMessage }), /* @__PURE__ */ jsx(Button, { fullWidth: true, id: "submit", mt: "xl", color: "green", type: "submit", children: "Sign in" }), /* @__PURE__ */ jsx(LoadingOverlay, { visible: loader, transitionDuration: 300, overlayBlur: 1, loaderProps: { color: "green" } })] }) }) }); } function ChangePasswordSuccessForm() { useDocumentTitle("Success"); return /* @__PURE__ */ jsx(Container, { size: 420, my: 40, children: /* @__PURE__ */ jsxs(Paper, { withBorder: true, shadow: "md", p: 30, mt: 30, radius: "md", children: [/* @__PURE__ */ jsx(Text, { align: "center", color: "green", children: /* @__PURE__ */ jsx(zB, { size: "6rem" }) }), /* @__PURE__ */ jsx(Title, { align: "center", children: "Success!" }), /* @__PURE__ */ jsx(Text, { align: "center", size: 16, mt: "md", children: "Your Password has been changed. Please use your new password to login!" }), /* @__PURE__ */ jsx(Button, { fullWidth: true, id: "submit", mt: "xl", color: "green", type: "submit", component: "a", href: "/auth/", children: "Sign In" })] }) }); } const routesItems = [{ path: "/auth/logout", element: /* @__PURE__ */ jsx(LogoutForm, {}) }, { path: "/auth/change", element: /* @__PURE__ */ jsx(ChangePasswordForm, {}) }, { path: "/auth/changeSuccess", element: /* @__PURE__ */ jsx(ChangePasswordSuccessForm, {}) }, { path: "/auth/", element: /* @__PURE__ */ jsx(LoginForm, {}) }]; function ToggleThemeButton({ colorScheme, toggleColorScheme }) { const dark = colorScheme === "dark"; const theme = useMantineTheme(); return /* @__PURE__ */ jsx(Group, { mr: 28, position: "right", title: `Switch to ${dark ? "light" : "dark"} theme`, children: /* @__PURE__ */ jsx(Switch, { "data-test": "theme-button", size: "md", styles: () => ({ track: { backgroundColor: theme.colors.gray[8], borderColor: theme.colors.gray[8] } }), color: "gray.8", checked: colorScheme === "light", onChange: () => { toggleColorScheme(); }, onLabel: /* @__PURE__ */ jsx(GCe, { size: 16, stroke: 2.5, color: theme.colors.yellow[4] }), offLabel: /* @__PURE__ */ jsx(Nie, { size: 16, stroke: 2.5, color: theme.colors.blue[6] }) }) }); } const queryClient = new QueryClient(); function App() { const routes = useRoutes(routesItems); const [colorScheme, setColorScheme] = useLocalStorage({ key: "mantine-color-scheme", defaultValue: "light", getInitialValueInEffect: true }); const isDark = () => colorScheme === "dark"; react.exports.useEffect(function onColorSchemeChange() { if (!isDark()) { document.body.style.backgroundColor = "#1e1e1e"; document.body.style.setProperty("--before-opacity", "0.7"); return; } document.body.style.backgroundColor = "#000000"; document.body.style.setProperty("--before-opacity", "0.7"); }, [colorScheme]); const toggleColorScheme = (value) => { setColorScheme(value || (isDark() ? "light" : "dark")); }; useHotkeys([["mod+J", () => toggleColorScheme()]]); return /* @__PURE__ */ jsx(QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsxs(ColorSchemeProvider, { colorScheme, toggleColorScheme, children: [/* @__PURE__ */ jsx(ToggleThemeButton, { colorScheme, toggleColorScheme }), /* @__PURE__ */ jsxs(MantineProvider, { withGlobalStyles: true, withNormalizeCSS: true, theme: { fontSizes: { md: 24 }, colorScheme }, children: [/* @__PURE__ */ jsx(Box, { sx: () => ({ display: "flex", justifyContent: "center" }), children: /* @__PURE__ */ jsx(AuthLogo, {}) }), /* @__PURE__ */ jsx(Box, { children: routes }), /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(AuthFooter, {}) })] })] }) }); } createRoot(document.getElementById("root")).render(/* @__PURE__ */ jsx(react.exports.StrictMode, { children: /* @__PURE__ */ jsx(BrowserRouter, { children: /* @__PURE__ */ jsx(App, {}) }) }));