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
cf750541
Unverified
Commit
cf750541
authored
Nov 03, 2022
by
boojack
Committed by
GitHub
Nov 03, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: add system setting to allow user signup (#407)
parent
4ed98722
Changes
21
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
755 additions
and
45 deletions
+755
-45
system.go
api/system.go
+2
-0
system_setting.go
api/system_setting.go
+70
-0
main.go
bin/server/main.go
+1
-1
collector.go
plugin/metrics/segment/collector.go
+4
-2
auth.go
server/auth.go
+23
-5
system.go
server/system.go
+72
-2
10005__system_setting.sql
store/db/seed/10005__system_setting.sql
+12
-0
system_setting.go
store/system_setting.go
+154
-0
user.go
store/user.go
+0
-2
package.json
web/package.json
+3
-0
App.tsx
web/src/App.tsx
+6
-1
SettingDialog.tsx
web/src/components/SettingDialog.tsx
+10
-1
SystemSection.tsx
web/src/components/Settings/SystemSection.tsx
+65
-0
api.ts
web/src/helpers/api.ts
+4
-0
auth.less
web/src/less/auth.less
+4
-0
en.json
web/src/locales/en.json
+1
-0
vi.json
web/src/locales/vi.json
+2
-1
zh.json
web/src/locales/zh.json
+1
-0
Auth.tsx
web/src/pages/Auth.tsx
+39
-21
system.d.ts
web/src/types/modules/system.d.ts
+7
-0
yarn.lock
web/yarn.lock
+275
-9
No files found.
api/system.go
View file @
cf750541
...
@@ -5,4 +5,6 @@ import "github.com/usememos/memos/server/profile"
...
@@ -5,4 +5,6 @@ import "github.com/usememos/memos/server/profile"
type
SystemStatus
struct
{
type
SystemStatus
struct
{
Host
*
User
`json:"host"`
Host
*
User
`json:"host"`
Profile
*
profile
.
Profile
`json:"profile"`
Profile
*
profile
.
Profile
`json:"profile"`
// System settings
AllowSignUp
bool
`json:"allowSignUp"`
}
}
api/system_setting.go
0 → 100644
View file @
cf750541
package
api
import
(
"encoding/json"
"fmt"
)
type
SystemSettingName
string
const
(
// SystemSettingAllowSignUpName is the key type of allow signup setting.
SystemSettingAllowSignUpName
SystemSettingName
=
"allowSignUp"
SystemSettingPlaceholderName
SystemSettingName
=
"placeholder"
)
func
(
key
SystemSettingName
)
String
()
string
{
switch
key
{
case
SystemSettingAllowSignUpName
:
return
"allowSignUp"
case
SystemSettingPlaceholderName
:
return
"placeholder"
}
return
""
}
var
(
SystemSettingAllowSignUpValue
=
[]
bool
{
true
,
false
}
)
type
SystemSetting
struct
{
Name
SystemSettingName
// Value is a JSON string with basic value
Value
string
Description
string
}
type
SystemSettingUpsert
struct
{
Name
SystemSettingName
`json:"name"`
Value
string
`json:"value"`
Description
string
`json:"description"`
}
func
(
upsert
SystemSettingUpsert
)
Validate
()
error
{
if
upsert
.
Name
==
SystemSettingAllowSignUpName
{
value
:=
false
err
:=
json
.
Unmarshal
([]
byte
(
upsert
.
Value
),
&
value
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to unmarshal system setting allow signup value"
)
}
invalid
:=
true
for
_
,
v
:=
range
SystemSettingAllowSignUpValue
{
if
value
==
v
{
invalid
=
false
break
}
}
if
invalid
{
return
fmt
.
Errorf
(
"invalid system setting allow signup value"
)
}
}
else
{
return
fmt
.
Errorf
(
"invalid system setting name"
)
}
return
nil
}
type
SystemSettingFind
struct
{
Name
*
SystemSettingName
`json:"name"`
}
bin/server/main.go
View file @
cf750541
...
@@ -45,7 +45,7 @@ func run(profile *profile.Profile) error {
...
@@ -45,7 +45,7 @@ func run(profile *profile.Profile) error {
println
(
greetingBanner
)
println
(
greetingBanner
)
fmt
.
Printf
(
"Version %s has started at :%d
\n
"
,
profile
.
Version
,
profile
.
Port
)
fmt
.
Printf
(
"Version %s has started at :%d
\n
"
,
profile
.
Version
,
profile
.
Port
)
metricCollector
.
Collect
(
ctx
,
&
metric
.
Metric
{
metricCollector
.
Collect
(
ctx
,
&
metric
.
Metric
{
Name
:
"servi
v
e started"
,
Name
:
"servi
c
e started"
,
})
})
return
serverInstance
.
Run
()
return
serverInstance
.
Run
()
...
...
plugin/metrics/segment/collector.go
View file @
cf750541
...
@@ -8,7 +8,9 @@ import (
...
@@ -8,7 +8,9 @@ import (
metric
"github.com/usememos/memos/plugin/metrics"
metric
"github.com/usememos/memos/plugin/metrics"
)
)
var
_
metric
.
Collector
=
(
*
collector
)(
nil
)
var
(
sessionUUID
=
uuid
.
NewString
()
)
// collector is the metrics collector https://segment.com/.
// collector is the metrics collector https://segment.com/.
type
collector
struct
{
type
collector
struct
{
...
@@ -33,7 +35,7 @@ func (c *collector) Collect(metric *metric.Metric) error {
...
@@ -33,7 +35,7 @@ func (c *collector) Collect(metric *metric.Metric) error {
return
c
.
client
.
Enqueue
(
analytics
.
Track
{
return
c
.
client
.
Enqueue
(
analytics
.
Track
{
Event
:
string
(
metric
.
Name
),
Event
:
string
(
metric
.
Name
),
AnonymousId
:
uuid
.
NewString
()
,
AnonymousId
:
sessionUUID
,
Properties
:
properties
,
Properties
:
properties
,
Timestamp
:
time
.
Now
()
.
UTC
(),
Timestamp
:
time
.
Now
()
.
UTC
(),
})
})
...
...
server/auth.go
View file @
cf750541
...
@@ -70,7 +70,11 @@ func (s *Server) registerAuthRoutes(g *echo.Group) {
...
@@ -70,7 +70,11 @@ func (s *Server) registerAuthRoutes(g *echo.Group) {
g
.
POST
(
"/auth/signup"
,
func
(
c
echo
.
Context
)
error
{
g
.
POST
(
"/auth/signup"
,
func
(
c
echo
.
Context
)
error
{
ctx
:=
c
.
Request
()
.
Context
()
ctx
:=
c
.
Request
()
.
Context
()
// Don't allow to signup by this api if site host existed.
signup
:=
&
api
.
Signup
{}
if
err
:=
json
.
NewDecoder
(
c
.
Request
()
.
Body
)
.
Decode
(
signup
);
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Malformatted signup request"
)
.
SetInternal
(
err
)
}
hostUserType
:=
api
.
Host
hostUserType
:=
api
.
Host
hostUserFind
:=
api
.
UserFind
{
hostUserFind
:=
api
.
UserFind
{
Role
:
&
hostUserType
,
Role
:
&
hostUserType
,
...
@@ -79,13 +83,27 @@ func (s *Server) registerAuthRoutes(g *echo.Group) {
...
@@ -79,13 +83,27 @@ func (s *Server) registerAuthRoutes(g *echo.Group) {
if
err
!=
nil
&&
common
.
ErrorCode
(
err
)
!=
common
.
NotFound
{
if
err
!=
nil
&&
common
.
ErrorCode
(
err
)
!=
common
.
NotFound
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to find host user"
)
.
SetInternal
(
err
)
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to find host user"
)
.
SetInternal
(
err
)
}
}
if
hostUser
!=
nil
{
if
signup
.
Role
==
api
.
Host
&&
hostUser
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusUnauthorized
,
"Site Host existed, please contact the site host to signin account firstly."
)
.
SetInternal
(
err
)
return
echo
.
NewHTTPError
(
http
.
StatusUnauthorized
,
"Site Host existed, please contact the site host to signin account firstly."
)
.
SetInternal
(
err
)
}
}
signup
:=
&
api
.
Signup
{}
systemSettingAllowSignUpName
:=
api
.
SystemSettingAllowSignUpName
if
err
:=
json
.
NewDecoder
(
c
.
Request
()
.
Body
)
.
Decode
(
signup
);
err
!=
nil
{
allowSignUpSetting
,
err
:=
s
.
Store
.
FindSystemSetting
(
ctx
,
&
api
.
SystemSettingFind
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Malformatted signup request"
)
.
SetInternal
(
err
)
Name
:
&
systemSettingAllowSignUpName
,
})
if
err
!=
nil
&&
common
.
ErrorCode
(
err
)
!=
common
.
NotFound
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to find system setting"
)
.
SetInternal
(
err
)
}
allowSignUpSettingValue
:=
false
if
allowSignUpSetting
!=
nil
{
err
=
json
.
Unmarshal
([]
byte
(
allowSignUpSetting
.
Value
),
&
allowSignUpSettingValue
)
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to unmarshal system setting allow signup"
)
.
SetInternal
(
err
)
}
}
if
!
allowSignUpSettingValue
&&
hostUser
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusUnauthorized
,
"Site Host existed, please contact the site host to signin account firstly."
)
.
SetInternal
(
err
)
}
}
userCreate
:=
&
api
.
UserCreate
{
userCreate
:=
&
api
.
UserCreate
{
...
...
server/system.go
View file @
cf750541
...
@@ -40,6 +40,23 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
...
@@ -40,6 +40,23 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
systemStatus
:=
api
.
SystemStatus
{
systemStatus
:=
api
.
SystemStatus
{
Host
:
hostUser
,
Host
:
hostUser
,
Profile
:
s
.
Profile
,
Profile
:
s
.
Profile
,
AllowSignUp
:
false
,
}
systemSettingList
,
err
:=
s
.
Store
.
FindSystemSettingList
(
ctx
,
&
api
.
SystemSettingFind
{})
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to find system setting list"
)
.
SetInternal
(
err
)
}
for
_
,
systemSetting
:=
range
systemSettingList
{
var
value
interface
{}
err
=
json
.
Unmarshal
([]
byte
(
systemSetting
.
Value
),
&
value
)
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to unmarshal system setting"
)
.
SetInternal
(
err
)
}
if
systemSetting
.
Name
==
api
.
SystemSettingAllowSignUpName
{
systemStatus
.
AllowSignUp
=
value
.
(
bool
)
}
}
}
c
.
Response
()
.
Header
()
.
Set
(
echo
.
HeaderContentType
,
echo
.
MIMEApplicationJSONCharsetUTF8
)
c
.
Response
()
.
Header
()
.
Set
(
echo
.
HeaderContentType
,
echo
.
MIMEApplicationJSONCharsetUTF8
)
...
@@ -48,4 +65,57 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
...
@@ -48,4 +65,57 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
}
}
return
nil
return
nil
})
})
g
.
POST
(
"/system/setting"
,
func
(
c
echo
.
Context
)
error
{
ctx
:=
c
.
Request
()
.
Context
()
userID
,
ok
:=
c
.
Get
(
getUserIDContextKey
())
.
(
int
)
if
!
ok
{
return
echo
.
NewHTTPError
(
http
.
StatusUnauthorized
,
"Missing user in session"
)
}
user
,
err
:=
s
.
Store
.
FindUser
(
ctx
,
&
api
.
UserFind
{
ID
:
&
userID
,
})
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to find user"
)
.
SetInternal
(
err
)
}
if
user
==
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusNotFound
,
"Current signin user not found"
)
}
else
if
user
.
Role
!=
api
.
Host
{
return
echo
.
NewHTTPError
(
http
.
StatusUnauthorized
,
"Unauthorized"
)
}
systemSettingUpsert
:=
&
api
.
SystemSettingUpsert
{}
if
err
:=
json
.
NewDecoder
(
c
.
Request
()
.
Body
)
.
Decode
(
systemSettingUpsert
);
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"Malformatted post system setting request"
)
.
SetInternal
(
err
)
}
if
err
:=
systemSettingUpsert
.
Validate
();
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"system setting invalidate"
)
.
SetInternal
(
err
)
}
systemSetting
,
err
:=
s
.
Store
.
UpsertSystemSetting
(
ctx
,
systemSettingUpsert
)
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to upsert system setting"
)
.
SetInternal
(
err
)
}
c
.
Response
()
.
Header
()
.
Set
(
echo
.
HeaderContentType
,
echo
.
MIMEApplicationJSONCharsetUTF8
)
if
err
:=
json
.
NewEncoder
(
c
.
Response
()
.
Writer
)
.
Encode
(
composeResponse
(
systemSetting
));
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to encode system setting response"
)
.
SetInternal
(
err
)
}
return
nil
})
g
.
GET
(
"/system/setting"
,
func
(
c
echo
.
Context
)
error
{
ctx
:=
c
.
Request
()
.
Context
()
systemSettingList
,
err
:=
s
.
Store
.
FindSystemSettingList
(
ctx
,
&
api
.
SystemSettingFind
{})
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to find system setting list"
)
.
SetInternal
(
err
)
}
c
.
Response
()
.
Header
()
.
Set
(
echo
.
HeaderContentType
,
echo
.
MIMEApplicationJSONCharsetUTF8
)
if
err
:=
json
.
NewEncoder
(
c
.
Response
()
.
Writer
)
.
Encode
(
composeResponse
(
systemSettingList
));
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to encode system setting list response"
)
.
SetInternal
(
err
)
}
return
nil
})
}
}
store/db/seed/10005__system_setting.sql
0 → 100644
View file @
cf750541
INSERT
INTO
system_setting
(
`name`
,
`value`
,
`description`
)
VALUES
(
'allowSignUp'
,
'true'
,
''
);
store/system_setting.go
0 → 100644
View file @
cf750541
package
store
import
(
"context"
"database/sql"
"fmt"
"strings"
"github.com/usememos/memos/api"
"github.com/usememos/memos/common"
)
type
systemSettingRaw
struct
{
Name
api
.
SystemSettingName
Value
string
Description
string
}
func
(
raw
*
systemSettingRaw
)
toSystemSetting
()
*
api
.
SystemSetting
{
return
&
api
.
SystemSetting
{
Name
:
raw
.
Name
,
Value
:
raw
.
Value
,
Description
:
raw
.
Description
,
}
}
func
(
s
*
Store
)
UpsertSystemSetting
(
ctx
context
.
Context
,
upsert
*
api
.
SystemSettingUpsert
)
(
*
api
.
SystemSetting
,
error
)
{
tx
,
err
:=
s
.
db
.
BeginTx
(
ctx
,
nil
)
if
err
!=
nil
{
return
nil
,
FormatError
(
err
)
}
defer
tx
.
Rollback
()
systemSettingRaw
,
err
:=
upsertSystemSetting
(
ctx
,
tx
,
upsert
)
if
err
!=
nil
{
return
nil
,
err
}
if
err
:=
tx
.
Commit
();
err
!=
nil
{
return
nil
,
err
}
systemSetting
:=
systemSettingRaw
.
toSystemSetting
()
return
systemSetting
,
nil
}
func
(
s
*
Store
)
FindSystemSettingList
(
ctx
context
.
Context
,
find
*
api
.
SystemSettingFind
)
([]
*
api
.
SystemSetting
,
error
)
{
tx
,
err
:=
s
.
db
.
BeginTx
(
ctx
,
nil
)
if
err
!=
nil
{
return
nil
,
FormatError
(
err
)
}
defer
tx
.
Rollback
()
systemSettingRawList
,
err
:=
findSystemSettingList
(
ctx
,
tx
,
find
)
if
err
!=
nil
{
return
nil
,
err
}
if
err
:=
tx
.
Commit
();
err
!=
nil
{
return
nil
,
err
}
list
:=
[]
*
api
.
SystemSetting
{}
for
_
,
raw
:=
range
systemSettingRawList
{
list
=
append
(
list
,
raw
.
toSystemSetting
())
}
return
list
,
nil
}
func
(
s
*
Store
)
FindSystemSetting
(
ctx
context
.
Context
,
find
*
api
.
SystemSettingFind
)
(
*
api
.
SystemSetting
,
error
)
{
tx
,
err
:=
s
.
db
.
BeginTx
(
ctx
,
nil
)
if
err
!=
nil
{
return
nil
,
FormatError
(
err
)
}
defer
tx
.
Rollback
()
systemSettingRawList
,
err
:=
findSystemSettingList
(
ctx
,
tx
,
find
)
if
err
!=
nil
{
return
nil
,
err
}
if
len
(
systemSettingRawList
)
==
0
{
return
nil
,
&
common
.
Error
{
Code
:
common
.
NotFound
,
Err
:
fmt
.
Errorf
(
"not found"
)}
}
return
systemSettingRawList
[
0
]
.
toSystemSetting
(),
nil
}
func
upsertSystemSetting
(
ctx
context
.
Context
,
tx
*
sql
.
Tx
,
upsert
*
api
.
SystemSettingUpsert
)
(
*
systemSettingRaw
,
error
)
{
query
:=
`
INSERT INTO system_setting (
name, value, description
)
VALUES (?, ?, ?)
ON CONFLICT(name) DO UPDATE
SET
value = EXCLUDED.value,
description = EXCLUDED.description
RETURNING name, value, description
`
var
systemSettingRaw
systemSettingRaw
if
err
:=
tx
.
QueryRowContext
(
ctx
,
query
,
upsert
.
Name
,
upsert
.
Value
,
upsert
.
Description
)
.
Scan
(
&
systemSettingRaw
.
Name
,
&
systemSettingRaw
.
Value
,
&
systemSettingRaw
.
Description
,
);
err
!=
nil
{
return
nil
,
FormatError
(
err
)
}
return
&
systemSettingRaw
,
nil
}
func
findSystemSettingList
(
ctx
context
.
Context
,
tx
*
sql
.
Tx
,
find
*
api
.
SystemSettingFind
)
([]
*
systemSettingRaw
,
error
)
{
where
,
args
:=
[]
string
{
"1 = 1"
},
[]
interface
{}{}
if
v
:=
find
.
Name
;
v
!=
nil
{
where
,
args
=
append
(
where
,
"name = ?"
),
append
(
args
,
v
.
String
())
}
query
:=
`
SELECT
name,
value,
description
FROM system_setting
WHERE `
+
strings
.
Join
(
where
,
" AND "
)
rows
,
err
:=
tx
.
QueryContext
(
ctx
,
query
,
args
...
)
if
err
!=
nil
{
return
nil
,
FormatError
(
err
)
}
defer
rows
.
Close
()
systemSettingRawList
:=
make
([]
*
systemSettingRaw
,
0
)
for
rows
.
Next
()
{
var
systemSettingRaw
systemSettingRaw
if
err
:=
rows
.
Scan
(
&
systemSettingRaw
.
Name
,
&
systemSettingRaw
.
Value
,
&
systemSettingRaw
.
Description
,
);
err
!=
nil
{
return
nil
,
FormatError
(
err
)
}
systemSettingRawList
=
append
(
systemSettingRawList
,
&
systemSettingRaw
)
}
if
err
:=
rows
.
Err
();
err
!=
nil
{
return
nil
,
FormatError
(
err
)
}
return
systemSettingRawList
,
nil
}
store/user.go
View file @
cf750541
...
@@ -155,8 +155,6 @@ func (s *Store) FindUser(ctx context.Context, find *api.UserFind) (*api.User, er
...
@@ -155,8 +155,6 @@ func (s *Store) FindUser(ctx context.Context, find *api.UserFind) (*api.User, er
if
len
(
list
)
==
0
{
if
len
(
list
)
==
0
{
return
nil
,
&
common
.
Error
{
Code
:
common
.
NotFound
,
Err
:
fmt
.
Errorf
(
"not found user with filter %+v"
,
find
)}
return
nil
,
&
common
.
Error
{
Code
:
common
.
NotFound
,
Err
:
fmt
.
Errorf
(
"not found user with filter %+v"
,
find
)}
}
else
if
len
(
list
)
>
1
{
return
nil
,
&
common
.
Error
{
Code
:
common
.
Conflict
,
Err
:
fmt
.
Errorf
(
"found %d users with filter %+v, expect 1"
,
len
(
list
),
find
)}
}
}
userRaw
:=
list
[
0
]
userRaw
:=
list
[
0
]
...
...
web/package.json
View file @
cf750541
...
@@ -8,6 +8,9 @@
...
@@ -8,6 +8,9 @@
"test"
:
"jest --passWithNoTests"
"test"
:
"jest --passWithNoTests"
},
},
"dependencies"
:
{
"dependencies"
:
{
"@emotion/react"
:
"^11.10.5"
,
"@emotion/styled"
:
"^11.10.5"
,
"@mui/joy"
:
"^5.0.0-alpha.52"
,
"@reduxjs/toolkit"
:
"^1.8.1"
,
"@reduxjs/toolkit"
:
"^1.8.1"
,
"axios"
:
"^0.27.2"
,
"axios"
:
"^0.27.2"
,
"copy-to-clipboard"
:
"^3.3.2"
,
"copy-to-clipboard"
:
"^3.3.2"
,
...
...
web/src/App.tsx
View file @
cf750541
import
{
CssVarsProvider
}
from
"@mui/joy/styles"
;
import
{
useEffect
}
from
"react"
;
import
{
useEffect
}
from
"react"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
{
RouterProvider
}
from
"react-router-dom"
;
import
{
RouterProvider
}
from
"react-router-dom"
;
...
@@ -26,7 +27,11 @@ function App() {
...
@@ -26,7 +27,11 @@ function App() {
});
});
},
[
global
.
locale
]);
},
[
global
.
locale
]);
return
<
RouterProvider
router=
{
router
}
/>;
return
(
<
CssVarsProvider
>
<
RouterProvider
router=
{
router
}
/>
</
CssVarsProvider
>
);
}
}
export
default
App
;
export
default
App
;
web/src/components/SettingDialog.tsx
View file @
cf750541
...
@@ -6,11 +6,12 @@ import { generateDialog } from "./Dialog";
...
@@ -6,11 +6,12 @@ import { generateDialog } from "./Dialog";
import
MyAccountSection
from
"./Settings/MyAccountSection"
;
import
MyAccountSection
from
"./Settings/MyAccountSection"
;
import
PreferencesSection
from
"./Settings/PreferencesSection"
;
import
PreferencesSection
from
"./Settings/PreferencesSection"
;
import
MemberSection
from
"./Settings/MemberSection"
;
import
MemberSection
from
"./Settings/MemberSection"
;
import
SystemSection
from
"./Settings/SystemSection"
;
import
"../less/setting-dialog.less"
;
import
"../less/setting-dialog.less"
;
type
Props
=
DialogProps
;
type
Props
=
DialogProps
;
type
SettingSection
=
"my-account"
|
"preferences"
|
"member"
;
type
SettingSection
=
"my-account"
|
"preferences"
|
"member"
|
"system"
;
interface
State
{
interface
State
{
selectedSection
:
SettingSection
;
selectedSection
:
SettingSection
;
...
@@ -61,6 +62,12 @@ const SettingDialog: React.FC<Props> = (props: Props) => {
...
@@ -61,6 +62,12 @@ const SettingDialog: React.FC<Props> = (props: Props) => {
>
>
<
span
className=
"icon-text"
>
👤
</
span
>
{
t
(
"setting.member"
)
}
<
span
className=
"icon-text"
>
👤
</
span
>
{
t
(
"setting.member"
)
}
</
span
>
</
span
>
<
span
onClick=
{
()
=>
handleSectionSelectorItemClick
(
"system"
)
}
className=
{
`section-item ${state.selectedSection === "system" ? "selected" : ""}`
}
>
<
span
className=
"icon-text"
>
🧑🔧
</
span
>
System Setting
</
span
>
</
div
>
</
div
>
</>
</>
)
:
null
}
)
:
null
}
...
@@ -72,6 +79,8 @@ const SettingDialog: React.FC<Props> = (props: Props) => {
...
@@ -72,6 +79,8 @@ const SettingDialog: React.FC<Props> = (props: Props) => {
<
PreferencesSection
/>
<
PreferencesSection
/>
)
:
state
.
selectedSection
===
"member"
?
(
)
:
state
.
selectedSection
===
"member"
?
(
<
MemberSection
/>
<
MemberSection
/>
)
:
state
.
selectedSection
===
"system"
?
(
<
SystemSection
/>
)
:
null
}
)
:
null
}
</
div
>
</
div
>
</
div
>
</
div
>
...
...
web/src/components/Settings/SystemSection.tsx
0 → 100644
View file @
cf750541
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useTranslation
}
from
"react-i18next"
;
import
Switch
from
"@mui/joy/Switch"
;
import
*
as
api
from
"../../helpers/api"
;
import
{
globalService
,
userService
}
from
"../../services"
;
import
Selector
from
"../common/Selector"
;
import
"../../less/settings/preferences-section.less"
;
const
localeSelectorItems
=
[
{
text
:
"English"
,
value
:
"en"
,
},
{
text
:
"中文"
,
value
:
"zh"
,
},
{
text
:
"Tiếng Việt"
,
value
:
"vi"
,
},
];
interface
State
{
allowSignUp
:
boolean
;
}
const
SystemSection
=
()
=>
{
const
{
t
}
=
useTranslation
();
const
[
state
,
setState
]
=
useState
<
State
>
({
allowSignUp
:
false
,
});
useEffect
(()
=>
{
api
.
getSystemStatus
().
then
(({
data
})
=>
{
const
{
data
:
status
}
=
data
;
setState
({
allowSignUp
:
status
.
allowSignUp
,
});
});
},
[]);
const
handleAllowSignUpChanged
=
async
(
value
:
boolean
)
=>
{
setState
({
...
state
,
allowSignUp
:
value
,
});
await
api
.
upsertSystemSetting
({
name
:
"allowSignUp"
,
value
:
JSON
.
stringify
(
value
),
});
};
return
(
<
div
className=
"section-container preferences-section-container"
>
<
p
className=
"title-text"
>
{
t
(
"common.basic"
)
}
</
p
>
<
label
className=
"form-label selector"
>
<
span
className=
"normal-text"
>
Allow user signUp
</
span
>
<
Switch
size=
"sm"
checked=
{
state
.
allowSignUp
}
onChange=
{
(
event
)
=>
handleAllowSignUpChanged
(
event
.
target
.
checked
)
}
/>
</
label
>
</
div
>
);
};
export
default
SystemSection
;
web/src/helpers/api.ts
View file @
cf750541
...
@@ -10,6 +10,10 @@ export function getSystemStatus() {
...
@@ -10,6 +10,10 @@ export function getSystemStatus() {
return
axios
.
get
<
ResponseObject
<
SystemStatus
>>
(
"/api/status"
);
return
axios
.
get
<
ResponseObject
<
SystemStatus
>>
(
"/api/status"
);
}
}
export
function
upsertSystemSetting
(
systemSetting
:
SystemSetting
)
{
return
axios
.
post
<
ResponseObject
<
SystemSetting
>>
(
"/api/system/setting"
,
systemSetting
);
}
export
function
signin
(
email
:
string
,
password
:
string
)
{
export
function
signin
(
email
:
string
,
password
:
string
)
{
return
axios
.
post
<
ResponseObject
<
User
>>
(
"/api/auth/signin"
,
{
return
axios
.
post
<
ResponseObject
<
User
>>
(
"/api/auth/signin"
,
{
email
,
email
,
...
...
web/src/less/auth.less
View file @
cf750541
...
@@ -59,6 +59,10 @@
...
@@ -59,6 +59,10 @@
> .btn {
> .btn {
@apply flex flex-row justify-center items-center px-1 py-2 text-sm rounded hover:opacity-80;
@apply flex flex-row justify-center items-center px-1 py-2 text-sm rounded hover:opacity-80;
&.signup-btn {
@apply px-3;
}
&.signin-btn {
&.signin-btn {
@apply bg-green-600 text-white px-3 shadow;
@apply bg-green-600 text-white px-3 shadow;
}
}
...
...
web/src/locales/en.json
View file @
cf750541
...
@@ -28,6 +28,7 @@
...
@@ -28,6 +28,7 @@
"admin"
:
"Admin"
,
"admin"
:
"Admin"
,
"explore"
:
"Explore"
,
"explore"
:
"Explore"
,
"sign-in"
:
"Sign in"
,
"sign-in"
:
"Sign in"
,
"sign-up"
:
"Sign up"
,
"sign-out"
:
"Sign out"
,
"sign-out"
:
"Sign out"
,
"back-to-home"
:
"Back to Home"
,
"back-to-home"
:
"Back to Home"
,
"type"
:
"Type"
,
"type"
:
"Type"
,
...
...
web/src/locales/vi.json
View file @
cf750541
...
@@ -28,6 +28,7 @@
...
@@ -28,6 +28,7 @@
"admin"
:
"Admin"
,
"admin"
:
"Admin"
,
"explore"
:
"Khám phá"
,
"explore"
:
"Khám phá"
,
"sign-in"
:
"Đăng nhập"
,
"sign-in"
:
"Đăng nhập"
,
"sign-up"
:
"Sign up"
,
"sign-out"
:
"Đăng xuất"
,
"sign-out"
:
"Đăng xuất"
,
"back-to-home"
:
"Về trang chủ"
,
"back-to-home"
:
"Về trang chủ"
,
"type"
:
"Kiểu"
,
"type"
:
"Kiểu"
,
...
...
web/src/locales/zh.json
View file @
cf750541
...
@@ -28,6 +28,7 @@
...
@@ -28,6 +28,7 @@
"admin"
:
"管理员"
,
"admin"
:
"管理员"
,
"explore"
:
"探索"
,
"explore"
:
"探索"
,
"sign-in"
:
"登录"
,
"sign-in"
:
"登录"
,
"sign-up"
:
"注册"
,
"sign-out"
:
"退出登录"
,
"sign-out"
:
"退出登录"
,
"back-to-home"
:
"回到主页"
,
"back-to-home"
:
"回到主页"
,
"type"
:
"类型"
,
"type"
:
"类型"
,
...
...
web/src/pages/Auth.tsx
View file @
cf750541
...
@@ -5,7 +5,6 @@ import * as api from "../helpers/api";
...
@@ -5,7 +5,6 @@ import * as api from "../helpers/api";
import
{
validate
,
ValidatorConfig
}
from
"../helpers/validator"
;
import
{
validate
,
ValidatorConfig
}
from
"../helpers/validator"
;
import
useLoading
from
"../hooks/useLoading"
;
import
useLoading
from
"../hooks/useLoading"
;
import
{
globalService
,
userService
}
from
"../services"
;
import
{
globalService
,
userService
}
from
"../services"
;
import
Icon
from
"../components/Icon"
;
import
toastHelper
from
"../components/Toast"
;
import
toastHelper
from
"../components/Toast"
;
import
"../less/auth.less"
;
import
"../less/auth.less"
;
...
@@ -20,7 +19,7 @@ const Auth = () => {
...
@@ -20,7 +19,7 @@ const Auth = () => {
const
{
t
,
i18n
}
=
useTranslation
();
const
{
t
,
i18n
}
=
useTranslation
();
const
navigate
=
useNavigate
();
const
navigate
=
useNavigate
();
const
pageLoadingState
=
useLoading
(
true
);
const
pageLoadingState
=
useLoading
(
true
);
const
[
s
iteHost
,
setSiteHost
]
=
useState
<
User
>
();
const
[
s
ystemStatus
,
setSystemStatus
]
=
useState
<
SystemStatus
>
();
const
[
email
,
setEmail
]
=
useState
(
""
);
const
[
email
,
setEmail
]
=
useState
(
""
);
const
[
password
,
setPassword
]
=
useState
(
""
);
const
[
password
,
setPassword
]
=
useState
(
""
);
const
actionBtnLoadingState
=
useLoading
(
false
);
const
actionBtnLoadingState
=
useLoading
(
false
);
...
@@ -28,7 +27,7 @@ const Auth = () => {
...
@@ -28,7 +27,7 @@ const Auth = () => {
useEffect
(()
=>
{
useEffect
(()
=>
{
api
.
getSystemStatus
().
then
(({
data
})
=>
{
api
.
getSystemStatus
().
then
(({
data
})
=>
{
const
{
data
:
status
}
=
data
;
const
{
data
:
status
}
=
data
;
setS
iteHost
(
status
.
host
);
setS
ystemStatus
(
status
);
if
(
status
.
profile
.
mode
===
"dev"
)
{
if
(
status
.
profile
.
mode
===
"dev"
)
{
setEmail
(
"demo@usememos.com"
);
setEmail
(
"demo@usememos.com"
);
setPassword
(
"secret"
);
setPassword
(
"secret"
);
...
@@ -47,9 +46,7 @@ const Auth = () => {
...
@@ -47,9 +46,7 @@ const Auth = () => {
setPassword
(
text
);
setPassword
(
text
);
};
};
const
handleSigninBtnsClick
=
async
(
e
:
React
.
FormEvent
<
EventTarget
>
)
=>
{
const
handleSigninBtnsClick
=
async
()
=>
{
e
.
preventDefault
();
if
(
actionBtnLoadingState
.
isLoading
)
{
if
(
actionBtnLoadingState
.
isLoading
)
{
return
;
return
;
}
}
...
@@ -77,14 +74,12 @@ const Auth = () => {
...
@@ -77,14 +74,12 @@ const Auth = () => {
}
}
}
catch
(
error
:
any
)
{
}
catch
(
error
:
any
)
{
console
.
error
(
error
);
console
.
error
(
error
);
toastHelper
.
error
(
error
.
response
.
data
.
message
);
toastHelper
.
error
(
error
.
response
.
data
.
error
);
}
}
actionBtnLoadingState
.
setFinish
();
actionBtnLoadingState
.
setFinish
();
};
};
const
handleSignUpAsHostBtnsClick
=
async
(
e
:
React
.
FormEvent
<
EventTarget
>
)
=>
{
const
handleSignUpBtnsClick
=
async
(
role
:
UserRole
)
=>
{
e
.
preventDefault
();
if
(
actionBtnLoadingState
.
isLoading
)
{
if
(
actionBtnLoadingState
.
isLoading
)
{
return
;
return
;
}
}
...
@@ -103,7 +98,7 @@ const Auth = () => {
...
@@ -103,7 +98,7 @@ const Auth = () => {
try
{
try
{
actionBtnLoadingState
.
setLoading
();
actionBtnLoadingState
.
setLoading
();
await
api
.
signup
(
email
,
password
,
"HOST"
);
await
api
.
signup
(
email
,
password
,
role
);
const
user
=
await
userService
.
doSignIn
();
const
user
=
await
userService
.
doSignIn
();
if
(
user
)
{
if
(
user
)
{
navigate
(
"/"
);
navigate
(
"/"
);
...
@@ -112,7 +107,7 @@ const Auth = () => {
...
@@ -112,7 +107,7 @@ const Auth = () => {
}
}
}
catch
(
error
:
any
)
{
}
catch
(
error
:
any
)
{
console
.
error
(
error
);
console
.
error
(
error
);
toastHelper
.
error
(
error
.
response
.
data
.
message
);
toastHelper
.
error
(
error
.
response
.
data
.
error
);
}
}
actionBtnLoadingState
.
setFinish
();
actionBtnLoadingState
.
setFinish
();
};
};
...
@@ -124,7 +119,7 @@ const Auth = () => {
...
@@ -124,7 +119,7 @@ const Auth = () => {
return
(
return
(
<
div
className=
"page-wrapper auth"
>
<
div
className=
"page-wrapper auth"
>
<
div
className=
"page-container"
>
<
div
className=
"page-container"
>
<
form
className=
"auth-form-wrapper"
onSubmit=
{
(
e
)
=>
(
siteHost
?
handleSigninBtnsClick
(
e
)
:
handleSignUpAsHostBtnsClick
(
e
))
}
>
<
div
className=
"auth-form-wrapper"
>
<
div
className=
"page-header-container"
>
<
div
className=
"page-header-container"
>
<
div
className=
"title-container"
>
<
div
className=
"title-container"
>
<
img
className=
"logo-img"
src=
"/logo-full.webp"
alt=
""
/>
<
img
className=
"logo-img"
src=
"/logo-full.webp"
alt=
""
/>
...
@@ -143,16 +138,39 @@ const Auth = () => {
...
@@ -143,16 +138,39 @@ const Auth = () => {
</
div
>
</
div
>
<
div
className=
"action-btns-container"
>
<
div
className=
"action-btns-container"
>
{
!
pageLoadingState
.
isLoading
&&
(
{
!
pageLoadingState
.
isLoading
&&
(
<
button
className=
{
`btn signin-btn ${actionBtnLoadingState.isLoading ? "requesting" : ""}`
}
type=
"submit"
>
<>
{
actionBtnLoadingState
.
isLoading
&&
<
Icon
.
Loader
className=
"img-icon"
/>
}
{
systemStatus
?.
host
?
(
{
siteHost
?
t
(
"common.sign-in"
)
:
t
(
"auth.signup-as-host"
)
}
<>
{
systemStatus
?.
allowSignUp
&&
(
<
button
className=
{
`btn signup-btn ${actionBtnLoadingState.isLoading ? "requesting" : ""}`
}
onClick=
{
()
=>
handleSignUpBtnsClick
(
"USER"
)
}
>
{
t
(
"common.sign-up"
)
}
</
button
>
</
button
>
)
}
)
}
<
button
className=
{
`btn signin-btn ${actionBtnLoadingState.isLoading ? "requesting" : ""}`
}
onClick=
{
handleSigninBtnsClick
}
>
{
t
(
"common.sign-in"
)
}
</
button
>
</>
)
:
(
<>
<
button
className=
{
`btn signin-btn ${actionBtnLoadingState.isLoading ? "requesting" : ""}`
}
onClick=
{
()
=>
handleSignUpBtnsClick
(
"HOST"
)
}
>
{
t
(
"auth.signup-as-host"
)
}
</
button
>
</>
)
}
</>
)
}
</
div
>
{
!
systemStatus
?.
host
&&
<
p
className=
"tip-text"
>
{
t
(
"auth.host-tip"
)
}
</
p
>
}
</
div
>
</
div
>
<
p
className=
{
`tip-text ${siteHost || pageLoadingState.isLoading ? "" : "host-tip"}`
}
>
{
siteHost
||
pageLoadingState
.
isLoading
?
t
(
"auth.not-host-tip"
)
:
t
(
"auth.host-tip"
)
}
</
p
>
</
form
>
<
div
className=
"footer-container"
>
<
div
className=
"footer-container"
>
<
div
className=
"language-container"
>
<
div
className=
"language-container"
>
<
span
className=
{
`locale-item ${i18n.language === "en" ? "active" : ""}`
}
onClick=
{
()
=>
handleLocaleItemClick
(
"en"
)
}
>
<
span
className=
{
`locale-item ${i18n.language === "en" ? "active" : ""}`
}
onClick=
{
()
=>
handleLocaleItemClick
(
"en"
)
}
>
...
...
web/src/types/modules/system.d.ts
View file @
cf750541
...
@@ -6,4 +6,11 @@ interface Profile {
...
@@ -6,4 +6,11 @@ interface Profile {
interface
SystemStatus
{
interface
SystemStatus
{
host
:
User
;
host
:
User
;
profile
:
Profile
;
profile
:
Profile
;
// System settings
allowSignUp
:
boolean
;
}
interface
SystemSetting
{
name
:
string
;
value
:
string
;
}
}
web/yarn.lock
View file @
cf750541
This diff is collapsed.
Click to expand it.
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