more nav-based routing

This commit is contained in:
Brandon Thomas committed 2021-05-26 00:51:16 -04:00
1 parent 84006c85a7
commit 7da7fb75cb
10 files changed
+601 -87

No files matched your search

+3 -1
View File
@@ -133,7 +133,9 @@ class App extends React.Component<Props, State> {
/>
</Route>
<Route path="/">
<h1>New Vocodes</h1>
<NewVocodesContainer
sessionWrapper={this.state.sessionWrapper}
/>
</Route>
</Switch>
@@ -1,6 +1,7 @@
import React from 'react';
import { MigrationMode } from '../App'
import { SessionWrapper } from '../session/SessionWrapper';
import { Link } from 'react-router-dom';
interface Props {
sessionWrapper: SessionWrapper,
@@ -29,10 +30,11 @@ function MigrationTopNav_Session(props: Props) {
onClick={() => props.setMigrationModeCallback(MigrationMode.OLD_VOCODES)}
>{displayName}</button>
} else {
sessionLink = <button
sessionLink = <Link
to="/signup"
className="button is-danger is-pulled-right"
onClick={() => props.setMigrationModeCallback(MigrationMode.OLD_VOCODES)}
>Sign Up / Login</button>
>Sign Up / Login</Link>
}
return sessionLink;
+7
View File
@@ -1,5 +1,12 @@
import { SessionStateResponse } from "./SessionState";
/**
* SessionWrapper can wrap each of the following states:
*
* 1) non-existing session
* 2) logged-out session
* 3) logged-in session
*/
export class SessionWrapper {
sessionStateResponse?: SessionStateResponse;
+2 -2
View File
@@ -15,10 +15,10 @@ import { VideoJob, VideoJobStatus } from './modes/video/VideoJob';
import { VideoJobPoller } from './modes/video/VideoJobPoller';
import { VideoQueuePoller } from './modes/video/VideoQueuePoller';
import { VideoQueueStats } from './modes/video/VideoQueueStats';
import { SignupComponent } from './modes/signup/SignupComponent';
import { SignupComponent } from '../v2/signup/SignupComponent';
import { ApiConfig } from './api/ApiConfig';
import { SessionStateResponse } from './api/SessionState';
import { LoginComponent } from './modes/login/LoginComponent';
import { LoginComponent } from '../v2/login/LoginComponent';
import { UploadComponent } from './modes/upload/UploadComponent';
interface Props {
@@ -1,6 +1,6 @@
import React from 'react';
import { ApiConfig } from '../../api/ApiConfig';
import { Mode } from '../../../AppMode';
import { ApiConfig } from '../../v1/api/ApiConfig';
import { Mode } from '../../AppMode';
enum FieldTriState {
EMPTY_FALSE,
@@ -1,6 +1,6 @@
import React from 'react';
import { ApiConfig } from '../../api/ApiConfig';
import { Mode } from '../../../AppMode';
import { ApiConfig } from '../../v1/api/ApiConfig';
import { Mode } from '../../AppMode';
enum FieldTriState {
EMPTY_FALSE,
+18 -19
View File
@@ -1,13 +1,12 @@
import React from 'react';
import { NewMode } from '../../v2/NewVocodesContainer'
import { SessionStateResponse } from '../api/SessionState';
import { SessionWrapper } from '../../session/SessionWrapper';
interface Props {
sessionState?: SessionStateResponse,
mode: NewMode,
sessionWrapper: SessionWrapper,
///historyBadgeCount: number,
//isHistoryCountBadgeVisible: boolean,
switchModeCallback: (mode: NewMode ) => void,
logoutHandler: () => void,
}
@@ -46,30 +45,30 @@ function NewTopNav(props: Props) {
if (loggedIn) {
loginManagement = (
<span>
<a href="#profile" onClick={() => props.switchModeCallback(NewMode.MY_PROFILE_MODE)}>{displayName}</a>
<a href="#logout" onClick={() => props.logoutHandler()}>Log Out</a>
<a href="#profile" onClick={() => {}}>{displayName}</a>
<a href="#logout" onClick={() => {}}>Log Out</a>
</span>
);
column1 = (
<div className="column">
<button
className="button is-link is-large is-inverted"
onClick={() => props.switchModeCallback(NewMode.MY_PROFILE_MODE)}
onClick={() => {}}
>Firehose</button>
</div>
);
} else {
loginManagement = (
<span>
<a href="#login" onClick={() => props.switchModeCallback(NewMode.LOGIN_MODE)}>Login</a>
<a href="#signup" onClick={() => props.switchModeCallback(NewMode.SIGNUP_MODE)} className="signup">Sign Up</a>
<a href="#login" onClick={() => {}}>Login</a>
<a href="#signup" onClick={() => {}} className="signup">Sign Up</a>
</span>
);
column1 = (
<div className="column">
<button
className="button is-link is-large is-inverted"
onClick={() => props.switchModeCallback(NewMode.SIGNUP_MODE)}
onClick={() => {}}
>Sign Up</button>
</div>
);
@@ -77,7 +76,7 @@ function NewTopNav(props: Props) {
<div className="column">
<button
className="button is-link is-large is-inverted"
onClick={() => props.switchModeCallback(NewMode.LOGIN_MODE)}
onClick={() => {}}
>Login</button>
</div>
);
@@ -88,10 +87,10 @@ function NewTopNav(props: Props) {
<strong>NEW Community Features let you use Your Own Voices and Video:</strong>
<br />
<br />
<a href="#community_tts" onClick={() => props.switchModeCallback(NewMode.COMMUNITY_TTS_MODE)}>TTS</a>
<a href="#community_video" onClick={() => props.switchModeCallback(NewMode.COMMUNITY_W2L_MODE)}>Video</a>
<a href="#upload" onClick={() => props.switchModeCallback(NewMode.UPLOAD_MODE)}>Upload</a>
<a href="#my_data" onClick={() => props.switchModeCallback(NewMode.MY_DATA_MODE)}>My Data</a>
<a href="#community_tts" onClick={() => {}}>TTS</a>
<a href="#community_video" onClick={() => {}}>Video</a>
<a href="#upload" onClick={() => {}}>Upload</a>
<a href="#my_data" onClick={() => {}}>My Data</a>
</div>
);
}
@@ -105,13 +104,13 @@ function NewTopNav(props: Props) {
<div className="column">
<button
className="button is-link is-large is-inverted"
onClick={() => props.switchModeCallback(NewMode.COMMUNITY_TTS_MODE)}
onClick={() => {}}
>Text to Speech 🗣️</button>
</div>
<div className="column">
<button
className="button is-link is-large is-inverted"
onClick={() => props.switchModeCallback(NewMode.COMMUNITY_TTS_MODE)}
onClick={() => {}}
>Video 🎥</button>
</div>
</div>
@@ -119,19 +118,19 @@ function NewTopNav(props: Props) {
<div className="column">
<button
className="button is-link is-large is-inverted"
onClick={() => props.switchModeCallback(NewMode.COMMUNITY_TTS_MODE)}
onClick={() => {}}
>Upload TTS ⬆️</button>
</div>
<div className="column">
<button
className="button is-link is-large is-inverted"
onClick={() => props.switchModeCallback(NewMode.COMMUNITY_TTS_MODE)}
onClick={() => {}}
>Upload Video ⬆️</button>
</div>
<div className="column">
<button
className="button is-link is-large is-inverted"
onClick={() => props.switchModeCallback(NewMode.COMMUNITY_TTS_MODE)}
onClick={() => {}}
>My Data</button>
</div>
</div>
+21 -59
View File
@@ -1,27 +1,16 @@
import React from 'react';
import { NewTopNav } from '../v1/navigation/NewTopNav';
import { SessionStateResponse } from '../v1/api/SessionState';
import { LoginComponent } from '../v1/modes/login/LoginComponent';
import { SignupComponent } from '../v1/modes/signup/SignupComponent';
import { LoginComponent } from './login/LoginComponent';
import { SignupComponent } from './signup/SignupComponent';
import { Switch, Route } from 'react-router-dom';
import { SessionWrapper } from '../session/SessionWrapper';
enum NewMode {
LOGIN_MODE,
SIGNUP_MODE,
COMMUNITY_TTS_MODE,
COMMUNITY_W2L_MODE,
UPLOAD_MODE,
MY_DATA_MODE,
MY_PROFILE_MODE,
EDIT_MY_PROFILE_MODE,
}
interface Props {
sessionState?: SessionStateResponse,
sessionWrapper: SessionWrapper,
}
interface State {
mode: NewMode,
}
class NewVocodesContainer extends React.Component<Props, State> {
@@ -30,64 +19,37 @@ class NewVocodesContainer extends React.Component<Props, State> {
super(props);
this.state = {
mode: NewMode.SIGNUP_MODE,
}
}
switchMode = (mode: NewMode) => {
this.setState({mode: mode});
}
logout = () => {
}
public render() {
let innerComponent = <div />;
let loggedIn = false;
let displayName = "My Account";
if (this.props.sessionState !== undefined) {
console.log('sessionstate', this.props.sessionState);
loggedIn = this.props.sessionState.logged_in;
if (this.props.sessionState.user !== undefined &&
this.props.sessionState.user !== null) {
displayName = this.props.sessionState.user.display_name;
}
}
switch (this.state.mode) {
case NewMode.SIGNUP_MODE:
innerComponent = (
<SignupComponent
querySessionCallback={()=>{}}
loggedIn={loggedIn}
/>
);
break;
case NewMode.LOGIN_MODE:
innerComponent = (
<LoginComponent
querySessionCallback={()=>{}}
loggedIn={loggedIn}
/>
);
break;
}
return (
<div id="main" className="mainwrap">
<div id="viewable">
<NewTopNav
mode={this.state.mode}
switchModeCallback={this.switchMode}
logoutHandler={this.logout}
sessionState={this.props.sessionState}
sessionWrapper={this.props.sessionWrapper}
/>
{innerComponent}
<Switch>
<Route path="/login">
<LoginComponent
querySessionCallback={()=>{}}
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
<Route path="/signup">
<SignupComponent
querySessionCallback={()=>{}}
sessionWrapper={this.props.sessionWrapper}
/>
</Route>
</Switch>
</div>
</div>
@@ -95,4 +57,4 @@ class NewVocodesContainer extends React.Component<Props, State> {
}
}
export { NewVocodesContainer, NewMode }
export { NewVocodesContainer }
+160
View File
@@ -0,0 +1,160 @@
import React from 'react';
import { ApiConfig } from '../../v1/api/ApiConfig';
import { Mode } from '../../AppMode';
import { SessionWrapper } from '../../session/SessionWrapper';
enum FieldTriState {
EMPTY_FALSE,
FALSE,
TRUE,
}
interface Props {
sessionWrapper: SessionWrapper,
querySessionCallback : () => void,
switchModeCallback?: (mode: Mode) => void,
}
interface State {
usernameOrEmail: string,
usernameOrEmailValid: FieldTriState,
usernameOrEmailInvalidReason: string,
password: string,
passwordValid: FieldTriState,
passwordInvalidReason: string,
}
class LoginComponent extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
usernameOrEmail: "",
usernameOrEmailValid: FieldTriState.EMPTY_FALSE,
usernameOrEmailInvalidReason: "",
password: "",
passwordValid: FieldTriState.EMPTY_FALSE,
passwordInvalidReason: "",
};
}
handleUsernameOrEmailChange = (ev: React.FormEvent<HTMLInputElement>) => {
ev.preventDefault();
const usernameOrEmail = (ev.target as HTMLInputElement).value;
this.setState({
usernameOrEmail: usernameOrEmail,
})
return false;
}
handlePasswordChange = (ev: React.FormEvent<HTMLInputElement>) => {
ev.preventDefault();
const password = (ev.target as HTMLInputElement).value;
this.setState({
password: password,
})
return false;
}
handleFormSubmit = (ev: React.FormEvent<HTMLFormElement>) : boolean => {
ev.preventDefault();
const api = new ApiConfig();
const endpointUrl = api.login();
const request = {
username_or_email: this.state.usernameOrEmail,
password: this.state.password,
}
fetch(endpointUrl, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify(request),
})
.then(res => res.json())
.then(res => {
console.log('login response', res)
if (res.success) {
console.log('querying new session');
this.props.querySessionCallback();
if (this.props.switchModeCallback !== undefined) {
this.props.switchModeCallback(Mode.SPEAK_MODE);
}
return;
}
})
.catch(e => {
//this.props.onSpeakErrorCallback();
});
return false;
}
public render() {
if (this.props.sessionWrapper.isLoggedIn()) {
return <div>Invalid view for logged in users.</div>;
}
return (
<div>
<h1 className="title is-1"> Login </h1>
<p></p>
<form onSubmit={this.handleFormSubmit}>
<div className="field">
<label className="label">Username or Email</label>
<div className="control has-icons-left has-icons-right">
<input className="input" type="text" placeholder="Username or Email" value={this.state.usernameOrEmail} onChange={this.handleUsernameOrEmailChange} />
<span className="icon is-small is-left">
<i className="fas fa-user"></i>
</span>
<span className="icon is-small is-right">
<i className="fas fa-check"></i>
</span>
</div>
{/*<p className="help"></p>*/}
</div>
<div className="field">
<label className="label">Password</label>
<div className="control has-icons-left has-icons-right">
<input className="input" type="password" placeholder="Password" value={this.state.password} onChange={this.handlePasswordChange} />
<span className="icon is-small is-left">
<i className="fas fa-envelope"></i>
</span>
<span className="icon is-small is-right">
<i className="fas fa-exclamation-triangle"></i>
</span>
</div>
{/*<p className="help"></p>*/}
</div>
<br />
<div className="field is-grouped">
<div className="control">
<button className="button is-link is-large">Login</button>
</div>
</div>
</form>
</div>
)
}
}
export { LoginComponent };
+382
View File
@@ -0,0 +1,382 @@
import React from 'react';
import { ApiConfig } from '../../v1/api/ApiConfig';
import { Mode } from '../../AppMode';
import { SessionWrapper } from '../../session/SessionWrapper';
enum FieldTriState {
EMPTY_FALSE,
FALSE,
TRUE,
}
interface Props {
sessionWrapper: SessionWrapper,
querySessionCallback : () => void,
switchModeCallback?: (mode: Mode) => void,
}
interface State {
username: string,
usernameValid: FieldTriState,
usernameInvalidReason: string,
email: string,
emailValid: FieldTriState,
emailInvalidReason: string,
password: string,
passwordValid: FieldTriState,
passwordInvalidReason: string,
passwordConfirmation: string,
passwordConfirmationValid: FieldTriState,
passwordConfirmationInvalidReason: string,
}
class SignupComponent extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
username: "",
usernameValid: FieldTriState.EMPTY_FALSE,
usernameInvalidReason: "",
email: "",
emailValid: FieldTriState.EMPTY_FALSE,
emailInvalidReason: "",
password: "",
passwordValid: FieldTriState.EMPTY_FALSE,
passwordInvalidReason: "",
passwordConfirmation: "",
passwordConfirmationValid: FieldTriState.EMPTY_FALSE,
passwordConfirmationInvalidReason: "",
};
}
handleUsernameChange = (ev: React.FormEvent<HTMLInputElement>) => {
ev.preventDefault();
const username = (ev.target as HTMLInputElement).value;
let usernameValid = FieldTriState.EMPTY_FALSE;
let usernameInvalidReason = "";
if (username.length > 1) {
if (username.length < 3) {
usernameValid = FieldTriState.FALSE;
usernameInvalidReason = "username is too short";
} else if (username.length > 15) {
usernameValid = FieldTriState.FALSE;
usernameInvalidReason = "username is too long";
} else {
usernameValid = FieldTriState.TRUE;
}
}
this.setState({
username: username,
usernameValid: usernameValid,
usernameInvalidReason: usernameInvalidReason,
})
return false;
}
handleEmailChange = (ev: React.FormEvent<HTMLInputElement>) => {
ev.preventDefault();
const email = (ev.target as HTMLInputElement).value;
let emailValid = FieldTriState.EMPTY_FALSE;
let emailInvalidReason = "";
if (email.length > 1) {
if (email.length < 3) {
emailValid = FieldTriState.FALSE;
emailInvalidReason = "email is too short";
} else if (!email.includes("@")) {
emailValid = FieldTriState.FALSE;
emailInvalidReason = "email is invalid";
} else {
emailValid = FieldTriState.TRUE;
}
}
this.setState({
email: email,
emailValid: emailValid,
emailInvalidReason: emailInvalidReason,
})
return false;
}
handlePasswordChange = (ev: React.FormEvent<HTMLInputElement>) => {
ev.preventDefault();
const password = (ev.target as HTMLInputElement).value;
let passwordValid = FieldTriState.EMPTY_FALSE;
let passwordInvalidReason = "";
let passwordConfirmationValid = FieldTriState.EMPTY_FALSE;
let passwordConfirmationInvalidReason = "";
if (password.length > 1) {
if (password.length < 5) {
passwordValid = FieldTriState.FALSE;
passwordInvalidReason = "password is too short";
} else {
passwordValid = FieldTriState.TRUE;
}
if (password !== this.state.passwordConfirmation) {
passwordConfirmationValid = FieldTriState.FALSE;
passwordConfirmationInvalidReason = "passwords do not match";
} else {
passwordConfirmationValid = FieldTriState.TRUE;
passwordConfirmationInvalidReason = "";
}
}
this.setState({
password: password,
passwordValid: passwordValid,
passwordInvalidReason: passwordInvalidReason,
passwordConfirmationValid: passwordConfirmationValid,
passwordConfirmationInvalidReason: passwordConfirmationInvalidReason,
})
return false;
}
handlePasswordConfirmationChange = (ev: React.FormEvent<HTMLInputElement>) => {
ev.preventDefault();
const passwordConfirmation = (ev.target as HTMLInputElement).value;
let passwordConfirmationValid = FieldTriState.EMPTY_FALSE;
let passwordConfirmationInvalidReason = "";
if (passwordConfirmation.length > 1) {
if (passwordConfirmation !== this.state.password) {
passwordConfirmationValid = FieldTriState.FALSE;
passwordConfirmationInvalidReason = "passwords do not match";
} else {
passwordConfirmationValid = FieldTriState.TRUE;
passwordConfirmationInvalidReason = "";
}
}
this.setState({
passwordConfirmation: passwordConfirmation,
passwordConfirmationValid: passwordConfirmationValid,
passwordConfirmationInvalidReason: passwordConfirmationInvalidReason,
})
return false;
}
handleFormSubmit = (ev: React.FormEvent<HTMLFormElement>) : boolean => {
ev.preventDefault();
if (!this.state.usernameValid ||
!this.state.emailValid ||
!this.state.passwordValid ||
!this.state.passwordConfirmationValid) {
return false;
}
const api = new ApiConfig();
const endpointUrl = api.createAccount();
const request = {
username: this.state.username,
email_address: this.state.email,
password: this.state.password,
password_confirmation: this.state.passwordConfirmation,
}
fetch(endpointUrl, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify(request),
})
.then(res => res.json())
.then(res => {
console.log('create account response', res)
if (res.success) {
console.log('querying new session');
this.props.querySessionCallback();
if (this.props.switchModeCallback !== undefined) {
this.props.switchModeCallback(Mode.SPEAK_MODE);
}
return;
}
})
.catch(e => {
//this.props.onSpeakErrorCallback();
});
return false;
}
public render() {
if (this.props.sessionWrapper.isLoggedIn()) {
return <div>Invalid view for logged in users.</div>;
}
let usernameInputClass = "input";
let usernameHelpClass = "help";
switch (this.state.usernameValid) {
case FieldTriState.EMPTY_FALSE:
break;
case FieldTriState.FALSE:
usernameInputClass += " is-danger";
usernameHelpClass += " is-danger";
break;
case FieldTriState.TRUE:
usernameInputClass += " is-success";
usernameHelpClass += " is-success";
break;
}
let emailInputClass = "input";
let emailHelpClass = "help";
switch (this.state.emailValid) {
case FieldTriState.EMPTY_FALSE:
break;
case FieldTriState.FALSE:
emailInputClass += " is-danger";
emailHelpClass += " is-danger";
break;
case FieldTriState.TRUE:
emailInputClass += " is-success";
emailHelpClass += " is-success";
break;
}
let passwordInputClass = "input";
let passwordHelpClass = "help";
switch (this.state.passwordValid) {
case FieldTriState.EMPTY_FALSE:
break;
case FieldTriState.FALSE:
passwordInputClass += " is-danger";
passwordHelpClass += " is-danger";
break;
case FieldTriState.TRUE:
passwordInputClass += " is-success";
passwordHelpClass += " is-success";
break;
}
let passwordConfirmationInputClass = "input";
let passwordConfirmationHelpClass = "help";
switch (this.state.passwordConfirmationValid) {
case FieldTriState.EMPTY_FALSE:
break;
case FieldTriState.FALSE:
passwordConfirmationInputClass += " is-danger";
passwordConfirmationHelpClass += " is-danger";
break;
case FieldTriState.TRUE:
passwordConfirmationInputClass += " is-success";
passwordConfirmationHelpClass += " is-success";
break;
}
return (
<div>
<h1 className="title is-1"> Sign Up </h1>
<form onSubmit={this.handleFormSubmit}>
<div className="field">
<label className="label">Username</label>
<div className="control has-icons-left has-icons-right">
<input className={usernameInputClass} type="text" placeholder="Username" value={this.state.username} onChange={this.handleUsernameChange} />
<span className="icon is-small is-left">
<i className="fas fa-user"></i>
</span>
<span className="icon is-small is-right">
<i className="fas fa-check"></i>
</span>
</div>
<p className={usernameHelpClass}>{this.state.usernameInvalidReason}</p>
</div>
<div className="field">
<label className="label">Email</label>
<div className="control has-icons-left has-icons-right">
<input className={emailInputClass} type="email" placeholder="Email" value={this.state.email} onChange={this.handleEmailChange} />
<span className="icon is-small is-left">
<i className="fas fa-envelope"></i>
</span>
<span className="icon is-small is-right">
<i className="fas fa-exclamation-triangle"></i>
</span>
</div>
<p className={emailHelpClass}>{this.state.emailInvalidReason}</p>
</div>
<div className="field">
<label className="label">Password</label>
<div className="control has-icons-left has-icons-right">
<input className={passwordInputClass} type="password" placeholder="Password" value={this.state.password} onChange={this.handlePasswordChange} />
<span className="icon is-small is-left">
<i className="fas fa-envelope"></i>
</span>
<span className="icon is-small is-right">
<i className="fas fa-exclamation-triangle"></i>
</span>
</div>
<p className={passwordHelpClass}>{this.state.passwordInvalidReason}</p>
</div>
<div className="field">
<label className="label">Password Confirmation</label>
<div className="control has-icons-left has-icons-right">
<input className={passwordConfirmationInputClass} type="password" placeholder="Password confirmation" value={this.state.passwordConfirmation} onChange={this.handlePasswordConfirmationChange} />
<span className="icon is-small is-left">
<i className="fas fa-envelope"></i>
</span>
<span className="icon is-small is-right">
<i className="fas fa-exclamation-triangle"></i>
</span>
</div>
<p className={passwordConfirmationHelpClass}>{this.state.passwordConfirmationInvalidReason}</p>
</div>
<br />
<div className="notification is-warning">
<strong>Remember your password!</strong> We don't have password reset currently, and it'll be a
few more weeks before it's added (there are more important features to work on). If you lose your
password, please let us know in Discord.
</div>
<div className="field is-grouped">
<div className="control">
<button className="button is-link is-large">Sign up</button>
</div>
</div>
</form>
</div>
)
}
}
export { SignupComponent };