OurBigBook logoOurBigBook Docs OurBigBook logoOurBigBook.comSite Source code
web/pages/go/settings/[uid].tsx
import Router from 'next/router'
import React from 'react'

import { contactUrl } from 'front/config'
import Label from 'front/Label'
import MapErrors from 'front/MapErrors'
import {
  MyHead,
  SettingsIcon,
  setupUserLocalStorage,
  useCtrlEnterSubmit
} from 'front'
import { webApi } from 'front/api'
import routes from 'front/routes'
import { CommonPropsType } from 'front/types/CommonPropsType'
import { UserType } from 'front/types/UserType'

interface SettingsProps extends CommonPropsType {
  user?: UserType;
}

const Settings = ({
  user: user0,
  loggedInUser,
}: SettingsProps) => {
  const [isLoading, setLoading] = React.useState(false);
  const [errors, setErrors] = React.useState([]);
  const [userInfo, setUserInfo] = React.useState(user0);
  const updateState = (field) => (e) => {
    const state = userInfo;
    const newState = { ...state, [field]: e.target.value };
    setUserInfo(newState);
  };
  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);
    const user = { ...userInfo };
    if (!user.password) {
      delete user.password;
    }
    const { data, status } = await webApi.userUpdate(user.username, user)
    setLoading(false);
    if (status === 200) {
      if (
        data.user &&
        // Possible for admin edits.
        data.user.username === loggedInUser.username
      ) {
        await setupUserLocalStorage(data.user, setErrors)
      }
      Router.push(routes.user(data.user.username));
    } else {
      setErrors(data.errors);
    }
  };
  useCtrlEnterSubmit(handleSubmit)
  const maxArticleSizeLabel = "Maximum number of articles, issues and comments (maxArticles)"
  const maxArticlesLabel = "Maximum article/issue/comment size (maxArticleSize)"
  const maxIssuesPerMinuteLabel = "Maximum issues/comments per minute (maxIssuesPerMinute)"
  const maxIssuesPerHourLabel = "Maximum issues/comments per hour (maxIssuesPerHour)"
  const title = "Account settings"
  return <>
    <MyHead title={title} />
    <div className="settings-page content-not-ourbigbook">
      <h1><SettingsIcon /> {title}</h1>
      <>
        <MapErrors errors={errors} />
        <form onSubmit={handleSubmit}>
          <Label label="Username">
            <input
              type="text"
              disabled={true}
              placeholder="Username"
              value={userInfo.username}
              title="Cannot be currently modified"
              autoComplete="username"
              //onChange={updateState("username")}
            />
          </Label>
          <Label label="Display name">
            <input
              type="text"
              placeholder="Display name"
              value={userInfo.displayName}
              onChange={updateState("displayName")}
            />
          </Label>
          <Label label="Profile picture">
            <input
              type="text"
              placeholder="URL of profile picture"
              value={userInfo.image ? userInfo.image : ""}
              onChange={updateState("image")}
            />
          </Label>
          <Label label="Email">
            <input
              type="email"
              placeholder="Email"
              value={userInfo.email}
              onChange={updateState("email")}
              // https://github.com/ourbigbook/ourbigbook/issues/268
              disabled={true}
              title="Cannot be currently modified"
            />
          </Label>
          <Label label="Password">
            <input
              type="password"
              placeholder="New Password"
              value={userInfo.password}
              onChange={updateState("password")}
              autoComplete="new-password"
            />
          </Label>
          <Label label="Email notifications" inline={true}>
            <input
              type="checkbox"
              defaultChecked={userInfo.emailNotifications}
              onChange={() => setUserInfo((state) => { return {
                ...state,
                emailNotifications: !state.emailNotifications
              }})}
            />
          </Label>
          <Label label="Hide article dates" inline={true}>
            <input
              type="checkbox"
              defaultChecked={userInfo.hideArticleDates}
              title="Hardcode the created and updated date of every edited or created article to January 1st 1970. That fake date is stored in the database instead of the real dates which are lost forever. Sequential IDs are still stored in the database, which would allos for a subpoena to infer dates from nearby ID ranges."
              onChange={() => setUserInfo((state) => { return {
                ...state,
                hideArticleDates: !state.hideArticleDates
              }})}
            />
          </Label>
          <button
            className="btn"
            type="submit"
            disabled={isLoading}
          >
            Update Settings
          </button>
          <h2>Extra information</h2>
          {cant.setUserLimits(loggedInUser)
            ? <>
                <p><b>Limits:</b></p>
                <ul>
                  <li>{maxArticleSizeLabel}: <b>{userInfo.maxArticleSize}</b></li>
                  <li>{maxArticlesLabel}: <b>{userInfo.maxArticles}</b></li>
                  <li>{maxIssuesPerMinuteLabel}: <b>{userInfo.maxIssuesPerMinute}</b></li>
                  <li>{maxIssuesPerHourLabel}: <b>{userInfo.maxIssuesPerHour}</b></li>
                </ul>
                <div>You may <a href={contactUrl}><b>ask an admin</b></a> to raise any of those limits for you.</div>
              </>
            : <>
                <Label label={maxArticlesLabel}>
                  <input
                    type="number"
                    value={userInfo.maxArticleSize}
                    onChange={updateState("maxArticleSize")}
                  />
                </Label>
                <Label label={maxArticleSizeLabel}>
                  <input
                    type="number"
                    value={userInfo.maxArticles}
                    onChange={updateState("maxArticles")}
                  />
                </Label>
                <Label label={maxIssuesPerMinuteLabel}>
                  <input
                    type="number"
                    value={userInfo.maxIssuesPerMinute}
                    onChange={updateState("maxIssuesPerMinute")}
                  />
                </Label>
                <Label label={maxIssuesPerHourLabel}>
                  <input
                    type="number"
                    value={userInfo.maxIssuesPerHour}
                    onChange={updateState("maxIssuesPerHour")}
                  />
                </Label>
              </>
          }
          <p>Signup IP: <b>{userInfo.ip || 'not set'}</b></p>
          <p>nestedSetNeedsUpdate: <b>{userInfo.nestedSetNeedsUpdate.toString()}</b></p>
          {loggedInUser.admin &&
            <p>Verified: <b>{userInfo.verified.toString()}</b></p>
          }
        </form>
      </>
    </div>
  </>
};

export default Settings;

import { getLoggedInUser } from 'back'
import { cant } from 'front/cant'

export async function getServerSideProps(context) {
  const { params: { uid }, req, res } = context
  if (
    typeof uid === 'string'
  ) {
    const sequelize = req.sequelize
    const [loggedInUser, user] = await Promise.all([
      getLoggedInUser(req, res),
      sequelize.models.User.findOne({
        where: { username: uid },
      }),
    ])
    if (!user) { return { notFound: true } }
    const props: SettingsProps = {}
    if (!loggedInUser) {
      return {
        redirect: {
          destination: routes.userNew(),
          permanent: false,
        }
      }
    }
    if (cant.viewUserSettings(loggedInUser, user)) {
      return { notFound: true }
    } else {
      ;[props.user, props.loggedInUser] = await Promise.all([
        user.toJson(loggedInUser),
        loggedInUser.toJson(loggedInUser),
      ])
    }
    return { props }
  } else {
    throw new TypeError
  }
}