redirecting to in-client page automatic

This commit is contained in:
Martina 2020-11-16 18:10:43 -08:00
parent f24e130791
commit 349fc0f66a
14 changed files with 682 additions and 371 deletions

View File

@ -82,6 +82,19 @@ export const getFileExtension = (name) => {
return ""; return "";
}; };
export const createQueryParams = (params) => {
let query = "?";
let first = true;
for (const [key, value] of Object.entries(params)) {
if (!first) {
query += "&";
}
query += `${key}=${value}`;
first = false;
}
return query;
};
export const getCIDFromIPFS = (url) => { export const getCIDFromIPFS = (url) => {
// NOTE(andrew) // NOTE(andrew)
const cid = url.includes("/ipfs/") const cid = url.includes("/ipfs/")

View File

@ -129,6 +129,7 @@ export default class ApplicationPage extends React.Component {
const id = Window.getQueryParameterByName("scene"); const id = Window.getQueryParameterByName("scene");
const user = Window.getQueryParameterByName("user"); const user = Window.getQueryParameterByName("user");
const slate = Window.getQueryParameterByName("slate"); const slate = Window.getQueryParameterByName("slate");
const cid = Window.getQueryParameterByName("cid");
let wsclient = Websockets.getClient(); let wsclient = Websockets.getClient();
if (wsclient) { if (wsclient) {
@ -153,7 +154,7 @@ export default class ApplicationPage extends React.Component {
if (!Strings.isEmpty(id) && this.state.viewer) { if (!Strings.isEmpty(id) && this.state.viewer) {
console.log("redirecting to page"); console.log("redirecting to page");
return this._handleNavigateTo({ id, user, slate }); return this._handleNavigateTo({ id, user, slate, cid });
} }
} }
@ -494,7 +495,6 @@ export default class ApplicationPage extends React.Component {
_handleSidebarLoading = (sidebarLoading) => this.setState({ sidebarLoading }); _handleSidebarLoading = (sidebarLoading) => this.setState({ sidebarLoading });
//change the naem to hydrate. and use this only upon initial sign in (should be the only time you need it)
rehydrate = async (options) => { rehydrate = async (options) => {
const response = await Actions.hydrateAuthenticatedUser(); const response = await Actions.hydrateAuthenticatedUser();
@ -653,8 +653,6 @@ export default class ApplicationPage extends React.Component {
}); });
} }
this._handleAction({ type: "NAVIGATE", value: "V1_NAVIGATION_HOME" });
let unseenAnnouncements = []; let unseenAnnouncements = [];
for (let feature of announcements) { for (let feature of announcements) {
if (!Object.keys(this.state.viewer.onboarding).includes(feature)) { if (!Object.keys(this.state.viewer.onboarding).includes(feature)) {
@ -682,6 +680,17 @@ export default class ApplicationPage extends React.Component {
if (newAccount) { if (newAccount) {
Actions.updateSearch("create-user"); Actions.updateSearch("create-user");
} }
const id = Window.getQueryParameterByName("scene");
const user = Window.getQueryParameterByName("user");
const slate = Window.getQueryParameterByName("slate");
const cid = Window.getQueryParameterByName("cid");
if (!Strings.isEmpty(id) && this.state.viewer) {
console.log("redirecting to page");
this._handleNavigateTo({ id, user, slate, cid });
} else {
this._handleAction({ type: "NAVIGATE", value: "V1_NAVIGATION_HOME" });
}
return response; return response;
}; };
@ -696,8 +705,8 @@ export default class ApplicationPage extends React.Component {
if (jwt) { if (jwt) {
cookies.remove(Credentials.session.key); cookies.remove(Credentials.session.key);
window.location.reload();
} }
window.location.replace("/_");
}; };
_handleViewerChange = (e) => { _handleViewerChange = (e) => {
@ -722,7 +731,13 @@ export default class ApplicationPage extends React.Component {
// + e.g. to display <SceneProfile/> while on the Home tab // + e.g. to display <SceneProfile/> while on the Home tab
// + `scene` should be the decorator of the component you want displayed // + `scene` should be the decorator of the component you want displayed
return this._handleNavigateTo( return this._handleNavigateTo(
{ id: options.value, scene: options.scene, user: options.user, slate: options.slate }, {
id: options.value,
scene: options.scene,
user: options.user,
slate: options.slate,
cid: options.cid,
},
options.data, options.data,
options.redirect options.redirect
); );
@ -760,13 +775,17 @@ export default class ApplicationPage extends React.Component {
return alert(JSON.stringify(options)); return alert(JSON.stringify(options));
}; };
_handleUpdateData = ({ data }) => {
this.setState({ data });
};
_handleNavigateTo = (next, data = null, redirect = false) => { _handleNavigateTo = (next, data = null, redirect = false) => {
if (next.id) { if (next.id) {
window.history.replaceState( window.history.replaceState(
{ ...next }, { ...next },
"Slate", "Slate",
`?scene=${next.id}${next.user ? `&user=${next.user}` : ""}${ `?scene=${next.id}${next.user ? `&user=${next.user}` : ""}${
next.slate ? `&slate=${next.slate}` : "" next.slate ? `&slate=${next.slate}${next.cid ? `&cid=${next.cid}` : ""}` : ""
}` }`
); );
} }
@ -909,6 +928,7 @@ export default class ApplicationPage extends React.Component {
onUpload: this._handleUploadFiles, onUpload: this._handleUploadFiles,
onBack: this._handleBack, onBack: this._handleBack,
onForward: this._handleForward, onForward: this._handleForward,
onUpdateData: this._handleUpdateData,
sceneId: current.target.id, sceneId: current.target.id,
mobile: this.state.mobile, mobile: this.state.mobile,
resources: this.props.resources, resources: this.props.resources,

View File

@ -152,7 +152,6 @@ export default class ApplicationHeader extends React.Component {
//--> argue with haris about how that looks //--> argue with haris about how that looks
_handleKeyDown = (e) => { _handleKeyDown = (e) => {
console.log(e.key);
let prevValue = this.keysPressed[e.key]; let prevValue = this.keysPressed[e.key];
if (prevValue) { if (prevValue) {
return; return;
@ -166,12 +165,10 @@ export default class ApplicationHeader extends React.Component {
}; };
_handleKeyUp = (e) => { _handleKeyUp = (e) => {
console.log("key up");
this.keysPressed = {}; this.keysPressed = {};
}; };
_handleCreateSearch = (e) => { _handleCreateSearch = (e) => {
console.log("create search");
dispatchCustomEvent({ dispatchCustomEvent({
name: "show-search", name: "show-search",
detail: {}, detail: {},

View File

@ -1,9 +1,12 @@
import * as React from "react"; import * as React from "react";
import * as Constants from "~/common/constants"; import * as Constants from "~/common/constants";
import * as System from "~/components/system"; import * as System from "~/components/system";
import * as Actions from "~/common/actions";
import * as Credentials from "~/common/credentials";
import { css } from "@emotion/core"; import { css } from "@emotion/core";
import { Logo } from "~/common/logo"; import { Logo } from "~/common/logo";
import { SignIn } from "~/components/core/SignIn";
const STYLES_BACKGROUND = css` const STYLES_BACKGROUND = css`
z-index: ${Constants.zindex.tooltip}; z-index: ${Constants.zindex.tooltip};
@ -79,49 +82,39 @@ const STYLES_LINK_ITEM = css`
transition: 200ms ease all; transition: 200ms ease all;
word-wrap: break-word; word-wrap: break-word;
:visited {
color: ${Constants.system.black};
}
:hover { :hover {
color: ${Constants.system.brand}; color: ${Constants.system.brand};
} }
`; `;
export const CTATransition = (props) => { export default class CTATransition extends React.Component {
const [open, setOpen] = React.useState(false); render() {
return (
<div>
{open && (
<div css={STYLES_BACKGROUND}>
<div css={STYLES_TRANSITION}>
<div css={STYLES_EXPLAINER}>Sign up or sign in to continue</div>
<br />
<div css={STYLES_POPOVER}>
<Logo height="36px" style={{ display: "block", margin: "56px auto 0px auto" }} />
React.useEffect(() => { <System.P style={{ margin: "56px 0", textAlign: "center" }}>
setOpen(props.open); An open-source file sharing network for research and collaboration
}, [props.open]); </System.P>
<a href={this.props.redirectURL} style={{ textDecoration: `none` }}>
return ( <System.ButtonPrimary full style={{ marginBottom: 16 }}>
<div> Continue to sign up
{open && ( </System.ButtonPrimary>{" "}
<div css={STYLES_BACKGROUND}> </a>
<div css={STYLES_TRANSITION}> <a css={STYLES_LINK_ITEM} href={this.props.redirectURL}>
<div css={STYLES_EXPLAINER}>Sign up or sign in to continue</div> Already have an account?
<br /> </a>
<div css={STYLES_POPOVER}> </div>
<Logo height="36px" style={{ display: "block", margin: "56px auto 0px auto" }} />
<System.P style={{ margin: "56px 0", textAlign: "center" }}>
An open-source file sharing network for research and collaboration
</System.P>
<a href="https://slate.host/_" style={{ textDecoration: `none` }}>
<System.ButtonPrimary full style={{ marginBottom: 16 }}>
Continue to sign up
</System.ButtonPrimary>{" "}
</a>
<a css={STYLES_LINK_ITEM} href="https://slate.host/_">
Already have an account?
</a>
</div> </div>
</div> </div>
</div> )}
)} </div>
</div> );
); }
}; }
export default CTATransition;

View File

@ -274,10 +274,14 @@ export default class Profile extends React.Component {
{this.state.visible && ( {this.state.visible && (
<div> <div>
<CTATransition open={this.state.visible} /> <CTATransition
<a css={STYLES_DISMISS_BOX} onClick={() => this.setState({ visible: false })}> viewer={this.props.viewer}
open={this.state.visible}
redirectURL={`/_?scene=V1_NAVIGATION_PROFILE&user=${data.username}`}
/>
<div css={STYLES_DISMISS_BOX} onClick={() => this.setState({ visible: false })}>
<SVG.Dismiss height="24px" /> <SVG.Dismiss height="24px" />
</a> </div>
</div> </div>
)} )}

449
components/core/SignIn.js Normal file
View File

@ -0,0 +1,449 @@
import * as React from "react";
import * as Actions from "~/common/actions";
import * as System from "~/components/system";
import * as Constants from "~/common/constants";
import * as Validations from "~/common/validations";
import * as Strings from "~/common/strings";
import { css } from "@emotion/core";
import { Logo, Symbol } from "~/common/logo";
import { dispatchCustomEvent } from "~/common/custom-events";
const delay = (time) =>
new Promise((resolve) =>
setTimeout(() => {
resolve();
}, time)
);
const STYLES_POPOVER = css`
height: 424px;
padding: 32px 36px;
border-radius: 4px;
max-width: 376px;
width: 95vw;
display: flex;
flex-direction: column;
background: ${Constants.system.white};
color: ${Constants.system.black};
${"" /* box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.25); */}
box-shadow: 0 0 30px 0 rgba(0, 0, 0, 0.05);
@keyframes authentication-popover-fade-in {
from {
transform: translateY(-8px);
opacity: 0;
}
to {
transform: translateY(0px);
opacity: 1;
}
}
animation: authentication-popover-fade-in 400ms ease;
`;
const STYLES_LINKS = css`
margin-top: 24px;
max-width: 376px;
width: 100%;
padding-left: 26px;
`;
const STYLES_LINK_ITEM = css`
display: block;
text-decoration: none;
font-weight: 400;
font-size: 14px;
font-family: ${Constants.font.semiBold};
user-select: none;
cursor: pointer;
margin-top: 2px;
color: ${Constants.system.black};
transition: 200ms ease all;
word-wrap: break-word;
:visited {
color: ${Constants.system.black};
}
:hover {
color: ${Constants.system.brand};
}
`;
export class SignIn extends React.Component {
state = {
scene: "WELCOME",
username: "",
password: "",
loading: false,
usernameTaken: false,
};
componentDidMount() {
// window.history.replaceState({ id: null }, "Slate", `/_`);
}
_handleChange = (e) => {
if (e.target.name === "accepted" && e.target.value) {
const hash = Strings.generateRandomString();
const confirm = window.prompt(`Please type ${hash} to continue.`);
if (confirm !== hash) {
window.alert("Please try again.");
return;
}
}
this.setState({ [e.target.name]: e.target.value });
};
_handleUsernameChange = (e) => {
const value = Strings.createSlug(e.target.value, "");
this.setState({ [e.target.name]: value, usernameTaken: false });
};
_handleSubmit = async () => {
this.setState({ loading: true });
await delay(100);
if (!this.state.accepted && this.state.scene === "CREATE_ACCOUNT") {
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: {
message: "You must accept the terms of service to create an account",
},
},
});
this.setState({ loading: false });
return;
}
if (!Validations.username(this.state.username)) {
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: {
message:
this.state.scene === "CREATE_ACCOUNT"
? "Usernames must between 1-48 characters and consist of only characters and numbers"
: "Invalid username",
},
},
});
this.setState({ loading: false });
return;
}
if (!Validations.password(this.state.password)) {
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: {
message:
this.state.scene === "CREATE_ACCOUNT"
? "Your password must be at least 8 characters"
: "Incorrect password",
},
},
});
this.setState({ loading: false });
return;
}
let response = null;
if (this.state.scene === "CREATE_ACCOUNT") {
response = await this.props.onCreateUser({
username: this.state.username.toLowerCase(),
password: this.state.password,
accepted: this.state.accepted,
});
} else {
response = await this.props.onAuthenticate({
username: this.state.username.toLowerCase(),
password: this.state.password,
});
}
if (!response) {
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: {
message: "1We're having trouble connecting right now. Please try again later.",
},
},
});
this.setState({ loading: false });
return;
}
if (response.error) {
dispatchCustomEvent({
name: "create-alert",
detail: { alert: { decorator: response.decorator } },
});
this.setState({ loading: false });
return;
}
this.setState({ scene: "whatever" });
};
_handleCheckUsername = async () => {
if (!this.state.username || !this.state.username.length) {
return;
}
if (!Validations.username(this.state.username)) {
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: {
message:
"Usernames must between 1-48 characters and consist of only characters and numbers",
},
},
});
return;
}
const response = await Actions.checkUsername({
username: this.state.username.toLowerCase(),
});
if (!response) {
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: {
message: "2We're having trouble connecting right now. Please try again later.",
},
},
});
return;
}
if (response.error) {
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: {
decorator: response.decorator,
},
},
});
return;
}
if (response.data) {
//NOTE(martina): username taken
this.setState({ usernameTaken: true });
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: {
message: "That username is taken",
},
},
});
return;
}
//NOTE(martina): username not taken
return this.setState({
usernameTaken: false,
});
};
render() {
if (this.state.scene === "WELCOME") {
return (
<React.Fragment>
<div css={STYLES_POPOVER} key={this.state.scene}>
<Logo height="36px" style={{ display: "block", margin: "56px auto 0px auto" }} />
<System.P style={{ margin: "56px 0", textAlign: "center" }}>
{this.props.external
? "Sign up or sign in to continue"
: "An open-source file sharing network for research and collaboration"}
</System.P>
<System.ButtonPrimary
full
style={{ marginTop: 24 }}
onClick={() => this.setState({ scene: "CREATE_ACCOUNT" })}
loading={this.state.loading}
>
Sign up
</System.ButtonPrimary>
<System.ButtonSecondary
full
style={{ marginTop: 16 }}
onClick={() => this.setState({ scene: "SIGN_IN" })}
loading={this.state.loading}
>
Log in
</System.ButtonSecondary>
</div>
<div css={STYLES_LINKS}>
<a css={STYLES_LINK_ITEM} href="/terms" target="_blank">
Terms of service
</a>
<a css={STYLES_LINK_ITEM} href="/guidelines" target="_blank">
Community guidelines
</a>
</div>
</React.Fragment>
);
}
if (this.state.scene === "CREATE_ACCOUNT") {
return (
<React.Fragment>
<div css={STYLES_POPOVER} key={this.state.scene} style={{ minHeight: 496 }}>
<div
style={{
display: "flex",
justifyContent: "center",
paddingTop: 56,
}}
>
<Symbol height="36px" />
</div>
<System.P
style={{
marginTop: 56,
textAlign: "center",
fontFamily: Constants.font.medium,
}}
>
Create your account
</System.P>
<System.Input
autoFocus
containerStyle={{ marginTop: 32 }}
placeholder="Username"
name="username"
type="text"
value={this.state.username}
onChange={this._handleUsernameChange}
onBlur={this._handleCheckUsername}
onSubmit={this._handleSubmit}
/>
<System.Input
containerStyle={{ marginTop: 16 }}
placeholder="Password"
name="password"
type="password"
value={this.state.password}
onChange={this._handleChange}
onSubmit={this._handleSubmit}
/>
<System.CheckBox
style={{ marginTop: 24 }}
name="accepted"
value={this.state.accepted}
onChange={this._handleChange}
>
To create an account you must accept the <a href="/terms">terms of service</a>.
</System.CheckBox>
<System.ButtonPrimary
full
style={{ marginTop: 24 }}
onClick={!this.state.loading ? this._handleSubmit : () => {}}
loading={this.state.loading}
>
Sign up
</System.ButtonPrimary>
</div>
<div css={STYLES_LINKS}>
<div
css={STYLES_LINK_ITEM}
onClick={() => {
this.setState({ scene: "SIGN_IN", loading: false });
}}
>
Already have an account?
</div>
</div>
</React.Fragment>
);
}
return (
<React.Fragment>
<div css={STYLES_POPOVER} key={this.state.scene}>
<div
style={{
display: "flex",
justifyContent: "center",
paddingTop: 56,
}}
>
<Symbol height="36px" />
</div>
<System.P
style={{
marginTop: 56,
textAlign: "center",
fontFamily: Constants.font.medium,
}}
>
Welcome back
</System.P>
<System.Input
autoFocus
containerStyle={{ marginTop: 32 }}
placeholder="Username"
name="username"
type="text"
value={this.state.username}
onChange={this._handleUsernameChange}
onSubmit={this._handleSubmit}
/>
<System.Input
containerStyle={{ marginTop: 16 }}
placeholder="Password"
name="password"
type="password"
value={this.state.password}
onChange={this._handleChange}
onSubmit={this._handleSubmit}
/>
<System.ButtonPrimary
full
style={{ marginTop: 24 }}
onClick={!this.state.loading ? this._handleSubmit : () => {}}
loading={this.state.loading}
>
Log in
</System.ButtonPrimary>
</div>
<div css={STYLES_LINKS}>
<div
css={STYLES_LINK_ITEM}
onClick={() => {
this.setState({ scene: "CREATE_ACCOUNT", loading: false });
}}
>
Not registered? Sign up instead
</div>
</div>
</React.Fragment>
);
}
}

View File

@ -6,6 +6,7 @@ import * as Window from "~/common/window";
import * as Validations from "~/common/validations"; import * as Validations from "~/common/validations";
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview"; import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
import CTATransition from "~/components/core/CTATransition";
import { CheckBox } from "~/components/system/components/CheckBox"; import { CheckBox } from "~/components/system/components/CheckBox";
import { css } from "@emotion/core"; import { css } from "@emotion/core";
@ -282,6 +283,19 @@ const STYLES_ICON_ROW = css`
left: calc(50% - 60px); left: calc(50% - 60px);
`; `;
const STYLES_DISMISS_BOX = css`
position: absolute;
top: 16px;
right: 16px;
color: ${Constants.system.darkGray};
cursor: pointer;
z-index: ${Constants.zindex.tooltip};
:hover {
color: ${Constants.system.white};
}
`;
export class SlateLayout extends React.Component { export class SlateLayout extends React.Component {
_ref; _ref;
_input; _input;
@ -306,6 +320,7 @@ export class SlateLayout extends React.Component {
copyValue: "", copyValue: "",
tooltip: null, tooltip: null,
keyboardTooltip: false, keyboardTooltip: false,
signInModal: false,
}; };
//LEFT OFF HERE: //LEFT OFF HERE:
@ -971,13 +986,6 @@ export class SlateLayout extends React.Component {
}); });
}; };
_handleLoginModal = (e) => {
e.preventDefault();
e.stopPropagation();
//TODO(martina): add a modal popup that says "login or sign up to use this feature", and automatically redirect to in-client view if already logged in
window.location.pathname = "/_";
};
_handleSetPreview = (e, i) => { _handleSetPreview = (e, i) => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
@ -1094,6 +1102,12 @@ export class SlateLayout extends React.Component {
e.preventDefault(); e.preventDefault();
}; };
_handleLoginModal = (e) => {
e.preventDefault();
e.stopPropagation();
this.setState({ signInModal: true });
};
render() { render() {
let numChecked = Object.keys(this.state.checked).length; let numChecked = Object.keys(this.state.checked).length;
let unit = this.state.unit; let unit = this.state.unit;
@ -1800,6 +1814,18 @@ export class SlateLayout extends React.Component {
value={this.state.copyValue} value={this.state.copyValue}
css={STYLES_COPY_INPUT} css={STYLES_COPY_INPUT}
/> />
{this.props.external && this.state.signInModal && (
<div>
<CTATransition
viewer={this.props.viewer}
open={this.state.signInModal}
redirectURL={`/_?scene=V1_NAVIGATION_SLATE&user=${this.props.creator.username}&slate=${this.props.slate.slatename}`}
/>
<div css={STYLES_DISMISS_BOX} onClick={() => this.setState({ signInModal: false })}>
<SVG.Dismiss height="24px" />
</div>
</div>
)}
</div> </div>
); );
} }

View File

@ -300,6 +300,17 @@ export const hydrate = async (id) => {
websocketSend("UPDATE", data); websocketSend("UPDATE", data);
}; };
export const checkId = async ({ id }) => {
const user = await Data.getUserById({
id,
});
if (!user || user.error) {
return false;
}
return true;
};
// TODO(jim): Work on better serialization when adoption starts occuring. // TODO(jim): Work on better serialization when adoption starts occuring.
export const getById = async ({ id }) => { export const getById = async ({ id }) => {
const user = await Data.getUserById({ const user = await Data.getUserById({

View File

@ -10,12 +10,12 @@ import { css } from "@emotion/core";
import { ProcessedText } from "~/components/system/components/Typography"; import { ProcessedText } from "~/components/system/components/Typography";
import { Alert } from "~/components/core/Alert"; import { Alert } from "~/components/core/Alert";
import { ViewAllButton } from "~/components/core/ViewAll"; import { ViewAllButton } from "~/components/core/ViewAll";
import { SlateLayout } from "~/components/core/SlateLayout";
import { SlateLayoutMobile } from "~/components/core/SlateLayoutMobile";
import WebsitePrototypeWrapper from "~/components/core/WebsitePrototypeWrapper"; import WebsitePrototypeWrapper from "~/components/core/WebsitePrototypeWrapper";
import WebsitePrototypeHeader from "~/components/core/WebsitePrototypeHeader"; import WebsitePrototypeHeader from "~/components/core/WebsitePrototypeHeader";
import WebsitePrototypeFooter from "~/components/core/WebsitePrototypeFooter"; import WebsitePrototypeFooter from "~/components/core/WebsitePrototypeFooter";
import { SlateLayout } from "~/components/core/SlateLayout";
import { SlateLayoutMobile } from "~/components/core/SlateLayoutMobile";
import SlateMediaObject from "~/components/core/SlateMediaObject"; import SlateMediaObject from "~/components/core/SlateMediaObject";
import CTATransition from "~/components/core/CTATransition"; import CTATransition from "~/components/core/CTATransition";
@ -320,19 +320,19 @@ export default class SlatePage extends React.Component {
<div css={STYLES_ROOT}> <div css={STYLES_ROOT}>
<WebsitePrototypeHeader /> <WebsitePrototypeHeader />
<div css={STYLES_SLATE_INTRO}> <div css={STYLES_SLATE_INTRO}>
<a css={STYLES_CREATOR} href={headerURL}> <a css={STYLES_CREATOR} href={`/${this.props.creator.username}`}>
{slateCreator} {slateCreator}
</a> </a>
<div css={STYLES_DESCTIPTION}> <div css={STYLES_DESCTIPTION}>
<div css={STYLES_FLEX}> <div css={STYLES_FLEX}>
<div css={STYLES_TITLE}>{slateTitle} </div> <div css={STYLES_TITLE}>{slateTitle} </div>
<div css={STYLES_BUTTONS}> <div css={STYLES_BUTTONS}>
<a css={STYLES_BUTTON} onClick={() => this.setState({ visible: true })}> <div css={STYLES_BUTTON} onClick={() => this.setState({ visible: true })}>
Follow Follow
</a> </div>
<a css={STYLES_BUTTON} onClick={() => this.setState({ visible: true })}> <div css={STYLES_BUTTON} onClick={() => this.setState({ visible: true })}>
Download Download
</a> </div>
</div> </div>
</div> </div>
<ViewAllButton fullText={this.props.slate.data.body} maxCharacter={208}> <ViewAllButton fullText={this.props.slate.data.body} maxCharacter={208}>
@ -375,6 +375,8 @@ export default class SlatePage extends React.Component {
items={objects} items={objects}
onSelect={this._handleSelect} onSelect={this._handleSelect}
defaultLayout={layouts && layouts.ver === "2.0" ? layouts.defaultLayout : true} defaultLayout={layouts && layouts.ver === "2.0" ? layouts.defaultLayout : true}
creator={this.props.creator}
slate={this.props.slate}
/> />
)} )}
</div> </div>
@ -383,10 +385,14 @@ export default class SlatePage extends React.Component {
<System.GlobalModal /> <System.GlobalModal />
{this.state.visible && ( {this.state.visible && (
<div> <div>
<CTATransition open={this.state.visible} /> <CTATransition
<a css={STYLES_DISMISS_BOX} onClick={() => this.setState({ visible: false })}> viewer={this.props.viewer}
open={this.state.visible}
redirectURL={`/_?scene=V1_NAVIGATION_SLATE&user=${this.props.creator.username}&slate=${this.props.slate.slatename}`}
/>
<div css={STYLES_DISMISS_BOX} onClick={() => this.setState({ visible: false })}>
<SVG.Dismiss height="24px" /> <SVG.Dismiss height="24px" />
</a> </div>
</div> </div>
)} )}
<WebsitePrototypeFooter /> <WebsitePrototypeFooter />

View File

@ -56,5 +56,9 @@ export default async (req, res) => {
const token = JWT.sign({ id: user.id, username: user.username }, Environment.JWT_SECRET); const token = JWT.sign({ id: user.id, username: user.username }, Environment.JWT_SECRET);
return res.status(200).send({ decorator: "SERVER_SIGN_IN", success: true, token }); res.status(200).send({ decorator: "SERVER_SIGN_IN", success: true, token });
if (req.body.data.redirectURL) {
console.log(req.body.data.redirectURL);
res.redirect(req.body.data.redirectURL);
}
}; };

View File

@ -26,19 +26,15 @@ export default class ScenePublicProfile extends React.Component {
}; };
componentDidUpdate = async (prevProps) => { componentDidUpdate = async (prevProps) => {
if (!this.props.data) { if (
return null; this.props.data &&
prevProps.data &&
this.props.data.id &&
prevProps.data.id &&
this.props.data.id !== prevProps.data.id
) {
await this.fetchProfile();
} }
if (!prevProps.data) {
return null;
}
if (!prevProps.data.id || this.props.data.id === prevProps.data.id) {
return null;
}
await this.fetchProfile();
}; };
fetchProfile = async () => { fetchProfile = async () => {
@ -55,12 +51,12 @@ export default class ScenePublicProfile extends React.Component {
} else { } else {
query = { id: this.props.viewer.id }; query = { id: this.props.viewer.id };
} }
let profile; let response;
if (query) { if (query) {
profile = await Actions.getSerializedProfile(query); response = await Actions.getSerializedProfile(query);
} }
if (!profile || profile.error) { if (!response || response.error) {
dispatchCustomEvent({ dispatchCustomEvent({
name: "create-alert", name: "create-alert",
detail: { alert: { message: "We're having trouble fetching that user right now." } }, detail: { alert: { message: "We're having trouble fetching that user right now." } },
@ -69,7 +65,8 @@ export default class ScenePublicProfile extends React.Component {
return; return;
} }
this.setState({ profile: profile.data }); this.props.onUpdateData({ data: response.data });
this.setState({ profile: response.data });
}; };
render() { render() {

View File

@ -1,6 +1,7 @@
import * as React from "react"; import * as React from "react";
import * as Actions from "~/common/actions"; import * as Actions from "~/common/actions";
import * as Window from "~/common/window"; import * as Window from "~/common/window";
import * as Strings from "~/common/strings";
import { LoaderSpinner } from "~/components/system/components/Loaders"; import { LoaderSpinner } from "~/components/system/components/Loaders";
import { css } from "@emotion/core"; import { css } from "@emotion/core";
@ -26,24 +27,22 @@ export default class ScenePublicSlate extends React.Component {
}; };
componentDidUpdate = async (prevProps) => { componentDidUpdate = async (prevProps) => {
if (!this.props.data) { if (
return null; this.props.data &&
prevProps.data &&
this.props.data.id &&
prevProps.data.id &&
this.props.data.id !== prevProps.data.id
) {
await this.fetchSlate();
} }
if (!prevProps.data) {
return null;
}
if (this.props.data.id === prevProps.data.id) {
return null;
}
await this.fetchSlate();
}; };
fetchSlate = async () => { fetchSlate = async () => {
console.log(this.props.data);
const username = Window.getQueryParameterByName("user"); const username = Window.getQueryParameterByName("user");
const slatename = Window.getQueryParameterByName("slate"); const slatename = Window.getQueryParameterByName("slate");
const cid = Window.getQueryParameterByName("cid");
if ( if (
!this.props.data && !this.props.data &&
!username && !username &&
@ -92,20 +91,40 @@ export default class ScenePublicSlate extends React.Component {
} else if (this.props.data && this.props.data.id) { } else if (this.props.data && this.props.data.id) {
query = { id: this.props.data.id }; query = { id: this.props.data.id };
} }
let slate; let response;
if (query) { if (query) {
slate = await Actions.getSerializedSlate(query); response = await Actions.getSerializedSlate(query);
} }
if (!slate || slate.error) { if (!response || response.error) {
dispatchCustomEvent({ dispatchCustomEvent({
name: "create-alert", name: "create-alert",
detail: { alert: { message: "We're having trouble fetching that slate right now." } }, detail: { alert: { message: "We're having trouble fetching that slate right now." } },
}); });
this.props.onBack(); this.props.onBack();
return;
} }
this.setState({ slate: slate.data }); this.props.onUpdateData({ data: response.data });
this.setState({ slate: response.data });
if (!Strings.isEmpty(cid)) {
let index = -1;
for (let i = 0; i < response.data.data.objects.length; i++) {
let obj = response.data.data.objects[i];
if ((obj.cid && obj.cid === cid) || (obj.url && obj.url.includes(cid))) {
index = i;
break;
}
}
if (index !== -1) {
dispatchCustomEvent({
name: "slate-global-open-carousel",
detail: { index },
});
}
}
}; };
render() { render() {

View File

@ -6,7 +6,7 @@ import * as Validations from "~/common/validations";
import * as Strings from "~/common/strings"; import * as Strings from "~/common/strings";
import { css } from "@emotion/core"; import { css } from "@emotion/core";
import { Logo, Symbol } from "~/common/logo"; import { SignIn } from "~/components/core/SignIn";
import { dispatchCustomEvent } from "~/common/custom-events"; import { dispatchCustomEvent } from "~/common/custom-events";
import WebsitePrototypeHeader from "~/components/core/WebsitePrototypeHeader"; import WebsitePrototypeHeader from "~/components/core/WebsitePrototypeHeader";
@ -47,71 +47,6 @@ const STYLES_MIDDLE = css`
padding: 24px; padding: 24px;
`; `;
const STYLES_POPOVER = css`
height: 424px;
padding: 32px 36px;
border-radius: 4px;
max-width: 376px;
width: 95vw;
display: flex;
flex-direction: column;
background: ${Constants.system.white};
color: ${Constants.system.black};
${"" /* box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.25); */}
box-shadow: 0 0 30px 0 rgba(0, 0, 0, 0.05);
@keyframes authentication-popover-fade-in {
from {
transform: translateY(-8px);
opacity: 0;
}
to {
transform: translateY(0px);
opacity: 1;
}
}
animation: authentication-popover-fade-in 400ms ease;
`;
const STYLES_LINKS = css`
margin-top: 24px;
max-width: 376px;
width: 100%;
padding-left: 26px;
`;
const STYLES_LINK_ITEM = css`
display: block;
text-decoration: none;
font-weight: 400;
font-size: 14px;
font-family: ${Constants.font.semiBold};
user-select: none;
cursor: pointer;
margin-top: 2px;
color: ${Constants.system.black};
transition: 200ms ease all;
word-wrap: break-word;
:visited {
color: ${Constants.system.black};
}
:hover {
color: ${Constants.system.brand};
}
`;
const STYLES_CODE_PREVIEW = css`
color: ${Constants.system.black};
font-family: ${Constants.font.code};
font-size: 12px;
text-transform: uppercase;
margin-bottom: 24px;
`;
export default class SceneSignIn extends React.Component { export default class SceneSignIn extends React.Component {
state = { state = {
scene: "WELCOME", scene: "WELCOME",
@ -121,9 +56,9 @@ export default class SceneSignIn extends React.Component {
usernameTaken: false, usernameTaken: false,
}; };
componentDidMount() { // componentDidMount() {
window.history.replaceState({ id: null }, "Slate", `/_`); // window.history.replaceState({ id: null }, "Slate", `/_`);
} // }
_handleChange = (e) => { _handleChange = (e) => {
if (e.target.name === "accepted" && e.target.value) { if (e.target.name === "accepted" && e.target.value) {
@ -297,205 +232,13 @@ export default class SceneSignIn extends React.Component {
}); });
}; };
_getPopoverComponent = () => {
if (this.state.scene === "WELCOME") {
return (
<React.Fragment>
<div css={STYLES_POPOVER} key={this.state.scene}>
<Logo height="36px" style={{ display: "block", margin: "56px auto 0px auto" }} />
<System.P style={{ margin: "56px 0", textAlign: "center" }}>
An open-source file sharing network for research and collaboration
</System.P>
<System.ButtonPrimary
full
style={{ marginTop: 24 }}
onClick={() => this.setState({ scene: "CREATE_ACCOUNT" })}
loading={this.state.loading}
>
Sign up
</System.ButtonPrimary>
<System.ButtonSecondary
full
style={{ marginTop: 16 }}
onClick={() => this.setState({ scene: "SIGN_IN" })}
loading={this.state.loading}
>
Sign in
</System.ButtonSecondary>
</div>
<div css={STYLES_LINKS}>
<a css={STYLES_LINK_ITEM} href="/terms" target="_blank">
Terms of service
</a>
<a css={STYLES_LINK_ITEM} href="/guidelines" target="_blank">
Community guidelines
</a>
</div>
</React.Fragment>
);
}
if (this.state.scene === "CREATE_ACCOUNT") {
return (
<React.Fragment>
<div css={STYLES_POPOVER} key={this.state.scene} style={{ minHeight: 496 }}>
<div
style={{
display: "flex",
justifyContent: "center",
paddingTop: 56,
}}
>
<Symbol height="36px" />
</div>
<System.P
style={{
marginTop: 56,
textAlign: "center",
fontFamily: Constants.font.medium,
}}
>
Create your account
</System.P>
<System.Input
autoFocus
containerStyle={{ marginTop: 32 }}
placeholder="Username"
name="username"
type="text"
value={this.state.username}
onChange={this._handleUsernameChange}
onBlur={this._handleCheckUsername}
onSubmit={this._handleSubmit}
/>
<System.Input
containerStyle={{ marginTop: 16 }}
placeholder="Password"
name="password"
type="password"
value={this.state.password}
onChange={this._handleChange}
onSubmit={this._handleSubmit}
/>
<System.CheckBox
style={{ marginTop: 24 }}
name="accepted"
value={this.state.accepted}
onChange={this._handleChange}
>
To create an account you must accept the <a href="/terms">terms of service</a>.
</System.CheckBox>
<System.ButtonPrimary
full
style={{ marginTop: 24 }}
onClick={!this.state.loading ? this._handleSubmit : () => {}}
loading={this.state.loading}
>
Sign up
</System.ButtonPrimary>
</div>
<div css={STYLES_LINKS}>
<div
css={STYLES_LINK_ITEM}
onClick={() => {
this.setState({ scene: "SIGN_IN", loading: false });
}}
>
Already have an account?
</div>
</div>
</React.Fragment>
);
}
return (
<React.Fragment>
<div css={STYLES_POPOVER} key={this.state.scene}>
<div
style={{
display: "flex",
justifyContent: "center",
paddingTop: 56,
}}
>
<Symbol height="36px" />
</div>
<System.P
style={{
marginTop: 56,
textAlign: "center",
fontFamily: Constants.font.medium,
}}
>
Welcome back
</System.P>
<System.Input
autoFocus
containerStyle={{ marginTop: 32 }}
placeholder="Username"
name="username"
type="text"
value={this.state.username}
onChange={this._handleUsernameChange}
onSubmit={this._handleSubmit}
/>
<System.Input
containerStyle={{ marginTop: 16 }}
placeholder="Password"
name="password"
type="password"
value={this.state.password}
onChange={this._handleChange}
onSubmit={this._handleSubmit}
/>
<System.ButtonPrimary
full
style={{ marginTop: 24 }}
onClick={!this.state.loading ? this._handleSubmit : () => {}}
loading={this.state.loading}
>
Sign in
</System.ButtonPrimary>
</div>
<div css={STYLES_LINKS}>
<div
css={STYLES_LINK_ITEM}
onClick={() => {
this.setState({ scene: "CREATE_ACCOUNT", loading: false });
}}
>
Not registered? Sign up instead
</div>
</div>
</React.Fragment>
);
};
render() { render() {
const popover = this._getPopoverComponent();
return ( return (
<div <div css={STYLES_ROOT}>
css={STYLES_ROOT}
// style={{
// backgroundImage: `url(${"https://slate.textile.io/ipfs/bafybeigrydo6q24ra4hnqpv6dpoosuar2rwbx6fslug2e2xdlcshayis2q"})`,
// }}
>
<WebsitePrototypeHeader style={{ background: `none` }} /> <WebsitePrototypeHeader style={{ background: `none` }} />
<div css={STYLES_MIDDLE}>{popover}</div> <div css={STYLES_MIDDLE}>
<SignIn {...this.props} />
</div>
<WebsitePrototypeFooter /> <WebsitePrototypeFooter />
</div> </div>
); );

View File

@ -199,7 +199,15 @@ app.prepare().then(async () => {
} }
const id = Utilities.getIdFromCookie(req); const id = Utilities.getIdFromCookie(req);
const loggedIn = ViewerManager.checkId({ id });
if (loggedIn) {
return res.redirect(
`/_${Strings.createQueryParams({
scene: "V1_NAVIGATION_PROFILE",
user: req.params.username,
})}`
);
}
let viewer = null; let viewer = null;
if (id) { if (id) {
viewer = await ViewerManager.getById({ viewer = await ViewerManager.getById({
@ -243,6 +251,18 @@ app.prepare().then(async () => {
}); });
} }
const id = Utilities.getIdFromCookie(req);
const loggedIn = ViewerManager.checkId({ id });
if (loggedIn) {
return res.redirect(
`/_${Strings.createQueryParams({
scene: "V1_NAVIGATION_SLATE",
user: req.params.username,
slate: req.params.slatename,
})}`
);
}
const slate = await Data.getSlateByName({ const slate = await Data.getSlateByName({
slatename: req.params.slatename, slatename: req.params.slatename,
username: req.params.username, username: req.params.username,
@ -274,8 +294,6 @@ app.prepare().then(async () => {
return res.redirect("/403"); return res.redirect("/403");
} }
const id = Utilities.getIdFromCookie(req);
let viewer = null; let viewer = null;
if (id) { if (id) {
viewer = await ViewerManager.getById({ viewer = await ViewerManager.getById({
@ -298,6 +316,19 @@ app.prepare().then(async () => {
return handler(req, res, req.url); return handler(req, res, req.url);
} }
const id = Utilities.getIdFromCookie(req);
const loggedIn = ViewerManager.checkId({ id });
if (loggedIn) {
return res.redirect(
`/_${Strings.createQueryParams({
scene: "V1_NAVIGATION_SLATE",
user: req.params.username,
slate: req.params.slatename,
cid: req.params.cid,
})}`
);
}
const slate = await Data.getSlateByName({ const slate = await Data.getSlateByName({
slatename: req.params.slatename, slatename: req.params.slatename,
username: req.params.username, username: req.params.username,
@ -329,8 +360,6 @@ app.prepare().then(async () => {
return res.redirect("/403"); return res.redirect("/403");
} }
const id = Utilities.getIdFromCookie(req);
let viewer = null; let viewer = null;
if (id) { if (id) {
viewer = await ViewerManager.getById({ viewer = await ViewerManager.getById({