diff --git a/webclient/src/components/Guard/AuthGuard.tsx b/webclient/src/components/Guard/AuthGuard.tsx index 3dc82d12f..243455264 100644 --- a/webclient/src/components/Guard/AuthGuard.tsx +++ b/webclient/src/components/Guard/AuthGuard.tsx @@ -9,7 +9,7 @@ import { AuthenticationService } from "api"; const AuthGuard = ({ state }: AuthGuardProps) => { return !AuthenticationService.isConnected(state) - ? + ? :
; }; diff --git a/webclient/src/components/InputField/InputField.css b/webclient/src/components/InputField/InputField.css new file mode 100644 index 000000000..7ddfd0de6 --- /dev/null +++ b/webclient/src/components/InputField/InputField.css @@ -0,0 +1,20 @@ +.inputField { + position: relative; +} + +.inputField-validation { + position: absolute; + top: 0; + right: 0; + transform: translateY(-50%); + font-weight: bold; +} + +.inputField-error { + display: flex; + align-items: center; +} + +.inputField-error svg { + margin-left: 4px; +} diff --git a/webclient/src/components/InputField/InputField.tsx b/webclient/src/components/InputField/InputField.tsx index 3cf16c63c..bbfefed93 100644 --- a/webclient/src/components/InputField/InputField.tsx +++ b/webclient/src/components/InputField/InputField.tsx @@ -1,17 +1,47 @@ import React from "react"; +import { styled } from '@material-ui/core/styles'; import TextField from "@material-ui/core/TextField"; +import ErrorOutlinedIcon from '@material-ui/icons/ErrorOutlined'; -const InputField = ({ input, label, name, autoComplete, type }) => ( - +import './InputField.css'; + +const InputField = ({ input, label, name, autoComplete, type, meta: { touched, error, warning } }) => ( +
+ { touched && ( +
+ { + ( error && + + {error} + + + ) || + + ( warning && {warning} ) + } +
+ ) } + + +
); +const ThemedFieldError = styled('div')(({ theme }) => ({ + color: theme.palette.error.main +})); + +const ThemedFieldWarning = styled('div')(({ theme }) => ({ + color: theme.palette.warning.main +})); + export default InputField; \ No newline at end of file diff --git a/webclient/src/components/KnownHosts/KnownHosts.css b/webclient/src/components/KnownHosts/KnownHosts.css new file mode 100644 index 000000000..4b499b52f --- /dev/null +++ b/webclient/src/components/KnownHosts/KnownHosts.css @@ -0,0 +1,3 @@ +.KnownHosts { + width: 100%; +} diff --git a/webclient/src/components/KnownHosts/KnownHosts.tsx b/webclient/src/components/KnownHosts/KnownHosts.tsx new file mode 100644 index 000000000..3783564fb --- /dev/null +++ b/webclient/src/components/KnownHosts/KnownHosts.tsx @@ -0,0 +1,79 @@ +// eslint-disable-next-line +import React, { useEffect, useState } from 'react'; +import { Select, MenuItem } from '@material-ui/core'; +import Button from '@material-ui/core/Button'; +import FormControl from '@material-ui/core/FormControl'; +import IconButton from '@material-ui/core/IconButton'; +import InputLabel from '@material-ui/core/InputLabel'; +import EditRoundedIcon from '@material-ui/icons/Edit'; + +import { HostDTO } from 'services'; +import { DefaultHosts, getHostPort } from 'types'; + +import './KnownHosts.css'; + +const KnownHosts = ({ onChange }) => { + const [state, setState] = useState({ + hosts: [], + selectedHost: 0, + }); + + useEffect(() => { + HostDTO.getAll().then(async hosts => { + if (hosts?.length) { + setState(s => ({ ...s, hosts })); + } else { + setState(s => ({ ...s, hosts: DefaultHosts })); + await HostDTO.bulkAdd(DefaultHosts); + } + }); + }, []); + + useEffect(() => { + if (state.hosts.length) { + onChange(getHostPort(state.hosts[state.selectedHost])); + } + }, [state, onChange]); + + const selectHost = (selectedHost) => { + setState(s => ({ ...s, selectedHost })); + }; + + const addKnownHost = () => { + console.log('KnownHosts->addKnownHost'); + }; + + const editKnownHost = (hostIndex) => { + console.log('KnownHosts->editKnownHost: ', state.hosts[hostIndex]); + }; + + return ( + + Host + + + ) +}; + +export default KnownHosts; diff --git a/webclient/src/components/index.ts b/webclient/src/components/index.ts index 4fcf62bb3..5f392f565 100644 --- a/webclient/src/components/index.ts +++ b/webclient/src/components/index.ts @@ -4,6 +4,7 @@ export { default as CardDetails } from './CardDetails/CardDetails'; export { default as Header } from './Header/Header'; export { default as InputField } from './InputField/InputField'; export { default as InputAction } from './InputAction/InputAction'; +export { default as KnownHosts } from './KnownHosts/KnownHosts'; export { default as Message } from './Message/Message'; export { default as VirtualList } from './VirtualList/VirtualList'; export { default as UserDisplay} from './UserDisplay/UserDisplay'; diff --git a/webclient/src/containers/App/AppShellRoutes.tsx b/webclient/src/containers/App/AppShellRoutes.tsx index 61a6b68ea..414dfc6a8 100644 --- a/webclient/src/containers/App/AppShellRoutes.tsx +++ b/webclient/src/containers/App/AppShellRoutes.tsx @@ -9,6 +9,7 @@ import { Player, Room, Server, + Login, Logs } from "containers"; @@ -22,7 +23,8 @@ const Routes = () => ( } /> { } />} } /> - + } /> + ); diff --git a/webclient/src/containers/Login/Login.css b/webclient/src/containers/Login/Login.css new file mode 100644 index 000000000..2258ae1d6 --- /dev/null +++ b/webclient/src/containers/Login/Login.css @@ -0,0 +1,72 @@ +.login { + height: 100%; + padding: 50px; +} + +.login__wrapper { + display: flex; + flex-direction: column; + align-items: center; +} + +.login-content { + width: 100%; + max-width: 1000px; + display: flex; + border-radius: 8px; + overflow: hidden; +} + +.login-content__header { + font-family: Teko, sans-serif; + font-size: 34px; + font-weight: bold; + display: flex; + align-items: center; +} + +.login-content__header img { + height: 60px; + margin-right: 15px; + margin-bottom: 20px; +} + +.login-content__form, +.login-content__description { + width: 50%; +} + +.login-content__form { + padding: 50px 50px 33px; +} + +.login-content__form h1 { + margin-bottom: 20px; +} + +.login-form { + margin-top: 30px; +} + +.login-content__description { + display: flex; + justify-content: center; + align-items: center; + font-size: 24px; + text-transform: uppercase; +} + +.login-footer { + margin-top: 30px; +} + +.login-footer_register { + margin-bottom: 10px; + font-weight: bold; +} + +.login-content__connectionStatus { + text-align: center; + margin: 20px 0; + padding: 20px; +} diff --git a/webclient/src/containers/Login/Login.tsx b/webclient/src/containers/Login/Login.tsx new file mode 100644 index 000000000..5e43ba3d6 --- /dev/null +++ b/webclient/src/containers/Login/Login.tsx @@ -0,0 +1,97 @@ +// eslint-disable-next-line +import React from "react"; +import { connect } from "react-redux"; +import { Redirect } from "react-router-dom"; +import { styled } from '@material-ui/core/styles'; +import Button from '@material-ui/core/Button'; +import Paper from '@material-ui/core/Paper'; +import Typography from '@material-ui/core/Typography'; + +import { AuthenticationService } from "api"; +import { LoginForm } from "forms"; +import { RouteEnum } from "types"; +import { /* ServerDispatch, */ ServerSelectors } from "store"; + +import "./Login.css"; +import logo from "images/logo.png"; + +const Login = ({ state, description }: LoginProps) => { + const isConnected = AuthenticationService.isConnected(state); + + const showDescription = () => { + return !isConnected && description?.length; + } + + const createAccount = () => { + console.log('Login.createAccount->openForgotPasswordDialog'); + }; + + return ( +
+ { isConnected && } + +
+ +
+ + logo + COCKATRICE + + Login + A cross-platform virtual tabletop for multiplayer card games. +
+ +
+ + { + showDescription() && ( + + {description} + + ) + } + +
+
+ Not registered yet? + +
+ + Cockatrice is an open source project @{ new Date().getUTCFullYear() } + +
+
+ + + description + +
+
+
+ ); +} + +const ThemedLoginContent = styled('div')(({ theme }) => ({ + backgroundColor: theme.palette.background.paper +})); + +const ThemedLoginDescription = styled('div')(({ theme }) => ({ + backgroundColor: theme.palette.primary.main, + color: theme.palette.primary.contrastText, +})); + +const ThemedLoginHeader = styled('div')(({ theme }) => ({ + color: theme.palette.success.light +})); + +interface LoginProps { + state: number; + description: string; +} + +const mapStateToProps = state => ({ + state: ServerSelectors.getState(state), + description: ServerSelectors.getDescription(state), +}); + +export default connect(mapStateToProps)(Login); diff --git a/webclient/src/containers/Room/SayMessage.tsx b/webclient/src/containers/Room/SayMessage.tsx index 2c33825c1..382f3f336 100644 --- a/webclient/src/containers/Room/SayMessage.tsx +++ b/webclient/src/containers/Room/SayMessage.tsx @@ -7,7 +7,7 @@ import { InputAction } from 'components'; const SayMessage = ({ handleSubmit }) => (
- + ); diff --git a/webclient/src/containers/Server/Server.css b/webclient/src/containers/Server/Server.css index ea5e2dec7..8d4e2e8dd 100644 --- a/webclient/src/containers/Server/Server.css +++ b/webclient/src/containers/Server/Server.css @@ -10,33 +10,6 @@ align-items: center; } -.server .form-wrapper { - display: flex; - flex-direction: column; -} - -.server-connect { - width: 100%; - display: flex; - flex-direction: column; - align-items: center; -} - -.server-connect__form, -.server-connect__description { - width: 100%; - max-width: 300px; -} - -.server-connect__form { - margin: 50px 0; -} - -.server-connect__description { - text-align: center; - margin: 20px 0; - padding: 20px; -} .serverRoomWrapper { height: 100%; @@ -58,4 +31,4 @@ padding: 10px; background: white; z-index: 1; -} \ No newline at end of file +} diff --git a/webclient/src/containers/Server/Server.tsx b/webclient/src/containers/Server/Server.tsx index 5f3edf4ae..4ced89ed8 100644 --- a/webclient/src/containers/Server/Server.tsx +++ b/webclient/src/containers/Server/Server.tsx @@ -3,135 +3,59 @@ import React, { Component } from "react"; import { connect } from "react-redux"; import { withRouter } from "react-router-dom"; -import Button from "@material-ui/core/Button"; import ListItem from "@material-ui/core/ListItem"; import Paper from "@material-ui/core/Paper"; import { RoomsSelectors, ServerSelectors } from "store"; -import { AuthenticationService } from "api"; - import { ThreePaneLayout, UserDisplay, VirtualList } from "components"; -import { ConnectForm, RegisterForm } from "forms"; -import { Room, StatusEnum, User } from "types"; +import { Room, User } from "types"; import Rooms from './Rooms'; import "./Server.css"; class Server extends Component { - constructor(props) { - super(props); - - this.showDescription = this.showDescription.bind(this); - this.showRegisterForm = this.showRegisterForm.bind(this); - this.hideRegisterForm = this.hideRegisterForm.bind(this); - - this.state = { - register: false - }; - } - - showDescription(state, description) { - const isDisconnected = state === StatusEnum.DISCONNECTED; - const hasDescription = description && !!description.length; - - return isDisconnected && hasDescription; - } - - showRegisterForm() { - this.setState({register: true}); - } - - hideRegisterForm() { - this.setState({register: false}); - } - render() { - const { message, rooms, joinedRooms, history, state, description, users } = this.props; - const { register } = this.state; - const isConnected = AuthenticationService.isConnected(state); + const { message, rooms, joinedRooms, history, users } = this.props; return ( -
- { - isConnected - ? ( ) - : ( -
- - { - register - ? ( ) - : ( ) - } - -
- ) - } - { - !isConnected && this.showDescription(state, description) && ( - - {description} - - ) - } +
+ + + + )} + + bottom={( + +
+ + )} + + side={( + +
+ Users connected to server: {users.length} +
+ users[index].name } + items={ users.map(user => ( + + + + ) ) } + /> +
+ )} + />
); } } -const ServerRooms = ({ rooms, joinedRooms, history, message, users}) => ( -
- - - - )} - - bottom={( - -
- - )} - - side={( - -
- Users connected to server: {users.length} -
- users[index].name } - items={ users.map(user => ( - - - - ) ) } - /> -
- )} - /> -
-); - -const Connect = ({register}) => ( -
- - -
-); - -const Register = ({ connect }) => ( -
- - -
-); - interface ServerProps { message: string; - state: number; - description: string; rooms: Room[]; joinedRooms: Room[]; users: User[]; @@ -139,16 +63,14 @@ interface ServerProps { } interface ServerState { - register: boolean; + } const mapStateToProps = state => ({ message: ServerSelectors.getMessage(state), - state: ServerSelectors.getState(state), - description: ServerSelectors.getDescription(state), rooms: RoomsSelectors.getRooms(state), joinedRooms: RoomsSelectors.getJoinedRooms(state), users: ServerSelectors.getUsers(state) }); -export default withRouter(connect(mapStateToProps)(Server)); \ No newline at end of file +export default withRouter(connect(mapStateToProps)(Server)); diff --git a/webclient/src/containers/index.ts b/webclient/src/containers/index.ts index f39d3725b..e9efbd6fe 100644 --- a/webclient/src/containers/index.ts +++ b/webclient/src/containers/index.ts @@ -5,4 +5,5 @@ export { default as Decks } from './Decks/Decks'; export { default as Room } from "./Room/Room"; export { default as Player } from "./Player/Player"; export { default as Server } from "./Server/Server"; -export { default as Logs } from "./Logs/Logs"; \ No newline at end of file +export { default as Logs } from "./Logs/Logs"; +export { default as Login } from "./Login/Login"; diff --git a/webclient/src/forms/LoginForm/LoginForm.css b/webclient/src/forms/LoginForm/LoginForm.css new file mode 100644 index 000000000..4b176476f --- /dev/null +++ b/webclient/src/forms/LoginForm/LoginForm.css @@ -0,0 +1,20 @@ +.loginForm { + width: 100%; +} + +.loginForm-item { + margin-bottom: 20px; +} + +.loginForm-actions { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: -20px; + margin-bottom: 20px; + font-weight: bold; +} + +.loginForm-submit { + width: 100%; +} diff --git a/webclient/src/forms/LoginForm/LoginForm.tsx b/webclient/src/forms/LoginForm/LoginForm.tsx new file mode 100644 index 000000000..0ce753665 --- /dev/null +++ b/webclient/src/forms/LoginForm/LoginForm.tsx @@ -0,0 +1,73 @@ +// eslint-disable-next-line +import React from "react"; +import { connect } from "react-redux"; +import { Form, Field, reduxForm, change } from "redux-form" + +import Button from "@material-ui/core/Button"; + +import { InputField, KnownHosts } from "components"; +// import { ServerDispatch } from "store"; +import { FormKey } from 'types'; + +import "./LoginForm.css"; + +const LoginForm = (props) => { + const { dispatch, handleSubmit } = props; + + const forgotPassword = () => { + console.log('LoginForm.forgotPassword->openForgotPasswordDialog'); + }; + + const onHostChange = ({ host, port }) => { + dispatch(change(FormKey.LOGIN, 'host', host)); + dispatch(change(FormKey.LOGIN, 'port', port)); + } + + return ( +
+
+
+ +
+
+ +
+
+ Auto Connect + +
+
+ +
+
+ +
+ ); +} + +const propsMap = { + form: FormKey.LOGIN, + validate: values => { + const errors: any = {}; + + if (!values.user) errors.user = 'Required'; + if (!values.pass) errors.pass = 'Required'; + if (!values.host) errors.host = 'Required'; + if (!values.port) errors.port = 'Required'; + + return errors; + } +}; + +const mapStateToProps = () => ({ + initialValues: { + // host: "mtg.tetrarch.co/servatrice", + // port: "443" + // host: "server.cockatrice.us", + // port: "4748" + } +}); + +export default connect(mapStateToProps)(reduxForm(propsMap)(LoginForm)); diff --git a/webclient/src/forms/index.ts b/webclient/src/forms/index.ts index 72fa688ec..141ff1bed 100644 --- a/webclient/src/forms/index.ts +++ b/webclient/src/forms/index.ts @@ -1,4 +1,5 @@ export { default as CardImportForm } from './CardImportForm/CardImportForm'; export { default as ConnectForm } from './ConnectForm/ConnectForm'; +export { default as LoginForm } from './LoginForm/LoginForm'; export { default as RegisterForm } from './RegisterForm/RegisterForm'; export { default as SearchForm } from './SearchForm/SearchForm'; diff --git a/webclient/src/images/logo.png b/webclient/src/images/logo.png new file mode 100644 index 000000000..7ce83bd20 Binary files /dev/null and b/webclient/src/images/logo.png differ diff --git a/webclient/src/index.css b/webclient/src/index.css index 3dc46340d..015c037cd 100644 --- a/webclient/src/index.css +++ b/webclient/src/index.css @@ -1,3 +1,5 @@ +@import url('https://fonts.googleapis.com/css2?family=Teko&display=swap'); + :root { } diff --git a/webclient/src/material-theme.ts b/webclient/src/material-theme.ts index 472c27d58..d2a373450 100644 --- a/webclient/src/material-theme.ts +++ b/webclient/src/material-theme.ts @@ -1,69 +1,157 @@ import { createMuiTheme } from '@material-ui/core'; +const palette = { + background: { + default: 'dimgrey', + paper: '#FFFFFF', + }, + primary: { + main: '#7033DB', + light: 'rgba(112, 51, 219, .3)', + dark: '#401C7F', + contrastText: '#FFFFFF', + }, + // secondary: { + // main: '', + // light: '', + // dark: '', + // contrastText: '', + // }, + // error: { + // main: '', + // light: '', + // dark: '', + // contrastText: '', + // }, + // warning: { + // main: '', + // light: '', + // dark: '', + // contrastText: '', + // }, + // info: { + // main: '', + // light: '', + // dark: '', + // contrastText: '', + // }, + success: { + main: '#6CDF39', + light: '#6CDF39', + // dark: '', + // contrastText: '', + }, +}; + export const materialTheme = createMuiTheme({ - // overrides: { + palette, + + overrides: { // MuiCssBaseline: { // '@global': { // '@font-face': [], // }, // }, - // MuiButton: { - // text: { - // color: 'white', - // }, - // }, - // }, + MuiButton: { + root: { + fontWeight: 'bold', + textTransform: 'none', - palette: { - primary: { - main: '#7033DB', - light: 'rgba(112, 51, 219, .3)', - dark: '#401C7F', - contrastText: '#FFFFFF', + '&.rounded': { + // 'border-radius': '50px', + }, + + '&.tall': { + 'height': '40px', + }, + }, + }, + + MuiList: { + root: { + padding: '8px', + + '&.MuiList-padding': { + paddingBottom: '4px', + }, + + '& .MuiButton-root': { + width: '100%', + }, + + '& > .MuiButtonBase-root': { + padding: '8px 16px', + marginBottom: '4px', + borderRadius: 0, + justifyContent: 'space-between', + }, + + '& .MuiButtonBase-root.Mui-selected, & .MuiButtonBase-root.Mui-selected:hover, & .MuiButtonBase-root:hover': { + background: palette.primary.light + }, + + [[ + '& .MuiButtonBase-root.Mui-selected', + '& .MuiButtonBase-root.Mui-selected:hover', + '& .MuiButtonBase-root:hover' + ].join(', ')]: { + background: palette.primary.light + }, + }, + }, + + MuiListItem: { + root: { + }, + }, + + MuiInputBase: { + formControl: { + '& .MuiSelect-root svg': { + display: 'none', + }, + }, + }, + + MuiOutlinedInput: { + root: { + '&.Mui-focused .MuiOutlinedInput-notchedOutline': { + borderWidth: '1px', + }, + + '.rounded &': { + // 'border-radius': '50px', + }, + + '.tall &': { + height: '40px', + }, + }, }, - // secondary: { - // main: '', - // light: '', - // dark: '', - // contrastText: '', - // }, - // error: { - // main: '', - // light: '', - // dark: '', - // contrastText: '', - // }, - // warning: { - // main: '', - // light: '', - // dark: '', - // contrastText: '', - // }, - // info: { - // main: '', - // light: '', - // dark: '', - // contrastText: '', - // }, - // success: { - // main: '', - // light: '', - // dark: '', - // contrastText: '', - // }, }, typography: { fontSize: 12, - // h1: {}, + h1: { + fontSize: 28, + fontWeight: 'bold', + }, // h2: {}, // h3: {}, // h4: {}, // h5: {}, // h6: {}, - // subtitle1: {}, - // subtitle2: {}, + subtitle1: { + fontSize: 14, + fontWeight: 'bold', + lineHeight: 1.4, + color: '#9E9E9E', + }, + subtitle2: { + lineHeight: 1.4, + color: '#9E9E9E', + }, // body1: {}, // body2: {}, // button: {}, diff --git a/webclient/src/services/DexieDTOs/HostDTO.ts b/webclient/src/services/DexieDTOs/HostDTO.ts new file mode 100644 index 000000000..e58c82313 --- /dev/null +++ b/webclient/src/services/DexieDTOs/HostDTO.ts @@ -0,0 +1,28 @@ +import { IndexableType } from 'dexie'; +import { Host } from 'types'; + +import { dexieService } from '../DexieService'; + +export class HostDTO extends Host { + save() { + return dexieService.hosts.put(this); + } + + static add(host: HostDTO): Promise { + return dexieService.hosts.add(host); + } + + static get(id): Promise { + return dexieService.hosts.where('id').equals(id).first(); + } + + static getAll(): Promise { + return dexieService.hosts.toArray(); + } + + static bulkAdd(hosts: Host[]): Promise { + return dexieService.hosts.bulkAdd(hosts); + } +}; + +dexieService.hosts.mapToClass(HostDTO); diff --git a/webclient/src/services/DexieDTOs/index.ts b/webclient/src/services/DexieDTOs/index.ts index 2ec930169..f95954c55 100644 --- a/webclient/src/services/DexieDTOs/index.ts +++ b/webclient/src/services/DexieDTOs/index.ts @@ -1,3 +1,4 @@ export * from './CardDTO'; export * from './SetDTO'; export * from './TokenDTO'; +export * from './HostDTO'; diff --git a/webclient/src/services/DexieService.ts b/webclient/src/services/DexieService.ts index 2429481b8..b9141ddb7 100644 --- a/webclient/src/services/DexieService.ts +++ b/webclient/src/services/DexieService.ts @@ -4,12 +4,14 @@ enum Stores { CARDS = 'cards', SETS = 'sets', TOKENS = 'tokens', + HOSTS = 'hosts', } const StoreKeyIndexes = { - [Stores.CARDS]: "name", - [Stores.SETS]: "code", - [Stores.TOKENS]: "name.value", + [Stores.CARDS]: 'name', + [Stores.SETS]: 'code', + [Stores.TOKENS]: 'name.value', + [Stores.HOSTS]: '++id,name', }; class DexieService { @@ -30,6 +32,10 @@ class DexieService { get tokens() { return this.db.table(Stores.TOKENS); } + + get hosts() { + return this.db.table(Stores.HOSTS); + } } export const dexieService = new DexieService(); diff --git a/webclient/src/types/forms.ts b/webclient/src/types/forms.ts index 753885ead..32cf7e408 100644 --- a/webclient/src/types/forms.ts +++ b/webclient/src/types/forms.ts @@ -3,6 +3,7 @@ export enum FormKey { ADD_TO_IGNORE = "ADD_TO_IGNORE", CARD_IMPORT = "CARD_IMPORT", CONNECT = "CONNECT", + LOGIN = "LOGIN", REGISTER = "REGISTER", SEARCH_LOGS = "SEARCH_LOGS", } diff --git a/webclient/src/types/routes.tsx b/webclient/src/types/routes.tsx index e5e1bf63d..0dd3bba6f 100644 --- a/webclient/src/types/routes.tsx +++ b/webclient/src/types/routes.tsx @@ -1,10 +1,11 @@ export enum RouteEnum { - PLAYER = "/player/:name", - SERVER = "/server", - ROOM = "/room/:roomId", - LOGS = "/logs", - GAME = "/game", - DECKS = "/decks", - DECK = "/deck", - ACCOUNT = "/account", + PLAYER = '/player/:name', + SERVER = '/server', + ROOM = '/room/:roomId', + LOGIN = '/', + LOGS = '/logs', + GAME = '/game', + DECKS = '/decks', + DECK = '/deck', + ACCOUNT = '/account', } diff --git a/webclient/src/types/server.tsx b/webclient/src/types/server.tsx index 23a70d25a..d3a158811 100644 --- a/webclient/src/types/server.tsx +++ b/webclient/src/types/server.tsx @@ -31,6 +31,48 @@ export enum StatusEnumLabel { "Disconnecting" = 99 } +export class Host { + id?: number; + name: string; + host: string; + port: string; + localHost?: string; + localPort?: string; + editable: boolean; +} + +export const DefaultHosts: Host[] = [ + { + name: 'Rooster', + host: 'server.cockatrice.us/servatrice', + port: '4748', + localHost: 'server.cockatrice.us', + editable: false, + }, + { + name: 'Tetrarch', + host: 'mtg.tetrarch.co/servatrice', + port: '4748', + editable: false, + }, +]; + +export const getHostPort = (host: Host): { host: string, port: string } => { + const isLocal = window.location.hostname === 'localhost'; + + if (!host) { + return { + host: '', + port: '' + }; + } + + return { + host: !isLocal ? host.host : host.localHost || host.host, + port: !isLocal ? host.port : host.localPort || host.port, + } +}; + export enum KnownHost { ROOSTER = 'Rooster', TETRARCH = 'Tetrarch',