Compare commits

...

17 Commits

Author SHA1 Message Date
Yaodong Yu
afd3c4ed25 fix: fix bug form country code init error (#1591) 2023-02-27 22:07:28 +08:00
Yaodong Yu
5caceb4ae2 feat: fix bug that signup country code is undefined (#1590)
* feat: fix signup country code is undefined

* refactor: valid phone number in CN
2023-02-27 20:10:59 +08:00
Gucheng Wang
f5672357e6 fix resetting phone bug 2023-02-25 15:46:54 +08:00
Gucheng Wang
181e7c8c7d Refactor out getCountryCodeOption() 2023-02-25 15:25:47 +08:00
Gucheng Wang
36c5a9d09b Sort country list 2023-02-25 15:08:08 +08:00
Gucheng Wang
9acb3c499e Can search country code 2023-02-25 14:57:23 +08:00
Gucheng Wang
0e9a3b0f30 don't update provider in preview 2023-02-25 12:31:08 +08:00
Gucheng Wang
d104a292e7 fix normal user phone edit control 2023-02-25 11:47:34 +08:00
Gucheng Wang
8fbd5b1a74 disable demo prompt for get-organizations API 2023-02-25 11:01:48 +08:00
Gucheng Wang
f5a05ac534 improve application homepage 2023-02-25 10:50:50 +08:00
Gucheng Wang
05fade1d05 fix role list link error 2023-02-25 09:39:19 +08:00
Gucheng Wang
8aefa02036 fix message length 2023-02-25 08:36:24 +08:00
Yaodong Yu
3b6ec3e7c4 feat: improve saml idp err message (#1584) 2023-02-24 21:20:57 +08:00
Yang Luo
910816c7a3 Fix bug in GetLanguage() 2023-02-24 20:17:23 +08:00
Yaodong Yu
412a8b5da7 fix: init name is inconsistent with frontend (#1583) 2023-02-24 14:28:34 +08:00
Yaodong Yu
8ebd16a14e feat: fix resetting email and phone bug (#1579) 2023-02-23 18:06:13 +08:00
Gucheng Wang
44ec854465 Refactor getClientIdLabel() and getClientSecretLabel() 2023-02-23 17:57:46 +08:00
31 changed files with 174 additions and 174 deletions

View File

@@ -106,12 +106,15 @@ func GetConfigDataSourceName() string {
}
func GetLanguage(language string) string {
if language == "" {
if language == "" || language == "*" {
return "en"
}
if len(language) < 2 {
return "en"
}
language = language[0:2]
if strings.Contains(GetConfigString("languages"), language) {
return language
} else {

View File

@@ -188,7 +188,7 @@ func (c *ApiController) ResetEmailOrPhone() {
checkDest := dest
organization := object.GetOrganizationByUser(user)
if destType == "phone" {
if object.HasUserByField(user.Owner, "phone", user.Phone) {
if object.HasUserByField(user.Owner, "phone", dest) {
c.ResponseError(c.T("check:Phone already exists"))
return
}
@@ -208,7 +208,7 @@ func (c *ApiController) ResetEmailOrPhone() {
return
}
} else if destType == "email" {
if object.HasUserByField(user.Owner, "email", user.Email) {
if object.HasUserByField(user.Owner, "email", dest) {
c.ResponseError(c.T("check:Email already exists"))
return
}

View File

@@ -50,7 +50,7 @@ func getBuiltInAccountItems() []*AccountItem {
{Name: "Password", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "Email", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Phone", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "CountryCode", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Country code", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Country/Region", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Location", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Affiliation", Visible: true, ViewRule: "Public", ModifyRule: "Self"},

View File

@@ -43,7 +43,7 @@ type Payment struct {
PayUrl string `xorm:"varchar(2000)" json:"payUrl"`
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
State string `xorm:"varchar(100)" json:"state"`
Message string `xorm:"varchar(1000)" json:"message"`
Message string `xorm:"varchar(2000)" json:"message"`
PersonName string `xorm:"varchar(100)" json:"personName"`
PersonIdCard string `xorm:"varchar(100)" json:"personIdCard"`

View File

@@ -230,16 +230,20 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
// base64 decode
defated, err := base64.StdEncoding.DecodeString(samlRequest)
if err != nil {
return "", "", method, fmt.Errorf("err: %s", err.Error())
return "", "", method, fmt.Errorf("err: Failed to decode SAML request , %s", err.Error())
}
// decompress
var buffer bytes.Buffer
rdr := flate.NewReader(bytes.NewReader(defated))
io.Copy(&buffer, rdr)
_, err = io.Copy(&buffer, rdr)
if err != nil {
return "", "", "", err
}
var authnRequest saml.AuthnRequest
err = xml.Unmarshal(buffer.Bytes(), &authnRequest)
if err != nil {
return "", "", method, fmt.Errorf("err: %s", err.Error())
return "", "", method, fmt.Errorf("err: Failed to unmarshal AuthnRequest, please check the SAML request. %s", err.Error())
}
// verify samlRequest
@@ -252,14 +256,15 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
block, _ := pem.Decode([]byte(cert.Certificate))
certificate := base64.StdEncoding.EncodeToString(block.Bytes)
_, originBackend := getOriginFromHost(host)
// redirect Url (Assertion Consumer Url)
if application.SamlReplyUrl != "" {
method = "POST"
authnRequest.AssertionConsumerServiceURL = application.SamlReplyUrl
} else if authnRequest.AssertionConsumerServiceURL == "" {
return "", "", "", fmt.Errorf("err: SAML request don't has attribute 'AssertionConsumerServiceURL' in <samlp:AuthnRequest>")
}
_, originBackend := getOriginFromHost(host)
// build signedResponse
samlResponse, _ := NewSamlResponse(user, originBackend, certificate, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, authnRequest.ID, application.RedirectUris)
randomKeyStore := &X509Key{
@@ -279,7 +284,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
doc.SetRoot(samlResponse)
xmlBytes, err := doc.WriteToBytes()
if err != nil {
return "", "", method, fmt.Errorf("err: %s", err.Error())
return "", "", method, fmt.Errorf("err: Failed to serializes the SAML request into bytes, %s", err.Error())
}
// compress
@@ -287,15 +292,21 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
flated := bytes.NewBuffer(nil)
writer, err := flate.NewWriter(flated, flate.DefaultCompression)
if err != nil {
return "", "", method, fmt.Errorf("err: %s", err.Error())
return "", "", method, err
}
_, err = writer.Write(xmlBytes)
if err != nil {
return "", "", "", err
}
err = writer.Close()
if err != nil {
return "", "", "", err
}
writer.Write(xmlBytes)
writer.Close()
xmlBytes = flated.Bytes()
}
// base64 encode
res := base64.StdEncoding.EncodeToString(xmlBytes)
return res, authnRequest.AssertionConsumerServiceURL, method, nil
return res, authnRequest.AssertionConsumerServiceURL, method, err
}
// NewSamlResponse11 return a saml1.1 response(not 2.0)
@@ -359,7 +370,10 @@ func NewSamlResponse11(user *User, requestID string, host string) *etree.Element
data, _ := json.Marshal(user)
tmp := map[string]string{}
json.Unmarshal(data, &tmp)
err := json.Unmarshal(data, &tmp)
if err != nil {
panic(err)
}
for k, v := range tmp {
if v != "" {

View File

@@ -449,7 +449,7 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
if len(columns) == 0 {
columns = []string{
"owner", "display_name", "avatar",
"location", "address", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag", "signup_application",
"location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag", "signup_application",
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts",
"signin_wrong_times", "last_signin_wrong_time",
}

View File

@@ -41,7 +41,7 @@ func IsPhoneValid(phone string, countryCode string) bool {
}
func IsPhoneAllowInRegin(countryCode string, allowRegions []string) bool {
return !ContainsString(allowRegions, countryCode)
return ContainsString(allowRegions, countryCode)
}
func GetE164Number(phone string, countryCode string) (string, bool) {

View File

@@ -184,20 +184,19 @@ class OrganizationEditPage extends React.Component {
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Supported country code"), i18next.t("general:Supported country code - Tooltip"))} :
{Setting.getLabel(i18next.t("general:Supported country codes"), i18next.t("general:Supported country codes - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode={"multiple"} style={{width: "100%"}} value={this.state.organization.countryCodes ?? []}
options={Setting.getCountriesData().map(country => {
return Setting.getOption(
<>
{Setting.countryFlag(country)}
{`${country.name} +${country.phone}`}
</>,
country.code);
})} onChange={value => {
onChange={value => {
this.updateOrganizationField("countryCodes", value);
}} />
}}
filterOption={(input, option) => (option?.text ?? "").toLowerCase().includes(input.toLowerCase())}
>
{
Setting.getCountryCodeData().map((country) => Setting.getCountryCodeOption(country))
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >

View File

@@ -37,7 +37,7 @@ class OrganizationListPage extends BaseListPage {
defaultAvatar: `${Setting.StaticBaseUrl}/img/casbin.svg`,
defaultApplication: "",
tags: [],
languages: ["en", "zh", "es", "fr", "de", "ja", "ko", "ru", "vi"],
languages: Setting.Countries.map(item => item.key),
masterPassword: "",
enableSoftDeletion: false,
isProfilePublic: true,

View File

@@ -82,20 +82,20 @@ class ProviderEditPage extends React.Component {
});
}
getClientIdLabel() {
switch (this.state.provider.category) {
getClientIdLabel(provider) {
switch (provider.category) {
case "Email":
return Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"));
case "SMS":
if (this.state.provider.type === "Volc Engine SMS") {
if (provider.type === "Volc Engine SMS") {
return Setting.getLabel(i18next.t("provider:Access key"), i18next.t("provider:Access key - Tooltip"));
} else if (this.state.provider.type === "Huawei Cloud SMS") {
} else if (provider.type === "Huawei Cloud SMS") {
return Setting.getLabel(i18next.t("provider:App key"), i18next.t("provider:App key - Tooltip"));
} else {
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
}
case "Captcha":
if (this.state.provider.type === "Aliyun Captcha") {
if (provider.type === "Aliyun Captcha") {
return Setting.getLabel(i18next.t("provider:Access key"), i18next.t("provider:Access key - Tooltip"));
} else {
return Setting.getLabel(i18next.t("provider:Site key"), i18next.t("provider:Site key - Tooltip"));
@@ -105,20 +105,20 @@ class ProviderEditPage extends React.Component {
}
}
getClientSecretLabel() {
switch (this.state.provider.category) {
getClientSecretLabel(provider) {
switch (provider.category) {
case "Email":
return Setting.getLabel(i18next.t("login:Password"), i18next.t("login:Password - Tooltip"));
case "SMS":
if (this.state.provider.type === "Volc Engine SMS") {
if (provider.type === "Volc Engine SMS") {
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:SecretAccessKey - Tooltip"));
} else if (this.state.provider.type === "Huawei Cloud SMS") {
} else if (provider.type === "Huawei Cloud SMS") {
return Setting.getLabel(i18next.t("provider:App secret"), i18next.t("provider:AppSecret - Tooltip"));
} else {
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
}
case "Captcha":
if (this.state.provider.type === "Aliyun Captcha") {
if (provider.type === "Aliyun Captcha") {
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:SecretAccessKey - Tooltip"));
} else {
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
@@ -416,7 +416,7 @@ class ProviderEditPage extends React.Component {
<React.Fragment>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{this.getClientIdLabel()}
{this.getClientIdLabel(this.state.provider)}
</Col>
<Col span={22} >
<Input value={this.state.provider.clientId} onChange={e => {
@@ -426,7 +426,7 @@ class ProviderEditPage extends React.Component {
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{this.getClientSecretLabel()}
{this.getClientSecretLabel(this.state.provider)}
</Col>
<Col span={22} >
<Input value={this.state.provider.clientSecret} onChange={e => {

View File

@@ -25,7 +25,7 @@ export const ResetModal = (props) => {
const [confirmLoading, setConfirmLoading] = React.useState(false);
const [dest, setDest] = React.useState("");
const [code, setCode] = React.useState("");
const {buttonText, destType, application} = props;
const {buttonText, destType, application, account} = props;
const showModal = () => {
setVisible(true);
@@ -87,7 +87,7 @@ export const ResetModal = (props) => {
<Row style={{width: "100%", marginBottom: "20px"}}>
<Input
addonBefore={destType === "email" ? i18next.t("user:New Email") : i18next.t("user:New phone")}
prefix={destType === "email" ? <MailOutlined /> : <PhoneOutlined />}
prefix={destType === "email" ? <React.Fragment><MailOutlined />&nbsp;&nbsp;</React.Fragment> : (<React.Fragment><PhoneOutlined />&nbsp;&nbsp;{`+${Setting.getCountryCode(account.countryCode)}`}&nbsp;</React.Fragment>)}
placeholder={placeholder}
onChange={e => setDest(e.target.value)}
/>

View File

@@ -82,7 +82,7 @@ class RoleListPage extends BaseListPage {
...this.getColumnSearchProps("name"),
render: (text, record, index) => {
return (
<Link to={`/roles/${text}`}>
<Link to={`/roles/${record.owner}/${record.name}`}>
{text}
</Link>
);

View File

@@ -41,17 +41,15 @@ class SelectRegionBox extends React.Component {
defaultValue={this.props.defaultValue || undefined}
placeholder="Please select country/region"
onChange={(value => {this.onChange(value);})}
filterOption={(input, option) =>
(option?.label ?? "").toLowerCase().includes(input.toLowerCase())
}
filterOption={(input, option) => (option?.label ?? "").toLowerCase().includes(input.toLowerCase())}
filterSort={(optionA, optionB) =>
(optionA?.label ?? "").toLowerCase().localeCompare((optionB?.label ?? "").toLowerCase())
}
>
{
Setting.getCountriesData().map((item) => (
Setting.getCountryCodeData().map((item) => (
<Option key={item.code} value={item.code} label={`${item.name} (${item.code})`} >
{Setting.countryFlag(item)}
{Setting.getCountryImage(item)}
{`${item.name} (${item.code})`}
</Option>
))

View File

@@ -14,7 +14,7 @@
import React from "react";
import {Link} from "react-router-dom";
import {Checkbox, Form, Modal, Tag, Tooltip, message, theme} from "antd";
import {Checkbox, Form, Modal, Select, Tag, Tooltip, message, theme} from "antd";
import {QuestionCircleTwoTone} from "@ant-design/icons";
import {isMobile as isMobileDevice} from "react-device-detect";
import "./i18n";
@@ -26,6 +26,8 @@ import * as Conf from "./Conf";
import * as phoneNumber from "libphonenumber-js";
import * as path from "path-browserify";
const {Option} = Select;
export const ServerUrl = "";
// export const StaticBaseUrl = "https://cdn.jsdelivr.net/gh/casbin/static";
@@ -92,7 +94,7 @@ export const OtherProviderInfo = {
url: "https://www.huaweicloud.com/product/msgsms.html",
},
"Twilio SMS": {
logo: `${StaticBaseUrl}/img/social_twilio.png`,
logo: `${StaticBaseUrl}/img/social_twilio.svg`,
url: "https://www.twilio.com/messaging",
},
"SmsBao SMS": {
@@ -206,7 +208,11 @@ export function initCountries() {
return countries;
}
export function getCountriesData(countryCodes = phoneNumber.getCountries()) {
export function getCountryCode(country) {
return phoneNumber.getCountryCallingCode(country);
}
export function getCountryCodeData(countryCodes = phoneNumber.getCountries()) {
return countryCodes?.map((countryCode) => {
if (phoneNumber.isSupportedCountry(countryCode)) {
const name = initCountries().getName(countryCode, getLanguage());
@@ -216,17 +222,28 @@ export function getCountriesData(countryCodes = phoneNumber.getCountries()) {
phone: phoneNumber.getCountryCallingCode(countryCode),
};
}
});
}).filter(item => item.name !== "")
.sort((a, b) => a.phone - b.phone);
}
export function countryFlag(country) {
export function getCountryCodeOption(country) {
return (
<Option key={country.code} value={country.code} label={`+${country.phone}`} text={`${country.name}, ${country.code}, ${country.phone}`} >
<div style={{display: "flex", justifyContent: "space-between", marginRight: "10px"}}>
<div>
{getCountryImage(country)}
{`${country.name}`}
</div>
{`+${country.phone}`}
</div>
</Option>
);
}
export function getCountryImage(country) {
return <img src={`${StaticBaseUrl}/flag-icons/${country.code}.svg`} alt={country.name} height={20} style={{marginRight: 10}} />;
}
export function getPhoneCodeFromCountryCode(countryCode) {
return phoneNumber.isSupportedCountry(countryCode) ? phoneNumber.getCountryCallingCode(countryCode) : "";
}
export function initServerUrl() {
// const hostname = window.location.hostname;
// if (hostname === "localhost") {
@@ -332,13 +349,15 @@ export function isValidEmail(email) {
}
export function isValidPhone(phone, countryCode = "") {
if (countryCode !== "") {
if (countryCode !== "" && countryCode !== "CN") {
return phoneNumber.isValidPhoneNumber(phone, countryCode);
}
// // https://learnku.com/articles/31543, `^s*$` filter empty email individually.
// https://learnku.com/articles/31543, `^s*$` filter empty email individually.
const phoneCnRegex = /^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$/;
const phoneRegex = /[0-9]{4,15}$/;
return phoneRegex.test(phone);
return countryCode === "CN" ? phoneCnRegex.test(phone) : phoneRegex.test(phone);
}
export function isValidInvoiceTitle(invoiceTitle) {

View File

@@ -29,7 +29,7 @@ import SelectRegionBox from "./SelectRegionBox";
import WebAuthnCredentialTable from "./WebauthnCredentialTable";
import ManagedAccountTable from "./ManagedAccountTable";
import PropertyTable from "./propertyTable";
import PhoneNumberInput from "./common/PhoneNumberInput";
import {PhoneNumberInput} from "./common/PhoneNumberInput";
const {Option} = Select;
@@ -285,25 +285,18 @@ class UserEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Email"), i18next.t("general:Email - Tooltip"))} :
</Col>
<Col style={{paddingRight: "20px"}} span={11} >
{Setting.isLocalAdminUser(this.props.account) ?
(<Input value={this.state.user.email}
<Input
value={this.state.user.email}
style={{width: "280Px"}}
disabled={disabled}
disabled={!Setting.isLocalAdminUser(this.props.account) ? true : disabled}
onChange={e => {
this.updateUserField("email", e.target.value);
}} />) :
(<Select virtual={false} value={this.state.user.email}
style={{width: "280Px"}}
options={[Setting.getItem(this.state.user.email, this.state.user.email)]}
disabled={disabled}
onChange={e => {
this.updateUserField("email", e.target.value);
}} />)
}
}}
/>
</Col>
<Col span={Setting.isMobile() ? 22 : 11} >
{/* backend auto get the current user, so admin can not edit. Just self can reset*/}
{this.isSelf() ? <ResetModal application={this.state.application} disabled={disabled} buttonText={i18next.t("user:Reset Email...")} destType={"email"} /> : null}
{this.isSelf() ? <ResetModal application={this.state.application} account={this.props.account} disabled={disabled} buttonText={i18next.t("user:Reset Email...")} destType={"email"} /> : null}
</Col>
</Row>
);
@@ -314,10 +307,10 @@ class UserEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Phone"), i18next.t("general:Phone - Tooltip"))} :
</Col>
<Col style={{paddingRight: "20px"}} span={11} >
{Setting.isLocalAdminUser(this.props.account) ?
<Input.Group compact style={{width: "280Px"}}>
<PhoneNumberInput
style={{width: "30%"}}
// disabled={!Setting.isLocalAdminUser(this.props.account) ? true : disabled}
value={this.state.user.countryCode}
onChange={(value) => {
this.updateUserField("countryCode", value);
@@ -326,22 +319,14 @@ class UserEditPage extends React.Component {
/>
<Input value={this.state.user.phone}
style={{width: "70%"}}
disabled={disabled}
disabled={!Setting.isLocalAdminUser(this.props.account) ? true : disabled}
onChange={e => {
this.updateUserField("phone", e.target.value);
}} />
</Input.Group>
:
(<Select virtual={false} value={this.state.user.phone === "" ? null : `+${Setting.getPhoneCodeFromCountryCode(this.state.user.countryCode)} ${this.state.user.phone}`}
options={this.state.user.phone === "" ? null : [Setting.getItem(`+${Setting.getPhoneCodeFromCountryCode(this.state.user.countryCode)} ${this.state.user.phone}`, this.state.user.phone)]}
disabled={disabled}
style={{width: "280px"}}
onChange={e => {
this.updateUserField("phone", e.target.value);
}} />)}
</Col>
<Col span={Setting.isMobile() ? 24 : 11} >
{this.isSelf() ? (<ResetModal application={this.state.application} disabled={disabled} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null}
{this.isSelf() ? (<ResetModal application={this.state.application} account={this.props.account} disabled={disabled} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null}
</Col>
</Row>
);

View File

@@ -26,7 +26,7 @@ import SelectRegionBox from "../SelectRegionBox";
import CustomGithubCorner from "../CustomGithubCorner";
import SelectLanguageBox from "../SelectLanguageBox";
import {withRouter} from "react-router-dom";
import PhoneNumberInput from "../common/PhoneNumberInput";
import {PhoneNumberInput} from "../common/PhoneNumberInput";
const formItemLayout = {
labelCol: {
@@ -82,7 +82,7 @@ class SignupPage extends React.Component {
this.form = React.createRef();
}
UNSAFE_componentWillMount() {
componentDidMount() {
let applicationName = this.state.applicationName;
const oAuthParams = Util.getOAuthGetParameters();
if (oAuthParams !== null) {
@@ -390,39 +390,26 @@ class SignupPage extends React.Component {
required: required,
message: i18next.t("signup:Please select your country code!"),
},
{
validator: (_, value) => {
if (this.state.phone !== "" && !Setting.isValidPhone(this.state.phone, this.state.countryCode)) {
this.setState({validPhone: false});
return Promise.reject(i18next.t("signup:The input is not valid Phone!"));
}
this.setState({validPhone: true});
return Promise.resolve();
},
},
]}
>
<PhoneNumberInput
showSearsh={true}
style={{width: "35%"}}
value={this.state.countryCode}
onChange={(value) => {this.setState({countryCode: value});}}
countryCodes={this.getApplicationObj().organizationObj.countryCodes}
/>
</Form.Item>
<Form.Item
name="phone"
key="phone"
dependencies={["countryCode"]}
noStyle
rules={[
{
required: required,
message: i18next.t("signup:Please input your phone number!"),
},
{
({getFieldValue}) => ({
validator: (_, value) => {
if (this.state.phone !== "" && !Setting.isValidPhone(this.state.phone, this.state.countryCode)) {
if (value !== "" && !Setting.isValidPhone(value, getFieldValue("countryCode"))) {
this.setState({validPhone: false});
return Promise.reject(i18next.t("signup:The input is not valid Phone!"));
}
@@ -430,7 +417,7 @@ class SignupPage extends React.Component {
this.setState({validPhone: true});
return Promise.resolve();
},
},
}),
]}
>
<Input
@@ -456,6 +443,7 @@ class SignupPage extends React.Component {
method={"signup"}
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationName(application)]}
application={application}
countryCode={this.state.countryCode}
/>
</Form.Item>
</React.Fragment>
@@ -560,6 +548,7 @@ class SignupPage extends React.Component {
initialValues={{
application: application.name,
organization: application.organization,
countryCode: application.organizationObj.countryCodes?.[0],
}}
size="large"
layout={Setting.isMobile() ? "vertical" : "horizontal"}

View File

@@ -51,7 +51,9 @@ window.fetch = async(url, option = {}) => {
return new Promise((resolve, reject) => {
originalFetch(url, option).then(res => {
if (!url.startsWith("/api/get-organizations")) {
responseFilters.forEach(filter => filter(res.clone()));
}
resolve(res);
});
});

View File

@@ -109,12 +109,13 @@ export function setPassword(userOwner, userName, oldPassword, newPassword) {
}).then(res => res.json());
}
export function sendCode(checkType, checkId, checkKey, method, dest, type, applicationId, checkUser = "") {
export function sendCode(checkType, checkId, checkKey, method, countryCode, dest, type, applicationId, checkUser = "") {
const formData = new FormData();
formData.append("checkType", checkType);
formData.append("checkId", checkId);
formData.append("checkKey", checkKey);
formData.append("method", method);
formData.append("countryCode", countryCode);
formData.append("dest", dest);
formData.append("type", type);
formData.append("applicationId", applicationId);

View File

@@ -61,8 +61,13 @@ class HomePage extends React.Component {
}
} else {
this.state.applications.forEach(application => {
let homepageUrl = application.homepageUrl;
if (homepageUrl === "<custom-url>") {
homepageUrl = this.props.account.homepage;
}
items.push({
link: application.homepageUrl, name: application.displayName, organizer: application.description, logo: application.logo, createdTime: "",
link: homepageUrl, name: application.displayName, organizer: application.description, logo: application.logo, createdTime: "",
});
});
}

View File

@@ -16,12 +16,10 @@ import {Button} from "antd";
import React from "react";
import i18next from "i18next";
import {CaptchaModal} from "./CaptchaModal";
import * as ProviderBackend from "../backend/ProviderBackend";
import * as UserBackend from "../backend/UserBackend";
export const CaptchaPreview = ({
provider,
providerName,
clientSecret,
captchaType,
subType,
@@ -41,9 +39,10 @@ export const CaptchaPreview = ({
provider.providerUrl = providerUrl;
if (clientSecret !== "***") {
provider.clientSecret = clientSecret;
ProviderBackend.updateProvider(owner, providerName, provider).then(() => {
// ProviderBackend.updateProvider(owner, providerName, provider).then(() => {
// setOpen(true);
// });
setOpen(true);
});
} else {
setOpen(true);
}

View File

@@ -16,43 +16,29 @@ import {Select} from "antd";
import * as Setting from "../Setting";
import React from "react";
const {Option} = Select;
export default function PhoneNumberInput(props) {
const {onChange, style, showSearch} = props;
const value = props.value ?? "CN";
export const PhoneNumberInput = (props) => {
const {onChange, style, disabled, value} = props;
const countryCodes = props.countryCodes ?? [];
const handleOnChange = (e) => {
onChange?.(e);
const handleOnChange = (value) => {
onChange?.(value);
};
return (
<Select
virtual={false}
showSearch
style={style}
disabled={disabled}
value={value}
dropdownMatchSelectWidth={false}
optionLabelProp={"label"}
showSearch={showSearch}
onChange={handleOnChange}
filterOption={(input, option) =>
(option?.label ?? "").toLowerCase().includes(input.toLowerCase())
}
filterOption={(input, option) => (option?.text ?? "").toLowerCase().includes(input.toLowerCase())}
>
{
Setting.getCountriesData(countryCodes).map((country) => (
<Option key={country.code} value={country.code} label={`+${country.phone}`} >
<div style={{display: "flex", justifyContent: "space-between"}}>
<div>
{Setting.countryFlag(country)}
{`${country.name}`}
</div>
{`+${country.phone}`}
</div>
</Option>
))
Setting.getCountryCodeData(countryCodes).map((country) => Setting.getCountryCodeOption(country))
}
</Select>
);
}
};

View File

@@ -22,7 +22,7 @@ import {CaptchaWidget} from "./CaptchaWidget";
const {Search} = Input;
export const SendCodeInput = (props) => {
const {disabled, textBefore, onChange, onButtonClickArgs, application, method} = props;
const {disabled, textBefore, onChange, onButtonClickArgs, application, method, countryCode} = props;
const [visible, setVisible] = React.useState(false);
const [key, setKey] = React.useState("");
const [captchaImg, setCaptchaImg] = React.useState("");
@@ -53,7 +53,7 @@ export const SendCodeInput = (props) => {
const handleOk = () => {
setVisible(false);
setButtonLoading(true);
UserBackend.sendCode(checkType, checkId, key, method, ...onButtonClickArgs).then(res => {
UserBackend.sendCode(checkType, checkId, key, method, countryCode, ...onButtonClickArgs).then(res => {
setKey("");
setButtonLoading(false);
if (res) {
@@ -70,7 +70,7 @@ export const SendCodeInput = (props) => {
const loadCaptcha = () => {
UserBackend.getCaptcha(application.owner, application.name, false).then(res => {
if (res.type === "none") {
UserBackend.sendCode("none", "", "", method, ...onButtonClickArgs).then(res => {
UserBackend.sendCode("none", "", "", method, countryCode, ...onButtonClickArgs).then(res => {
if (res) {
handleCountDown(60);
}

View File

@@ -251,8 +251,8 @@
"Successfully added": "Successfully added",
"Successfully deleted": "Successfully deleted",
"Successfully saved": "Successfully saved",
"Supported country code": "Supported country code",
"Supported country code - Tooltip": "Supported country code - Tooltip",
"Supported country codes": "Supported country codes",
"Supported country codes - Tooltip": "Supported country codes - Tooltip",
"Swagger": "Swagger",
"Sync": "Sync",
"Syncers": "Syncers",

View File

@@ -251,8 +251,8 @@
"Successfully added": "Successfully added",
"Successfully deleted": "Successfully deleted",
"Successfully saved": "Successfully saved",
"Supported country code": "Supported country code",
"Supported country code - Tooltip": "Supported country code - Tooltip",
"Supported country codes": "Supported country codes",
"Supported country codes - Tooltip": "Supported country codes - Tooltip",
"Swagger": "Swagger",
"Sync": "Sync",
"Syncers": "Syncers",

View File

@@ -251,8 +251,8 @@
"Successfully added": "Successfully added",
"Successfully deleted": "Successfully deleted",
"Successfully saved": "Successfully saved",
"Supported country code": "Supported country code",
"Supported country code - Tooltip": "Supported country code - Tooltip",
"Supported country codes": "Supported country codes",
"Supported country codes - Tooltip": "Supported country codes - Tooltip",
"Swagger": "Swagger",
"Sync": "Sincronizador",
"Syncers": "Sincronizadores",

View File

@@ -251,8 +251,8 @@
"Successfully added": "Successfully added",
"Successfully deleted": "Successfully deleted",
"Successfully saved": "Successfully saved",
"Supported country code": "Supported country code",
"Supported country code - Tooltip": "Supported country code - Tooltip",
"Supported country codes": "Supported country codes",
"Supported country codes - Tooltip": "Supported country codes - Tooltip",
"Swagger": "Swagger",
"Sync": "Sync",
"Syncers": "Synchronisateurs",

View File

@@ -251,8 +251,8 @@
"Successfully added": "Successfully added",
"Successfully deleted": "Successfully deleted",
"Successfully saved": "Successfully saved",
"Supported country code": "Supported country code",
"Supported country code - Tooltip": "Supported country code - Tooltip",
"Supported country codes": "Supported country codes",
"Supported country codes - Tooltip": "Supported country codes - Tooltip",
"Swagger": "Swagger",
"Sync": "Sync",
"Syncers": "Syncers",

View File

@@ -251,8 +251,8 @@
"Successfully added": "Successfully added",
"Successfully deleted": "Successfully deleted",
"Successfully saved": "Successfully saved",
"Supported country code": "Supported country code",
"Supported country code - Tooltip": "Supported country code - Tooltip",
"Supported country codes": "Supported country codes",
"Supported country codes - Tooltip": "Supported country codes - Tooltip",
"Swagger": "Swagger",
"Sync": "Sync",
"Syncers": "Syncers",

View File

@@ -251,8 +251,8 @@
"Successfully added": "Successfully added",
"Successfully deleted": "Successfully deleted",
"Successfully saved": "Successfully saved",
"Supported country code": "Supported country code",
"Supported country code - Tooltip": "Supported country code - Tooltip",
"Supported country codes": "Supported country codes",
"Supported country codes - Tooltip": "Supported country codes - Tooltip",
"Swagger": "Swagger",
"Sync": "Sync",
"Syncers": "Синхронизаторы",

View File

@@ -251,8 +251,8 @@
"Successfully added": "Successfully added",
"Successfully deleted": "Successfully deleted",
"Successfully saved": "Successfully saved",
"Supported country code": "Supported country code",
"Supported country code - Tooltip": "Supported country code - Tooltip",
"Supported country codes": "Supported country codes",
"Supported country codes - Tooltip": "Supported country codes - Tooltip",
"Swagger": "Swagger",
"Sync": "Sync",
"Syncers": "Syncers",

View File

@@ -251,8 +251,8 @@
"Successfully added": "添加成功",
"Successfully deleted": "删除成功",
"Successfully saved": "保存成功",
"Supported country code": "支持的国家代码",
"Supported country code - Tooltip": "支持发送短信的国家 - Tooltip",
"Supported country codes": "支持的国家代码",
"Supported country codes - Tooltip": "支持发送短信的国家 - Tooltip",
"Swagger": "API文档",
"Sync": "同步",
"Syncers": "同步器",