mirror of
https://github.com/spacebarchat/client.git
synced 2024-11-21 18:02:32 +01:00
change default instance url + formatting
This commit is contained in:
parent
28784bec9e
commit
092de4eac5
44
.editorconfig
Normal file
44
.editorconfig
Normal file
@ -0,0 +1,44 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
# end_of_line = lf
|
||||
# indent_size = 4
|
||||
indent_style = tab
|
||||
insert_final_newline = true
|
||||
# max_line_length = 120
|
||||
# tab_width = 4
|
||||
|
||||
[*.less]
|
||||
# indent_size = 2
|
||||
|
||||
[*.sass]
|
||||
# indent_size = 2
|
||||
|
||||
[*.scss]
|
||||
# indent_size = 2
|
||||
|
||||
[*.vue]
|
||||
# indent_style = tabq
|
||||
|
||||
[{*.ats,*.cts,*.mts,*.ts}]
|
||||
# indent_style = tab
|
||||
|
||||
[{*.bash,*.sh,*.zsh}]
|
||||
# indent_size = 2
|
||||
# tab_width = 2
|
||||
|
||||
[{*.cjs,*.js}]
|
||||
# indent_style = tab
|
||||
|
||||
[{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,bowerrc,jest.config}]
|
||||
# indent_size = 2
|
||||
|
||||
[{*.htm,*.html,*.sht,*.shtm,*.shtml}]
|
||||
# indent_style = tab
|
||||
|
||||
[{*.http,*.rest}]
|
||||
# indent_size = 0
|
||||
|
||||
[{*.yaml,*.yml}]
|
||||
# indent_size = 2
|
@ -1,5 +1,5 @@
|
||||
const CracoEsbuildPlugin = require("craco-esbuild");
|
||||
|
||||
module.exports = {
|
||||
plugins: [{ plugin: CracoEsbuildPlugin }],
|
||||
plugins: [{ plugin: CracoEsbuildPlugin }],
|
||||
};
|
||||
|
33974
package-lock.json
generated
33974
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
134
package.json
134
package.json
@ -1,69 +1,69 @@
|
||||
{
|
||||
"name": "spacebar-client-react",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.6",
|
||||
"@fontsource/roboto": "^4.5.8",
|
||||
"@mattjennings/react-modal-stack": "^1.0.4",
|
||||
"@mui/material": "^5.11.13",
|
||||
"@puyodead1/fosscord-ts": "github:Puyodead1/fosscord.ts",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.18.16",
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"mobx": "^6.8.0",
|
||||
"mobx-react-lite": "^3.4.3",
|
||||
"react": "^18.2.0",
|
||||
"react-advanced-cropper": "^0.18.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-error-boundary": "^3.1.4",
|
||||
"react-hook-form": "^7.43.7",
|
||||
"react-icons": "^4.8.0",
|
||||
"react-loading-skeleton": "^3.2.0",
|
||||
"react-router-dom": "^6.9.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-secure-storage": "^1.2.0",
|
||||
"react-select-search": "^4.1.6",
|
||||
"react-spinners": "^0.13.8",
|
||||
"reoverlay": "^1.0.3",
|
||||
"slate": "^0.91.4",
|
||||
"slate-react": "^0.92.0",
|
||||
"styled-components": "^5.3.9",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "craco start",
|
||||
"build": "craco build",
|
||||
"test": "craco test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@craco/craco": "^7.1.0",
|
||||
"@types/styled-components": "^5.1.26",
|
||||
"craco-esbuild": "^0.5.2"
|
||||
}
|
||||
"name": "spacebar-client-react",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.6",
|
||||
"@fontsource/roboto": "^4.5.8",
|
||||
"@mattjennings/react-modal-stack": "^1.0.4",
|
||||
"@mui/material": "^5.11.13",
|
||||
"@puyodead1/fosscord-ts": "github:Puyodead1/fosscord.ts",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.18.16",
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"mobx": "^6.8.0",
|
||||
"mobx-react-lite": "^3.4.3",
|
||||
"react": "^18.2.0",
|
||||
"react-advanced-cropper": "^0.18.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-error-boundary": "^3.1.4",
|
||||
"react-hook-form": "^7.43.7",
|
||||
"react-icons": "^4.8.0",
|
||||
"react-loading-skeleton": "^3.2.0",
|
||||
"react-router-dom": "^6.9.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-secure-storage": "^1.2.0",
|
||||
"react-select-search": "^4.1.6",
|
||||
"react-spinners": "^0.13.8",
|
||||
"reoverlay": "^1.0.3",
|
||||
"slate": "^0.91.4",
|
||||
"slate-react": "^0.92.0",
|
||||
"styled-components": "^5.3.9",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "craco start",
|
||||
"build": "craco build",
|
||||
"test": "craco test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@craco/craco": "^7.1.0",
|
||||
"@types/styled-components": "^5.1.26",
|
||||
"craco-esbuild": "^0.5.2"
|
||||
}
|
||||
}
|
||||
|
11994
pnpm-lock.yaml
11994
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -1,29 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<title>Spacebar</title>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<title>Spacebar</title>
|
||||
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
#root,
|
||||
#root > div {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
#root,
|
||||
#root > div {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,25 +1,25 @@
|
||||
{
|
||||
"short_name": "Spacebar",
|
||||
"name": "Spacebar",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
"short_name": "Spacebar",
|
||||
"name": "Spacebar",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
|
20
src/App.css
20
src/App.css
@ -1,18 +1,18 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
color: #61dafb;
|
||||
}
|
||||
|
98
src/App.tsx
98
src/App.tsx
@ -11,60 +11,60 @@ import RootPage from "./pages/RootPage";
|
||||
import { useAppStore } from "./stores/AppStore";
|
||||
|
||||
function App() {
|
||||
const app = useAppStore();
|
||||
const navigate = useNavigate();
|
||||
const [isLoading, setLoading] = React.useState(true);
|
||||
const app = useAppStore();
|
||||
const navigate = useNavigate();
|
||||
const [isLoading, setLoading] = React.useState(true);
|
||||
|
||||
React.useEffect(() => {
|
||||
const token = secureLocalStorage.getItem("token");
|
||||
if (token) {
|
||||
app.api.loginWithToken(token as string).then(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
} else {
|
||||
// set timeout to prevent flashing
|
||||
setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 1000);
|
||||
}
|
||||
}, []);
|
||||
React.useEffect(() => {
|
||||
const token = secureLocalStorage.getItem("token");
|
||||
if (token) {
|
||||
app.api.loginWithToken(token as string).then(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
} else {
|
||||
// set timeout to prevent flashing
|
||||
setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 1000);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// handles token changes
|
||||
React.useEffect(() => {
|
||||
// FIXME: this triggers on load and causes a redirect to login no matter what page we actually want to go to
|
||||
// if (!app.api.token && !isLoading) {
|
||||
// console.log("TOKEN REMOVED");
|
||||
// // remove token
|
||||
// secureLocalStorage.removeItem("token");
|
||||
// // navigate to login page if token is removed
|
||||
// navigate("/login", { replace: true });
|
||||
// }
|
||||
// handles token changes
|
||||
React.useEffect(() => {
|
||||
// FIXME: this triggers on load and causes a redirect to login no matter what page we actually want to go to
|
||||
// if (!app.api.token && !isLoading) {
|
||||
// console.log("TOKEN REMOVED");
|
||||
// // remove token
|
||||
// secureLocalStorage.removeItem("token");
|
||||
// // navigate to login page if token is removed
|
||||
// navigate("/login", { replace: true });
|
||||
// }
|
||||
|
||||
if (app.api.token) {
|
||||
console.log("TOKEN ADDED");
|
||||
// save token
|
||||
secureLocalStorage.setItem("token", app.api.token);
|
||||
// navigate to root page if token is added
|
||||
navigate("/", { replace: true });
|
||||
}
|
||||
}, [app.api.token, isLoading]);
|
||||
if (app.api.token) {
|
||||
console.log("TOKEN ADDED");
|
||||
// save token
|
||||
secureLocalStorage.setItem("token", app.api.token);
|
||||
// navigate to root page if token is added
|
||||
navigate("/", { replace: true });
|
||||
}
|
||||
}, [app.api.token, isLoading]);
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingPage />;
|
||||
}
|
||||
if (isLoading) {
|
||||
return <LoadingPage />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
<Route
|
||||
index
|
||||
path="/"
|
||||
element={<AuthenticationGuard component={RootPage} />}
|
||||
/>
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/register" element={<RegistrationPage />} />
|
||||
<Route path="*" element={<NotFoundPage />} />
|
||||
</Routes>
|
||||
);
|
||||
return (
|
||||
<Routes>
|
||||
<Route
|
||||
index
|
||||
path="/"
|
||||
element={<AuthenticationGuard component={RootPage} />}
|
||||
/>
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/register" element={<RegistrationPage />} />
|
||||
<Route path="*" element={<NotFoundPage />} />
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(App);
|
||||
|
@ -2,16 +2,16 @@ import { Navigate } from "react-router-dom";
|
||||
import { useAppStore } from "../stores/AppStore";
|
||||
|
||||
interface Props {
|
||||
component: React.FC;
|
||||
component: React.FC;
|
||||
}
|
||||
|
||||
export const AuthenticationGuard = ({ component }: Props) => {
|
||||
const app = useAppStore();
|
||||
const app = useAppStore();
|
||||
|
||||
if (!app.api.token) {
|
||||
return <Navigate to="/login" replace />;
|
||||
}
|
||||
if (!app.api.token) {
|
||||
return <Navigate to="/login" replace />;
|
||||
}
|
||||
|
||||
const Component = component;
|
||||
return <Component />;
|
||||
const Component = component;
|
||||
return <Component />;
|
||||
};
|
||||
|
@ -1,45 +1,45 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
interface Props {
|
||||
variant?: "primary" | "secondary" | "danger" | "success" | "warning";
|
||||
outlined?: boolean;
|
||||
variant?: "primary" | "secondary" | "danger" | "success" | "warning";
|
||||
outlined?: boolean;
|
||||
}
|
||||
|
||||
export default styled.button<Props>`
|
||||
background: ${(props) => {
|
||||
if (props.outlined) return "transparent";
|
||||
switch (props.variant) {
|
||||
case "primary":
|
||||
return "var(--button-primary)";
|
||||
case "secondary":
|
||||
return "var(--button-secondary)";
|
||||
case "danger":
|
||||
return "var(--button-danger)";
|
||||
case "success":
|
||||
return "var(--button-success)";
|
||||
case "warning":
|
||||
return "var(--button-warning)";
|
||||
default:
|
||||
return "var(--button-primary)";
|
||||
}
|
||||
if (props.outlined) return "transparent";
|
||||
switch (props.variant) {
|
||||
case "primary":
|
||||
return "var(--button-primary)";
|
||||
case "secondary":
|
||||
return "var(--button-secondary)";
|
||||
case "danger":
|
||||
return "var(--button-danger)";
|
||||
case "success":
|
||||
return "var(--button-success)";
|
||||
case "warning":
|
||||
return "var(--button-warning)";
|
||||
default:
|
||||
return "var(--button-primary)";
|
||||
}
|
||||
}};
|
||||
|
||||
border: ${(props) => {
|
||||
if (!props.outlined) return "none";
|
||||
switch (props.variant) {
|
||||
case "primary":
|
||||
return "1px solid var(--button-primary)";
|
||||
case "secondary":
|
||||
return "1px solid var(--button-secondary)";
|
||||
case "danger":
|
||||
return "1px solid var(--button-danger)";
|
||||
case "success":
|
||||
return "1px solid var(--button-success)";
|
||||
case "warning":
|
||||
return "1px solid var(--button-warning)";
|
||||
default:
|
||||
return "1px solid var(--button-primary)";
|
||||
}
|
||||
if (!props.outlined) return "none";
|
||||
switch (props.variant) {
|
||||
case "primary":
|
||||
return "1px solid var(--button-primary)";
|
||||
case "secondary":
|
||||
return "1px solid var(--button-secondary)";
|
||||
case "danger":
|
||||
return "1px solid var(--button-danger)";
|
||||
case "success":
|
||||
return "1px solid var(--button-success)";
|
||||
case "warning":
|
||||
return "1px solid var(--button-warning)";
|
||||
default:
|
||||
return "1px solid var(--button-primary)";
|
||||
}
|
||||
}};
|
||||
|
||||
color: var(--text);
|
||||
@ -55,37 +55,37 @@ opacity: ${(props) => (props.disabled ? 0.5 : 1)};
|
||||
|
||||
&:hover {
|
||||
background: ${(props) => {
|
||||
switch (props.variant) {
|
||||
case "primary":
|
||||
return "var(--button-primary-hover)";
|
||||
case "secondary":
|
||||
return "var(--button-secondary-hover)";
|
||||
case "danger":
|
||||
return "var(--button-danger-hover)";
|
||||
case "success":
|
||||
return "var(--button-success-hover)";
|
||||
case "warning":
|
||||
return "var(--button-warning-hover)";
|
||||
default:
|
||||
return "var(--button-primary-hover)";
|
||||
}
|
||||
}};
|
||||
switch (props.variant) {
|
||||
case "primary":
|
||||
return "var(--button-primary-hover)";
|
||||
case "secondary":
|
||||
return "var(--button-secondary-hover)";
|
||||
case "danger":
|
||||
return "var(--button-danger-hover)";
|
||||
case "success":
|
||||
return "var(--button-success-hover)";
|
||||
case "warning":
|
||||
return "var(--button-warning-hover)";
|
||||
default:
|
||||
return "var(--button-primary-hover)";
|
||||
}
|
||||
}};
|
||||
|
||||
&:active {
|
||||
background: ${(props) => {
|
||||
switch (props.variant) {
|
||||
case "primary":
|
||||
return "var(--button-primary-active)";
|
||||
case "secondary":
|
||||
return "var(--button-secondary-active)";
|
||||
case "danger":
|
||||
return "var(--button-danger-active)";
|
||||
case "success":
|
||||
return "var(--button-success-active)";
|
||||
case "warning":
|
||||
return "var(--button-warning-active)";
|
||||
default:
|
||||
return "var(--button-primary-active)";
|
||||
}
|
||||
}};
|
||||
switch (props.variant) {
|
||||
case "primary":
|
||||
return "var(--button-primary-active)";
|
||||
case "secondary":
|
||||
return "var(--button-secondary-active)";
|
||||
case "danger":
|
||||
return "var(--button-danger-active)";
|
||||
case "success":
|
||||
return "var(--button-success-active)";
|
||||
case "warning":
|
||||
return "var(--button-warning-active)";
|
||||
default:
|
||||
return "var(--button-primary-active)";
|
||||
}
|
||||
}};
|
||||
`;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
export default styled.div`
|
||||
background-color: var(--tertiary);
|
||||
color: var(--text);
|
||||
overflow: hidden;
|
||||
background-color: var(--tertiary);
|
||||
color: var(--text);
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
@ -1,56 +1,56 @@
|
||||
.select-search-container {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.select-search-input {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: 10px;
|
||||
background: var(--secondary);
|
||||
border: none;
|
||||
color: var(--text);
|
||||
outline: none;
|
||||
font-size: 16px;
|
||||
text-align: left;
|
||||
border-radius: 12px;
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: 10px;
|
||||
background: var(--secondary);
|
||||
border: none;
|
||||
color: var(--text);
|
||||
outline: none;
|
||||
font-size: 16px;
|
||||
text-align: left;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.select-search-is-multiple .select-search-input {
|
||||
margin-bottom: -2px;
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
|
||||
.select-search-is-multiple .select-search-input {
|
||||
border-radius: 3px 3px 0 0;
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
|
||||
.select-search-input[readonly] {
|
||||
cursor: pointer;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.select-search-is-disabled .select-search-input {
|
||||
cursor: not-allowed;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.select-search-select {
|
||||
border: none;
|
||||
overflow: auto;
|
||||
max-height: 230px;
|
||||
position: relative;
|
||||
bottom: 4vh;
|
||||
border: none;
|
||||
overflow: auto;
|
||||
max-height: 230px;
|
||||
position: relative;
|
||||
bottom: 4vh;
|
||||
}
|
||||
|
||||
.select-search-container:not(.select-search-is-multiple) .select-search-select {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
border-radius: 3px;
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
border-radius: 3px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.select-search-container:not(.select-search-is-multiple).select-search-has-focus
|
||||
.select-search-select {
|
||||
display: block;
|
||||
.select-search-select {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* .select-search-has-focus .select-search-select {
|
||||
@ -58,48 +58,48 @@
|
||||
} */
|
||||
|
||||
.select-search-options {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.select-search-option,
|
||||
.select-search-not-found {
|
||||
display: block;
|
||||
height: 42px;
|
||||
width: 100%;
|
||||
padding: 0 16px;
|
||||
background: var(--primary);
|
||||
border: none;
|
||||
outline: none;
|
||||
color: var(--text);
|
||||
font-size: 16px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
display: block;
|
||||
height: 42px;
|
||||
width: 100%;
|
||||
padding: 0 16px;
|
||||
background: var(--primary);
|
||||
border: none;
|
||||
outline: none;
|
||||
color: var(--text);
|
||||
font-size: 16px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.select-search-option:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
background: transparent !important;
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.select-search-is-highlighted,
|
||||
.select-search-option:not(.select-search-is-selected):hover {
|
||||
background: hsl(20, 4%, 20%);
|
||||
background: hsl(20, 4%, 20%);
|
||||
}
|
||||
|
||||
.select-search-is-selected {
|
||||
font-weight: bold;
|
||||
color: var(--text);
|
||||
background-color: hsl(20, 4%, 25%);
|
||||
font-weight: bold;
|
||||
color: var(--text);
|
||||
background-color: hsl(20, 4%, 25%);
|
||||
}
|
||||
|
||||
.select-search-row:not(:first-child) .select-search-group-header {
|
||||
margin-top: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.select-search-row:not(:last-child) .select-search-group-header {
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
@ -8,209 +8,209 @@ const MIN_AGE = 3; // we do this instead so we can show an age gate if they are
|
||||
const MAX_AGE = 120;
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const Input = styled.input<{ error?: boolean }>`
|
||||
outline: none;
|
||||
background: var(--secondary);
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
border-radius: 12px;
|
||||
color: var(--text);
|
||||
margin: 0 0 0 5px;
|
||||
border: none;
|
||||
aria-invalid: ${(props) => (props.error ? "true" : "false")};
|
||||
border: ${(props) => (props.error ? "1px solid red" : "none")};
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
background: var(--secondary);
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
border-radius: 12px;
|
||||
color: var(--text);
|
||||
margin: 0 0 0 5px;
|
||||
border: none;
|
||||
aria-invalid: ${(props) => (props.error ? "true" : "false")};
|
||||
border: ${(props) => (props.error ? "1px solid red" : "none")};
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const MONTHS = [
|
||||
{
|
||||
value: "01",
|
||||
name: "January",
|
||||
},
|
||||
{
|
||||
value: "02",
|
||||
name: "February",
|
||||
},
|
||||
{
|
||||
value: "03",
|
||||
name: "March",
|
||||
},
|
||||
{
|
||||
value: "04",
|
||||
name: "April",
|
||||
},
|
||||
{
|
||||
value: "05",
|
||||
name: "May",
|
||||
},
|
||||
{
|
||||
value: "06",
|
||||
name: "June",
|
||||
},
|
||||
{
|
||||
value: "07",
|
||||
name: "July",
|
||||
},
|
||||
{
|
||||
value: "08",
|
||||
name: "August",
|
||||
},
|
||||
{
|
||||
value: "09",
|
||||
name: "September",
|
||||
},
|
||||
{
|
||||
value: "10",
|
||||
name: "October",
|
||||
},
|
||||
{
|
||||
value: "11",
|
||||
name: "November",
|
||||
},
|
||||
{
|
||||
value: "12",
|
||||
name: "December",
|
||||
},
|
||||
{
|
||||
value: "01",
|
||||
name: "January",
|
||||
},
|
||||
{
|
||||
value: "02",
|
||||
name: "February",
|
||||
},
|
||||
{
|
||||
value: "03",
|
||||
name: "March",
|
||||
},
|
||||
{
|
||||
value: "04",
|
||||
name: "April",
|
||||
},
|
||||
{
|
||||
value: "05",
|
||||
name: "May",
|
||||
},
|
||||
{
|
||||
value: "06",
|
||||
name: "June",
|
||||
},
|
||||
{
|
||||
value: "07",
|
||||
name: "July",
|
||||
},
|
||||
{
|
||||
value: "08",
|
||||
name: "August",
|
||||
},
|
||||
{
|
||||
value: "09",
|
||||
name: "September",
|
||||
},
|
||||
{
|
||||
value: "10",
|
||||
name: "October",
|
||||
},
|
||||
{
|
||||
value: "11",
|
||||
name: "November",
|
||||
},
|
||||
{
|
||||
value: "12",
|
||||
name: "December",
|
||||
},
|
||||
];
|
||||
|
||||
export class DOBInput extends Component<
|
||||
{
|
||||
onChange: (value: string) => void;
|
||||
onErrorChange: (errors: {
|
||||
month?: string;
|
||||
day?: string;
|
||||
year?: string;
|
||||
}) => void;
|
||||
error: boolean;
|
||||
},
|
||||
{
|
||||
month?: string;
|
||||
day?: string;
|
||||
year?: string;
|
||||
errors: { month?: string; day?: string; year?: string };
|
||||
}
|
||||
{
|
||||
onChange: (value: string) => void;
|
||||
onErrorChange: (errors: {
|
||||
month?: string;
|
||||
day?: string;
|
||||
year?: string;
|
||||
}) => void;
|
||||
error: boolean;
|
||||
},
|
||||
{
|
||||
month?: string;
|
||||
day?: string;
|
||||
year?: string;
|
||||
errors: { month?: string; day?: string; year?: string };
|
||||
}
|
||||
> {
|
||||
state = {
|
||||
month: "",
|
||||
day: "",
|
||||
year: "",
|
||||
errors: {
|
||||
month: undefined,
|
||||
day: undefined,
|
||||
year: undefined,
|
||||
},
|
||||
};
|
||||
state = {
|
||||
month: "",
|
||||
day: "",
|
||||
year: "",
|
||||
errors: {
|
||||
month: undefined,
|
||||
day: undefined,
|
||||
year: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps: any, prevState: any) {
|
||||
if (prevState !== this.state) {
|
||||
this.props.onErrorChange(this.state.errors);
|
||||
componentDidUpdate(prevProps: any, prevState: any) {
|
||||
if (prevState !== this.state) {
|
||||
this.props.onErrorChange(this.state.errors);
|
||||
|
||||
this.props.onChange(
|
||||
this.constructDate({
|
||||
month: this.state.month,
|
||||
day: this.state.day,
|
||||
year: this.state.year,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
this.props.onChange(
|
||||
this.constructDate({
|
||||
month: this.state.month,
|
||||
day: this.state.day,
|
||||
year: this.state.year,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
onInputChange =
|
||||
(type: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
onInputChange =
|
||||
(type: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
|
||||
// clear error for field
|
||||
this.setState(
|
||||
{
|
||||
...this.state,
|
||||
errors: { ...this.state.errors, [type]: undefined },
|
||||
},
|
||||
() => {
|
||||
// ensure only numbers
|
||||
if (isNaN(Number(value))) {
|
||||
this.setState({
|
||||
...this.state,
|
||||
errors: { ...this.state.errors, [type]: "Invalid Date" },
|
||||
});
|
||||
return;
|
||||
}
|
||||
// clear error for field
|
||||
this.setState(
|
||||
{
|
||||
...this.state,
|
||||
errors: { ...this.state.errors, [type]: undefined },
|
||||
},
|
||||
() => {
|
||||
// ensure only numbers
|
||||
if (isNaN(Number(value))) {
|
||||
this.setState({
|
||||
...this.state,
|
||||
errors: { ...this.state.errors, [type]: "Invalid Date" },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === "day") {
|
||||
// day should be a number between 1-31 and not more than 2 digits
|
||||
if (
|
||||
value !== "" &&
|
||||
(value.length > 2 || Number(value) > 31 || Number(value) < 1)
|
||||
) {
|
||||
this.setState({
|
||||
...this.state,
|
||||
day: value,
|
||||
errors: { ...this.state.errors, [type]: "Invalid Date" },
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (type === "day") {
|
||||
// day should be a number between 1-31 and not more than 2 digits
|
||||
if (
|
||||
value !== "" &&
|
||||
(value.length > 2 || Number(value) > 31 || Number(value) < 1)
|
||||
) {
|
||||
this.setState({
|
||||
...this.state,
|
||||
day: value,
|
||||
errors: { ...this.state.errors, [type]: "Invalid Date" },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ ...this.state, day: value });
|
||||
}
|
||||
this.setState({ ...this.state, day: value });
|
||||
}
|
||||
|
||||
if (type === "year") {
|
||||
// year must be between now-min and now-max
|
||||
if (
|
||||
value.length === 4 &&
|
||||
(Number(value) > new Date().getFullYear() - MIN_AGE ||
|
||||
Number(value) < new Date().getFullYear() - MAX_AGE)
|
||||
) {
|
||||
this.setState({
|
||||
...this.state,
|
||||
year: value,
|
||||
errors: { ...this.state.errors, [type]: "Invalid Date" },
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (type === "year") {
|
||||
// year must be between now-min and now-max
|
||||
if (
|
||||
value.length === 4 &&
|
||||
(Number(value) > new Date().getFullYear() - MIN_AGE ||
|
||||
Number(value) < new Date().getFullYear() - MAX_AGE)
|
||||
) {
|
||||
this.setState({
|
||||
...this.state,
|
||||
year: value,
|
||||
errors: { ...this.state.errors, [type]: "Invalid Date" },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ ...this.state, year: value });
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
this.setState({ ...this.state, year: value });
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
constructDate = (values: { month: string; day: string; year: string }) => {
|
||||
const { month, day, year } = values;
|
||||
// pad day with 0 if needed
|
||||
const dayPadded = day?.length === 1 ? `0${day}` : day;
|
||||
return `${year}-${month}-${dayPadded}`;
|
||||
};
|
||||
constructDate = (values: { month: string; day: string; year: string }) => {
|
||||
const { month, day, year } = values;
|
||||
// pad day with 0 if needed
|
||||
const dayPadded = day?.length === 1 ? `0${day}` : day;
|
||||
return `${year}-${month}-${dayPadded}`;
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Container>
|
||||
<SelectSearch
|
||||
placeholder="Month"
|
||||
search
|
||||
options={MONTHS}
|
||||
onChange={(e) => this.setState({ ...this.state, month: e as string })}
|
||||
value={this.state.month}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Day"
|
||||
onChange={this.onInputChange("day")}
|
||||
value={this.state.day}
|
||||
error={this.state.errors.day || this.props.error}
|
||||
maxLength={2}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Year"
|
||||
onChange={this.onInputChange("year")}
|
||||
value={this.state.year}
|
||||
error={this.state.errors.year || this.props.error}
|
||||
maxLength={4}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<Container>
|
||||
<SelectSearch
|
||||
placeholder="Month"
|
||||
search
|
||||
options={MONTHS}
|
||||
onChange={(e) => this.setState({ ...this.state, month: e as string })}
|
||||
value={this.state.month}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Day"
|
||||
onChange={this.onInputChange("day")}
|
||||
value={this.state.day}
|
||||
error={this.state.errors.day || this.props.error}
|
||||
maxLength={2}
|
||||
/>
|
||||
<Input
|
||||
placeholder="Year"
|
||||
onChange={this.onInputChange("year")}
|
||||
value={this.state.year}
|
||||
error={this.state.errors.year || this.props.error}
|
||||
maxLength={4}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DOBInput;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
export default styled.div`
|
||||
color: var(--text);
|
||||
color: var(--text);
|
||||
`;
|
||||
|
@ -3,99 +3,99 @@ import { createGlobalStyle } from "styled-components";
|
||||
import { useAppStore } from "../stores/AppStore";
|
||||
|
||||
export type ThemeVariables =
|
||||
| "brandPrimary"
|
||||
| "brandSecondary"
|
||||
| "primary"
|
||||
| "primaryAlt"
|
||||
| "secondary"
|
||||
| "tertiary"
|
||||
| "text"
|
||||
| "textMuted"
|
||||
| "textLink"
|
||||
| "inputBackground"
|
||||
| "error"
|
||||
| "buttonPrimary"
|
||||
| "buttonPrimaryHover"
|
||||
| "buttonPrimaryActive"
|
||||
| "buttonSecondary"
|
||||
| "buttonSecondaryHover"
|
||||
| "buttonSecondaryActive"
|
||||
| "buttonDanger"
|
||||
| "buttonDangerHover"
|
||||
| "buttonDangerActive"
|
||||
| "buttonSuccess"
|
||||
| "buttonSuccessHover"
|
||||
| "buttonSuccessActive"
|
||||
| "buttonWarning"
|
||||
| "buttonWarningHover"
|
||||
| "buttonWarningActive";
|
||||
| "brandPrimary"
|
||||
| "brandSecondary"
|
||||
| "primary"
|
||||
| "primaryAlt"
|
||||
| "secondary"
|
||||
| "tertiary"
|
||||
| "text"
|
||||
| "textMuted"
|
||||
| "textLink"
|
||||
| "inputBackground"
|
||||
| "error"
|
||||
| "buttonPrimary"
|
||||
| "buttonPrimaryHover"
|
||||
| "buttonPrimaryActive"
|
||||
| "buttonSecondary"
|
||||
| "buttonSecondaryHover"
|
||||
| "buttonSecondaryActive"
|
||||
| "buttonDanger"
|
||||
| "buttonDangerHover"
|
||||
| "buttonDangerActive"
|
||||
| "buttonSuccess"
|
||||
| "buttonSuccessHover"
|
||||
| "buttonSuccessActive"
|
||||
| "buttonWarning"
|
||||
| "buttonWarningHover"
|
||||
| "buttonWarningActive";
|
||||
|
||||
export type Overrides = {
|
||||
[variable in ThemeVariables]: string;
|
||||
[variable in ThemeVariables]: string;
|
||||
};
|
||||
|
||||
export type Theme = Overrides & {
|
||||
light?: boolean;
|
||||
light?: boolean;
|
||||
};
|
||||
|
||||
export const ThemePresets: Record<string, Theme> = {
|
||||
light: {
|
||||
brandPrimary: "#0185ff",
|
||||
brandSecondary: "#ffffff",
|
||||
primary: "#ede8e7",
|
||||
primaryAlt: "",
|
||||
secondary: "#ebe5e4",
|
||||
tertiary: "#e9e2e1",
|
||||
text: "#000000",
|
||||
textMuted: "#232120",
|
||||
textLink: "#00a8fc",
|
||||
inputBackground: "#757575",
|
||||
error: "#e83f36",
|
||||
buttonPrimary: "",
|
||||
buttonPrimaryHover: "",
|
||||
buttonPrimaryActive: "",
|
||||
buttonSecondary: "",
|
||||
buttonSecondaryHover: "",
|
||||
buttonSecondaryActive: "",
|
||||
buttonDanger: "",
|
||||
buttonDangerHover: "",
|
||||
buttonDangerActive: "",
|
||||
buttonSuccess: "",
|
||||
buttonSuccessHover: "",
|
||||
buttonSuccessActive: "",
|
||||
buttonWarning: "",
|
||||
buttonWarningHover: "",
|
||||
buttonWarningActive: "",
|
||||
},
|
||||
dark: {
|
||||
brandPrimary: "#0185ff",
|
||||
brandSecondary: "#ffffff",
|
||||
primary: "#232120",
|
||||
primaryAlt: "#312e2d",
|
||||
secondary: "#1b1918",
|
||||
tertiary: "#141212",
|
||||
text: "#e9e2e1",
|
||||
textMuted: "#85898f",
|
||||
textLink: "#00a8fc",
|
||||
inputBackground: "#121212",
|
||||
error: "#e83f36",
|
||||
// buttons
|
||||
buttonPrimary: "#0185ff",
|
||||
buttonPrimaryHover: "#0078e6",
|
||||
buttonPrimaryActive: "#006acd",
|
||||
buttonSecondary: "#4a4544",
|
||||
buttonSecondaryHover: "#746d69",
|
||||
buttonSecondaryActive: "#5f5a59",
|
||||
buttonDanger: "#ff3a3b",
|
||||
buttonDangerHover: "#ff2d2f",
|
||||
buttonDangerActive: "#ff2425",
|
||||
buttonSuccess: "#34af65",
|
||||
buttonSuccessHover: "#31a660",
|
||||
buttonSuccessActive: "#2d9657",
|
||||
buttonWarning: "#faa61a",
|
||||
buttonWarningHover: "#e69105",
|
||||
buttonWarningActive: "#c27b04",
|
||||
},
|
||||
light: {
|
||||
brandPrimary: "#0185ff",
|
||||
brandSecondary: "#ffffff",
|
||||
primary: "#ede8e7",
|
||||
primaryAlt: "",
|
||||
secondary: "#ebe5e4",
|
||||
tertiary: "#e9e2e1",
|
||||
text: "#000000",
|
||||
textMuted: "#232120",
|
||||
textLink: "#00a8fc",
|
||||
inputBackground: "#757575",
|
||||
error: "#e83f36",
|
||||
buttonPrimary: "",
|
||||
buttonPrimaryHover: "",
|
||||
buttonPrimaryActive: "",
|
||||
buttonSecondary: "",
|
||||
buttonSecondaryHover: "",
|
||||
buttonSecondaryActive: "",
|
||||
buttonDanger: "",
|
||||
buttonDangerHover: "",
|
||||
buttonDangerActive: "",
|
||||
buttonSuccess: "",
|
||||
buttonSuccessHover: "",
|
||||
buttonSuccessActive: "",
|
||||
buttonWarning: "",
|
||||
buttonWarningHover: "",
|
||||
buttonWarningActive: "",
|
||||
},
|
||||
dark: {
|
||||
brandPrimary: "#0185ff",
|
||||
brandSecondary: "#ffffff",
|
||||
primary: "#232120",
|
||||
primaryAlt: "#312e2d",
|
||||
secondary: "#1b1918",
|
||||
tertiary: "#141212",
|
||||
text: "#e9e2e1",
|
||||
textMuted: "#85898f",
|
||||
textLink: "#00a8fc",
|
||||
inputBackground: "#121212",
|
||||
error: "#e83f36",
|
||||
// buttons
|
||||
buttonPrimary: "#0185ff",
|
||||
buttonPrimaryHover: "#0078e6",
|
||||
buttonPrimaryActive: "#006acd",
|
||||
buttonSecondary: "#4a4544",
|
||||
buttonSecondaryHover: "#746d69",
|
||||
buttonSecondaryActive: "#5f5a59",
|
||||
buttonDanger: "#ff3a3b",
|
||||
buttonDangerHover: "#ff2d2f",
|
||||
buttonDangerActive: "#ff2425",
|
||||
buttonSuccess: "#34af65",
|
||||
buttonSuccessHover: "#31a660",
|
||||
buttonSuccessActive: "#2d9657",
|
||||
buttonWarning: "#faa61a",
|
||||
buttonWarningHover: "#e69105",
|
||||
buttonWarningActive: "#c27b04",
|
||||
},
|
||||
};
|
||||
|
||||
const GlobalTheme = createGlobalStyle<{ theme: Theme }>`
|
||||
@ -105,29 +105,29 @@ const GlobalTheme = createGlobalStyle<{ theme: Theme }>`
|
||||
`;
|
||||
|
||||
const toDashed = (str: string) =>
|
||||
str.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
|
||||
str.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
|
||||
|
||||
export const generateVariables = (theme: Theme) => {
|
||||
return (Object.keys(theme) as ThemeVariables[]).map((key) => {
|
||||
const colour = theme[key];
|
||||
try {
|
||||
const r = parseInt(colour.substring(1, 3), 16);
|
||||
const g = parseInt(colour.substring(3, 5), 16);
|
||||
const b = parseInt(colour.substring(5, 7), 16);
|
||||
return `--${toDashed(key)}: ${theme[key]}; --${toDashed(
|
||||
key
|
||||
)}-rgb: rgb(${r}, ${g}, ${b});`;
|
||||
} catch {
|
||||
return `--${toDashed(key)}: ${theme[key]};`;
|
||||
}
|
||||
});
|
||||
return (Object.keys(theme) as ThemeVariables[]).map((key) => {
|
||||
const colour = theme[key];
|
||||
try {
|
||||
const r = parseInt(colour.substring(1, 3), 16);
|
||||
const g = parseInt(colour.substring(3, 5), 16);
|
||||
const b = parseInt(colour.substring(5, 7), 16);
|
||||
return `--${toDashed(key)}: ${theme[key]}; --${toDashed(
|
||||
key
|
||||
)}-rgb: rgb(${r}, ${g}, ${b});`;
|
||||
} catch {
|
||||
return `--${toDashed(key)}: ${theme[key]};`;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default observer(() => {
|
||||
const appStore = useAppStore();
|
||||
const theme = appStore.theme;
|
||||
const appStore = useAppStore();
|
||||
const theme = appStore.theme;
|
||||
|
||||
const variables = theme.computeVariables();
|
||||
const variables = theme.computeVariables();
|
||||
|
||||
return <GlobalTheme theme={variables} />;
|
||||
return <GlobalTheme theme={variables} />;
|
||||
});
|
||||
|
@ -1,13 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
|
||||
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
||||
monospace;
|
||||
}
|
||||
|
@ -9,11 +9,11 @@ import Theme from "./contexts/Theme";
|
||||
import "./index.css";
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById("root") as HTMLElement
|
||||
document.getElementById("root") as HTMLElement
|
||||
);
|
||||
root.render(
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
<Theme />
|
||||
</BrowserRouter>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
<Theme />
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
@ -2,16 +2,16 @@ import Container from "../components/Container";
|
||||
import Text from "../components/Text";
|
||||
|
||||
interface Props {
|
||||
error: Error;
|
||||
error: Error;
|
||||
}
|
||||
|
||||
function ErrorPage({ error }: Props) {
|
||||
return (
|
||||
<Container>
|
||||
<Text>Oops, Something went wrong!</Text>
|
||||
<pre>{error.message}</pre>
|
||||
</Container>
|
||||
);
|
||||
return (
|
||||
<Container>
|
||||
<Text>Oops, Something went wrong!</Text>
|
||||
<pre>{error.message}</pre>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default ErrorPage;
|
||||
|
@ -5,27 +5,27 @@ import { ReactComponent as SpacebarLogoBlue } from "../assets/images/logo/Logo-B
|
||||
import Container from "../components/Container";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const SpacebarLogo = styled(SpacebarLogoBlue)`
|
||||
height: 120px;
|
||||
margin-bottom: 32px;
|
||||
height: 120px;
|
||||
margin-bottom: 32px;
|
||||
`;
|
||||
|
||||
function LoadingPage() {
|
||||
return (
|
||||
<Container>
|
||||
<Wrapper>
|
||||
<SpacebarLogo />
|
||||
<PulseLoader color="var(--brand-secondary)" />
|
||||
</Wrapper>
|
||||
</Container>
|
||||
);
|
||||
return (
|
||||
<Container>
|
||||
<Wrapper>
|
||||
<SpacebarLogo />
|
||||
<PulseLoader color="var(--brand-secondary)" />
|
||||
</Wrapper>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(LoadingPage);
|
||||
|
@ -7,261 +7,260 @@ import Container from "../components/Container";
|
||||
import { useAppStore } from "../stores/AppStore";
|
||||
|
||||
const Wrapper = styled(Container)`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background-color: var(--secondary);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background-color: var(--secondary);
|
||||
`;
|
||||
|
||||
const LoginBox = styled(Container)`
|
||||
background-color: var(--primary-alt);
|
||||
padding: 32px;
|
||||
font-size: 18px;
|
||||
color: var(--text-muted);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
background-color: var(--primary-alt);
|
||||
padding: 32px;
|
||||
font-size: 18px;
|
||||
color: var(--text-muted);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
|
||||
@media (max-width: 480px) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 480px) {
|
||||
width: 480px;
|
||||
border-radius: 18px;
|
||||
}
|
||||
@media (min-width: 480px) {
|
||||
width: 480px;
|
||||
border-radius: 18px;
|
||||
}
|
||||
`;
|
||||
|
||||
const HeaderContainer = styled.div`
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const Header = styled.h1`
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
font-size: 24px;
|
||||
color: var(--text);
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
font-size: 24px;
|
||||
color: var(--text);
|
||||
`;
|
||||
|
||||
const SubHeader = styled.h2`
|
||||
color: var(--text-muted);
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: var(--text-muted);
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
`;
|
||||
|
||||
const FormContainer = styled.form`
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const InputContainer = styled.h1<{ marginBottom: boolean }>`
|
||||
margin-bottom: ${(props) => (props.marginBottom ? "20px" : "0")};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin-bottom: ${(props) => (props.marginBottom ? "20px" : "0")};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
`;
|
||||
|
||||
const LabelWrapper = styled.div<{ error?: boolean }>`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 8px;
|
||||
color: ${(props) => (props.error ? "var(--error)" : "#b1b5bc")};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 8px;
|
||||
color: ${(props) => (props.error ? "var(--error)" : "#b1b5bc")};
|
||||
`;
|
||||
|
||||
const InputErrorText = styled.label`
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
`;
|
||||
|
||||
const InputLabel = styled.label`
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
`;
|
||||
|
||||
const InputWrapper = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const Input = styled.input<{ error?: boolean }>`
|
||||
outline: none;
|
||||
background: var(--secondary);
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
flex: 1;
|
||||
border-radius: 12px;
|
||||
color: var(--text);
|
||||
margin: 0;
|
||||
border: none;
|
||||
aria-invalid: ${(props) => (props.error ? "true" : "false")};
|
||||
outline: none;
|
||||
background: var(--secondary);
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
flex: 1;
|
||||
border-radius: 12px;
|
||||
color: var(--text);
|
||||
margin: 0;
|
||||
border: none;
|
||||
aria-invalid: ${(props) => (props.error ? "true" : "false")};
|
||||
`;
|
||||
|
||||
const PasswordResetLink = styled.button`
|
||||
margin-bottom: 20px;
|
||||
margin-top: 4px;
|
||||
padding: 2px 0;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
color: var(--text-link);
|
||||
background: none;
|
||||
border: none;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 4px;
|
||||
padding: 2px 0;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
color: var(--text-link);
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
const LoginButton = styled(Button)`
|
||||
margin-bottom: 8px;
|
||||
width: 100%;
|
||||
min-width: 130px;
|
||||
min-height: 44px;
|
||||
margin-bottom: 8px;
|
||||
width: 100%;
|
||||
min-width: 130px;
|
||||
min-height: 44px;
|
||||
`;
|
||||
|
||||
const RegisterContainer = styled.div`
|
||||
margin-top: 4px;
|
||||
text-align: initial;
|
||||
margin-top: 4px;
|
||||
text-align: initial;
|
||||
`;
|
||||
|
||||
const RegisterLabel = styled.label`
|
||||
font-size: 14px;
|
||||
font-size: 14px;
|
||||
`;
|
||||
|
||||
const RegisterLink = styled.button`
|
||||
font-size: 14px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-link);
|
||||
font-size: 14px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-link);
|
||||
|
||||
@media (max-width: 480px) {
|
||||
display: inline-block;
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
const Divider = styled.span`
|
||||
padding: 0 4px;
|
||||
padding: 0 4px;
|
||||
`;
|
||||
|
||||
type LoginFormValues = {
|
||||
login: string;
|
||||
password: string;
|
||||
login: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
function LoginPage() {
|
||||
const app = useAppStore();
|
||||
const navigate = useNavigate();
|
||||
const app = useAppStore();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
watch,
|
||||
formState: { errors },
|
||||
setError,
|
||||
} = useForm<LoginFormValues>();
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors },
|
||||
setError,
|
||||
} = useForm<LoginFormValues>();
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
app.api.login(data).catch((e) => {
|
||||
if (e instanceof MFAError) {
|
||||
console.log("MFA Required", e);
|
||||
} else if (e instanceof CaptchaError) {
|
||||
console.log("Captcha Required", e);
|
||||
} else if (e instanceof APIError) {
|
||||
console.log("APIError", e.message, e.code, e.fieldErrors);
|
||||
e.fieldErrors.forEach((fieldError) => {
|
||||
setError(fieldError.field as any, {
|
||||
type: "manual",
|
||||
message: fieldError.error,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.log("General Error", e);
|
||||
}
|
||||
});
|
||||
});
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
app.api.login(data).catch((e) => {
|
||||
if (e instanceof MFAError) {
|
||||
console.log("MFA Required", e);
|
||||
} else if (e instanceof CaptchaError) {
|
||||
console.log("Captcha Required", e);
|
||||
} else if (e instanceof APIError) {
|
||||
console.log("APIError", e.message, e.code, e.fieldErrors);
|
||||
e.fieldErrors.forEach((fieldError) => {
|
||||
setError(fieldError.field as any, {
|
||||
type: "manual",
|
||||
message: fieldError.error,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.log("General Error", e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<LoginBox>
|
||||
<HeaderContainer>
|
||||
<Header>Welcome Back!</Header>
|
||||
<SubHeader>We're so excited to see you again!</SubHeader>
|
||||
</HeaderContainer>
|
||||
return (
|
||||
<Wrapper>
|
||||
<LoginBox>
|
||||
<HeaderContainer>
|
||||
<Header>Welcome Back!</Header>
|
||||
<SubHeader>We're so excited to see you again!</SubHeader>
|
||||
</HeaderContainer>
|
||||
|
||||
<FormContainer onSubmit={onSubmit}>
|
||||
<InputContainer marginBottom={true} style={{ marginTop: 0 }}>
|
||||
<LabelWrapper error={!!errors.login}>
|
||||
<InputLabel>Email</InputLabel>
|
||||
{errors.login && (
|
||||
<InputErrorText>
|
||||
<>
|
||||
<Divider>-</Divider>
|
||||
{errors.login.message}
|
||||
</>
|
||||
</InputErrorText>
|
||||
)}
|
||||
</LabelWrapper>
|
||||
<InputWrapper>
|
||||
<Input
|
||||
type="email"
|
||||
autoFocus
|
||||
{...register("login", { required: true })}
|
||||
error={!!errors.login}
|
||||
/>
|
||||
</InputWrapper>
|
||||
</InputContainer>
|
||||
<FormContainer onSubmit={onSubmit}>
|
||||
<InputContainer marginBottom={true} style={{ marginTop: 0 }}>
|
||||
<LabelWrapper error={!!errors.login}>
|
||||
<InputLabel>Email</InputLabel>
|
||||
{errors.login && (
|
||||
<InputErrorText>
|
||||
<>
|
||||
<Divider>-</Divider>
|
||||
{errors.login.message}
|
||||
</>
|
||||
</InputErrorText>
|
||||
)}
|
||||
</LabelWrapper>
|
||||
<InputWrapper>
|
||||
<Input
|
||||
type="email"
|
||||
autoFocus
|
||||
{...register("login", { required: true })}
|
||||
error={!!errors.login}
|
||||
/>
|
||||
</InputWrapper>
|
||||
</InputContainer>
|
||||
|
||||
<InputContainer marginBottom={false}>
|
||||
<LabelWrapper error={!!errors.password}>
|
||||
<InputLabel>Password</InputLabel>
|
||||
{errors.password && (
|
||||
<InputErrorText>
|
||||
<>
|
||||
<Divider>-</Divider>
|
||||
{errors.password.message}
|
||||
</>
|
||||
</InputErrorText>
|
||||
)}
|
||||
</LabelWrapper>
|
||||
<InputWrapper>
|
||||
<Input
|
||||
type="password"
|
||||
{...register("password", { required: true })}
|
||||
error={!!errors.password}
|
||||
/>
|
||||
</InputWrapper>
|
||||
</InputContainer>
|
||||
<InputContainer marginBottom={false}>
|
||||
<LabelWrapper error={!!errors.password}>
|
||||
<InputLabel>Password</InputLabel>
|
||||
{errors.password && (
|
||||
<InputErrorText>
|
||||
<>
|
||||
<Divider>-</Divider>
|
||||
{errors.password.message}
|
||||
</>
|
||||
</InputErrorText>
|
||||
)}
|
||||
</LabelWrapper>
|
||||
<InputWrapper>
|
||||
<Input
|
||||
type="password"
|
||||
{...register("password", { required: true })}
|
||||
error={!!errors.password}
|
||||
/>
|
||||
</InputWrapper>
|
||||
</InputContainer>
|
||||
|
||||
<PasswordResetLink onClick={() => {}} type="button">
|
||||
Forgot your password?
|
||||
</PasswordResetLink>
|
||||
<LoginButton variant="primary" type="submit">
|
||||
Log In
|
||||
</LoginButton>
|
||||
<PasswordResetLink onClick={() => {}} type="button">
|
||||
Forgot your password?
|
||||
</PasswordResetLink>
|
||||
<LoginButton variant="primary" type="submit">
|
||||
Log In
|
||||
</LoginButton>
|
||||
|
||||
<RegisterContainer>
|
||||
<RegisterLabel>Don't have an account? </RegisterLabel>
|
||||
<RegisterLink
|
||||
onClick={() => {
|
||||
navigate("/register");
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Sign Up
|
||||
</RegisterLink>
|
||||
</RegisterContainer>
|
||||
</FormContainer>
|
||||
</LoginBox>
|
||||
</Wrapper>
|
||||
);
|
||||
<RegisterContainer>
|
||||
<RegisterLabel>Don't have an account? </RegisterLabel>
|
||||
<RegisterLink
|
||||
onClick={() => {
|
||||
navigate("/register");
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Sign Up
|
||||
</RegisterLink>
|
||||
</RegisterContainer>
|
||||
</FormContainer>
|
||||
</LoginBox>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export default LoginPage;
|
||||
|
@ -2,11 +2,11 @@ import Container from "../components/Container";
|
||||
import Text from "../components/Text";
|
||||
|
||||
function NotFoundPage() {
|
||||
return (
|
||||
<Container>
|
||||
<Text>NotFound</Text>
|
||||
</Container>
|
||||
);
|
||||
return (
|
||||
<Container>
|
||||
<Text>NotFound</Text>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default NotFoundPage;
|
||||
|
@ -8,299 +8,299 @@ import DOBInput from "../components/DOBInput";
|
||||
import { useAppStore } from "../stores/AppStore";
|
||||
|
||||
const Wrapper = styled(Container)`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background-color: var(--secondary);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background-color: var(--secondary);
|
||||
`;
|
||||
|
||||
const LoginBox = styled(Container)`
|
||||
background-color: var(--primary-alt);
|
||||
padding: 32px;
|
||||
font-size: 18px;
|
||||
color: var(--text-muted);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
background-color: var(--primary-alt);
|
||||
padding: 32px;
|
||||
font-size: 18px;
|
||||
color: var(--text-muted);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
|
||||
@media (max-width: 480px) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 480px) {
|
||||
width: 480px;
|
||||
border-radius: 18px;
|
||||
}
|
||||
@media (min-width: 480px) {
|
||||
width: 480px;
|
||||
border-radius: 18px;
|
||||
}
|
||||
`;
|
||||
|
||||
const HeaderContainer = styled.div`
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const Header = styled.h1`
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
font-size: 24px;
|
||||
color: var(--text);
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
font-size: 24px;
|
||||
color: var(--text);
|
||||
`;
|
||||
|
||||
const SubHeader = styled.h2`
|
||||
color: var(--text-muted);
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: var(--text-muted);
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
`;
|
||||
|
||||
const FormContainer = styled.form`
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const InputContainer = styled.h1<{ marginBottom: boolean }>`
|
||||
margin-bottom: ${(props) => (props.marginBottom ? "20px" : "0")};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin-bottom: ${(props) => (props.marginBottom ? "20px" : "0")};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
`;
|
||||
|
||||
const LabelWrapper = styled.div<{ error?: boolean }>`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 8px;
|
||||
color: ${(props) => (props.error ? "var(--error)" : "#b1b5bc")};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 8px;
|
||||
color: ${(props) => (props.error ? "var(--error)" : "#b1b5bc")};
|
||||
`;
|
||||
|
||||
const InputErrorText = styled.label`
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
`;
|
||||
|
||||
const InputLabel = styled.label`
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
`;
|
||||
|
||||
const InputWrapper = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const Input = styled.input<{ error?: boolean }>`
|
||||
outline: none;
|
||||
background: var(--secondary);
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
border-radius: 12px;
|
||||
color: var(--text);
|
||||
margin: 0;
|
||||
border: none;
|
||||
aria-invalid: ${(props) => (props.error ? "true" : "false")};
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
background: var(--secondary);
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
border-radius: 12px;
|
||||
color: var(--text);
|
||||
margin: 0;
|
||||
border: none;
|
||||
aria-invalid: ${(props) => (props.error ? "true" : "false")};
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const LoginButton = styled(Button)`
|
||||
margin-bottom: 8px;
|
||||
width: 100%;
|
||||
min-width: 130px;
|
||||
min-height: 44px;
|
||||
margin-bottom: 8px;
|
||||
width: 100%;
|
||||
min-width: 130px;
|
||||
min-height: 44px;
|
||||
`;
|
||||
|
||||
const LoginLink = styled.button`
|
||||
margin-top: 4px;
|
||||
float: left;
|
||||
font-size: 14px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-link);
|
||||
margin-top: 4px;
|
||||
float: left;
|
||||
font-size: 14px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-link);
|
||||
|
||||
@media (max-width: 480px) {
|
||||
display: inline-block;
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
const Divider = styled.span`
|
||||
padding: 0 4px;
|
||||
padding: 0 4px;
|
||||
`;
|
||||
|
||||
type RegisterFormValues = {
|
||||
email: string;
|
||||
username: string;
|
||||
password: string;
|
||||
date_of_birth: string;
|
||||
email: string;
|
||||
username: string;
|
||||
password: string;
|
||||
date_of_birth: string;
|
||||
};
|
||||
|
||||
function RegistrationPage() {
|
||||
const app = useAppStore();
|
||||
const navigate = useNavigate();
|
||||
const app = useAppStore();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
setError,
|
||||
clearErrors,
|
||||
} = useForm<RegisterFormValues>();
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
setError,
|
||||
clearErrors,
|
||||
} = useForm<RegisterFormValues>();
|
||||
|
||||
const dobRegister = register("date_of_birth", {
|
||||
required: true,
|
||||
pattern: /^\d{4}-\d{2}-\d{2}$/,
|
||||
});
|
||||
const dobRegister = register("date_of_birth", {
|
||||
required: true,
|
||||
pattern: /^\d{4}-\d{2}-\d{2}$/,
|
||||
});
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
app.api
|
||||
.register({
|
||||
...data,
|
||||
consent: true,
|
||||
})
|
||||
.catch((e) => {
|
||||
if (e instanceof CaptchaError) {
|
||||
console.log("Captcha Required", e);
|
||||
} else if (e instanceof APIError) {
|
||||
console.log("APIError", e.message, e.code, e.fieldErrors);
|
||||
e.fieldErrors.forEach((fieldError) => {
|
||||
setError(fieldError.field as any, {
|
||||
type: "manual",
|
||||
message: fieldError.error,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.log("General Error", e);
|
||||
}
|
||||
});
|
||||
});
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
app.api
|
||||
.register({
|
||||
...data,
|
||||
consent: true,
|
||||
})
|
||||
.catch((e) => {
|
||||
if (e instanceof CaptchaError) {
|
||||
console.log("Captcha Required", e);
|
||||
} else if (e instanceof APIError) {
|
||||
console.log("APIError", e.message, e.code, e.fieldErrors);
|
||||
e.fieldErrors.forEach((fieldError) => {
|
||||
setError(fieldError.field as any, {
|
||||
type: "manual",
|
||||
message: fieldError.error,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.log("General Error", e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const hasErrors = () => {
|
||||
return Object.values(errors).some((error) => error);
|
||||
};
|
||||
const hasErrors = () => {
|
||||
return Object.values(errors).some((error) => error);
|
||||
};
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<LoginBox>
|
||||
<HeaderContainer>
|
||||
<Header>Create an account</Header>
|
||||
{/* <SubHeader>We're so excited to see you again!</SubHeader> */}
|
||||
</HeaderContainer>
|
||||
return (
|
||||
<Wrapper>
|
||||
<LoginBox>
|
||||
<HeaderContainer>
|
||||
<Header>Create an account</Header>
|
||||
{/* <SubHeader>We're so excited to see you again!</SubHeader> */}
|
||||
</HeaderContainer>
|
||||
|
||||
<FormContainer onSubmit={onSubmit}>
|
||||
<InputContainer marginBottom={true} style={{ marginTop: 0 }}>
|
||||
<LabelWrapper error={!!errors.email}>
|
||||
<InputLabel>Email</InputLabel>
|
||||
{errors.email && (
|
||||
<InputErrorText>
|
||||
<>
|
||||
<Divider>-</Divider>
|
||||
{errors.email.message}
|
||||
</>
|
||||
</InputErrorText>
|
||||
)}
|
||||
</LabelWrapper>
|
||||
<InputWrapper>
|
||||
<Input
|
||||
type="email"
|
||||
autoFocus
|
||||
{...register("email", { required: true })}
|
||||
error={!!errors.email}
|
||||
/>
|
||||
</InputWrapper>
|
||||
</InputContainer>
|
||||
<FormContainer onSubmit={onSubmit}>
|
||||
<InputContainer marginBottom={true} style={{ marginTop: 0 }}>
|
||||
<LabelWrapper error={!!errors.email}>
|
||||
<InputLabel>Email</InputLabel>
|
||||
{errors.email && (
|
||||
<InputErrorText>
|
||||
<>
|
||||
<Divider>-</Divider>
|
||||
{errors.email.message}
|
||||
</>
|
||||
</InputErrorText>
|
||||
)}
|
||||
</LabelWrapper>
|
||||
<InputWrapper>
|
||||
<Input
|
||||
type="email"
|
||||
autoFocus
|
||||
{...register("email", { required: true })}
|
||||
error={!!errors.email}
|
||||
/>
|
||||
</InputWrapper>
|
||||
</InputContainer>
|
||||
|
||||
<InputContainer marginBottom={true} style={{ marginTop: 0 }}>
|
||||
<LabelWrapper error={!!errors.username}>
|
||||
<InputLabel>Username</InputLabel>
|
||||
{errors.username && (
|
||||
<InputErrorText>
|
||||
<>
|
||||
<Divider>-</Divider>
|
||||
{errors.username.message}
|
||||
</>
|
||||
</InputErrorText>
|
||||
)}
|
||||
</LabelWrapper>
|
||||
<InputWrapper>
|
||||
<Input
|
||||
{...register("username", { required: true })}
|
||||
error={!!errors.username}
|
||||
/>
|
||||
</InputWrapper>
|
||||
</InputContainer>
|
||||
<InputContainer marginBottom={true} style={{ marginTop: 0 }}>
|
||||
<LabelWrapper error={!!errors.username}>
|
||||
<InputLabel>Username</InputLabel>
|
||||
{errors.username && (
|
||||
<InputErrorText>
|
||||
<>
|
||||
<Divider>-</Divider>
|
||||
{errors.username.message}
|
||||
</>
|
||||
</InputErrorText>
|
||||
)}
|
||||
</LabelWrapper>
|
||||
<InputWrapper>
|
||||
<Input
|
||||
{...register("username", { required: true })}
|
||||
error={!!errors.username}
|
||||
/>
|
||||
</InputWrapper>
|
||||
</InputContainer>
|
||||
|
||||
<InputContainer marginBottom={false}>
|
||||
<LabelWrapper error={!!errors.password}>
|
||||
<InputLabel>Password</InputLabel>
|
||||
{errors.password && (
|
||||
<InputErrorText>
|
||||
<>
|
||||
<Divider>-</Divider>
|
||||
{errors.password.message}
|
||||
</>
|
||||
</InputErrorText>
|
||||
)}
|
||||
</LabelWrapper>
|
||||
<InputWrapper>
|
||||
<Input
|
||||
type="password"
|
||||
{...register("password", { required: true })}
|
||||
error={!!errors.password}
|
||||
/>
|
||||
</InputWrapper>
|
||||
</InputContainer>
|
||||
<InputContainer marginBottom={false}>
|
||||
<LabelWrapper error={!!errors.password}>
|
||||
<InputLabel>Password</InputLabel>
|
||||
{errors.password && (
|
||||
<InputErrorText>
|
||||
<>
|
||||
<Divider>-</Divider>
|
||||
{errors.password.message}
|
||||
</>
|
||||
</InputErrorText>
|
||||
)}
|
||||
</LabelWrapper>
|
||||
<InputWrapper>
|
||||
<Input
|
||||
type="password"
|
||||
{...register("password", { required: true })}
|
||||
error={!!errors.password}
|
||||
/>
|
||||
</InputWrapper>
|
||||
</InputContainer>
|
||||
|
||||
<InputContainer marginBottom={true}>
|
||||
<LabelWrapper error={!!errors.date_of_birth}>
|
||||
<InputLabel>Date of Birth</InputLabel>
|
||||
{errors.date_of_birth && (
|
||||
<InputErrorText>
|
||||
<>
|
||||
<Divider>-</Divider>
|
||||
{errors.date_of_birth.message}
|
||||
</>
|
||||
</InputErrorText>
|
||||
)}
|
||||
</LabelWrapper>
|
||||
<InputContainer marginBottom={true}>
|
||||
<LabelWrapper error={!!errors.date_of_birth}>
|
||||
<InputLabel>Date of Birth</InputLabel>
|
||||
{errors.date_of_birth && (
|
||||
<InputErrorText>
|
||||
<>
|
||||
<Divider>-</Divider>
|
||||
{errors.date_of_birth.message}
|
||||
</>
|
||||
</InputErrorText>
|
||||
)}
|
||||
</LabelWrapper>
|
||||
|
||||
<InputWrapper>
|
||||
<DOBInput
|
||||
onChange={(value) => setValue("date_of_birth", value)}
|
||||
onErrorChange={(errors) => {
|
||||
const hasError = Object.values(errors).some((error) => error);
|
||||
if (hasError) {
|
||||
// set to first error
|
||||
setError("date_of_birth", {
|
||||
type: "manual",
|
||||
message: Object.values(errors).filter((x) => x)[0],
|
||||
});
|
||||
} else clearErrors("date_of_birth");
|
||||
}}
|
||||
error={!!errors.date_of_birth}
|
||||
/>
|
||||
</InputWrapper>
|
||||
</InputContainer>
|
||||
<InputWrapper>
|
||||
<DOBInput
|
||||
onChange={(value) => setValue("date_of_birth", value)}
|
||||
onErrorChange={(errors) => {
|
||||
const hasError = Object.values(errors).some((error) => error);
|
||||
if (hasError) {
|
||||
// set to first error
|
||||
setError("date_of_birth", {
|
||||
type: "manual",
|
||||
message: Object.values(errors).filter((x) => x)[0],
|
||||
});
|
||||
} else clearErrors("date_of_birth");
|
||||
}}
|
||||
error={!!errors.date_of_birth}
|
||||
/>
|
||||
</InputWrapper>
|
||||
</InputContainer>
|
||||
|
||||
<LoginButton variant="primary" type="submit" disabled={hasErrors()}>
|
||||
Create Account
|
||||
</LoginButton>
|
||||
<LoginButton variant="primary" type="submit" disabled={hasErrors()}>
|
||||
Create Account
|
||||
</LoginButton>
|
||||
|
||||
<LoginLink
|
||||
onClick={() => {
|
||||
navigate("/login", { replace: true });
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Already have an account?
|
||||
</LoginLink>
|
||||
</FormContainer>
|
||||
</LoginBox>
|
||||
</Wrapper>
|
||||
);
|
||||
<LoginLink
|
||||
onClick={() => {
|
||||
navigate("/login", { replace: true });
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Already have an account?
|
||||
</LoginLink>
|
||||
</FormContainer>
|
||||
</LoginBox>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export default RegistrationPage;
|
||||
|
@ -4,13 +4,13 @@ import { useAppStore } from "../stores/AppStore";
|
||||
import LoadingPage from "./LoadingPage";
|
||||
|
||||
function RootPage() {
|
||||
const app = useAppStore();
|
||||
const app = useAppStore();
|
||||
|
||||
if (!app.ready) {
|
||||
return <LoadingPage />;
|
||||
}
|
||||
if (!app.ready) {
|
||||
return <LoadingPage />;
|
||||
}
|
||||
|
||||
return <Container>RootPage</Container>;
|
||||
return <Container>RootPage</Container>;
|
||||
}
|
||||
|
||||
export default observer(RootPage);
|
||||
|
@ -3,36 +3,36 @@ import { makeAutoObservable, observable, runInAction } from "mobx";
|
||||
import ThemeStore from "./ThemeStore";
|
||||
|
||||
export default class AppStore {
|
||||
theme: ThemeStore;
|
||||
api: Client;
|
||||
@observable ready = false;
|
||||
theme: ThemeStore;
|
||||
api: Client;
|
||||
@observable ready = false;
|
||||
|
||||
constructor() {
|
||||
this.theme = new ThemeStore();
|
||||
constructor() {
|
||||
this.theme = new ThemeStore();
|
||||
|
||||
this.api = new Client({
|
||||
rest: {
|
||||
url: "https://canary.slowcord.understars.dev/api",
|
||||
},
|
||||
});
|
||||
this.api = new Client({
|
||||
rest: {
|
||||
url: "https://old.server.spacebar.chat/api",
|
||||
},
|
||||
});
|
||||
|
||||
this.api.on("debug", console.debug);
|
||||
this.api.on("warn", console.warn);
|
||||
this.api.on("error", console.error);
|
||||
this.api.on("ready", this.onReady.bind(this));
|
||||
this.api.on("debug", console.debug);
|
||||
this.api.on("warn", console.warn);
|
||||
this.api.on("error", console.error);
|
||||
this.api.on("ready", this.onReady.bind(this));
|
||||
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
onReady() {
|
||||
runInAction(() => {
|
||||
this.ready = true;
|
||||
});
|
||||
}
|
||||
onReady() {
|
||||
runInAction(() => {
|
||||
this.ready = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const appStore = new AppStore();
|
||||
|
||||
export function useAppStore() {
|
||||
return appStore;
|
||||
return appStore;
|
||||
}
|
||||
|
@ -3,22 +3,22 @@ import type { Theme } from "../contexts/Theme";
|
||||
import { ThemePresets } from "../contexts/Theme";
|
||||
|
||||
export default class ThemeStore {
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
@computed
|
||||
getVariables(): Theme {
|
||||
return {
|
||||
...ThemePresets["dark"],
|
||||
light: false,
|
||||
};
|
||||
}
|
||||
@computed
|
||||
getVariables(): Theme {
|
||||
return {
|
||||
...ThemePresets["dark"],
|
||||
light: false,
|
||||
};
|
||||
}
|
||||
|
||||
@computed
|
||||
computeVariables() {
|
||||
const variables = this.getVariables();
|
||||
@computed
|
||||
computeVariables() {
|
||||
const variables = this.getVariables();
|
||||
|
||||
return variables as unknown as Theme;
|
||||
}
|
||||
return variables as unknown as Theme;
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
},
|
||||
"include": ["src"]
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user