stylistic changes and fixes, as well as resolving bugs

This commit is contained in:
Martina 2021-09-13 12:33:06 -07:00
parent ef725d8040
commit d99e5d7d28
43 changed files with 353 additions and 457 deletions

View File

@ -210,11 +210,13 @@ export const error = {
SERVER_FILECOIN_NETWORK_ERROR: "We're having trouble fetching your storage information right now",
//Sign in
SIGN_UP_RATE_LIMITED: "Too many signup attempts. Please try again in 10 minutes",
SIGN_IN_RATE_LIMITED: "Too many login attempts. Please try again in 10 minutes",
SERVER_SIGN_IN_NOT_ALLOWED: "You can only sign in to an account while on slate.host",
SERVER_SIGN_IN_NO_USERNAME: "Please provide a username to sign in",
SERVER_SIGN_IN_NO_PASSWORD: "Please provide a password to sign in",
SERVER_SIGN_IN_USER_NOT_FOUND: "We were unable to locate that account with those credentials",
SERVER_SIGN_IN_WRONG_CREDENTIALS: "You have entered an invalid username or password",
SERVER_SIGN_IN_WRONG_CREDENTIALS: "The username and password do not match",
//Subscribe
SERVER_SUBSCRIBE_MUST_PROVIDE_SLATE_OR_USER: "No collection or user to follow specified",

View File

@ -31,7 +31,8 @@ export const authenticate = async (state) => {
}
let response = await Actions.signIn(state);
if (!response || response.error) {
if (Events.hasError(response)) {
return response;
}

View File

@ -134,7 +134,7 @@ export class Alert extends React.Component {
_handleDismissPrivacyAlert = (e) => {
Actions.updateStatus({ onboarding: ["hidePrivacyAlert"] });
this.props.onAction({
type: "UDPATE_VIEWER",
type: "UPDATE_VIEWER",
viewer: {
onboarding: { ...this.props.viewer.onboarding, hidePrivacyAlert: true },
},

View File

@ -182,7 +182,7 @@ export default function Signin({
);
}
return (
<SignUpPopover title={`Enter Password for ${emailOrUsername}`}>
<SignUpPopover title={`Enter Password for ${emailOrUsername.toLowerCase()}`}>
<form {...getFormProps()} style={{ marginTop: message ? 24 : 41 }}>
{message && (
<div css={STYLES_MESSAGE}>

View File

@ -552,7 +552,7 @@ class CarouselSidebar extends React.Component {
...STYLES_INPUT,
}}
textStyle={{ color: Constants.system.white }}
maxlength="255"
maxLength="255"
/>
<Textarea
@ -561,7 +561,7 @@ class CarouselSidebar extends React.Component {
value={this.state.body}
onChange={this._handleChange}
style={STYLES_INPUT}
maxlength="2000"
maxLength="2000"
/>
<Input
full
@ -572,7 +572,7 @@ class CarouselSidebar extends React.Component {
id={`sidebar-label-source`}
style={STYLES_INPUT}
textStyle={{ color: Constants.system.white }}
maxlength="255"
maxLength="255"
/>
<Input
full
@ -583,7 +583,7 @@ class CarouselSidebar extends React.Component {
id={`sidebar-label-author`}
style={{ ...STYLES_INPUT, marginBottom: 12 }}
textStyle={{ color: Constants.system.white }}
maxlength="255"
maxLength="255"
/>
{/* <div css={STYLES_OPTIONS_SECTION}>
<Tag

View File

@ -12,10 +12,11 @@ const STYLES_BUTTON = (theme) => css`
background-color: ${theme.semantic.bgBlurWhite};
padding: 8px;
border-radius: 8px;
box-shadow: 0 0 0 1px ${theme.system.grayLight5}, ${theme.shadow.lightLarge};
box-shadow: 0 0 0 1px ${theme.semantic.borderGrayLight};
transition: box-shadow 0.3s;
:hover {
box-shadow: 0 0 0 1px ${theme.system.pink}, ${theme.shadow.lightLarge};
box-shadow: 0 0 0 1px ${theme.system.blue}, ${theme.shadow.lightLarge};
}
path {
@ -23,12 +24,27 @@ const STYLES_BUTTON = (theme) => css`
}
:hover path {
stroke: ${theme.system.pink};
stroke: ${theme.system.blue};
}
`;
const STYLES_DISABLED = css`
const STYLES_DISABLED = (theme) => css`
display: flex;
background-color: ${theme.semantic.bgBlurWhite};
padding: 8px;
border-radius: 8px;
${"" /* box-shadow: 0 0 0 1px ${theme.system.gray}, ${theme.shadow.lightLarge}; */}
box-shadow: 0 0 0 1px ${theme.semantic.borderGrayLight};
transition: box-shadow 0.3s;
cursor: not-allowed;
path {
transition: stroke 0.3s;
}
path {
stroke: ${theme.system.gray};
}
`;
const animate = async (controls) => {
@ -48,12 +64,12 @@ export default function FollowButton({ onFollow, isFollowed, disabled, followCou
return (
<motion.button
css={[Styles.BUTTON_RESET, STYLES_BUTTON, disabled && STYLES_DISABLED]}
css={[Styles.BUTTON_RESET, disabled ? STYLES_DISABLED : STYLES_BUTTON]}
initial={{
backgroundColor: isFollowed ? Constants.system.redLight6 : Constants.semantic.bgBlurWhite,
backgroundColor: isFollowed ? Constants.system.blueLight6 : Constants.semantic.bgBlurWhite,
}}
animate={{
backgroundColor: isFollowed ? Constants.system.redLight6 : Constants.semantic.bgBlurWhite,
backgroundColor: isFollowed ? Constants.system.blueLight6 : Constants.semantic.bgBlurWhite,
}}
onClick={(e) => {
if (disabled) {
@ -75,24 +91,24 @@ export default function FollowButton({ onFollow, isFollowed, disabled, followCou
>
<motion.path
d="M3.33334 9.66669C5.32247 9.66669 7.23012 10.4569 8.63664 11.8634C10.0432 13.2699 10.8333 15.1776 10.8333 17.1667"
initial={{ stroke: isFollowed ? Constants.system.pink : Constants.system.black }}
animate={{ stroke: isFollowed ? Constants.system.pink : Constants.system.black }}
initial={{ stroke: isFollowed ? Constants.system.blue : Constants.system.black }}
animate={{ stroke: isFollowed ? Constants.system.blue : Constants.system.black }}
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
/>
<motion.path
d="M3.33334 3.83331C6.86956 3.83331 10.2609 5.23807 12.7614 7.73856C15.2619 10.239 16.6667 13.6304 16.6667 17.1666"
initial={{ stroke: isFollowed ? Constants.system.pink : Constants.system.black }}
animate={{ stroke: isFollowed ? Constants.system.pink : Constants.system.black }}
initial={{ stroke: isFollowed ? Constants.system.blue : Constants.system.black }}
animate={{ stroke: isFollowed ? Constants.system.blue : Constants.system.black }}
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
/>
<motion.path
d="M4.16668 17.1667C4.62691 17.1667 5.00001 16.7936 5.00001 16.3333C5.00001 15.8731 4.62691 15.5 4.16668 15.5C3.70644 15.5 3.33334 15.8731 3.33334 16.3333C3.33334 16.7936 3.70644 17.1667 4.16668 17.1667Z"
initial={{ stroke: isFollowed ? Constants.system.pink : Constants.system.black }}
animate={{ stroke: isFollowed ? Constants.system.pink : Constants.system.black }}
initial={{ stroke: isFollowed ? Constants.system.blue : Constants.system.black }}
animate={{ stroke: isFollowed ? Constants.system.blue : Constants.system.black }}
strokeWidth={1.25}
strokeLinecap="round"
strokeLinejoin="round"
@ -101,8 +117,20 @@ export default function FollowButton({ onFollow, isFollowed, disabled, followCou
<P3
as={motion.p}
style={{ marginLeft: 4, y: -0.5 }}
initial={{ color: isFollowed ? Constants.system.pink : Constants.semantic.textGrayDark }}
animate={{ color: isFollowed ? Constants.system.pink : Constants.semantic.textGrayDark }}
initial={{
color: disabled
? Constants.system.gray
: isFollowed
? Constants.system.blue
: Constants.semantic.textGrayDark,
}}
animate={{
color: disabled
? Constants.system.gray
: isFollowed
? Constants.system.blue
: Constants.semantic.textGrayDark,
}}
>
{followCount}
</P3>

View File

@ -14,6 +14,8 @@ import { AspectRatio } from "~/components/system";
import { P3, H5, P2 } from "~/components/system/components/Typography";
import { useMediaQuery, useMounted } from "~/common/hooks";
import ProfilePhoto from "~/components/core/ProfilePhoto";
const STYLES_CONTAINER = (theme) => css`
position: relative;
display: flex;
@ -136,7 +138,7 @@ export default function CollectionPreview({ collection, viewer, owner, onAction
onClick={follow}
isFollowed={isFollowed}
followCount={followCount}
disabled={collection.ownerId === viewer?.id}
disabled={viewer && collection.ownerId === viewer.id}
/>
<ShareButton user={owner} preview={preview} collection={collection} />
</motion.div>
@ -231,12 +233,7 @@ function Metrics({ fileCount, owner, isOwner, onAction }) {
aria-label={`Visit ${owner.username}'s profile`}
title={`Visit ${owner.username}'s profile`}
>
<img
css={STYLES_PROFILE_IMAGE}
src={owner.photo}
alt={`${owner.username} profile`}
onError={(e) => (e.target.src = Constants.profileDefaultPicture)}
/>
<ProfilePhoto user={owner} style={{ borderRadius: "4px" }} size={16} />
</Link>
<Link
href={`/$/user/${owner.id}`}

View File

@ -39,7 +39,7 @@ const STYLES_HEADER = css`
color: ${Constants.system.black};
font-size: ${Constants.typescale.lvl1};
font-family: ${Constants.font.semiBold};
word-break: break-all;
word-break: break-word;
`;
const STYLES_SUB_HEADER = css`

View File

@ -219,8 +219,18 @@ export default class Profile extends React.Component {
index: -1,
};
componentDidMount = () => {
if (this.props.page.params?.tab === "subscribed") {
this.fetchSocial();
}
};
componentDidUpdate = (prevProps) => {
if (!this.state.fetched && this.props.page.params !== prevProps.page.params) {
if (
!this.state.fetched &&
this.props.page.params !== prevProps.page.params &&
this.props.page.params?.tab === "subscribed"
) {
this.fetchSocial();
}
};

View File

@ -131,7 +131,7 @@ export default class SidebarCreateSlate extends React.Component {
onSubmit={this._handleSubmit}
descriptionStyle={{ fontSize: "20px !important" }}
labelStyle={{ fontSize: "20px" }}
maxlength="255"
maxLength="255"
/>
<System.P1
style={{
@ -162,7 +162,7 @@ export default class SidebarCreateSlate extends React.Component {
value={this.state.body}
onChange={this._handleChange}
onSubmit={this._handleSubmit}
maxlength="2000"
maxLength="2000"
/>
</div>

View File

@ -169,7 +169,7 @@ export default class SidebarSingleSlateSettings extends React.Component {
onSubmit={this._handleSubmit}
descriptionStyle={{ fontSize: "20px !important" }}
labelStyle={{ fontSize: "20px" }}
maxlength="255"
maxLength="255"
/>
<System.P1
style={{
@ -200,7 +200,7 @@ export default class SidebarSingleSlateSettings extends React.Component {
value={this.state.body}
onChange={this._handleChange}
onSubmit={this._handleSubmit}
maxlength="2000"
maxLength="2000"
/>
</div>

View File

@ -304,7 +304,7 @@ export const ButtonWarning = (props) => {
css={props.transparent ? STYLES_BUTTON_WARNING_TRANSPARENT : STYLES_BUTTON_WARNING}
style={{ width: props.full ? "100%" : "auto", ...props.style }}
>
<LoaderSpinner style={{ height: 16, width: 16 }} />
<LoaderSpinner style={{ height: 16, width: 16, color: Constants.system.white }} />
</button>
);
}

View File

@ -269,7 +269,7 @@ export class Input extends React.Component {
disabled={this.props.disabled}
readOnly={this.props.readOnly}
required={this.props.required}
maxlength={this.props.maxlength}
maxLength={this.props.maxLength}
style={{
width: this.props.copyable || this.props.icon ? "calc(100% - 32px)" : "100%",
...this.props.textStyle,

View File

@ -54,7 +54,7 @@ export class Textarea extends React.Component {
name={this.props.name}
value={this.props.value}
readOnly={this.props.readOnly}
maxlength={this.props.maxlength}
maxLength={this.props.maxLength}
/>
);
}

View File

@ -17,5 +17,3 @@ export const MIN_ARCHIVE_SIZE_BYTES = 104857600;
// NOTE(amine): 15 minutes
export const TOKEN_EXPIRATION_TIME = 2 * 60 * 60 * 1000;
export const userPreviewProperties = ["users.id", "users.data", "users.username"];

View File

@ -11,7 +11,7 @@ export default async (slate) => {
const index = query ? query.pop() : null;
if (index) {
if (isPublic) {
if (slate.isPublic) {
const activityQuery = await DB.insert({
ownerId: slate.ownerId,
slateId: index.id,

View File

@ -1,4 +1,4 @@
import * as Constants from "~/node_common/constants";
import * as Serializers from "~/node_common/serializers";
import { runQuery } from "~/node_common/data/utilities";
@ -33,14 +33,14 @@ export default async ({
const slateOwnerFields = [
"slate_table",
"slate_with_objects.*",
...Constants.userPreviewProperties,
...Serializers.userPreviewProperties,
"owner",
"slate_with_objects",
"users",
"slate_with_objects.ownerId",
"users.id",
];
const slateOwnerQuery = `?? as (SELECT ??, json_build_object('id', ??, 'data', ??, 'username', ??) as ?? FROM ?? LEFT JOIN ?? ON ?? = ?? ) `;
const slateOwnerQuery = `?? as (SELECT ??, json_build_object('id', ??, 'name', ??, 'username', ??, 'photo', ??) as ?? FROM ?? LEFT JOIN ?? ON ?? = ?? ) `;
const slateFields = [
"slate_with_objects",
@ -87,14 +87,14 @@ export default async ({
const fileFields = [
"files_table",
"files.*",
...Constants.userPreviewProperties,
...Serializers.userPreviewProperties,
"owner",
"files",
"users",
"files.ownerId",
"users.id",
];
const fileQuery = `, ?? as (SELECT ??, json_build_object('id', ??, 'data', ??, 'username', ??) as ?? FROM ?? LEFT JOIN ?? on ?? = ??)`;
const fileQuery = `, ?? as (SELECT ??, json_build_object('id', ??, 'name', ??, 'username', ??, 'photo', ??) as ?? FROM ?? LEFT JOIN ?? on ?? = ??)`;
const selectFields = [
...slateFields,

View File

@ -1,5 +1,4 @@
import * as Logging from "~/common/logging";
import * as Serializers from "~/node_common/serializers";
import * as Constants from "~/node_common/constants";
import { runQuery } from "~/node_common/data/utilities";
@ -22,7 +21,7 @@ export default async ({ includeFiles = false, publicOnly = false } = {}) => {
let slates;
if (publicOnly) {
if (includeFiles) {
slates = await DB.select(...Serializers.slateProperties, slateFiles())
slates = await DB.select("slates.*", slateFiles())
.from("slates")
.leftJoin("slate_files", "slate_files.slateId", "=", "slates.id")
.leftJoin("files", "slate_files.fileId", "=", "files.id")
@ -33,7 +32,7 @@ export default async ({ includeFiles = false, publicOnly = false } = {}) => {
}
} else {
if (includeFiles) {
slates = await DB.select(...Serializers.slateProperties, slateFiles())
slates = await DB.select("slates.*", slateFiles())
.from("slates")
.leftJoin("slate_files", "slate_files.slateId", "=", "slates.id")
.leftJoin("files", "slate_files.fileId", "=", "files.id")

View File

@ -19,21 +19,27 @@ export default async ({ sanitize = false, includeFiles = false } = {}) => {
let users;
if (includeFiles) {
users = await DB.select(...Serializers.userProperties, userFiles())
.from("users")
.leftJoin("files", "files.ownerId", "users.id");
if (sanitize) {
users = await DB.select(...Serializers.userPublicProperties, userFiles())
.from("users")
.leftJoin("files", "files.ownerId", "users.id");
} else {
users = await DB.select("users.*", userFiles())
.from("users")
.leftJoin("files", "files.ownerId", "users.id");
}
} else {
users = await DB.select(...Serializers.userProperties).from("users");
if (sanitize) {
users = await DB.select(...Serializers.userPublicProperties).from("users");
} else {
users = await DB.select("*").from("users");
}
}
if (!users || users.error) {
return [];
}
if (sanitize) {
users = users.map((user) => Serializers.sanitizeUser(user));
}
return JSON.parse(JSON.stringify(users));
},
errorFn: async (e) => {

View File

@ -1,4 +1,4 @@
import * as Constants from "~/node_common/constants";
import * as Serializers from "~/node_common/serializers";
import { runQuery } from "~/node_common/data/utilities";
@ -9,14 +9,14 @@ export default async ({ earliestTimestamp, latestTimestamp }) => {
const slateOwnerFields = [
"slate_table",
"slate_with_objects.*",
...Constants.userPreviewProperties,
...Serializers.userPreviewProperties,
"owner",
"slate_with_objects",
"users",
"slate_with_objects.ownerId",
"users.id",
];
const slateOwnerQuery = `?? as (SELECT ??, json_build_object('id', ??, 'data', ??, 'username', ??) as ?? FROM ?? LEFT JOIN ?? ON ?? = ?? ) `;
const slateOwnerQuery = `?? as (SELECT ??, json_build_object('id', ??, 'name', ??, 'username', ??, 'photo', ??) as ?? FROM ?? LEFT JOIN ?? ON ?? = ?? ) `;
const slateFields = [
"slate_with_objects",
@ -63,14 +63,14 @@ export default async ({ earliestTimestamp, latestTimestamp }) => {
const fileFields = [
"files_table",
"files.*",
...Constants.userPreviewProperties,
...Serializers.userPreviewProperties,
"owner",
"files",
"users",
"files.ownerId",
"users.id",
];
const fileQuery = `, ?? as (SELECT ??, json_build_object('id', ??, 'data', ??, 'username', ??) as ?? FROM ?? LEFT JOIN ?? on ?? = ??)`;
const fileQuery = `, ?? as (SELECT ??, json_build_object('id', ??, 'name', ??, 'username', ??, 'photo', ??) as ?? FROM ?? LEFT JOIN ?? on ?? = ??)`;
const selectFields = [
...slateFields,

View File

@ -8,7 +8,7 @@ export default async ({ userId }) => {
return await runQuery({
label: "GET_FOLLOWERS_BY_USER_ID",
queryFn: async (DB) => {
const query = await DB.select(...Serializers.userProperties)
const query = await DB.select(...Serializers.userPublicProperties)
.from("users")
.join("subscriptions", "subscriptions.ownerId", "=", "users.id")
.where({ "subscriptions.userId": userId })
@ -17,12 +17,7 @@ export default async ({ userId }) => {
return [];
}
let serialized = [];
for (let user of query) {
serialized.push(Serializers.sanitizeUser(user));
}
return JSON.parse(JSON.stringify(serialized));
return JSON.parse(JSON.stringify(query));
},
errorFn: async (e) => {
Logging.error({

View File

@ -8,7 +8,7 @@ export default async ({ ownerId }) => {
return await runQuery({
label: "GET_FOLLOWING_BY_USER_ID",
queryFn: async (DB) => {
const query = await DB.select(...Serializers.userProperties)
const query = await DB.select(...Serializers.userPublicProperties)
.from("users")
.join("subscriptions", "subscriptions.userId", "=", "users.id")
.where({ "subscriptions.ownerId": ownerId })
@ -18,12 +18,7 @@ export default async ({ ownerId }) => {
return [];
}
let serialized = [];
for (let user of query) {
serialized.push(Serializers.sanitizeUser(user));
}
return JSON.parse(JSON.stringify(serialized));
return JSON.parse(JSON.stringify(query));
},
errorFn: async (e) => {
Logging.error({

View File

@ -1,6 +1,3 @@
import * as Serializers from "~/node_common/serializers";
import * as Constants from "~/node_common/constants";
import { runQuery } from "~/node_common/data/utilities";
export default async ({ id, includeFiles = false }) => {
@ -20,7 +17,7 @@ export default async ({ id, includeFiles = false }) => {
let query;
if (includeFiles) {
query = await DB.select(...Serializers.slateProperties, slateFiles())
query = await DB.select("slates.*", slateFiles())
.from("slates")
.leftJoin("slate_files", "slate_files.slateId", "=", "slates.id")
.leftJoin("files", "slate_files.fileId", "=", "files.id")
@ -28,10 +25,7 @@ export default async ({ id, includeFiles = false }) => {
.groupBy("slates.id")
.first();
} else {
query = await DB.select(...Serializers.slateProperties)
.from("slates")
.where({ id })
.first();
query = await DB.select("*").from("slates").where({ id }).first();
}
if (!query || query.error) {

View File

@ -1,6 +1,3 @@
import * as Serializers from "~/node_common/serializers";
import * as Constants from "~/node_common/constants";
import { runQuery } from "~/node_common/data/utilities";
export default async ({ slatename, ownerId, username, includeFiles = false }) => {
@ -32,7 +29,7 @@ export default async ({ slatename, ownerId, username, includeFiles = false }) =>
let query;
if (includeFiles) {
query = await DB.select(...Serializers.slateProperties, slateFiles())
query = await DB.select("slates.*", slateFiles())
.from("slates")
.leftJoin("slate_files", "slate_files.slateId", "=", "slates.id")
.leftJoin("files", "slate_files.fileId", "=", "files.id")
@ -40,10 +37,7 @@ export default async ({ slatename, ownerId, username, includeFiles = false }) =>
.groupBy("slates.id")
.first();
} else {
query = await DB.select(...Serializers.slateProperties)
.from("slates")
.where({ slatename, ownerId: id })
.first();
query = await DB.select("*").from("slates").where({ slatename, ownerId: id }).first();
}
if (!query || query.error) {

View File

@ -1,6 +1,3 @@
import * as Constants from "~/node_common/constants";
import * as Serializers from "~/node_common/serializers";
import { runQuery } from "~/node_common/data/utilities";
/**
@ -11,7 +8,7 @@ export default async ({ slateId, cids }) => {
return await runQuery({
label: "GET_SLATE_FILES_BY_CID",
queryFn: async (DB) => {
const query = await DB.select(...Serializers.fileProperties)
const query = await DB.select("files.*")
.from("files")
.join("slate_files", "slate_files.fileId", "=", "files.id")
.where("slate_files.slateId", slateId)

View File

@ -1,6 +1,3 @@
import * as Serializers from "~/node_common/serializers";
import * as Constants from "~/node_common/constants";
import { runQuery } from "~/node_common/data/utilities";
export default async ({ ids, includeFiles = false }) => {
@ -20,16 +17,14 @@ export default async ({ ids, includeFiles = false }) => {
]);
if (includeFiles) {
query = await DB.select(...Serializers.slateProperties, slateFiles())
query = await DB.select("slates.*", slateFiles())
.from("slates")
.leftJoin("slate_files", "slate_files.slateId", "=", "slates.id")
.leftJoin("files", "slate_files.fileId", "=", "files.id")
.whereIn("slates.id", ids)
.groupBy("slates.id");
} else {
query = await DB.select(...Serializers.slateProperties)
.from("slates")
.whereIn("id", ids);
query = await DB.select("*").from("slates").whereIn("id", ids);
}
if (!query || query.error) {

View File

@ -1,5 +1,3 @@
import * as Serializers from "~/node_common/serializers";
import * as Constants from "~/node_common/constants";
import * as Logging from "~/common/logging";
import { runQuery } from "~/node_common/data/utilities";
@ -28,7 +26,7 @@ export default async ({ ownerId, includeFiles = false, publicOnly = false }) =>
let query;
if (includeFiles) {
if (publicOnly) {
query = await DB.select(...Serializers.slateProperties, slateFiles())
query = await DB.select("slates.*", slateFiles())
.from("slates")
.leftJoin("slate_files", "slate_files.slateId", "=", "slates.id")
.leftJoin("files", "slate_files.fileId", "=", "files.id")
@ -36,7 +34,7 @@ export default async ({ ownerId, includeFiles = false, publicOnly = false }) =>
.groupBy("slates.id")
.orderBy("slates.updatedAt", "desc");
} else {
query = await DB.select(...Serializers.slateProperties, slateFiles())
query = await DB.select("slates.*", slateFiles())
.from("slates")
.leftJoin("slate_files", "slate_files.slateId", "=", "slates.id")
.leftJoin("files", "slate_files.fileId", "=", "files.id")
@ -46,14 +44,14 @@ export default async ({ ownerId, includeFiles = false, publicOnly = false }) =>
}
} else {
if (publicOnly) {
query = await DB.select(...Serializers.slateProperties)
query = await DB.select("*")
.from("slates")
.where({ "slates.ownerId": ownerId, "slates.isPublic": true })
.where({ ownerId: ownerId, isPublic: true })
.orderBy("updatedAt", "desc");
} else {
query = await DB.select(...Serializers.slateProperties)
query = await DB.select("*")
.from("slates")
.where({ "slates.ownerId": ownerId })
.where({ ownerId: ownerId })
.orderBy("updatedAt", "desc");
}
}

View File

@ -8,7 +8,7 @@ export default async ({ slateId }) => {
return await runQuery({
label: "GET_SUBSCRIBERS_BY_SLATE_ID",
queryFn: async (DB) => {
const query = await DB.select(...Serializers.userProperties)
const query = await DB.select(...Serializers.userPublicProperties)
.from("users")
.join("subscriptions", "subscriptions.ownerId", "=", "users.id")
.where({ "subscriptions.slateId": slateId })
@ -18,12 +18,7 @@ export default async ({ slateId }) => {
return [];
}
let serialized = [];
for (let user of query) {
serialized.push(Serializers.sanitizeUser(user));
}
return JSON.parse(JSON.stringify(serialized));
return JSON.parse(JSON.stringify(query));
},
errorFn: async (e) => {
Logging.error({

View File

@ -1,5 +1,4 @@
import * as Serializers from "~/node_common/serializers";
import * as Constants from "~/node_common/constants";
import * as Logging from "~/common/logging";
import { runQuery } from "~/node_common/data/utilities";
@ -11,9 +10,9 @@ export default async ({ ownerId }) => {
// const slateFiles = () =>
// DB.raw("json_agg(?? order by ?? asc) as ??", ["files", "slate_files.createdAt", "objects"]);
const ownerQueryFields = [...Constants.userPreviewProperties, "owner"];
const ownerQueryFields = [...Serializers.userPreviewProperties, "owner"];
const ownerQuery = DB.raw(
`json_build_object('id', ??, 'data', ??, 'username', ??) as ??`,
`json_build_object('id', ??, 'name', ??, 'username', ??, 'photo', ??) as ??`,
ownerQueryFields
);
@ -27,7 +26,7 @@ export default async ({ ownerId }) => {
const query = await DB.with("slates", (db) =>
db
.select(...Serializers.slateProperties, slateFiles())
.select("slates.*", slateFiles())
.from("slates")
.join("subscriptions", "subscriptions.slateId", "=", "slates.id")
.join("slate_files", "slate_files.slateId", "=", "slates.id")
@ -36,7 +35,7 @@ export default async ({ ownerId }) => {
// .orderBy("subscriptions.createdAt", "desc");
.groupBy("slates.id")
)
.select(...Serializers.slateProperties, "objects", ownerQuery)
.select("slates.*", "objects", ownerQuery)
.from("slates")
.join("users", "slates.ownerId", "users.id");

View File

@ -1,4 +1,5 @@
import * as Serializers from "~/node_common/serializers";
import { runQuery } from "~/node_common/data/utilities";
//NOTE(toast): should only be used for checking if an email is taken
@ -7,16 +8,20 @@ export default async ({ email, sanitize = false }) => {
return await runQuery({
label: "GET_USER_BY_EMAIL",
queryFn: async (DB) => {
let query = await DB.select("*").from("users").where({ email }).first();
let query;
if (sanitize) {
query = await DB.select(...Serializers.userPublicProperties)
.from("users")
.where({ email })
.first();
} else {
query = await DB.select("*").from("users").where({ email }).first();
}
if (!query || query.error) {
return null;
}
if (sanitize) {
query = Serializers.sanitizeUser(query);
}
return JSON.parse(JSON.stringify(query));
},
errorFn: async (e) => {

View File

@ -15,13 +15,23 @@ export default async ({ id, sanitize = false, includeFiles = false, publicOnly =
// const userFiles = () =>
// DB.raw("json_agg(?? order by ?? desc) as ??", ["files", "files.createdAt", "library"]);
const userFiles = () =>
DB.raw(
"coalesce(json_agg(?? order by ?? desc) filter (where ?? is not null), '[]') as ??",
["files", "files.createdAt", "files.id", "library"]
);
// const userFiles = () =>
// DB.raw(
// "coalesce(json_agg(?? order by ?? desc) filter (where ?? is not null), '[]') as ??",
// ["files", "files.createdAt", "files.id", "library"]
// );
let query;
if (sanitize) {
query = await DB.select(...Serializers.userPublicProperties)
.from("users")
.where({ id })
.first();
} else {
query = await DB.select("*").from("users").where({ id }).first();
}
if (includeFiles) {
if (publicOnly) {
//TODO(martina): fix this so can be done in one query. Right now, it's duplicating files for each slate_files entry. Need to do distinct on file.id for the json agg
@ -41,9 +51,7 @@ export default async ({ id, sanitize = false, includeFiles = false, publicOnly =
// .groupBy("users.id")
// .first();
query = await DB.select("*").from("users").where({ id }).first();
let library = await DB.select(...Serializers.fileProperties)
let library = await DB.select("files.*")
.from("files")
.leftJoin("slate_files", "slate_files.fileId", "=", "files.id")
.leftJoin("slates", "slate_files.slateId", "=", "slates.id")
@ -62,19 +70,21 @@ export default async ({ id, sanitize = false, includeFiles = false, publicOnly =
query.library = library;
} else {
query = await DB.select(...Serializers.userProperties, userFiles())
.from("users")
.where({ "users.id": id })
.leftJoin("files", "files.ownerId", "users.id")
.groupBy("users.id")
.first();
}
} else {
query = await DB.select("*").from("users").where({ id }).first();
}
let library = await DB.select("*")
.from("files")
.where({ ownerId: id })
.orderBy("createdAt", "desc")
.groupBy("id");
if (sanitize) {
query = Serializers.sanitizeUser(query);
query.library = library;
// query = await DB.select("users.*", userFiles())
// .from("users")
// .where({ "users.id": id })
// .leftJoin("files", "files.ownerId", "users.id")
// .groupBy("users.id")
// .first();
}
}
if (!query || query.error) {

View File

@ -1,4 +1,5 @@
import * as Serializers from "~/node_common/serializers";
import { runQuery } from "~/node_common/data/utilities";
//NOTE(toast): should only be used for checking if an email is taken
@ -7,14 +8,15 @@ export default async ({ twitterId }) => {
return await runQuery({
label: "GET_USER_BY_TWITTER_ID",
queryFn: async (DB) => {
let query = await DB.select("*").from("users").where({ twitterId }).first();
let query = await DB.select(...Serializers.userPublicProperties)
.from("users")
.where({ twitterId })
.first();
if (!query || query.error) {
return null;
}
query = Serializers.sanitizeUser(query);
return JSON.parse(JSON.stringify(query));
},
errorFn: async (e) => {

View File

@ -7,18 +7,26 @@ export default async ({ username, sanitize = false, includeFiles = false, public
return await runQuery({
label: "GET_USER_BY_USERNAME",
queryFn: async (DB) => {
// const userFiles = () =>
// DB.raw("json_agg(?? order by ?? desc) as ??", ["files", "files.createdAt", "library"]);
const userFiles = () =>
DB.raw(
"coalesce(json_agg(?? order by ?? desc) filter (where ?? is not null), '[]') as ??",
["files", "files.createdAt", "files.id", "library"]
);
let query;
if (sanitize) {
query = await DB.select(...Serializers.userPublicProperties)
.from("users")
.where({ username })
.first();
} else {
query = await DB.select("*").from("users").where({ username }).first();
}
const id = query?.id;
if (!id) {
return null;
}
if (includeFiles) {
if (publicOnly) {
//TODO(martina): fix this so can be done in one query. Right now, it's duplicating files for each slate_files entry. Need to do distinct on file.id for the json agg
// query = await DB.select(
// "users.id",
// "users.username",
@ -30,82 +38,51 @@ export default async ({ username, sanitize = false, includeFiles = false, public
// .join("files", "files.ownerId", "users.id")
// .join("slate_files", "files.id", "=", "slate_files.fileId")
// .join("slates", "slates.id", "=", "slate_files.slateId")
// .where({ "users.username": username, "files.isPublic": true })
// .orWhere({ "users.username": username, "slates.isPublic": true })
// .groupBy("users.id");
// .first();
// let subquery = () =>
// DB.select(
// "files.id",
// "files.ownerId",
// "files.cid",
// "files.isPublic",
// "files.filename",
// "files.data"
// )
// .from("files")
// .join("slate_files", "files.id", "=", "slate_files.fileId")
// .join("slates", "slates.id", "=", "slate_files.slateId")
// .where({ "files.isPublic": true })
// .orWhere({ "slates.isPublic": true })
// .as("files");
// query = await DB.select(
// "users.id",
// "users.username",
// "users.data",
// "users.email",
// userFiles()
// )
// .from("users")
// .join(subquery(), "publicFiles.ownerId", "=", "users.id")
// .where({ "users.username": username })
// .where({ "users.id": id, "files.isPublic": true })
// .orWhere({ "users.id": id, "slates.isPublic": true })
// .groupBy("users.id")
// .first();
query = await DB.select("*").from("users").where({ username }).first();
const id = query?.id;
let library = await DB.select("files.*")
.from("files")
.leftJoin("slate_files", "slate_files.fileId", "=", "files.id")
.leftJoin("slates", "slate_files.slateId", "=", "slates.id")
.whereRaw("?? = ? and (?? = ? or ?? = ?)", [
"files.ownerId",
id,
"files.isPublic",
true,
"slates.isPublic",
true,
])
// .where({ "files.ownerId": id, "slates.isPublic": true })
// .orWhere({ "files.ownerId": id, "files.isPublic": true })
.orderBy("files.createdAt", "desc")
.groupBy("files.id");
if (id) {
let library = await DB.select(...Serializers.fileProperties)
.from("files")
.leftJoin("slate_files", "slate_files.fileId", "=", "files.id")
.leftJoin("slates", "slate_files.slateId", "=", "slates.id")
// .where({ "files.ownerId": id, "slates.isPublic": true })
// .orWhere({ "files.ownerId": id, "files.isPublic": true })
.whereRaw("?? = ? and (?? = ? or ?? = ?)", [
"files.ownerId",
id,
"files.isPublic",
true,
"slates.isPublic",
true,
])
.orderBy("files.createdAt", "desc")
.groupBy("files.id");
query.library = library;
}
query.library = library;
} else {
query = await DB.select(...Serializers.userProperties, userFiles())
.from("users")
.where({ "users.username": username })
.leftJoin("files", "files.ownerId", "users.id")
.first();
let library = await DB.select("*")
.from("files")
.where({ ownerId: id })
.orderBy("createdAt", "desc")
.groupBy("id");
query.library = library;
// query = await DB.select("users.*", userFiles())
// .from("users")
// .where({ "users.id": id })
// .leftJoin("files", "files.ownerId", "users.id")
// .groupBy("users.id")
// .first();
}
} else {
query = await DB.select("*").from("users").where({ username }).first();
}
if (!query || query.error) {
return null;
}
if (sanitize) {
query = Serializers.sanitizeUser(query);
}
return JSON.parse(JSON.stringify(query));
},
errorFn: async (e) => {

View File

@ -1,55 +0,0 @@
import { runQuery } from "~/node_common/data/utilities";
export default async ({ id, ownerId, isPublic }) => {
return await runQuery({
label: "UPDATE_FILE_PRIVACY",
queryFn: async (DB) => {
if (isPublic === false) {
//NOTE(martina): if making a file private, remove any activity items involving it
const activity = await DB("activity").where("fileId", id).update({ ignore: true });
const repostedSlateFiles = await DB.from("slate_files")
.whereIn("id", function () {
this.select("slate_files.id")
.from("slate_files")
.join("slates", "slates.id", "=", "slate_files.slateId")
.where({ "slate_files.fileId": id })
.whereNot("slates.ownerId", "=", ownerId);
})
.del();
// .update({ fileId: null })
//NOTE(martina): and remove it from any of the user's public slates
const deletedSlateFiles = await DB.from("slate_files")
.whereIn("id", function () {
this.select("slate_files.id")
.from("slate_files")
.join("slates", "slates.id", "=", "slate_files.slateId")
.where({
"slates.isPublic": true,
"slates.ownerId": ownerId,
"slate_files.fileId": id,
});
})
.del()
.returning("slateId");
//NOTE(martina): then decrement the fileCount for those slates
const slatesSummaryQuery = await DB.from("slates")
.whereIn("id", deletedSlateFiles)
.decrement("fileCount", 1);
}
const response = await DB.from("files").where("id", id).update({ isPublic }).returning("*");
const index = response ? response.pop() : null;
return JSON.parse(JSON.stringify(index));
},
errorFn: async (e) => {
return {
error: true,
decorator: "UPDATE_FILE_PRIVACY",
};
},
});
};

View File

@ -1,43 +0,0 @@
import * as Logging from "~/common/logging";
import { runQuery } from "~/node_common/data/utilities";
//NOTE(martina): this method is specifically for making *multiple* files from one owner *public*. It will filter out the already public files
export default async ({ ids, ownerId }) => {
return await runQuery({
label: "UPDATE_FILES_PUBLIC",
queryFn: async (DB) => {
let privateFiles = await DB.select("id")
.from("files")
.whereIn("id", ids)
.where("isPublic", false);
if (!privateFiles?.length) {
return [];
}
let privateIds = privateFiles.map((file) => file.id);
let activityItems = [];
for (let id of privateIds) {
activityItems.push({
ownerId,
fileId: id,
type: "FILE_VISIBLE",
});
}
let activityQuery = await DB.insert(activityItems).into("activity");
const response = await DB.from("files")
.whereIn("id", privateIds)
.update({ isPublic: true })
.returning("*");
return JSON.parse(JSON.stringify(response));
},
errorFn: async (e) => {
return {
error: true,
decorator: "UPDATE_FILES_PUBLIC",
};
},
});
};

View File

@ -1,3 +1,31 @@
// //NOTE(martina): clean the object before adding it to the database
// export const cleanUser = (user) => {
// return {
// id: user.id,
// createdAt: user.createdAt,
// lastActive: user.lastActive,
// username: user.username,
// password: user.password,
// salt: user.salt,
// email: user.email,
// followerCount: user.followerCount,
// slateCount: user.slateCount,
// twitterId: user.twitterId,
// authVersion: user.authVersion,
// revertedVersion: user.revertedVersion,
// body: user.body,
// photo: user.photo,
// name: user.name,
// twitterUsername: user.twitterUsername,
// twitterVerified: user.twitterVerified,
// textileToken: user.textileToken,
// settingsDealAutoApprove: user.settingsDealAutoApprove,
// allowAutomaticDataStorage: user.allowAutomaticDataStorage,
// allowEncryptedDataStorage: user.allowEncryptedDataStorage,
// onboarding: user.onboarding,
// };
// };
//NOTE(martina): add a variable to sanitizeUser if it should be sent to the front end. Otherwise, it will be filtered out
export const sanitizeUser = (user) => {
@ -18,23 +46,8 @@ export const sanitizeUser = (user) => {
};
};
//NOTE(martina): list of the properties of the tables that should be returned by db queries. Convenience so we don't have to write these out each time and update in multiple places
export const slateProperties = [
"slates.id",
"slates.ownerId",
"slates.createdAt",
"slates.updatedAt",
"slates.slatename",
"slates.body",
"slates.name",
"slates.preview",
"slates.isPublic",
"slates.subscriberCount",
"slates.fileCount",
];
//NOTE(martina): the user properties list filters out sensitive information
export const userProperties = [
//NOTE(martina): the user public properties list filters out sensitive information
export const userPublicProperties = [
"users.id",
"users.createdAt",
"users.lastActive",
@ -48,34 +61,5 @@ export const userProperties = [
"users.twitterVerified",
];
export const fileProperties = [
"files.id",
"files.ownerId",
"files.createdAt",
"files.cid",
"files.isPublic",
"files.filename",
"files.name",
"files.body",
"files.size",
"files.type",
"files.blurhash",
"files.data",
"files.source",
"files.author",
"files.coverImage",
"files.downloadCount",
"files.saveCount",
"files.isLink",
"files.url",
"files.linkName",
"files.linkBody",
"files.linkAuthor",
"files.linkSource",
"files.linkDomain",
"files.linkImage",
"files.linkFavicon",
"files.linkHtml",
"files.linkIFrameAllowed",
"files.tags",
];
//NOTE(martina): the user preview properties list contains the minimal info needed to preview the owner of a file or slate
export const userPreviewProperties = ["users.id", "users.name", "users.username", "users.photo"];

View File

@ -110,12 +110,12 @@ export default async (req, res) => {
}
}
if (targetUser && !existingResponse) {
if (targetUser) {
ViewerManager.hydratePartial(id, { following: true });
Monitor.subscribeUser({ user, targetUser });
}
if (targetSlate && !existingResponse) {
if (targetSlate) {
ViewerManager.hydratePartial(id, { subscriptions: true });
Monitor.subscribeSlate({ user, targetSlate });
}

View File

@ -16,42 +16,77 @@ export default async (req, res) => {
if (!userInfo) return;
const { id, user } = userInfo;
let updates = req.body.data;
let updates = req.body.data.user;
if (updates.username && updates.username !== user.username) {
if (!Validations.username(req.body.data.username)) {
return res.status(400).send({
decorator: "SERVER_USER_UPDATE_INVALID_USERNAME",
error: true,
if (updates) {
if (updates.username && updates.username !== user.username) {
if (!Validations.username(updates.username)) {
return res.status(400).send({
decorator: "SERVER_USER_UPDATE_INVALID_USERNAME",
error: true,
});
}
const existing = await Data.getUserByUsername({
username: updates.username.toLowerCase(),
});
if (existing && existing.id !== id) {
return res
.status(500)
.send({ decorator: "SERVER_USER_UPDATE_USERNAME_IS_TAKEN", error: true });
}
}
const existing = await Data.getUserByUsername({
username: req.body.data.username.toLowerCase(),
});
if (updates.email && updates.email !== user.email) {
if (!Validations.email(updates.email)) {
return res.status(400).send({
decorator: "SERVER_USER_UPDATE_INVALID_EMAIL",
error: true,
});
}
if (existing && existing.id !== id) {
return res
.status(500)
.send({ decorator: "SERVER_USER_UPDATE_USERNAME_IS_TAKEN", error: true });
}
}
if (updates.email && updates.email !== user.email) {
if (!Validations.email(req.body.data.email)) {
return res.status(400).send({
decorator: "SERVER_USER_UPDATE_INVALID_EMAIL",
error: true,
const existing = await Data.getUserByEmail({
email: updates.email.toLowerCase(),
});
if (existing && existing.id !== id) {
return res.status(500).send({ decorator: "SERVER_USER_UPDATE_EMAIL", error: true });
}
}
const existing = await Data.getUserByEmail({
email: req.body.data.email.toLowerCase(),
});
if (req.body.data.type === "CHANGE_PASSWORD" && updates.password) {
if (!Validations.password(updates.password)) {
return res
.status(500)
.send({ decorator: "SERVER_USER_UPDATE_INVALID_PASSWORD", error: true });
}
if (existing && existing.id !== id) {
return res.status(500).send({ decorator: "SERVER_USER_UPDATE_EMAIL", error: true });
const rounds = Number(Environment.LOCAL_PASSWORD_ROUNDS);
const salt = await BCrypt.genSalt(rounds);
const hash = await Utilities.encryptPassword(updates.password, salt);
updates.salt = salt;
updates.password = hash;
}
if (updates.body && updates.body.length > 2000) {
return res.status(400).send({ decorator: "SERVER_USER_UPDATE_MAX_BODY_LENGTH", error: true });
}
let unsafeResponse = await Data.updateUserById({ id, ...updates });
if (unsafeResponse && !unsafeResponse.error) {
if (
user.username !== unsafeResponse.username ||
user.name !== unsafeResponse.name ||
user.photo !== unsafeResponse.photo
) {
SearchManager.updateUser(unsafeResponse, "EDIT");
}
}
ViewerManager.hydratePartial(id, { viewer: true });
}
if (req.body.data.type === "SAVE_DEFAULT_ARCHIVE_CONFIG") {
@ -95,38 +130,5 @@ export default async (req, res) => {
}
}
if (req.body.data.type === "CHANGE_PASSWORD" && req.body.data.password) {
if (!Validations.password(req.body.data.password)) {
return res
.status(500)
.send({ decorator: "SERVER_USER_UPDATE_INVALID_PASSWORD", error: true });
}
const rounds = Number(Environment.LOCAL_PASSWORD_ROUNDS);
const salt = await BCrypt.genSalt(rounds);
const hash = await Utilities.encryptPassword(req.body.data.password, salt);
updates.salt = salt;
updates.password = hash;
}
if (updates.body && updates.body.length > 2000) {
return res.status(400).send({ decorator: "SERVER_USER_UPDATE_MAX_BODY_LENGTH", error: true });
}
let unsafeResponse = await Data.updateUserById({ id, ...updates });
if (unsafeResponse && !unsafeResponse.error) {
if (
user.username !== unsafeResponse.username ||
user.name !== unsafeResponse.name ||
user.photo !== unsafeResponse.photo
) {
SearchManager.updateUser(unsafeResponse, "EDIT");
}
}
ViewerManager.hydratePartial(id, { viewer: true });
return res.status(200).send({ decorator: "SERVER_USER_UPDATE" });
};

View File

@ -73,8 +73,10 @@ export default class SceneArchive extends React.Component {
this.setState({ changingFilecoin: true });
await Actions.updateViewer({
allowAutomaticDataStorage: this.state.allowAutomaticDataStorage,
allowEncryptedDataStorage: this.state.allowEncryptedDataStorage,
user: {
allowAutomaticDataStorage: this.state.allowAutomaticDataStorage,
allowEncryptedDataStorage: this.state.allowEncryptedDataStorage,
},
});
this.setState({ changingFilecoin: false });

View File

@ -71,7 +71,9 @@ export default class SceneEditAccount extends React.Component {
const cid = file.cid;
const url = Strings.getURLfromCID(cid);
let updateResponse = await Actions.updateViewer({
photo: Strings.getURLfromCID(cid),
user: {
photo: Strings.getURLfromCID(cid),
},
});
Events.hasError(updateResponse);
@ -82,8 +84,10 @@ export default class SceneEditAccount extends React.Component {
this.setState({ changingFilecoin: true });
let response = await Actions.updateViewer({
allowAutomaticDataStorage: this.state.allowAutomaticDataStorage,
allowEncryptedDataStorage: this.state.allowEncryptedDataStorage,
user: {
allowAutomaticDataStorage: this.state.allowAutomaticDataStorage,
allowEncryptedDataStorage: this.state.allowEncryptedDataStorage,
},
});
Events.hasError(response);
@ -106,10 +110,12 @@ export default class SceneEditAccount extends React.Component {
this.setState({ savingNameBio: true });
let response = await Actions.updateViewer({
username: this.state.username,
photo: this.state.photo,
body: this.state.body,
name: this.state.name,
user: {
username: this.state.username,
photo: this.state.photo,
body: this.state.body,
name: this.state.name,
},
});
Events.hasError(response);
@ -121,13 +127,10 @@ export default class SceneEditAccount extends React.Component {
};
_handleChangePassword = async (e) => {
if (this.state.password !== this.state.confirm) {
Events.dispatchMessage({ message: "Passwords did not match" });
return;
}
if (!Validations.password(this.state.password)) {
Events.dispatchMessage({ message: "Password length must be more than 8 characters" });
Events.dispatchMessage({
message: "Password does not meet requirements",
});
return;
}
@ -135,7 +138,7 @@ export default class SceneEditAccount extends React.Component {
let response = await Actions.updateViewer({
type: "CHANGE_PASSWORD",
password: this.state.password,
user: { password: this.state.password },
});
if (Events.hasError(response)) {
@ -218,7 +221,7 @@ export default class SceneEditAccount extends React.Component {
<div css={STYLES_HEADER}>Bio</div>
<System.Textarea
maxlength="2000"
maxLength="2000"
name="body"
value={this.state.body}
placeholder="A bit about yourself..."
@ -281,7 +284,10 @@ export default class SceneEditAccount extends React.Component {
{tab === "security" ? (
<div>
<div css={STYLES_HEADER}>Change password</div>
<div>Passwords must be a minimum of eight characters.</div>
<div>
Passwords should be at least 8 characters long, contain a mix of upper and lowercase
letters, and have at least 1 number and 1 symbol
</div>
<System.Input
containerStyle={{ marginTop: 24 }}
@ -364,7 +370,7 @@ export default class SceneEditAccount extends React.Component {
header={`Are you sure you want to delete your account @${this.state.username}?`}
subHeader={`You will lose all your files and collections. You cant undo this action.`}
inputHeader={`Please type your username to confirm`}
inputPlaceholder={`username`}
inputPlaceholder={`Username`}
/>
)}
</ScenePage>

View File

@ -57,7 +57,10 @@ export default class SceneFilesFolder extends React.Component {
) : (
<EmptyState>
<FileTypeGroup />
<div style={{ marginTop: 24 }}>Drag and drop files into Slate to upload</div>
<div style={{ marginTop: 24 }}>
Drag and drop files into Slate to upload, or press the plus button to save a file or
link
</div>
</EmptyState>
)}
</ScenePage>

View File

@ -402,7 +402,7 @@ class SlatePage extends React.Component {
};
render() {
const { user, objects, body, isPublic, ownerId } = this.props.data;
const { user, name, objects, body, isPublic, ownerId } = this.props.data;
const isOwner = this.props.viewer ? ownerId === this.props.viewer.id : false;
let actions = isOwner ? (
@ -453,11 +453,11 @@ class SlatePage extends React.Component {
{user.username}
</span>{" "}
</Link>
/ {data.name}
/ {name}
</span>
) : (
<div css={Styles.HORIZONTAL_CONTAINER_CENTERED}>
<span>{data.name}</span>
<span>{name}</span>
{isOwner && !isPublic && (
<div css={STYLES_SECURITY_LOCK_WRAPPER} style={{ marginLeft: 16 }}>
<SVG.SecurityLock height="16px" style={{ display: "block" }} />

View File

@ -27,7 +27,7 @@ const app = next({
const createLimiter = limit({
windowMs: 10 * 60 * 1000, // 10 minutes
max: 5,
max: 10,
message: {
decorator: "SIGN_UP_RATE_LIMITED",
error: true,
@ -37,7 +37,7 @@ const createLimiter = limit({
const loginLimiter = limit({
windowMs: 10 * 60 * 1000, // 10 minutes
max: 5,
max: 10,
message: {
decorator: "SIGN_IN_RATE_LIMITED",
error: true,