mirror of
https://github.com/usememos/memos.git
synced 2025-02-14 18:30:42 +01:00
refactor: visitor view (#107)
* refactor: update api * refactor: visitor view * chore: update seed data
This commit is contained in:
parent
346d219cd5
commit
6f32643d7c
@ -1,6 +1,6 @@
|
||||
package api
|
||||
|
||||
type Login struct {
|
||||
type Signin struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
@ -13,33 +13,33 @@ import (
|
||||
)
|
||||
|
||||
func (s *Server) registerAuthRoutes(g *echo.Group) {
|
||||
g.POST("/auth/login", func(c echo.Context) error {
|
||||
login := &api.Login{}
|
||||
if err := json.NewDecoder(c.Request().Body).Decode(login); err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted login request").SetInternal(err)
|
||||
g.POST("/auth/signin", func(c echo.Context) error {
|
||||
signin := &api.Signin{}
|
||||
if err := json.NewDecoder(c.Request().Body).Decode(signin); err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted signin request").SetInternal(err)
|
||||
}
|
||||
|
||||
userFind := &api.UserFind{
|
||||
Email: &login.Email,
|
||||
Email: &signin.Email,
|
||||
}
|
||||
user, err := s.Store.FindUser(userFind)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find user by email %s", login.Email)).SetInternal(err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find user by email %s", signin.Email)).SetInternal(err)
|
||||
}
|
||||
if user == nil {
|
||||
return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("User not found with email %s", login.Email))
|
||||
return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("User not found with email %s", signin.Email))
|
||||
} else if user.RowStatus == api.Archived {
|
||||
return echo.NewHTTPError(http.StatusForbidden, fmt.Sprintf("User has been archived with email %s", login.Email))
|
||||
return echo.NewHTTPError(http.StatusForbidden, fmt.Sprintf("User has been archived with email %s", signin.Email))
|
||||
}
|
||||
|
||||
// Compare the stored hashed password, with the hashed version of the password that was received.
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(login.Password)); err != nil {
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(signin.Password)); err != nil {
|
||||
// If the two passwords don't match, return a 401 status.
|
||||
return echo.NewHTTPError(http.StatusUnauthorized, "Incorrect password").SetInternal(err)
|
||||
}
|
||||
|
||||
if err = setUserSession(c, user); err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to set login session").SetInternal(err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to set signin session").SetInternal(err)
|
||||
}
|
||||
|
||||
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
|
||||
|
@ -60,31 +60,17 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
|
||||
})
|
||||
|
||||
g.GET("/memo", func(c echo.Context) error {
|
||||
userID, ok := c.Get(getUserIDContextKey()).(int)
|
||||
if !ok {
|
||||
if c.QueryParam("userID") != "" {
|
||||
var err error
|
||||
userID, err = strconv.Atoi(c.QueryParam("userID"))
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.QueryParam("userID")))
|
||||
}
|
||||
} else {
|
||||
ownerUserType := api.Owner
|
||||
ownerUser, err := s.Store.FindUser(&api.UserFind{
|
||||
Role: &ownerUserType,
|
||||
})
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find owner user").SetInternal(err)
|
||||
}
|
||||
if ownerUser == nil {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "Owner user do not exist")
|
||||
}
|
||||
userID = ownerUser.ID
|
||||
}
|
||||
}
|
||||
memoFind := &api.MemoFind{}
|
||||
|
||||
memoFind := &api.MemoFind{
|
||||
CreatorID: &userID,
|
||||
if userID, err := strconv.Atoi(c.QueryParam("creatorId")); err == nil {
|
||||
memoFind.CreatorID = &userID
|
||||
} else {
|
||||
userID, ok := c.Get(getUserIDContextKey()).(int)
|
||||
if !ok {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Missing creatorId to find memo")
|
||||
}
|
||||
|
||||
memoFind.CreatorID = &userID
|
||||
}
|
||||
|
||||
rowStatus := api.RowStatus(c.QueryParam("rowStatus"))
|
||||
|
@ -45,7 +45,7 @@ func NewServer(profile *profile.Profile) *Server {
|
||||
HTML5: true,
|
||||
}))
|
||||
|
||||
// In dev mode, set the const secret key to make login session persistence.
|
||||
// In dev mode, set the const secret key to make signin session persistence.
|
||||
secret := []byte("usememos")
|
||||
if profile.Mode == "prod" {
|
||||
secret = securecookie.GenerateRandomKey(16)
|
||||
|
@ -59,32 +59,19 @@ func (s *Server) registerShortcutRoutes(g *echo.Group) {
|
||||
})
|
||||
|
||||
g.GET("/shortcut", func(c echo.Context) error {
|
||||
userID, ok := c.Get(getUserIDContextKey()).(int)
|
||||
if !ok {
|
||||
if c.QueryParam("userID") != "" {
|
||||
var err error
|
||||
userID, err = strconv.Atoi(c.QueryParam("userID"))
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.QueryParam("userID")))
|
||||
}
|
||||
} else {
|
||||
ownerUserType := api.Owner
|
||||
ownerUser, err := s.Store.FindUser(&api.UserFind{
|
||||
Role: &ownerUserType,
|
||||
})
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find owner user").SetInternal(err)
|
||||
}
|
||||
if ownerUser == nil {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "Owner user do not exist")
|
||||
}
|
||||
userID = ownerUser.ID
|
||||
shortcutFind := &api.ShortcutFind{}
|
||||
|
||||
if userID, err := strconv.Atoi(c.QueryParam("creatorId")); err == nil {
|
||||
shortcutFind.CreatorID = &userID
|
||||
} else {
|
||||
userID, ok := c.Get(getUserIDContextKey()).(int)
|
||||
if !ok {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Missing creatorId to find shortcut")
|
||||
}
|
||||
|
||||
shortcutFind.CreatorID = &userID
|
||||
}
|
||||
|
||||
shortcutFind := &api.ShortcutFind{
|
||||
CreatorID: &userID,
|
||||
}
|
||||
list, err := s.Store.FindShortcutList(shortcutFind)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch shortcut list").SetInternal(err)
|
||||
|
@ -2,7 +2,6 @@ package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sort"
|
||||
@ -15,37 +14,24 @@ import (
|
||||
|
||||
func (s *Server) registerTagRoutes(g *echo.Group) {
|
||||
g.GET("/tag", func(c echo.Context) error {
|
||||
userID, ok := c.Get(getUserIDContextKey()).(int)
|
||||
if !ok {
|
||||
if c.QueryParam("userID") != "" {
|
||||
var err error
|
||||
userID, err = strconv.Atoi(c.QueryParam("userID"))
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.QueryParam("userID")))
|
||||
}
|
||||
} else {
|
||||
ownerUserType := api.Owner
|
||||
ownerUser, err := s.Store.FindUser(&api.UserFind{
|
||||
Role: &ownerUserType,
|
||||
})
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find owner user").SetInternal(err)
|
||||
}
|
||||
if ownerUser == nil {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "Owner user do not exist")
|
||||
}
|
||||
userID = ownerUser.ID
|
||||
}
|
||||
}
|
||||
|
||||
contentSearch := "#"
|
||||
normalRowStatus := api.Normal
|
||||
memoFind := api.MemoFind{
|
||||
CreatorID: &userID,
|
||||
ContentSearch: &contentSearch,
|
||||
RowStatus: &normalRowStatus,
|
||||
}
|
||||
|
||||
if userID, err := strconv.Atoi(c.QueryParam("creatorId")); err == nil {
|
||||
memoFind.CreatorID = &userID
|
||||
} else {
|
||||
userID, ok := c.Get(getUserIDContextKey()).(int)
|
||||
if !ok {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Missing creatorId to find shortcut")
|
||||
}
|
||||
|
||||
memoFind.CreatorID = &userID
|
||||
}
|
||||
|
||||
memoList, err := s.Store.FindMemoList(&memoFind)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo list").SetInternal(err)
|
||||
|
@ -52,6 +52,24 @@ INSERT INTO
|
||||
VALUES
|
||||
(
|
||||
104,
|
||||
'好好学习,天天向上。🤜🤛',
|
||||
'#TODO
|
||||
- [x] Take more photos about **🌄 sunset**;
|
||||
- [ ] Clean the classroom;
|
||||
- [ ] Watch *👦 The Boys*;
|
||||
(👆 click to toggle status)
|
||||
',
|
||||
102
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
memo (
|
||||
`id`,
|
||||
`content`,
|
||||
`creator_id`
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
105,
|
||||
'三人行,必有我师焉!👨🏫',
|
||||
102
|
||||
);
|
||||
|
@ -11,12 +11,14 @@
|
||||
"@reduxjs/toolkit": "^1.8.1",
|
||||
"axios": "^0.27.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"qs": "^6.11.0",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-redux": "^8.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash-es": "^4.17.5",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@types/react": "^18.0.9",
|
||||
"@types/react-dom": "^18.0.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.6.0",
|
||||
|
@ -112,7 +112,11 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
} else {
|
||||
locationService.setTagQuery(tagName);
|
||||
}
|
||||
} else if (targetEl.classList.contains("todo-block") && userService.isNotVisitor()) {
|
||||
} else if (targetEl.classList.contains("todo-block")) {
|
||||
if (userService.isVisitorMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const status = targetEl.dataset?.value;
|
||||
const todoElementList = [...(memoContainerRef.current?.querySelectorAll(`span.todo-block[data-value=${status}]`) ?? [])];
|
||||
for (const element of todoElementList) {
|
||||
@ -158,40 +162,38 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
<span className="ml-2">PINNED</span>
|
||||
</Only>
|
||||
</span>
|
||||
{userService.isNotVisitor() && (
|
||||
<div className="btns-container">
|
||||
<span className="btn more-action-btn">
|
||||
<img className="icon-img" src="/icons/more.svg" />
|
||||
</span>
|
||||
<div className="more-action-btns-wrapper">
|
||||
<div className="more-action-btns-container">
|
||||
<div className="btns-container">
|
||||
<div className="btn" onClick={handleTogglePinMemoBtnClick}>
|
||||
<img className="icon-img" src="/icons/pin.svg" alt="" />
|
||||
<span className="tip-text">{memo.pinned ? "Unpin" : "Pin"}</span>
|
||||
</div>
|
||||
<div className="btn" onClick={handleEditMemoClick}>
|
||||
<img className="icon-img" src="/icons/edit.svg" alt="" />
|
||||
<span className="tip-text">Edit</span>
|
||||
</div>
|
||||
<div className="btn" onClick={handleGenMemoImageBtnClick}>
|
||||
<img className="icon-img" src="/icons/share.svg" alt="" />
|
||||
<span className="tip-text">Share</span>
|
||||
</div>
|
||||
<div className={`btns-container ${userService.isVisitorMode() ? "!hidden" : ""}`}>
|
||||
<span className="btn more-action-btn">
|
||||
<img className="icon-img" src="/icons/more.svg" />
|
||||
</span>
|
||||
<div className="more-action-btns-wrapper">
|
||||
<div className="more-action-btns-container">
|
||||
<div className="btns-container">
|
||||
<div className="btn" onClick={handleTogglePinMemoBtnClick}>
|
||||
<img className="icon-img" src="/icons/pin.svg" alt="" />
|
||||
<span className="tip-text">{memo.pinned ? "Unpin" : "Pin"}</span>
|
||||
</div>
|
||||
<div className="btn" onClick={handleEditMemoClick}>
|
||||
<img className="icon-img" src="/icons/edit.svg" alt="" />
|
||||
<span className="tip-text">Edit</span>
|
||||
</div>
|
||||
<div className="btn" onClick={handleGenMemoImageBtnClick}>
|
||||
<img className="icon-img" src="/icons/share.svg" alt="" />
|
||||
<span className="tip-text">Share</span>
|
||||
</div>
|
||||
<span className="btn" onClick={handleMarkMemoClick}>
|
||||
Mark
|
||||
</span>
|
||||
<span className="btn" onClick={handleShowMemoStoryDialog}>
|
||||
View Story
|
||||
</span>
|
||||
<span className="btn archive-btn" onClick={handleArchiveMemoClick}>
|
||||
Archive
|
||||
</span>
|
||||
</div>
|
||||
<span className="btn" onClick={handleMarkMemoClick}>
|
||||
Mark
|
||||
</span>
|
||||
<span className="btn" onClick={handleShowMemoStoryDialog}>
|
||||
View Story
|
||||
</span>
|
||||
<span className="btn archive-btn" onClick={handleArchiveMemoClick}>
|
||||
Archive
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
ref={memoContainerRef}
|
||||
|
@ -67,8 +67,8 @@ const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
|
||||
<button className="btn action-btn" onClick={handlePingBtnClick}>
|
||||
<span className="icon">🎯</span> Ping
|
||||
</button>
|
||||
<button className="btn action-btn" onClick={userService.isNotVisitor() ? handleSignOutBtnClick : handleSignInBtnClick}>
|
||||
<span className="icon">👋</span> {userService.isNotVisitor() ? "Sign out" : "Sign in"}
|
||||
<button className="btn action-btn" onClick={!userService.isVisitorMode() ? handleSignOutBtnClick : handleSignInBtnClick}>
|
||||
<span className="icon">👋</span> {!userService.isVisitorMode() ? "Sign out" : "Sign in"}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
@ -4,6 +4,7 @@ import { useAppSelector } from "../store";
|
||||
import * as utils from "../helpers/utils";
|
||||
import useToggle from "../hooks/useToggle";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
import Only from "./common/OnlyWhen";
|
||||
import toastHelper from "./Toast";
|
||||
import showCreateShortcutDialog from "./CreateShortcutDialog";
|
||||
import "../less/shortcut-list.less";
|
||||
@ -38,11 +39,11 @@ const ShortcutList: React.FC<Props> = () => {
|
||||
<div className="shortcuts-wrapper">
|
||||
<p className="title-text">
|
||||
<span className="normal-text">Shortcuts</span>
|
||||
{userService.isNotVisitor() && (
|
||||
<Only when={!userService.isVisitorMode()}>
|
||||
<span className="btn" onClick={() => showCreateShortcutDialog()}>
|
||||
<img src="/icons/add.svg" alt="add shortcut" />
|
||||
</span>
|
||||
)}
|
||||
</Only>
|
||||
</p>
|
||||
<div className="shortcuts-container">
|
||||
{sortedShortcuts.map((s) => {
|
||||
@ -66,9 +67,6 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont
|
||||
if (isActive) {
|
||||
locationService.setMemoShortcut(undefined);
|
||||
} else {
|
||||
if (!["/"].includes(locationService.getState().pathname)) {
|
||||
locationService.setPathname("/");
|
||||
}
|
||||
locationService.setMemoShortcut(shortcut.id);
|
||||
}
|
||||
};
|
||||
@ -116,30 +114,28 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont
|
||||
<div className="shortcut-text-container">
|
||||
<span className="shortcut-text">{shortcut.title}</span>
|
||||
</div>
|
||||
{userService.isNotVisitor() && (
|
||||
<div className="btns-container">
|
||||
<span className="action-btn toggle-btn">
|
||||
<img className="icon-img" src="/icons/more.svg" />
|
||||
</span>
|
||||
<div className="action-btns-wrapper">
|
||||
<div className="action-btns-container">
|
||||
<span className="btn" onClick={handlePinShortcutBtnClick}>
|
||||
{shortcut.rowStatus === "ARCHIVED" ? "Unpin" : "Pin"}
|
||||
</span>
|
||||
<span className="btn" onClick={handleEditShortcutBtnClick}>
|
||||
Edit
|
||||
</span>
|
||||
<span
|
||||
className={`btn delete-btn ${showConfirmDeleteBtn ? "final-confirm" : ""}`}
|
||||
onClick={handleDeleteMemoClick}
|
||||
onMouseLeave={handleDeleteBtnMouseLeave}
|
||||
>
|
||||
{showConfirmDeleteBtn ? "Delete!" : "Delete"}
|
||||
</span>
|
||||
</div>
|
||||
<div className={`btns-container ${userService.isVisitorMode() ? "!hidden" : ""}`}>
|
||||
<span className="action-btn toggle-btn">
|
||||
<img className="icon-img" src="/icons/more.svg" />
|
||||
</span>
|
||||
<div className="action-btns-wrapper">
|
||||
<div className="action-btns-container">
|
||||
<span className="btn" onClick={handlePinShortcutBtnClick}>
|
||||
{shortcut.rowStatus === "ARCHIVED" ? "Unpin" : "Pin"}
|
||||
</span>
|
||||
<span className="btn" onClick={handleEditShortcutBtnClick}>
|
||||
Edit
|
||||
</span>
|
||||
<span
|
||||
className={`btn delete-btn ${showConfirmDeleteBtn ? "final-confirm" : ""}`}
|
||||
onClick={handleDeleteMemoClick}
|
||||
onMouseLeave={handleDeleteBtnMouseLeave}
|
||||
>
|
||||
{showConfirmDeleteBtn ? "Delete!" : "Delete"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { useAppSelector } from "../store";
|
||||
import * as utils from "../helpers/utils";
|
||||
import { userService } from "../services";
|
||||
import Only from "./common/OnlyWhen";
|
||||
import showDailyReviewDialog from "./DailyReviewDialog";
|
||||
import showSettingDialog from "./SettingDialog";
|
||||
import showArchivedMemoDialog from "./ArchivedMemoDialog";
|
||||
@ -49,21 +50,19 @@ const Sidebar: React.FC<Props> = () => {
|
||||
</div>
|
||||
</div>
|
||||
<UsageHeatMap />
|
||||
{userService.isNotVisitor() && (
|
||||
<>
|
||||
<div className="action-btns-container">
|
||||
<button className="btn action-btn" onClick={() => showDailyReviewDialog()}>
|
||||
<span className="icon">📅</span> Daily Review
|
||||
</button>
|
||||
<button className="btn action-btn" onClick={handleMyAccountBtnClick}>
|
||||
<span className="icon">⚙️</span> Setting
|
||||
</button>
|
||||
<button className="btn action-btn" onClick={handleArchivedBtnClick}>
|
||||
<span className="icon">🗂</span> Archived
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<Only when={!userService.isVisitorMode()}>
|
||||
<div className="action-btns-container">
|
||||
<button className="btn action-btn" onClick={() => showDailyReviewDialog()}>
|
||||
<span className="icon">📅</span> Daily Review
|
||||
</button>
|
||||
<button className="btn action-btn" onClick={handleMyAccountBtnClick}>
|
||||
<span className="icon">⚙️</span> Setting
|
||||
</button>
|
||||
<button className="btn action-btn" onClick={handleArchivedBtnClick}>
|
||||
<span className="icon">🗂</span> Archived
|
||||
</button>
|
||||
</div>
|
||||
</Only>
|
||||
<ShortcutList />
|
||||
<TagList />
|
||||
</aside>
|
||||
|
@ -71,7 +71,7 @@ const TagList: React.FC<Props> = () => {
|
||||
{tags.map((t, idx) => (
|
||||
<TagItemContainer key={t.text + "-" + idx} tag={t} tagQuery={query?.tag} />
|
||||
))}
|
||||
<Only when={userService.isNotVisitor() && tags.length < 5}>
|
||||
<Only when={!userService.isVisitorMode() && tags.length < 5}>
|
||||
<p className="tag-tip-container">
|
||||
Enter <span className="code-text">#tag </span> to create a tag
|
||||
</p>
|
||||
@ -97,9 +97,6 @@ const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContain
|
||||
locationService.setTagQuery(undefined);
|
||||
} else {
|
||||
utils.copyTextToClipboard(`#${tag.text} `);
|
||||
if (!["/"].includes(locationService.getState().pathname)) {
|
||||
locationService.setPathname("/");
|
||||
}
|
||||
locationService.setTagQuery(tag.text);
|
||||
}
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import * as api from "../helpers/api";
|
||||
import { getUserIdFromPath } from "../services/userService";
|
||||
import userService from "../services/userService";
|
||||
import { locationService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import toastHelper from "./Toast";
|
||||
@ -31,15 +31,18 @@ const UserBanner: React.FC<Props> = () => {
|
||||
setUsername(status.owner.name);
|
||||
});
|
||||
} else {
|
||||
api
|
||||
.getUserNameById(Number(getUserIdFromPath()))
|
||||
.then(({ data }) => {
|
||||
const { data: username } = data;
|
||||
setUsername(username);
|
||||
})
|
||||
.catch(() => {
|
||||
toastHelper.error("User not found");
|
||||
});
|
||||
const currentUserId = userService.getCurrentUserId();
|
||||
if (currentUserId) {
|
||||
api
|
||||
.getUserNameById(currentUserId)
|
||||
.then(({ data }) => {
|
||||
const { data: username } = data;
|
||||
setUsername(username);
|
||||
})
|
||||
.catch(() => {
|
||||
toastHelper.error("User not found");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
@ -12,8 +12,8 @@ export function getSystemStatus() {
|
||||
return axios.get<ResponseObject<SystemStatus>>("/api/status");
|
||||
}
|
||||
|
||||
export function login(email: string, password: string) {
|
||||
return axios.post<ResponseObject<User>>("/api/auth/login", {
|
||||
export function signin(email: string, password: string) {
|
||||
return axios.post<ResponseObject<User>>("/api/auth/signin", {
|
||||
email,
|
||||
password,
|
||||
});
|
||||
@ -52,12 +52,15 @@ export function patchUser(userPatch: UserPatch) {
|
||||
return axios.patch<ResponseObject<User>>("/api/user/me", userPatch);
|
||||
}
|
||||
|
||||
export function getMemoList(userId?: number) {
|
||||
return axios.get<ResponseObject<Memo[]>>(`/api/memo${userId ? "?userID=" + userId : ""}`);
|
||||
}
|
||||
|
||||
export function getArchivedMemoList(userId?: number) {
|
||||
return axios.get<ResponseObject<Memo[]>>(`/api/memo?rowStatus=ARCHIVED${userId ? "&userID=" + userId : ""}`);
|
||||
export function getMemoList(memoFind?: MemoFind) {
|
||||
const queryList = [];
|
||||
if (memoFind?.creatorId) {
|
||||
queryList.push(`creatorId=${memoFind.creatorId}`);
|
||||
}
|
||||
if (memoFind?.rowStatus) {
|
||||
queryList.push(`rowStatus=${memoFind.rowStatus}`);
|
||||
}
|
||||
return axios.get<ResponseObject<Memo[]>>(`/api/memo?${queryList.join("&")}`);
|
||||
}
|
||||
|
||||
export function createMemo(memoCreate: MemoCreate) {
|
||||
@ -84,8 +87,12 @@ export function deleteMemo(memoId: MemoId) {
|
||||
return axios.delete(`/api/memo/${memoId}`);
|
||||
}
|
||||
|
||||
export function getShortcutList(userId?: number) {
|
||||
return axios.get<ResponseObject<Shortcut[]>>(`/api/shortcut${userId ? "?userID=" + userId : ""}`);
|
||||
export function getShortcutList(shortcutFind: ShortcutFind) {
|
||||
const queryList = [];
|
||||
if (shortcutFind?.creatorId) {
|
||||
queryList.push(`creatorId=${shortcutFind.creatorId}`);
|
||||
}
|
||||
return axios.get<ResponseObject<Shortcut[]>>(`/api/shortcut?${queryList.join("&")}`);
|
||||
}
|
||||
|
||||
export function createShortcut(shortcutCreate: ShortcutCreate) {
|
||||
@ -104,6 +111,10 @@ export function uploadFile(formData: FormData) {
|
||||
return axios.post<ResponseObject<Resource>>("/api/resource", formData);
|
||||
}
|
||||
|
||||
export function getTagList(userId?: number) {
|
||||
return axios.get<ResponseObject<string[]>>(`/api/tag${userId ? "?userID=" + userId : ""}`);
|
||||
export function getTagList(tagFind?: TagFind) {
|
||||
const queryList = [];
|
||||
if (tagFind?.creatorId) {
|
||||
queryList.push(`creatorId=${tagFind.creatorId}`);
|
||||
}
|
||||
return axios.get<ResponseObject<string[]>>(`/api/tag?${queryList.join("&")}`);
|
||||
}
|
||||
|
@ -126,38 +126,6 @@ export function throttle(fn: FunctionType, delay: number) {
|
||||
};
|
||||
}
|
||||
|
||||
export function transformObjectToParamsString(object: KVObject): string {
|
||||
const params = [];
|
||||
const keys = Object.keys(object).sort();
|
||||
|
||||
for (const key of keys) {
|
||||
const val = object[key];
|
||||
if (val) {
|
||||
if (typeof val === "object") {
|
||||
params.push(...transformObjectToParamsString(val).split("&"));
|
||||
} else {
|
||||
params.push(`${key}=${val}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return params.join("&");
|
||||
}
|
||||
|
||||
export function transformParamsStringToObject(paramsString: string): KVObject {
|
||||
const object: KVObject = {};
|
||||
const params = paramsString.split("&");
|
||||
|
||||
for (const p of params) {
|
||||
const [key, val] = p.split("=");
|
||||
if (key && val) {
|
||||
object[key] = val;
|
||||
}
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
export function filterObjectNullKeys(object: KVObject): KVObject {
|
||||
if (!object) {
|
||||
return {};
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { useEffect } from "react";
|
||||
import { locationService, userService } from "../services";
|
||||
import Sidebar from "../components/Sidebar";
|
||||
import { userService } from "../services";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
import Only from "../components/common/OnlyWhen";
|
||||
import Sidebar from "../components/Sidebar";
|
||||
import MemosHeader from "../components/MemosHeader";
|
||||
import MemoEditor from "../components/MemoEditor";
|
||||
import MemoFilter from "../components/MemoFilter";
|
||||
@ -12,25 +13,14 @@ function Home() {
|
||||
const loadingState = useLoading();
|
||||
|
||||
useEffect(() => {
|
||||
if (window.location.pathname !== locationService.getState().pathname) {
|
||||
locationService.replaceHistory("/");
|
||||
}
|
||||
const { user } = userService.getState();
|
||||
if (!user) {
|
||||
userService
|
||||
.doSignIn()
|
||||
.catch(() => {
|
||||
// do nth
|
||||
})
|
||||
.finally(() => {
|
||||
if (userService.getState().user && locationService.getState().pathname !== "/") {
|
||||
locationService.replaceHistory("/");
|
||||
}
|
||||
loadingState.setFinish();
|
||||
});
|
||||
} else {
|
||||
loadingState.setFinish();
|
||||
}
|
||||
userService
|
||||
.doSignIn()
|
||||
.catch(() => {
|
||||
// do nth
|
||||
})
|
||||
.finally(() => {
|
||||
loadingState.setFinish();
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@ -41,7 +31,9 @@ function Home() {
|
||||
<main className="memos-wrapper">
|
||||
<div className="memos-editor-wrapper">
|
||||
<MemosHeader />
|
||||
{userService.isNotVisitor() && <MemoEditor />}
|
||||
<Only when={!userService.isVisitorMode()}>
|
||||
<MemoEditor />
|
||||
</Only>
|
||||
<MemoFilter />
|
||||
</div>
|
||||
<MemoList />
|
||||
|
@ -63,7 +63,7 @@ const Signin: React.FC<Props> = () => {
|
||||
|
||||
try {
|
||||
actionBtnLoadingState.setLoading();
|
||||
await api.login(email, password);
|
||||
await api.signin(email, password);
|
||||
const user = await userService.doSignIn();
|
||||
if (user) {
|
||||
locationService.replaceHistory("/");
|
||||
|
@ -1,10 +1,10 @@
|
||||
import * as utils from "../helpers/utils";
|
||||
import { stringify } from "qs";
|
||||
import store from "../store";
|
||||
import { setQuery, setPathname, Query } from "../store/modules/location";
|
||||
|
||||
const updateLocationUrl = (method: "replace" | "push" = "replace") => {
|
||||
const { query, pathname, hash } = store.getState().location;
|
||||
let queryString = utils.transformObjectToParamsString(query ?? {});
|
||||
let queryString = stringify(query);
|
||||
if (queryString) {
|
||||
queryString = "?" + queryString;
|
||||
} else {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as api from "../helpers/api";
|
||||
import { createMemo, patchMemo, setMemos, setTags } from "../store/modules/memo";
|
||||
import store from "../store";
|
||||
import { getUserIdFromPath } from "./userService";
|
||||
import userService from "./userService";
|
||||
|
||||
const convertResponseModelMemo = (memo: Memo): Memo => {
|
||||
return {
|
||||
@ -17,7 +17,10 @@ const memoService = {
|
||||
},
|
||||
|
||||
fetchAllMemos: async () => {
|
||||
const { data } = (await api.getMemoList(getUserIdFromPath())).data;
|
||||
const memoFind: MemoFind = {
|
||||
creatorId: userService.getCurrentUserId(),
|
||||
};
|
||||
const { data } = (await api.getMemoList(memoFind)).data;
|
||||
const memos = data.filter((m) => m.rowStatus !== "ARCHIVED").map((m) => convertResponseModelMemo(m));
|
||||
store.dispatch(setMemos(memos));
|
||||
|
||||
@ -25,7 +28,11 @@ const memoService = {
|
||||
},
|
||||
|
||||
fetchArchivedMemos: async () => {
|
||||
const { data } = (await api.getArchivedMemoList(getUserIdFromPath())).data;
|
||||
const memoFind: MemoFind = {
|
||||
creatorId: userService.getCurrentUserId(),
|
||||
rowStatus: "ARCHIVED",
|
||||
};
|
||||
const { data } = (await api.getMemoList(memoFind)).data;
|
||||
const archivedMemos = data.map((m) => {
|
||||
return convertResponseModelMemo(m);
|
||||
});
|
||||
@ -43,7 +50,10 @@ const memoService = {
|
||||
},
|
||||
|
||||
updateTagsState: async () => {
|
||||
const { data } = (await api.getTagList(getUserIdFromPath())).data;
|
||||
const tagFind: TagFind = {
|
||||
creatorId: userService.getCurrentUserId(),
|
||||
};
|
||||
const { data } = (await api.getTagList(tagFind)).data;
|
||||
store.dispatch(setTags(data));
|
||||
},
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as api from "../helpers/api";
|
||||
import store from "../store/";
|
||||
import { createShortcut, deleteShortcut, patchShortcut, setShortcuts } from "../store/modules/shortcut";
|
||||
import { getUserIdFromPath } from "./userService";
|
||||
import userService from "./userService";
|
||||
|
||||
const convertResponseModelShortcut = (shortcut: Shortcut): Shortcut => {
|
||||
return {
|
||||
@ -17,7 +17,10 @@ const shortcutService = {
|
||||
},
|
||||
|
||||
getMyAllShortcuts: async () => {
|
||||
const { data } = (await api.getShortcutList(getUserIdFromPath())).data;
|
||||
const shortcutFind: ShortcutFind = {
|
||||
creatorId: userService.getCurrentUserId(),
|
||||
};
|
||||
const { data } = (await api.getShortcutList(shortcutFind)).data;
|
||||
const shortcuts = data.map((s) => convertResponseModelShortcut(s));
|
||||
store.dispatch(setShortcuts(shortcuts));
|
||||
},
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { isUndefined } from "lodash-es";
|
||||
import { locationService } from ".";
|
||||
import * as api from "../helpers/api";
|
||||
import store from "../store";
|
||||
@ -11,18 +12,26 @@ const convertResponseModelUser = (user: User): User => {
|
||||
};
|
||||
};
|
||||
|
||||
export const getUserIdFromPath = () => {
|
||||
const path = locationService.getState().pathname.slice(3);
|
||||
return !isNaN(Number(path)) ? Number(path) : undefined;
|
||||
};
|
||||
|
||||
const userService = {
|
||||
getState: () => {
|
||||
return store.getState().user;
|
||||
},
|
||||
|
||||
isNotVisitor: () => {
|
||||
return store.getState().user.user !== undefined;
|
||||
isVisitorMode: () => {
|
||||
return !isUndefined(userService.getUserIdFromPath());
|
||||
},
|
||||
|
||||
getCurrentUserId: () => {
|
||||
return userService.getUserIdFromPath() ?? store.getState().user.user?.id;
|
||||
},
|
||||
|
||||
getUserIdFromPath: () => {
|
||||
const userIdRegex = /^\/u\/(\d+).*/;
|
||||
const result = locationService.getState().pathname.match(userIdRegex);
|
||||
if (result && result.length === 2) {
|
||||
return Number(result[1]);
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
doSignIn: async () => {
|
||||
|
@ -20,7 +20,8 @@ interface State {
|
||||
}
|
||||
|
||||
const getValidPathname = (pathname: string): string => {
|
||||
if (["/", "/signin"].includes(pathname) || pathname.match(/^\/u\/(\d+)/)) {
|
||||
const userPageUrlRegex = /^\/u\/\d+.*/;
|
||||
if (["/", "/signin"].includes(pathname) || userPageUrlRegex.test(pathname)) {
|
||||
return pathname;
|
||||
} else {
|
||||
return "/";
|
||||
|
5
web/src/types/modules/memo.d.ts
vendored
5
web/src/types/modules/memo.d.ts
vendored
@ -22,3 +22,8 @@ interface MemoPatch {
|
||||
content?: string;
|
||||
rowStatus?: RowStatus;
|
||||
}
|
||||
|
||||
interface MemoFind {
|
||||
creatorId?: UserId;
|
||||
rowStatus?: RowStatus;
|
||||
}
|
||||
|
5
web/src/types/modules/shortcut.d.ts
vendored
5
web/src/types/modules/shortcut.d.ts
vendored
@ -3,6 +3,7 @@ type ShortcutId = number;
|
||||
interface Shortcut {
|
||||
id: ShortcutId;
|
||||
|
||||
creatorId: UserId;
|
||||
rowStatus: RowStatus;
|
||||
createdTs: TimeStamp;
|
||||
updatedTs: TimeStamp;
|
||||
@ -22,3 +23,7 @@ interface ShortcutPatch {
|
||||
payload?: string;
|
||||
rowStatus?: RowStatus;
|
||||
}
|
||||
|
||||
interface ShortcutFind {
|
||||
creatorId?: UserId;
|
||||
}
|
||||
|
3
web/src/types/modules/tag.d.ts
vendored
Normal file
3
web/src/types/modules/tag.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
interface TagFind {
|
||||
creatorId?: UserId;
|
||||
}
|
@ -358,6 +358,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
|
||||
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
|
||||
|
||||
"@types/qs@^6.9.7":
|
||||
version "6.9.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
|
||||
integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
|
||||
|
||||
"@types/react-dom@^18.0.4":
|
||||
version "18.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.4.tgz#dcbcadb277bcf6c411ceff70069424c57797d375"
|
||||
@ -1988,6 +1993,13 @@ punycode@^2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||
|
||||
qs@^6.11.0:
|
||||
version "6.11.0"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
|
||||
integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
|
||||
dependencies:
|
||||
side-channel "^1.0.4"
|
||||
|
||||
queue-microtask@^1.2.2:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||
|
Loading…
x
Reference in New Issue
Block a user