1
0
mirror of https://github.com/spacebarchat/client.git synced 2024-11-21 09:52:31 +01:00

change default instance url + formatting

This commit is contained in:
Puyodead1 2023-04-10 16:51:14 -04:00
parent 28784bec9e
commit 092de4eac5
No known key found for this signature in database
GPG Key ID: A4FA4FEC0DD353FC
27 changed files with 26372 additions and 21845 deletions

44
.editorconfig Normal file
View 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

View File

@ -1,5 +1,5 @@
const CracoEsbuildPlugin = require("craco-esbuild");
module.exports = {
plugins: [{ plugin: CracoEsbuildPlugin }],
plugins: [{ plugin: CracoEsbuildPlugin }],
};

33974
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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>

View File

@ -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"
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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 />;
};

View File

@ -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)";
}
}};
`;

View File

@ -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;
`;

View File

@ -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;
}

View File

@ -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;

View File

@ -1,5 +1,5 @@
import styled from "styled-components";
export default styled.div`
color: var(--text);
color: var(--text);
`;

View File

@ -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} />;
});

View File

@ -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;
}

View File

@ -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>
);

View File

@ -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;

View File

@ -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);

View File

@ -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?&nbsp;</RegisterLabel>
<RegisterLink
onClick={() => {
navigate("/register");
}}
type="button"
>
Sign Up
</RegisterLink>
</RegisterContainer>
</FormContainer>
</LoginBox>
</Wrapper>
);
<RegisterContainer>
<RegisterLabel>Don't have an account?&nbsp;</RegisterLabel>
<RegisterLink
onClick={() => {
navigate("/register");
}}
type="button"
>
Sign Up
</RegisterLink>
</RegisterContainer>
</FormContainer>
</LoginBox>
</Wrapper>
);
}
export default LoginPage;

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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"]
}