feat: add user role field (#49)

* feat: add user role field

* chore: fix typo

* feat: update signup api
pull/53/head
STEVEN 3 years ago committed by GitHub
parent 64374610ea
commit f1cca0f298
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,11 +1,13 @@
package api
type Login struct {
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password"`
}
type Signup struct {
Email string `json:"email"`
Role Role `json:"role"`
Name string `json:"name"`
Password string `json:"password"`
}

@ -0,0 +1,5 @@
package api
type SystemStatus struct {
Owner *User `json:"owner"`
}

@ -1,5 +1,15 @@
package api
// Role is the type of a role.
type Role string
const (
// Owner is the OWNER role.
Owner Role = "OWNER"
// NormalUser is the USER role.
NormalUser Role = "USER"
)
type User struct {
ID int `json:"id"`
@ -8,45 +18,44 @@ type User struct {
UpdatedTs int64 `json:"updatedTs"`
// Domain specific fields
OpenID string `json:"openId"`
Email string `json:"email"`
Role Role `json:"role"`
Name string `json:"name"`
PasswordHash string `json:"-"`
OpenID string `json:"openId"`
}
type UserCreate struct {
// Domain specific fields
OpenID string
Email string
Role Role
Name string
PasswordHash string
OpenID string
}
type UserPatch struct {
ID int
// Domain specific fields
OpenID *string
PasswordHash *string
Email *string `json:"email"`
Name *string `json:"name"`
Password *string `json:"password"`
ResetOpenID *bool `json:"resetOpenId"`
PasswordHash *string
OpenID *string
}
type UserFind struct {
ID *int `json:"id"`
// Domain specific fields
Email *string `json:"email"`
Role *Role
Name *string `json:"name"`
OpenID *string
}
type UserRenameCheck struct {
Name string `json:"name"`
}
type UserPasswordCheck struct {
Password string `json:"password"`
}
type UserService interface {
CreateUser(create *UserCreate) (*User, error)
PatchUser(patch *UserPatch) (*User, error)

Binary file not shown.

@ -19,14 +19,14 @@ func (s *Server) registerAuthRoutes(g *echo.Group) {
}
userFind := &api.UserFind{
Name: &login.Name,
Email: &login.Email,
}
user, err := s.UserService.FindUser(userFind)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find user by name %s", login.Name)).SetInternal(err)
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find user by email %s", login.Email)).SetInternal(err)
}
if user == nil {
return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("User not found with name %s", login.Name))
return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("User not found with email %s", login.Email))
}
// Compare the stored hashed password, with the hashed version of the password that was received.
@ -58,6 +58,19 @@ func (s *Server) registerAuthRoutes(g *echo.Group) {
})
g.POST("/auth/signup", func(c echo.Context) error {
// Don't allow to signup by this api if site owner existed.
ownerUserType := api.Owner
ownerUserFind := api.UserFind{
Role: &ownerUserType,
}
ownerUser, err := s.UserService.FindUser(&ownerUserFind)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find owner user").SetInternal(err)
}
if ownerUser != nil {
return echo.NewHTTPError(http.StatusUnauthorized, "Site Owner existed, please contact the site owner to signin account firstly.").SetInternal(err)
}
signup := &api.Signup{}
if err := json.NewDecoder(c.Request().Body).Decode(signup); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted signup request").SetInternal(err)
@ -65,6 +78,9 @@ func (s *Server) registerAuthRoutes(g *echo.Group) {
// Validate signup form.
// We can do stricter checks later.
if len(signup.Email) < 6 {
return echo.NewHTTPError(http.StatusBadRequest, "Email is too short, minimum length is 6.")
}
if len(signup.Name) < 6 {
return echo.NewHTTPError(http.StatusBadRequest, "Username is too short, minimum length is 6.")
}
@ -73,14 +89,14 @@ func (s *Server) registerAuthRoutes(g *echo.Group) {
}
userFind := &api.UserFind{
Name: &signup.Name,
Email: &signup.Email,
}
user, err := s.UserService.FindUser(userFind)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find user by name %s", signup.Name)).SetInternal(err)
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find user by email %s", signup.Email)).SetInternal(err)
}
if user != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Existed user found: %s", signup.Name))
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Existed user found: %s", signup.Email))
}
passwordHash, err := bcrypt.GenerateFromPassword([]byte(signup.Password), bcrypt.DefaultCost)
@ -89,6 +105,8 @@ func (s *Server) registerAuthRoutes(g *echo.Group) {
}
userCreate := &api.UserCreate{
Email: signup.Email,
Role: api.Role(signup.Role),
Name: signup.Name,
PasswordHash: string(passwordHash),
OpenID: common.GenUUID(),

@ -56,7 +56,7 @@ func removeUserSession(c echo.Context) error {
func BasicAuthMiddleware(us api.UserService, next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Skips auth
if common.HasPrefixes(c.Path(), "/api/auth", "/api/ping") {
if common.HasPrefixes(c.Path(), "/api/auth", "/api/ping", "/api/status") {
return next(c)
}

@ -2,6 +2,7 @@ package server
import (
"encoding/json"
"memos/api"
"net/http"
"github.com/labstack/echo/v4"
@ -16,4 +17,26 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
return nil
})
g.GET("/status", func(c echo.Context) error {
ownerUserType := api.Owner
ownerUserFind := api.UserFind{
Role: &ownerUserType,
}
ownerUser, err := s.UserService.FindUser(&ownerUserFind)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find owner user").SetInternal(err)
}
systemStatus := api.SystemStatus{
Owner: ownerUser,
}
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(systemStatus)); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode system status response").SetInternal(err)
}
return nil
})
}

@ -36,70 +36,6 @@ func (s *Server) registerUserRoutes(g *echo.Group) {
return nil
})
g.POST("/user/rename_check", func(c echo.Context) error {
userRenameCheck := &api.UserRenameCheck{}
if err := json.NewDecoder(c.Request().Body).Decode(userRenameCheck); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post user rename check request").SetInternal(err)
}
if userRenameCheck.Name == "" {
return echo.NewHTTPError(http.StatusBadRequest, "New name needed")
}
userFind := &api.UserFind{
Name: &userRenameCheck.Name,
}
user, err := s.UserService.FindUser(userFind)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find user by name %s", *userFind.Name)).SetInternal(err)
}
isUsable := true
if user != nil {
isUsable = false
}
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(isUsable)); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode rename check response").SetInternal(err)
}
return nil
})
g.POST("/user/password_check", func(c echo.Context) error {
userID := c.Get(getUserIDContextKey()).(int)
userPasswordCheck := &api.UserPasswordCheck{}
if err := json.NewDecoder(c.Request().Body).Decode(userPasswordCheck); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post user password check request").SetInternal(err)
}
if userPasswordCheck.Password == "" {
return echo.NewHTTPError(http.StatusBadRequest, "Password needed")
}
userFind := &api.UserFind{
ID: &userID,
}
user, err := s.UserService.FindUser(userFind)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find user by password").SetInternal(err)
}
isValid := false
// Compare the stored hashed password, with the hashed version of the password that was received.
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(userPasswordCheck.Password)); err == nil {
isValid = true
}
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(isValid)); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode password check response").SetInternal(err)
}
return nil
})
g.PATCH("/user/me", func(c echo.Context) error {
userID := c.Get(getUserIDContextKey()).(int)
userPatch := &api.UserPatch{
@ -109,9 +45,17 @@ func (s *Server) registerUserRoutes(g *echo.Group) {
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch user request").SetInternal(err)
}
if userPatch.ResetOpenID != nil && *userPatch.ResetOpenID {
openID := common.GenUUID()
userPatch.OpenID = &openID
if userPatch.Email != nil {
userFind := api.UserFind{
Email: userPatch.Email,
}
user, err := s.UserService.FindUser(&userFind)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find user by email %s", *userPatch.Email)).SetInternal(err)
}
if user != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("User with email %s existed", *userPatch.Email)).SetInternal(err)
}
}
if userPatch.Password != nil && *userPatch.Password != "" {
@ -124,6 +68,11 @@ func (s *Server) registerUserRoutes(g *echo.Group) {
userPatch.PasswordHash = &passwordHashStr
}
if userPatch.ResetOpenID != nil && *userPatch.ResetOpenID {
openID := common.GenUUID()
userPatch.OpenID = &openID
}
user, err := s.UserService.PatchUser(userPatch)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch user").SetInternal(err)

@ -1,12 +1,14 @@
-- user
CREATE TABLE user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL,
role TEXT NOT NULL CHECK (role IN ('OWNER', 'USER')) DEFAULT 'USER',
name TEXT NOT NULL,
password_hash TEXT NOT NULL,
open_id TEXT NOT NULL,
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
UNIQUE(`name`, `open_id`)
UNIQUE(`email`, `open_id`)
);
INSERT INTO

@ -2,6 +2,7 @@ INSERT INTO
user (
`id`,
`name`,
`email`,
`open_id`,
`password_hash`
)
@ -9,22 +10,7 @@ VALUES
(
101,
'guest',
'guest_open_id',
-- "secret"
'$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK'
);
INSERT INTO
user (
`id`,
`name`,
`open_id`,
`password_hash`
)
VALUES
(
102,
'dear_musk',
'guest@example.com',
'guest_open_id',
-- "secret"
'$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK'

@ -49,7 +49,6 @@ func (db *DB) Open() (err error) {
}
db.Db = sqlDB
// If db file not exists, we should migrate and seed the database.
if _, err := os.Stat(db.DSN); errors.Is(err, os.ErrNotExist) {
if err := db.migrate(); err != nil {

@ -51,13 +51,17 @@ func (s *UserService) FindUser(find *api.UserFind) (*api.User, error) {
func createUser(db *DB, create *api.UserCreate) (*api.User, error) {
row, err := db.Db.Query(`
INSERT INTO user (
email,
role,
name,
password_hash,
open_id
)
VALUES (?, ?, ?)
RETURNING id, name, password_hash, open_id, created_ts, updated_ts
VALUES (?, ?, ?, ?, ?)
RETURNING id, email, role, name, password_hash, open_id, created_ts, updated_ts
`,
create.Email,
create.Role,
create.Name,
create.PasswordHash,
create.OpenID,
@ -71,6 +75,8 @@ func createUser(db *DB, create *api.UserCreate) (*api.User, error) {
var user api.User
if err := row.Scan(
&user.ID,
&user.Email,
&user.Role,
&user.Name,
&user.PasswordHash,
&user.OpenID,
@ -86,6 +92,9 @@ func createUser(db *DB, create *api.UserCreate) (*api.User, error) {
func patchUser(db *DB, patch *api.UserPatch) (*api.User, error) {
set, args := []string{}, []interface{}{}
if v := patch.Email; v != nil {
set, args = append(set, "email = ?"), append(args, v)
}
if v := patch.Name; v != nil {
set, args = append(set, "name = ?"), append(args, v)
}
@ -102,7 +111,7 @@ func patchUser(db *DB, patch *api.UserPatch) (*api.User, error) {
UPDATE user
SET `+strings.Join(set, ", ")+`
WHERE id = ?
RETURNING id, name, password_hash, open_id, created_ts, updated_ts
RETURNING id, email, role, name, password_hash, open_id, created_ts, updated_ts
`, args...)
if err != nil {
return nil, FormatError(err)
@ -113,6 +122,8 @@ func patchUser(db *DB, patch *api.UserPatch) (*api.User, error) {
var user api.User
if err := row.Scan(
&user.ID,
&user.Email,
&user.Role,
&user.Name,
&user.PasswordHash,
&user.OpenID,
@ -134,6 +145,12 @@ func findUserList(db *DB, find *api.UserFind) ([]*api.User, error) {
if v := find.ID; v != nil {
where, args = append(where, "id = ?"), append(args, *v)
}
if v := find.Role; v != nil {
where, args = append(where, "role = ?"), append(args, *v)
}
if v := find.Email; v != nil {
where, args = append(where, "email = ?"), append(args, *v)
}
if v := find.Name; v != nil {
where, args = append(where, "name = ?"), append(args, *v)
}
@ -144,6 +161,8 @@ func findUserList(db *DB, find *api.UserFind) ([]*api.User, error) {
rows, err := db.Db.Query(`
SELECT
id,
email,
role,
name,
password_hash,
open_id,
@ -163,6 +182,8 @@ func findUserList(db *DB, find *api.UserFind) ([]*api.User, error) {
var user api.User
if err := rows.Scan(
&user.ID,
&user.Email,
&user.Role,
&user.Name,
&user.PasswordHash,
&user.OpenID,

@ -15,7 +15,6 @@ const validateConfig: ValidatorConfig = {
interface Props extends DialogProps {}
const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
const [oldPassword, setOldPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [newPasswordAgain, setNewPasswordAgain] = useState("");
@ -27,11 +26,6 @@ const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
destroy();
};
const handleOldPasswordChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
const text = e.target.value as string;
setOldPassword(text);
};
const handleNewPasswordChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
const text = e.target.value as string;
setNewPassword(text);
@ -43,7 +37,7 @@ const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
};
const handleSaveBtnClick = async () => {
if (oldPassword === "" || newPassword === "" || newPasswordAgain === "") {
if (newPassword === "" || newPasswordAgain === "") {
toastHelper.error("Please fill in all fields.");
return;
}
@ -61,14 +55,6 @@ const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
}
try {
const isValid = await userService.checkPasswordValid(oldPassword);
if (!isValid) {
toastHelper.error("Old password is invalid.");
setOldPassword("");
return;
}
await userService.updatePassword(newPassword);
toastHelper.info("Password changed.");
handleCloseBtnClick();
@ -86,16 +72,12 @@ const ChangePasswordDialog: React.FC<Props> = ({ destroy }: Props) => {
</button>
</div>
<div className="dialog-content-container">
<label className="form-label input-form-label">
<span className={"normal-text " + (oldPassword === "" ? "" : "not-null")}>Old password</span>
<input type="password" value={oldPassword} onChange={handleOldPasswordChanged} />
</label>
<label className="form-label input-form-label">
<span className={"normal-text " + (newPassword === "" ? "" : "not-null")}>New passworld</span>
<input type="password" value={newPassword} onChange={handleNewPasswordChanged} />
</label>
<label className="form-label input-form-label">
<span className={"normal-text " + (newPasswordAgain === "" ? "" : "not-null")}>New password again</span>
<span className={"normal-text " + (newPasswordAgain === "" ? "" : "not-null")}>Repeat the new password</span>
<input type="password" value={newPasswordAgain} onChange={handleNewPasswordAgainChanged} />
</label>
<div className="btns-container">

@ -40,13 +40,6 @@ const MyAccountSection: React.FC<Props> = () => {
}
try {
const isUsable = await userService.checkUsernameUsable(username);
if (!isUsable) {
toastHelper.error("Username is not available");
return;
}
await userService.updateUsername(username);
await userService.doSignIn();
toastHelper.info("Username changed");
@ -80,6 +73,10 @@ const MyAccountSection: React.FC<Props> = () => {
<span className="normal-text">Created at:</span>
<span className="normal-text">{utils.getDateString(user.createdAt)}</span>
</label>
<label className="form-label">
<span className="normal-text">Email:</span>
<span className="normal-text">{user.email}</span>
</label>
<label className="form-label input-form-label username-label">
<span className="normal-text">Username:</span>
<input type="text" value={username} onChange={handleUsernameChanged} />

@ -39,6 +39,13 @@ async function request<T>(config: RequestConfig): Promise<T> {
}
namespace api {
export function getSystemStatus() {
return request<API.SystemStatus>({
method: "GET",
url: "/api/status",
});
}
export function getUserInfo() {
return request<Model.User>({
method: "GET",
@ -46,22 +53,24 @@ namespace api {
});
}
export function login(name: string, password: string) {
export function login(email: string, password: string) {
return request<Model.User>({
method: "POST",
url: "/api/auth/login",
data: {
name,
email,
password,
},
});
}
export function signup(name: string, password: string) {
export function signup(email: string, role: UserRole, name: string, password: string) {
return request<Model.User>({
method: "POST",
url: "/api/auth/signup",
data: {
email,
role,
name,
password,
},

@ -18,11 +18,11 @@
> .page-content-container {
.flex(column, flex-start, flex-start);
@apply flex-nowrap;
@apply w-full;
> .form-item-container {
.flex(column, flex-start, flex-start);
@apply relative w-full text-base m-2;
@apply relative w-full text-base my-2;
> .normal-text {
@apply absolute top-3 left-3 px-1 leading-10 flex-shrink-0 text-base cursor-text text-gray-400 bg-transparent transition-all select-none;
@ -46,62 +46,41 @@
}
}
> .page-footer-container {
.flex(row, space-between, center);
@apply w-full mt-3;
> .btns-container {
.flex(row, flex-start, center);
> .action-btns-container {
.flex(row, flex-end, center);
@apply w-full mt-2;
> .btn {
@apply px-1 py-2 text-sm rounded;
> .btn {
@apply px-1 py-2 text-sm rounded;
&:hover {
@apply opacity-80;
}
&:hover {
@apply opacity-80;
}
&.disabled {
@apply text-gray-400 cursor-not-allowed;
}
&.disabled {
@apply text-gray-400 cursor-not-allowed;
}
&.signin-btn {
@apply bg-green-600 text-white px-3;
&.signin-btn {
@apply bg-green-600 text-white px-3;
&.requesting {
@apply cursor-wait opacity-80;
}
&.requesting {
@apply cursor-wait opacity-80;
}
}
}
> .btn-text {
@apply text-sm;
}
> .btn-text {
@apply text-sm;
}
> .split-text {
@apply text-gray-400 mx-2;
}
> .split-text {
@apply text-gray-400 mx-2;
}
}
> .quickly-btns-container {
.flex(column, flex-start, flex-start);
@apply w-full mt-6;
> .btn {
@apply mb-6 text-base leading-10 border border-solid border-gray-400 px-4 rounded-3xl;
&:hover {
@apply opacity-80;
}
&.guest-signin {
@apply text-green-600 border-2 border-green-600 font-bold;
}
&.requesting {
@apply cursor-wait opacity-80;
}
}
> .tip-text {
@apply w-full text-sm mt-4 text-gray-500 text-right;
}
}
}

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from "react";
import { useEffect, useState } from "react";
import api from "../helpers/api";
import { validate, ValidatorConfig } from "../helpers/validator";
import useLoading from "../hooks/useLoading";
@ -16,31 +16,22 @@ const validateConfig: ValidatorConfig = {
};
const Signin: React.FC<Props> = () => {
const [username, setUsername] = useState("");
const pageLoadingState = useLoading(true);
const [siteOwner, setSiteOwner] = useState<Model.User>();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [showAutoSigninAsGuest, setShowAutoSigninAsGuest] = useState(true);
const signinBtnsClickLoadingState = useLoading(false);
const autoSigninAsGuestBtn = useRef<HTMLDivElement>(null);
const signinBtn = useRef<HTMLButtonElement>(null);
const actionBtnLoadingState = useLoading(false);
useEffect(() => {
const handleKeyPress = (e: KeyboardEvent) => {
if (e.key === "Enter") {
autoSigninAsGuestBtn.current?.click();
signinBtn.current?.click();
}
};
document.body.addEventListener("keypress", handleKeyPress);
return () => {
document.body.removeEventListener("keypress", handleKeyPress);
};
api.getSystemStatus().then((status) => {
setSiteOwner(status.owner);
pageLoadingState.setFinish();
});
}, []);
const handleUsernameInputChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
const handleEmailInputChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
const text = e.target.value as string;
setUsername(text);
setEmail(text);
};
const handlePasswordInputChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
@ -48,14 +39,14 @@ const Signin: React.FC<Props> = () => {
setPassword(text);
};
const handleSigninBtnsClick = async (action: "signin" | "signup" = "signin") => {
if (signinBtnsClickLoadingState.isLoading) {
const handleSigninBtnsClick = async () => {
if (actionBtnLoadingState.isLoading) {
return;
}
const usernameValidResult = validate(username, validateConfig);
if (!usernameValidResult.result) {
toastHelper.error("Username: " + usernameValidResult.reason);
const emailValidResult = validate(email, validateConfig);
if (!emailValidResult.result) {
toastHelper.error("Email: " + emailValidResult.reason);
return;
}
@ -66,13 +57,8 @@ const Signin: React.FC<Props> = () => {
}
try {
signinBtnsClickLoadingState.setLoading();
let actionFunc = api.login;
if (action === "signup") {
actionFunc = api.signup;
}
await actionFunc(username, password);
actionBtnLoadingState.setLoading();
await api.login(email, password);
const user = await userService.doSignIn();
if (user) {
locationService.replaceHistory("/");
@ -83,25 +69,52 @@ const Signin: React.FC<Props> = () => {
console.error(error);
toastHelper.error("😟 " + error.message);
}
signinBtnsClickLoadingState.setFinish();
actionBtnLoadingState.setFinish();
};
const handleSwitchAccountSigninBtnClick = () => {
if (signinBtnsClickLoadingState.isLoading) {
const handleSignUpAsOwnerBtnsClick = async () => {
if (actionBtnLoadingState.isLoading) {
return;
}
const emailValidResult = validate(email, validateConfig);
if (!emailValidResult.result) {
toastHelper.error("Email: " + emailValidResult.reason);
return;
}
const passwordValidResult = validate(password, validateConfig);
if (!passwordValidResult.result) {
toastHelper.error("Password: " + passwordValidResult.reason);
return;
}
setShowAutoSigninAsGuest(false);
const name = email.split("@")[0];
try {
actionBtnLoadingState.setLoading();
await api.signup(email, "OWNER", name, password);
const user = await userService.doSignIn();
if (user) {
locationService.replaceHistory("/");
} else {
toastHelper.error("😟 Signup failed");
}
} catch (error: any) {
console.error(error);
toastHelper.error("😟 " + error.message);
}
actionBtnLoadingState.setFinish();
};
const handleAutoSigninAsGuestBtnClick = async () => {
if (signinBtnsClickLoadingState.isLoading) {
if (actionBtnLoadingState.isLoading) {
return;
}
try {
signinBtnsClickLoadingState.setLoading();
await api.login("guest", "secret");
actionBtnLoadingState.setLoading();
await api.login("guest@example.com", "secret");
const user = await userService.doSignIn();
if (user) {
@ -113,7 +126,7 @@ const Signin: React.FC<Props> = () => {
console.error(error);
toastHelper.error("😟 " + error.message);
}
signinBtnsClickLoadingState.setFinish();
actionBtnLoadingState.setFinish();
};
return (
@ -124,64 +137,42 @@ const Signin: React.FC<Props> = () => {
<span className="icon-text"></span> Memos
</p>
</div>
{showAutoSigninAsGuest ? (
<>
<div className="quickly-btns-container">
<div
ref={autoSigninAsGuestBtn}
className={`btn guest-signin ${signinBtnsClickLoadingState.isLoading ? "requesting" : ""}`}
onClick={handleAutoSigninAsGuestBtnClick}
>
👉 Quick login as a guest
</div>
<div
className={`btn ${signinBtnsClickLoadingState.isLoading ? "requesting" : ""}`}
onClick={handleSwitchAccountSigninBtnClick}
>
Sign in/up with account
</div>
</div>
</>
) : (
<>
<div className="page-content-container">
<div className="form-item-container input-form-container">
<span className={"normal-text " + (username === "" ? "" : "not-null")}>Username</span>
<input type="text" autoComplete="off" value={username} onChange={handleUsernameInputChanged} />
</div>
<div className="form-item-container input-form-container">
<span className={"normal-text " + (password === "" ? "" : "not-null")}>Password</span>
<input type="password" autoComplete="off" value={password} onChange={handlePasswordInputChanged} />
</div>
</div>
<div className="page-footer-container">
<div className="btns-container">{/* nth */}</div>
<div className="btns-container">
<button
className={`btn ${signinBtnsClickLoadingState.isLoading ? "requesting" : ""}`}
onClick={handleAutoSigninAsGuestBtnClick}
>
Login as Guest
</button>
<span className="split-text">/</span>
<button
className={`btn signup-btn ${signinBtnsClickLoadingState.isLoading ? "requesting" : ""}`}
onClick={() => handleSigninBtnsClick("signup")}
>
Sign up
</button>
<span className="split-text">/</span>
<button
ref={signinBtn}
className={`btn signin-btn ${signinBtnsClickLoadingState.isLoading ? "requesting" : ""}`}
onClick={() => handleSigninBtnsClick("signin")}
>
Sign in
</button>
</div>
</div>
</>
)}
<div className="page-content-container">
<div className="form-item-container input-form-container">
<span className={"normal-text " + (email === "" ? "" : "not-null")}>Email</span>
<input type="email" value={email} onChange={handleEmailInputChanged} />
</div>
<div className="form-item-container input-form-container">
<span className={"normal-text " + (password === "" ? "" : "not-null")}>Password</span>
<input type="password" value={password} onChange={handlePasswordInputChanged} />
</div>
</div>
<div className="action-btns-container">
<button className={`btn ${actionBtnLoadingState.isLoading ? "requesting" : ""}`} onClick={handleAutoSigninAsGuestBtnClick}>
Login as Guest
</button>
<span className="split-text">/</span>
{siteOwner || pageLoadingState.isLoading ? (
<button
className={`btn signin-btn ${actionBtnLoadingState.isLoading ? "requesting" : ""}`}
onClick={() => handleSigninBtnsClick()}
>
Sign in
</button>
) : (
<button
className={`btn signin-btn ${actionBtnLoadingState.isLoading ? "requesting" : ""}`}
onClick={() => handleSignUpAsOwnerBtnsClick()}
>
Sign up as Owner
</button>
)}
</div>
<p className="tip-text">
{siteOwner || pageLoadingState.isLoading
? "If you don't have an account, please contact the site owner or login as guest."
: "You are registering as the site owner."}
</p>
</div>
</div>
);

@ -32,22 +32,12 @@ class UserService {
});
}
public async checkUsernameUsable(username: string): Promise<boolean> {
const isUsable = await api.checkUsernameUsable(username);
return isUsable;
}
public async updateUsername(name: string): Promise<void> {
await api.updateUserinfo({
name,
});
}
public async checkPasswordValid(password: string): Promise<boolean> {
const isValid = await api.checkPasswordValid(password);
return isValid;
}
public async updatePassword(password: string): Promise<void> {
await api.updateUserinfo({
password,

@ -1 +1,5 @@
declare namespace Api {}
declare namespace API {
interface SystemStatus {
owner: Model.User;
}
}

@ -1,3 +1,5 @@
type UserRole = "OWNER" | "USER";
declare namespace Model {
interface BaseModel {
id: string;
@ -9,6 +11,8 @@ declare namespace Model {
}
interface User extends BaseModel {
role: UserRole;
email: string;
name: string;
openId: string;
}

Loading…
Cancel
Save