Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
c492317f
Commit
c492317f
authored
May 16, 2022
by
boojack
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: member manage section in setting dialog
parent
fbf4afff
Changes
22
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
421 additions
and
128 deletions
+421
-128
user.go
api/user.go
+4
-3
auth.go
server/auth.go
+1
-15
user.go
server/user.go
+39
-0
10001__schema.sql
store/migration/10001__schema.sql
+3
-4
user.go
store/user.go
+9
-0
MemoEditor.tsx
web/src/components/MemoEditor.tsx
+6
-6
SettingDialog.tsx
web/src/components/SettingDialog.tsx
+23
-4
MemberSection.tsx
web/src/components/Settings/MemberSection.tsx
+96
-0
MyAccountSection.tsx
web/src/components/Settings/MyAccountSection.tsx
+8
-8
PreferencesSection.tsx
web/src/components/Settings/PreferencesSection.tsx
+78
-0
api.ts
web/src/helpers/api.ts
+18
-23
dialog.less
web/src/less/dialog.less
+1
-5
global.less
web/src/less/global.less
+0
-4
preferences-section.less
web/src/less/preferences-section.less
+0
-44
setting-dialog.less
web/src/less/setting-dialog.less
+11
-8
member-section.less
web/src/less/settings/member-section.less
+39
-0
my-account-section.less
web/src/less/settings/my-account-section.less
+63
-0
preferences-section.less
web/src/less/settings/preferences-section.less
+12
-0
user-banner.less
web/src/less/user-banner.less
+1
-1
Signin.tsx
web/src/pages/Signin.tsx
+1
-3
api.d.ts
web/src/types/api.d.ts
+7
-0
tailwind.config.js
web/tailwind.config.js
+1
-0
No files found.
api/user.go
View file @
c492317f
...
...
@@ -27,9 +27,10 @@ type User struct {
type
UserCreate
struct
{
// Domain specific fields
Email
string
Role
Role
Name
string
Email
string
`json:"email"`
Role
Role
`json:"role"`
Name
string
`json:"name"`
Password
string
`json:"password"`
PasswordHash
string
OpenID
string
}
...
...
server/auth.go
View file @
c492317f
...
...
@@ -81,24 +81,10 @@ func (s *Server) registerAuthRoutes(g *echo.Group) {
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."
)
}
if
len
(
signup
.
Password
)
<
6
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Password is too short, minimum length is 6."
)
}
userFind
:=
&
api
.
UserFind
{
Email
:
&
signup
.
Email
,
}
user
,
err
:=
s
.
Store
.
FindUser
(
userFind
)
if
err
!=
nil
{
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
.
Email
))
}
passwordHash
,
err
:=
bcrypt
.
GenerateFromPassword
([]
byte
(
signup
.
Password
),
bcrypt
.
DefaultCost
)
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to generate password hash"
)
.
SetInternal
(
err
)
...
...
@@ -111,7 +97,7 @@ func (s *Server) registerAuthRoutes(g *echo.Group) {
PasswordHash
:
string
(
passwordHash
),
OpenID
:
common
.
GenUUID
(),
}
user
,
err
=
s
.
Store
.
CreateUser
(
userCreate
)
user
,
err
:
=
s
.
Store
.
CreateUser
(
userCreate
)
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to create user"
)
.
SetInternal
(
err
)
}
...
...
server/user.go
View file @
c492317f
...
...
@@ -12,6 +12,45 @@ import (
)
func
(
s
*
Server
)
registerUserRoutes
(
g
*
echo
.
Group
)
{
g
.
POST
(
"/user"
,
func
(
c
echo
.
Context
)
error
{
userCreate
:=
&
api
.
UserCreate
{}
if
err
:=
json
.
NewDecoder
(
c
.
Request
()
.
Body
)
.
Decode
(
userCreate
);
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Malformatted post user request"
)
.
SetInternal
(
err
)
}
passwordHash
,
err
:=
bcrypt
.
GenerateFromPassword
([]
byte
(
userCreate
.
Password
),
bcrypt
.
DefaultCost
)
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to generate password hash"
)
.
SetInternal
(
err
)
}
userCreate
.
PasswordHash
=
string
(
passwordHash
)
user
,
err
:=
s
.
Store
.
CreateUser
(
userCreate
)
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to create user"
)
.
SetInternal
(
err
)
}
c
.
Response
()
.
Header
()
.
Set
(
echo
.
HeaderContentType
,
echo
.
MIMEApplicationJSONCharsetUTF8
)
if
err
:=
json
.
NewEncoder
(
c
.
Response
()
.
Writer
)
.
Encode
(
composeResponse
(
user
));
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to encode user response"
)
.
SetInternal
(
err
)
}
return
nil
})
g
.
GET
(
"/user"
,
func
(
c
echo
.
Context
)
error
{
userList
,
err
:=
s
.
Store
.
FindUserList
(
&
api
.
UserFind
{})
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to fetch user list"
)
.
SetInternal
(
err
)
}
c
.
Response
()
.
Header
()
.
Set
(
echo
.
HeaderContentType
,
echo
.
MIMEApplicationJSONCharsetUTF8
)
if
err
:=
json
.
NewEncoder
(
c
.
Response
()
.
Writer
)
.
Encode
(
composeResponse
(
userList
));
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to encode user list response"
)
.
SetInternal
(
err
)
}
return
nil
})
// GET /api/user/me is used to check if the user is logged in.
g
.
GET
(
"/user/me"
,
func
(
c
echo
.
Context
)
error
{
userSessionID
:=
c
.
Get
(
getUserIDContextKey
())
...
...
store/migration/10001__schema.sql
View file @
c492317f
-- user
CREATE
TABLE
user
(
id
INTEGER
PRIMARY
KEY
AUTOINCREMENT
,
email
TEXT
NOT
NULL
,
email
TEXT
NOT
NULL
UNIQUE
,
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
,
open_id
TEXT
NOT
NULL
UNIQUE
,
created_ts
BIGINT
NOT
NULL
DEFAULT
(
strftime
(
'%s'
,
'now'
)),
updated_ts
BIGINT
NOT
NULL
DEFAULT
(
strftime
(
'%s'
,
'now'
)),
UNIQUE
(
`email`
,
`open_id`
)
updated_ts
BIGINT
NOT
NULL
DEFAULT
(
strftime
(
'%s'
,
'now'
))
);
INSERT
INTO
...
...
store/user.go
View file @
c492317f
...
...
@@ -25,6 +25,15 @@ func (s *Store) PatchUser(patch *api.UserPatch) (*api.User, error) {
return
user
,
nil
}
func
(
s
*
Store
)
FindUserList
(
find
*
api
.
UserFind
)
([]
*
api
.
User
,
error
)
{
list
,
err
:=
findUserList
(
s
.
db
,
find
)
if
err
!=
nil
{
return
nil
,
err
}
return
list
,
nil
}
func
(
s
*
Store
)
FindUser
(
find
*
api
.
UserFind
)
(
*
api
.
User
,
error
)
{
list
,
err
:=
findUserList
(
s
.
db
,
find
)
if
err
!=
nil
{
...
...
web/src/components/MemoEditor.tsx
View file @
c492317f
...
...
@@ -246,7 +246,7 @@ const MemoEditor: React.FC<Props> = () => {
const
file
=
inputEl
.
files
[
0
];
const
url
=
await
handleUploadFile
(
file
);
if
(
url
)
{
editorRef
.
current
?.
insertText
(
url
);
editorRef
.
current
?.
insertText
(
url
+
" "
);
}
};
inputEl
.
click
();
...
...
@@ -259,7 +259,7 @@ const MemoEditor: React.FC<Props> = () => {
}
},
[]);
const
showEditStatus
=
Boolean
(
globalState
.
editMemoId
);
const
isEditing
=
Boolean
(
globalState
.
editMemoId
);
const
editorConfig
=
useMemo
(
()
=>
({
...
...
@@ -267,17 +267,17 @@ const MemoEditor: React.FC<Props> = () => {
initialContent
:
getEditorContentCache
(),
placeholder
:
"Any thoughts..."
,
showConfirmBtn
:
true
,
showCancelBtn
:
showEditStatus
,
showCancelBtn
:
isEditing
,
onConfirmBtnClick
:
handleSaveBtnClick
,
onCancelBtnClick
:
handleCancelBtnClick
,
onContentChange
:
handleContentChange
,
}),
[
showEditStatus
]
[
isEditing
]
);
return
(
<
div
className=
{
"memo-editor-container "
+
(
showEditStatus
?
"edit-ing"
:
""
)
}
>
<
p
className=
{
"tip-text "
+
(
showEditStatus
?
""
:
"hidden"
)
}
>
Editting...
</
p
>
<
div
className=
{
"memo-editor-container "
+
(
isEditing
?
"edit-ing"
:
""
)
}
>
<
p
className=
{
"tip-text "
+
(
isEditing
?
""
:
"hidden"
)
}
>
Editting...
</
p
>
<
Editor
ref=
{
editorRef
}
{
...
editorConfig
}
...
...
web/src/components/SettingDialog.tsx
View file @
c492317f
import
{
useState
}
from
"react"
;
import
{
useContext
,
useState
}
from
"react"
;
import
appContext
from
"../stores/appContext"
;
import
{
showDialog
}
from
"./Dialog"
;
import
MyAccountSection
from
"./MyAccountSection"
;
import
PreferencesSection
from
"./PreferencesSection"
;
import
MyAccountSection
from
"./Settings/MyAccountSection"
;
import
PreferencesSection
from
"./Settings/PreferencesSection"
;
import
MemberSection
from
"./Settings/MemberSection"
;
import
"../less/setting-dialog.less"
;
interface
Props
extends
DialogProps
{}
type
SettingSection
=
"my-account"
|
"preferences"
;
type
SettingSection
=
"my-account"
|
"preferences"
|
"member"
;
interface
State
{
selectedSection
:
SettingSection
;
}
const
SettingDialog
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
{
userState
:
{
user
},
}
=
useContext
(
appContext
);
const
{
destroy
}
=
props
;
const
[
state
,
setState
]
=
useState
<
State
>
({
selectedSection
:
"my-account"
,
...
...
@@ -30,6 +35,7 @@ const SettingDialog: React.FC<Props> = (props: Props) => {
<
img
className=
"icon-img"
src=
"/icons/close.svg"
/>
</
button
>
<
div
className=
"section-selector-container"
>
<
span
className=
"section-title"
>
Basic
</
span
>
<
span
onClick=
{
()
=>
handleSectionSelectorItemClick
(
"my-account"
)
}
className=
{
`section-item ${state.selectedSection === "my-account" ? "selected" : ""}`
}
...
...
@@ -42,12 +48,25 @@ const SettingDialog: React.FC<Props> = (props: Props) => {
>
Preferences
</
span
>
{
user
?.
role
===
"OWNER"
?
(
<>
<
span
className=
"section-title"
>
Admin
</
span
>
<
span
onClick=
{
()
=>
handleSectionSelectorItemClick
(
"member"
)
}
className=
{
`section-item ${state.selectedSection === "member" ? "selected" : ""}`
}
>
Member
</
span
>
</>
)
:
null
}
</
div
>
<
div
className=
"section-content-container"
>
{
state
.
selectedSection
===
"my-account"
?
(
<
MyAccountSection
/>
)
:
state
.
selectedSection
===
"preferences"
?
(
<
PreferencesSection
/>
)
:
state
.
selectedSection
===
"member"
?
(
<
MemberSection
/>
)
:
null
}
</
div
>
</
div
>
...
...
web/src/components/Settings/MemberSection.tsx
0 → 100644
View file @
c492317f
import
React
,
{
useEffect
,
useState
}
from
"react"
;
import
{
isEmpty
}
from
"lodash-es"
;
import
api
from
"../../helpers/api"
;
import
toastHelper
from
"../Toast"
;
import
"../../less/settings/member-section.less"
;
interface
Props
{}
interface
State
{
createUserEmail
:
string
;
createUserPassword
:
string
;
}
const
PreferencesSection
:
React
.
FC
<
Props
>
=
()
=>
{
const
[
state
,
setState
]
=
useState
<
State
>
({
createUserEmail
:
""
,
createUserPassword
:
""
,
});
const
[
userList
,
setUserList
]
=
useState
<
Model
.
User
[]
>
([]);
useEffect
(()
=>
{
fetchUserList
();
},
[]);
const
fetchUserList
=
async
()
=>
{
const
data
=
await
api
.
getUserList
();
setUserList
(
data
);
};
const
handleEmailInputChange
=
(
event
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
setState
({
...
state
,
createUserEmail
:
event
.
target
.
value
,
});
};
const
handlePasswordInputChange
=
(
event
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
setState
({
...
state
,
createUserPassword
:
event
.
target
.
value
,
});
};
const
handleCreateUserBtnClick
=
async
()
=>
{
if
(
isEmpty
(
state
.
createUserEmail
)
||
isEmpty
(
state
.
createUserPassword
))
{
toastHelper
.
error
(
"Please fill out this form"
);
return
;
}
const
userCreate
:
API
.
UserCreate
=
{
email
:
state
.
createUserEmail
,
password
:
state
.
createUserPassword
,
role
:
"USER"
,
name
:
state
.
createUserEmail
,
};
try
{
await
api
.
createUser
(
userCreate
);
}
catch
(
error
:
any
)
{
toastHelper
.
error
(
error
.
message
);
}
await
fetchUserList
();
setState
({
createUserEmail
:
""
,
createUserPassword
:
""
,
});
};
return
(
<
div
className=
"section-container member-section-container"
>
<
p
className=
"title-text"
>
Create a member
</
p
>
<
div
className=
"create-member-container"
>
<
div
className=
"input-form-container"
>
<
span
className=
"field-text"
>
Email
</
span
>
<
input
type=
"email"
placeholder=
"Email"
value=
{
state
.
createUserEmail
}
onChange=
{
handleEmailInputChange
}
/>
</
div
>
<
div
className=
"input-form-container"
>
<
span
className=
"field-text"
>
Password
</
span
>
<
input
type=
"text"
placeholder=
"Password"
value=
{
state
.
createUserPassword
}
onChange=
{
handlePasswordInputChange
}
/>
</
div
>
<
div
className=
"btns-container"
>
<
button
onClick=
{
handleCreateUserBtnClick
}
>
Create
</
button
>
</
div
>
</
div
>
<
p
className=
"title-text"
>
Member list
</
p
>
{
userList
.
map
((
user
)
=>
(
<
div
key=
{
user
.
id
}
className=
"user-container"
>
<
span
className=
"field-text id-text"
>
{
user
.
id
}
</
span
>
<
span
className=
"field-text"
>
{
user
.
email
}
</
span
>
</
div
>
))
}
</
div
>
);
};
export
default
PreferencesSection
;
web/src/components/MyAccountSection.tsx
→
web/src/components/
Settings/
MyAccountSection.tsx
View file @
c492317f
import
{
useContext
,
useState
}
from
"react"
;
import
appContext
from
"../stores/appContext"
;
import
{
userService
}
from
"../services"
;
import
utils
from
"../helpers/utils"
;
import
{
validate
,
ValidatorConfig
}
from
"../helpers/validator"
;
import
toastHelper
from
"./Toast"
;
import
showChangePasswordDialog
from
"./ChangePasswordDialog"
;
import
showConfirmResetOpenIdDialog
from
"./ConfirmResetOpenIdDialog"
;
import
"../
les
s/my-account-section.less"
;
import
appContext
from
"../
../
stores/appContext"
;
import
{
userService
}
from
"../
../
services"
;
import
utils
from
"../
../
helpers/utils"
;
import
{
validate
,
ValidatorConfig
}
from
"../
../
helpers/validator"
;
import
toastHelper
from
".
.
/Toast"
;
import
showChangePasswordDialog
from
".
.
/ChangePasswordDialog"
;
import
showConfirmResetOpenIdDialog
from
".
.
/ConfirmResetOpenIdDialog"
;
import
"../
../less/setting
s/my-account-section.less"
;
const
validateConfig
:
ValidatorConfig
=
{
minLength
:
4
,
...
...
web/src/components/PreferencesSection.tsx
→
web/src/components/
Settings/
PreferencesSection.tsx
View file @
c492317f
import
{
useContext
}
from
"react"
;
import
appContext
from
"../stores/appContext"
;
import
{
globalStateService
,
memoService
}
from
"../services"
;
import
utils
from
"../helpers/utils"
;
import
{
formatMemoContent
}
from
"./Memo"
;
import
toastHelper
from
"./Toast"
;
import
"../less/preferences-section.less"
;
import
{
memoService
}
from
"../../services"
;
import
utils
from
"../../helpers/utils"
;
import
toastHelper
from
"../Toast"
;
import
"../../less/settings/preferences-section.less"
;
interface
Props
{}
const
PreferencesSection
:
React
.
FC
<
Props
>
=
()
=>
{
const
{
globalState
}
=
useContext
(
appContext
);
const
{
shouldHideImageUrl
,
shouldSplitMemoWord
,
shouldUseMarkdownParser
}
=
globalState
;
const
demoMemoContent
=
"👋 Hiya, welcome to memos!
\n
* ✨ **Open source project**;
\n
* 😋 What do you think;
\n
* 📑 Tell me something plz;"
;
const
handleSplitWordsValueChanged
=
()
=>
{
globalStateService
.
setAppSetting
({
shouldSplitMemoWord
:
!
shouldSplitMemoWord
,
});
};
const
handleHideImageUrlValueChanged
=
()
=>
{
globalStateService
.
setAppSetting
({
shouldHideImageUrl
:
!
shouldHideImageUrl
,
});
};
const
handleUseMarkdownParserChanged
=
()
=>
{
globalStateService
.
setAppSetting
({
shouldUseMarkdownParser
:
!
shouldUseMarkdownParser
,
});
};
const
handleExportBtnClick
=
async
()
=>
{
const
formatedMemos
=
memoService
.
getState
().
memos
.
map
((
m
)
=>
{
return
{
...
...
@@ -87,38 +61,17 @@ const PreferencesSection: React.FC<Props> = () => {
};
return
(
<>
<
div
className=
"section-container preferences-section-container"
>
<
p
className=
"title-text"
>
Memo Display
</
p
>
<
div
className=
"demo-content-container memo-content-text"
dangerouslySetInnerHTML=
{
{
__html
:
formatMemoContent
(
demoMemoContent
)
}
}
></
div
>
<
label
className=
"form-label checkbox-form-label hidden"
onClick=
{
handleSplitWordsValueChanged
}
>
<
span
className=
"normal-text"
>
Auto-space in English and Chinese
</
span
>
<
img
className=
"icon-img"
src=
{
shouldSplitMemoWord
?
"/icons/checkbox-active.svg"
:
"/icons/checkbox.svg"
}
/>
</
label
>
<
label
className=
"form-label checkbox-form-label"
onClick=
{
handleUseMarkdownParserChanged
}
>
<
span
className=
"normal-text"
>
Partial markdown format parsing
</
span
>
<
img
className=
"icon-img"
src=
{
shouldUseMarkdownParser
?
"/icons/checkbox-active.svg"
:
"/icons/checkbox.svg"
}
/>
</
label
>
<
label
className=
"form-label checkbox-form-label"
onClick=
{
handleHideImageUrlValueChanged
}
>
<
span
className=
"normal-text"
>
Hide image url
</
span
>
<
img
className=
"icon-img"
src=
{
shouldHideImageUrl
?
"/icons/checkbox-active.svg"
:
"/icons/checkbox.svg"
}
/>
</
label
>
</
div
>
<
div
className=
"section-container"
>
<
p
className=
"title-text"
>
Others
</
p
>
<
div
className=
"w-full flex flex-row justify-start items-center"
>
<
button
className=
"px-2 py-1 border rounded text-base hover:opacity-80"
onClick=
{
handleExportBtnClick
}
>
Export data as JSON
</
button
>
<
button
className=
"ml-2 px-2 py-1 border rounded text-base hover:opacity-80"
onClick=
{
handleImportBtnClick
}
>
Import from JSON
</
button
>
</
div
>
<
div
className=
"section-container preferences-section-container"
>
<
p
className=
"title-text"
>
Others
</
p
>
<
div
className=
"btns-container"
>
<
button
className=
"btn"
onClick=
{
handleExportBtnClick
}
>
Export data as JSON
</
button
>
<
button
className=
"btn"
onClick=
{
handleImportBtnClick
}
>
Import from JSON
</
button
>
</
div
>
</>
</
div
>
);
};
...
...
web/src/helpers/api.ts
View file @
c492317f
...
...
@@ -48,10 +48,18 @@ namespace api {
});
}
export
function
getUser
Info
()
{
return
request
<
Model
.
User
>
({
export
function
getUser
List
()
{
return
request
<
Model
.
User
[]
>
({
method
:
"GET"
,
url
:
"/api/user/me"
,
url
:
"/api/user"
,
});
}
export
function
createUser
(
userCreate
:
API
.
UserCreate
)
{
return
request
<
Model
.
User
[]
>
({
method
:
"POST"
,
url
:
"/api/user"
,
data
:
userCreate
,
});
}
...
...
@@ -66,15 +74,15 @@ namespace api {
});
}
export
function
signup
(
email
:
string
,
role
:
UserRole
,
name
:
string
,
password
:
string
)
{
export
function
signup
(
email
:
string
,
password
:
string
,
role
:
UserRole
)
{
return
request
<
Model
.
User
>
({
method
:
"POST"
,
url
:
"/api/auth/signup"
,
data
:
{
email
,
role
,
name
,
password
,
role
,
name
:
email
,
},
});
}
...
...
@@ -86,23 +94,10 @@ namespace api {
});
}
export
function
checkUsernameUsable
(
name
:
string
)
{
return
request
<
boolean
>
({
method
:
"POST"
,
url
:
"/api/user/rename_check"
,
data
:
{
name
,
},
});
}
export
function
checkPasswordValid
(
password
:
string
)
{
return
request
<
boolean
>
({
method
:
"POST"
,
url
:
"/api/user/password_check"
,
data
:
{
password
,
},
export
function
getUserInfo
()
{
return
request
<
Model
.
User
>
({
method
:
"GET"
,
url
:
"/api/user/me"
,
});
}
...
...
web/src/less/dialog.less
View file @
c492317f
...
...
@@ -29,15 +29,11 @@
.btn {
.flex(column, center, center);
@apply w-6 h-6 rounded;
@apply w-6 h-6 rounded
hover:bg-gray-200 hover:shadow
;
> .icon-img {
@apply w-5 h-5;
}
&:hover {
@apply bg-gray-200;
}
}
}
...
...
web/src/less/global.less
View file @
c492317f
...
...
@@ -66,10 +66,6 @@ a {
.btn {
@apply select-none cursor-pointer text-center;
border: unset;
background-color: unset;
text-align: unset;
font-size: unset;
}
.hidden {
...
...
web/src/less/preferences-section.less
deleted
100644 → 0
View file @
fbf4afff
@import "./mixin.less";
.preferences-section-container {
> .demo-content-container {
padding: 16px;
border-radius: 8px;
border: 2px solid @bg-gray;
margin: 12px 0;
}
> .form-label {
min-height: 28px;
cursor: pointer;
> .icon-img {
width: 16px;
height: 16px;
margin: 0 8px;
}
&:hover {
opacity: 0.8;
}
}
> .btn-container {
.flex(row, flex-start, center);
width: 100%;
margin: 4px 0;
.btn {
height: 28px;
padding: 0 12px;
margin-right: 8px;
border: 1px solid gray;
border-radius: 8px;
cursor: pointer;
&:hover {
opacity: 0.8;
}
}
}
}
web/src/less/setting-dialog.less
View file @
c492317f
...
...
@@ -3,7 +3,7 @@
.setting-dialog {
> .dialog-container {
@apply w-1
68
max-w-full mb-8 p-0;
@apply w-1
76
max-w-full mb-8 p-0;
> .dialog-content-container {
.flex(column, flex-start, flex-start);
...
...
@@ -12,7 +12,7 @@
> .close-btn {
.flex(column, center, center);
@apply absolute top-4 right-4 w-6 h-6 rounded hover:bg-gray-200;
@apply absolute top-4 right-4 w-6 h-6 rounded hover:bg-gray-200
hover:shadow
;
> .icon-img {
@apply w-5 h-5;
...
...
@@ -20,10 +20,14 @@
}
> .section-selector-container {
@apply w-40 h-full shrink-0 rounded-l-lg p-4 bg-gray-100 flex flex-col justify-start items-start;
@apply w-40 h-full shrink-0 rounded-l-lg p-4 border-r bg-gray-100 flex flex-col justify-start items-start;
> .section-title {
@apply text-sm mt-4 first:mt-3 mb-1 font-mono text-gray-400;
}
> .section-item {
@apply text-base left-6 mt-2
mb-1
cursor-pointer hover:opacity-80;
@apply text-base left-6 mt-2
text-gray-700
cursor-pointer hover:opacity-80;
&.selected {
@apply font-bold hover:opacity-100;
...
...
@@ -32,20 +36,19 @@
}
> .section-content-container {
@apply w-auto p-4
grow flex flex-col justify-start items-start
;
@apply w-auto p-4
px-6 grow flex flex-col justify-start items-start h-128 overflow-y-scroll
;
> .section-container {
.flex(column, flex-start, flex-start);
@apply w-full my-2;
> .title-text {
@apply text-base font-bold mb-2;
color: @text-black;
@apply text-sm mb-3 font-mono text-gray-500;
}
> .form-label {
.flex(row, flex-start, center);
@apply w-full
text-sm
mb-2;
@apply w-full mb-2;
> .normal-text {
@apply shrink-0 select-text;
...
...
web/src/less/settings/member-section.less
0 → 100644
View file @
c492317f
@import "../mixin.less";
.member-section-container {
> .create-member-container {
@apply w-full flex flex-col justify-start items-start;
> .input-form-container {
@apply w-full mb-2 flex flex-row justify-start items-center;
> .field-text {
@apply text-sm text-gray-600 w-20 text-right pr-2;
}
> input {
@apply border rounded text-sm leading-6 shadow-inner py-1 px-2;
}
}
> .btns-container {
@apply w-full mb-6 pl-20 flex flex-row justify-start items-center;
> button {
@apply border text-sm py-1 px-3 rounded leading-6 shadow hover:opacity-80;
}
}
}
> .user-container {
@apply w-full mb-4 grid grid-cols-5;
> .field-text {
@apply text-base mr-4 w-16;
&.id-text {
@apply font-mono;
}
}
}
}
web/src/less/my-account-section.less
→
web/src/less/
settings/
my-account-section.less
View file @
c492317f
@import "./mixin.less";
@import ".
.
/mixin.less";
.account-section-container {
> .form-label {
min-height: 28px;
> .normal-text {
@apply first:mr-2;
@apply first:mr-2
text-base
;
}
&.username-label {
> input {
flex-grow: 0;
width: 128px;
padding: 0 8px;
font-size: 14px;
border: 1px solid lightgray;
border-radius: 4px;
line-height: 26px;
background-color: transparent;
&:focus {
border-color: black;
}
@apply grow-0 shadow-inner w-auto px-2 py-1 text-base border rounded leading-6 bg-transparent focus:border-black;
}
> .btns-container {
.flex(row, flex-start, center);
margin-left: 8px;
flex-shrink: 0;
@apply ml-2 shrink-0;
> .btn {
font-size: 12px;
padding: 0 16px;
border-radius: 4px;
line-height: 28px;
margin-right: 8px;
background-color: lightgray;
&:hover {
opacity: 0.8;
}
@apply text-sm shadow px-4 py-1 leading-6 rounded border hover:opacity-80 bg-gray-50;
&.cancel-btn {
background-color: unse
t;
@apply shadow-none bg-transparen
t;
}
&.confirm-btn {
background-color: @text-green;
color: white;
@apply bg-green-600 text-white;
}
}
}
...
...
@@ -63,17 +41,11 @@
.openapi-section-container {
> .value-text {
width: 100%;
border: 1px solid lightgray;
padding: 4px 6px;
border-radius: 4px;
line-height: 1.6;
word-break: break-all;
white-space: pre-wrap;
@apply w-full font-mono text-sm shadow-inner border py-2 px-3 rounded leading-6 break-all whitespace-pre-wrap;
}
> .reset-btn {
@apply mt-2 py-1 px-2
bg-red-50 border border-red-500 text-red-600 rounded leading-4 cursor-pointer text-xs
select-none hover:opacity-80;
@apply mt-2 py-1 px-2
text-sm shadow bg-red-50 border border-red-500 text-red-600 rounded cursor-pointer
select-none hover:opacity-80;
}
> .usage-guide-container {
...
...
@@ -85,7 +57,7 @@
}
> pre {
@apply w-full bg-gray-
50 py-2 px-3 text-sm rounded
whitespace-pre-wrap;
@apply w-full bg-gray-
100 shadow-inner py-2 px-3 text-sm rounded font-mono break-all
whitespace-pre-wrap;
}
}
}
web/src/less/settings/preferences-section.less
0 → 100644
View file @
c492317f
@import "../mixin.less";
.preferences-section-container {
> .btns-container {
.flex(row, flex-start, center);
@apply w-full;
> .btn {
@apply border text-sm py-1 px-3 mr-2 rounded leading-6 shadow hover:opacity-80;
}
}
}
web/src/less/user-banner.less
View file @
c492317f
...
...
@@ -12,7 +12,7 @@
}
> .tag {
@apply text-xs px-1 bg-blue-
500 rounded text-white
;
@apply text-xs px-1 bg-blue-
600 rounded text-white shadow
;
}
}
...
...
web/src/pages/Signin.tsx
View file @
c492317f
...
...
@@ -89,11 +89,9 @@ const Signin: React.FC<Props> = () => {
return
;
}
const
name
=
email
.
split
(
"@"
)[
0
];
try
{
actionBtnLoadingState
.
setLoading
();
await
api
.
signup
(
email
,
"OWNER"
,
name
,
password
);
await
api
.
signup
(
email
,
password
,
"OWNER"
);
const
user
=
await
userService
.
doSignIn
();
if
(
user
)
{
locationService
.
replaceHistory
(
"/"
);
...
...
web/src/types/api.d.ts
View file @
c492317f
...
...
@@ -2,4 +2,11 @@ declare namespace API {
interface
SystemStatus
{
owner
:
Model
.
User
;
}
interface
UserCreate
{
email
:
string
;
password
:
string
;
name
:
string
;
role
:
UserRole
;
}
}
web/tailwind.config.js
View file @
c492317f
...
...
@@ -16,6 +16,7 @@ module.exports = {
spacing
:
{
128
:
"32rem"
,
168
:
"42rem"
,
176
:
"44rem"
,
200
:
"50rem"
,
},
zIndex
:
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment