mirror of https://github.com/usememos/memos
				
				
				
			
							parent
							
								
									a0667abec8
								
							
						
					
					
						commit
						2042737004
					
				@ -1,13 +1,12 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
type Signin struct {
 | 
			
		||||
	Email    string `json:"email"`
 | 
			
		||||
	Username string `json:"username"`
 | 
			
		||||
	Password string `json:"password"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Signup struct {
 | 
			
		||||
	Email    string `json:"email"`
 | 
			
		||||
	Role     Role   `json:"role"`
 | 
			
		||||
	Name     string `json:"name"`
 | 
			
		||||
	Username string `json:"username"`
 | 
			
		||||
	Password string `json:"password"`
 | 
			
		||||
	Role     Role   `json:"role"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,41 @@
 | 
			
		||||
-- add column username TEXT NOT NULL UNIQUE
 | 
			
		||||
-- rename column name to nickname
 | 
			
		||||
-- add role `ADMIN`
 | 
			
		||||
DROP TABLE IF EXISTS _user_old;
 | 
			
		||||
 | 
			
		||||
ALTER TABLE user RENAME TO _user_old;
 | 
			
		||||
 | 
			
		||||
-- user
 | 
			
		||||
CREATE TABLE user (
 | 
			
		||||
  id INTEGER PRIMARY KEY AUTOINCREMENT,
 | 
			
		||||
  created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
 | 
			
		||||
  updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
 | 
			
		||||
  row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
 | 
			
		||||
  username TEXT NOT NULL UNIQUE,
 | 
			
		||||
  role TEXT NOT NULL CHECK (role IN ('HOST', 'ADMIN', 'USER')) DEFAULT 'USER',
 | 
			
		||||
  email TEXT NOT NULL DEFAULT '',
 | 
			
		||||
  nickname TEXT NOT NULL DEFAULT '',
 | 
			
		||||
  password_hash TEXT NOT NULL,
 | 
			
		||||
  open_id TEXT NOT NULL UNIQUE
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
INSERT INTO user (
 | 
			
		||||
  id, created_ts, updated_ts, row_status, 
 | 
			
		||||
  username, role, email, nickname, password_hash, 
 | 
			
		||||
  open_id
 | 
			
		||||
) 
 | 
			
		||||
SELECT 
 | 
			
		||||
  id, 
 | 
			
		||||
  created_ts, 
 | 
			
		||||
  updated_ts, 
 | 
			
		||||
  row_status, 
 | 
			
		||||
  email, 
 | 
			
		||||
  role, 
 | 
			
		||||
  email, 
 | 
			
		||||
  name, 
 | 
			
		||||
  password_hash, 
 | 
			
		||||
  open_id 
 | 
			
		||||
FROM 
 | 
			
		||||
  _user_old;
 | 
			
		||||
 | 
			
		||||
DROP TABLE IF EXISTS _user_old;
 | 
			
		||||
@ -0,0 +1,118 @@
 | 
			
		||||
import { useEffect, useState } from "react";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { useAppSelector } from "../store";
 | 
			
		||||
import { userService } from "../services";
 | 
			
		||||
import Icon from "./Icon";
 | 
			
		||||
import { generateDialog } from "./Dialog";
 | 
			
		||||
import toastHelper from "./Toast";
 | 
			
		||||
 | 
			
		||||
type Props = DialogProps;
 | 
			
		||||
 | 
			
		||||
interface State {
 | 
			
		||||
  username: string;
 | 
			
		||||
  nickname: string;
 | 
			
		||||
  email: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const UpdateAccountDialog: React.FC<Props> = ({ destroy }: Props) => {
 | 
			
		||||
  const { t } = useTranslation();
 | 
			
		||||
  const user = useAppSelector((state) => state.user.user as User);
 | 
			
		||||
  const [state, setState] = useState<State>({
 | 
			
		||||
    username: user.username,
 | 
			
		||||
    nickname: user.nickname,
 | 
			
		||||
    email: user.email,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    // do nth
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  const handleCloseBtnClick = () => {
 | 
			
		||||
    destroy();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleNicknameChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
 | 
			
		||||
    setState((state) => {
 | 
			
		||||
      return {
 | 
			
		||||
        ...state,
 | 
			
		||||
        nickname: e.target.value as string,
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
  const handleUsernameChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
 | 
			
		||||
    setState((state) => {
 | 
			
		||||
      return {
 | 
			
		||||
        ...state,
 | 
			
		||||
        username: e.target.value as string,
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
  const handleEmailChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
 | 
			
		||||
    setState((state) => {
 | 
			
		||||
      return {
 | 
			
		||||
        ...state,
 | 
			
		||||
        email: e.target.value as string,
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleSaveBtnClick = async () => {
 | 
			
		||||
    if (state.username === "") {
 | 
			
		||||
      toastHelper.error(t("message.fill-all"));
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const user = userService.getState().user as User;
 | 
			
		||||
      await userService.patchUser({
 | 
			
		||||
        id: user.id,
 | 
			
		||||
        username: state.username,
 | 
			
		||||
        nickname: state.nickname,
 | 
			
		||||
        email: state.email,
 | 
			
		||||
      });
 | 
			
		||||
      toastHelper.info("Update succeed");
 | 
			
		||||
      handleCloseBtnClick();
 | 
			
		||||
    } catch (error: any) {
 | 
			
		||||
      console.error(error);
 | 
			
		||||
      toastHelper.error(error.response.data.error);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <div className="dialog-header-container !w-64">
 | 
			
		||||
        <p className="title-text">Update information</p>
 | 
			
		||||
        <button className="btn close-btn" onClick={handleCloseBtnClick}>
 | 
			
		||||
          <Icon.X />
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div className="dialog-content-container">
 | 
			
		||||
        <p className="text-sm mb-1">Nickname</p>
 | 
			
		||||
        <input type="text" className="input-text" value={state.nickname} onChange={handleNicknameChanged} />
 | 
			
		||||
        <p className="text-sm mb-1 mt-2">Username</p>
 | 
			
		||||
        <input type="text" className="input-text" value={state.username} onChange={handleUsernameChanged} />
 | 
			
		||||
        <p className="text-sm mb-1 mt-2">Email</p>
 | 
			
		||||
        <input type="text" className="input-text" value={state.email} onChange={handleEmailChanged} />
 | 
			
		||||
        <div className="mt-4 w-full flex flex-row justify-end items-center space-x-2">
 | 
			
		||||
          <span className="btn-text" onClick={handleCloseBtnClick}>
 | 
			
		||||
            {t("common.cancel")}
 | 
			
		||||
          </span>
 | 
			
		||||
          <span className="btn-primary" onClick={handleSaveBtnClick}>
 | 
			
		||||
            {t("common.save")}
 | 
			
		||||
          </span>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function showUpdateAccountDialog() {
 | 
			
		||||
  generateDialog(
 | 
			
		||||
    {
 | 
			
		||||
      className: "update-account-dialog",
 | 
			
		||||
    },
 | 
			
		||||
    UpdateAccountDialog
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default showUpdateAccountDialog;
 | 
			
		||||
@ -1,46 +0,0 @@
 | 
			
		||||
.change-password-dialog {
 | 
			
		||||
  > .dialog-container {
 | 
			
		||||
    @apply w-72;
 | 
			
		||||
 | 
			
		||||
    > .dialog-content-container {
 | 
			
		||||
      @apply flex flex-col justify-start items-start;
 | 
			
		||||
 | 
			
		||||
      > .tip-text {
 | 
			
		||||
        @apply bg-gray-400 text-xs p-2 rounded-lg;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      > .form-label {
 | 
			
		||||
        @apply flex flex-col justify-start items-start;
 | 
			
		||||
        @apply relative w-full leading-relaxed;
 | 
			
		||||
 | 
			
		||||
        &.input-form-label {
 | 
			
		||||
          @apply py-3 pb-1;
 | 
			
		||||
 | 
			
		||||
          > input {
 | 
			
		||||
            @apply w-full p-2 text-sm leading-6 rounded border border-gray-400 bg-transparent;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      > .btns-container {
 | 
			
		||||
        @apply mt-2 w-full flex flex-row justify-end items-center;
 | 
			
		||||
 | 
			
		||||
        > .btn {
 | 
			
		||||
          @apply text-sm px-4 py-2 rounded ml-2 bg-gray-400;
 | 
			
		||||
 | 
			
		||||
          &:hover {
 | 
			
		||||
            @apply opacity-80;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          &.confirm-btn {
 | 
			
		||||
            @apply bg-green-600 text-white shadow-inner;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          &.cancel-btn {
 | 
			
		||||
            background-color: unset;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
					Loading…
					
					
				
		Reference in New Issue