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