mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-04 21:30:24 +08:00
feat: support multiple country codes for sending SMS (#1557)
* feat: support multiple country code * feat: improve UI * feat: migrate accountItem * fix: Aliyun compatible * fix: phone validate * fix: typo
This commit is contained in:
@ -15,7 +15,6 @@ describe("Login test", () => {
|
||||
"password": "123",
|
||||
"autoSignin": true,
|
||||
"type": "login",
|
||||
"phonePrefix": "86",
|
||||
},
|
||||
}).then((Response) => {
|
||||
expect(Response).property("body").property("status").to.equal("ok");
|
||||
@ -40,7 +39,6 @@ describe("Login test", () => {
|
||||
"password": "1234",
|
||||
"autoSignin": true,
|
||||
"type": "login",
|
||||
"phonePrefix": "86",
|
||||
},
|
||||
}).then((Response) => {
|
||||
expect(Response).property("body").property("status").to.equal("error");
|
||||
|
@ -34,7 +34,6 @@ Cypress.Commands.add('login', ()=>{
|
||||
"password": "123",
|
||||
"autoSignin": true,
|
||||
"type": "login",
|
||||
"phonePrefix": "86",
|
||||
},
|
||||
}).then((Response) => {
|
||||
expect(Response).property("body").property("status").to.equal("ok");
|
||||
|
@ -21,6 +21,7 @@
|
||||
"file-saver": "^2.0.5",
|
||||
"i18n-iso-countries": "^7.0.0",
|
||||
"i18next": "^19.8.9",
|
||||
"libphonenumber-js": "^1.10.19",
|
||||
"moment": "^2.29.1",
|
||||
"qs": "^6.10.2",
|
||||
"react": "^18.2.0",
|
||||
|
@ -78,6 +78,7 @@ class AccountTable extends React.Component {
|
||||
{name: "Password", displayName: i18next.t("general:Password")},
|
||||
{name: "Email", displayName: i18next.t("general:Email")},
|
||||
{name: "Phone", displayName: i18next.t("general:Phone")},
|
||||
{name: "Country code", displayName: i18next.t("user:Country code")},
|
||||
{name: "Country/Region", displayName: i18next.t("user:Country/Region")},
|
||||
{name: "Location", displayName: i18next.t("user:Location")},
|
||||
{name: "Affiliation", displayName: i18next.t("user:Affiliation")},
|
||||
|
@ -184,12 +184,20 @@ class OrganizationEditPage extends React.Component {
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Phone prefix"), i18next.t("general:Phone prefix - Tooltip"))} :
|
||||
{Setting.getLabel(i18next.t("general:Supported country code"), i18next.t("general:Supported country code - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input addonBefore={"+"} value={this.state.organization.phonePrefix} onChange={e => {
|
||||
this.updateOrganizationField("phonePrefix", e.target.value);
|
||||
}} />
|
||||
<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 => {
|
||||
this.updateOrganizationField("countryCodes", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
@ -257,22 +265,13 @@ class OrganizationEditPage extends React.Component {
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} mode="tags" style={{width: "100%"}}
|
||||
value={this.state.organization.languages}
|
||||
options={Setting.Countries.map((item) => {
|
||||
return Setting.getOption(item.label, item.key);
|
||||
})}
|
||||
value={this.state.organization.languages ?? []}
|
||||
onChange={(value => {
|
||||
this.updateOrganizationField("languages", value);
|
||||
})} >
|
||||
{
|
||||
[
|
||||
{value: "en", label: "English"},
|
||||
{value: "zh", label: "简体中文"},
|
||||
{value: "es", label: "Español"},
|
||||
{value: "fr", label: "Français"},
|
||||
{value: "de", label: "Deutsch"},
|
||||
{value: "ja", label: "日本語"},
|
||||
{value: "ko", label: "한국어"},
|
||||
{value: "ru", label: "Русский"},
|
||||
].map((item, index) => <Option key={index} value={item.value}>{item.label}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
|
@ -33,7 +33,7 @@ class OrganizationListPage extends BaseListPage {
|
||||
favicon: `${Setting.StaticBaseUrl}/img/favicon.png`,
|
||||
passwordType: "plain",
|
||||
PasswordSalt: "",
|
||||
phonePrefix: "86",
|
||||
countryCodes: ["CN"],
|
||||
defaultAvatar: `${Setting.StaticBaseUrl}/img/casbin.svg`,
|
||||
defaultApplication: "",
|
||||
tags: [],
|
||||
|
@ -18,16 +18,6 @@ import {Dropdown} from "antd";
|
||||
import "./App.less";
|
||||
import {GlobalOutlined} from "@ant-design/icons";
|
||||
|
||||
export const Countries = [{label: "English", key: "en", country: "US", alt: "English"},
|
||||
{label: "简体中文", key: "zh", country: "CN", alt: "简体中文"},
|
||||
{label: "Español", key: "es", country: "ES", alt: "Español"},
|
||||
{label: "Français", key: "fr", country: "FR", alt: "Français"},
|
||||
{label: "Deutsch", key: "de", country: "DE", alt: "Deutsch"},
|
||||
{label: "日本語", key: "ja", country: "JP", alt: "日本語"},
|
||||
{label: "한국어", key: "ko", country: "KR", alt: "한국어"},
|
||||
{label: "Русский", key: "ru", country: "RU", alt: "Русский"},
|
||||
];
|
||||
|
||||
function flagIcon(country, alt) {
|
||||
return (
|
||||
<img width={24} alt={alt} src={`${Setting.StaticBaseUrl}/flag-icons/${country}.svg`} />
|
||||
@ -39,15 +29,15 @@ class SelectLanguageBox extends React.Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
languages: props.languages ?? ["en", "zh", "es", "fr", "de", "ja", "ko", "ru"],
|
||||
languages: props.languages ?? Setting.Countries.map(item => item.key),
|
||||
};
|
||||
|
||||
Countries.forEach((country) => {
|
||||
Setting.Countries.forEach((country) => {
|
||||
new Image().src = `${Setting.StaticBaseUrl}/flag-icons/${country.country}.svg`;
|
||||
});
|
||||
}
|
||||
|
||||
items = Countries.map((country) => Setting.getItem(country.label, country.key, flagIcon(country.country, country.alt)));
|
||||
items = Setting.Countries.map((country) => Setting.getItem(country.label, country.key, flagIcon(country.country, country.alt)));
|
||||
|
||||
getOrganizationLanguages(languages) {
|
||||
const select = [];
|
||||
|
@ -49,9 +49,9 @@ class SelectRegionBox extends React.Component {
|
||||
}
|
||||
>
|
||||
{
|
||||
Setting.getCountryNames().map((item) => (
|
||||
Setting.getCountriesData().map((item) => (
|
||||
<Option key={item.code} value={item.code} label={`${item.name} (${item.code})`} >
|
||||
<img src={`${Setting.StaticBaseUrl}/flag-icons/${item.code}.svg`} alt={item.name} height={20} style={{marginRight: 10}} />
|
||||
{Setting.countryFlag(item)}
|
||||
{`${item.name} (${item.code})`}
|
||||
</Option>
|
||||
))
|
||||
|
@ -23,6 +23,7 @@ import copy from "copy-to-clipboard";
|
||||
import {authConfig} from "./auth/Auth";
|
||||
import {Helmet} from "react-helmet";
|
||||
import * as Conf from "./Conf";
|
||||
import * as phoneNumber from "libphonenumber-js";
|
||||
import * as path from "path-browserify";
|
||||
|
||||
export const ServerUrl = "";
|
||||
@ -30,6 +31,16 @@ export const ServerUrl = "";
|
||||
// export const StaticBaseUrl = "https://cdn.jsdelivr.net/gh/casbin/static";
|
||||
export const StaticBaseUrl = "https://cdn.casbin.org";
|
||||
|
||||
export const Countries = [{label: "English", key: "en", country: "US", alt: "English"},
|
||||
{label: "简体中文", key: "zh", country: "CN", alt: "简体中文"},
|
||||
{label: "Español", key: "es", country: "ES", alt: "Español"},
|
||||
{label: "Français", key: "fr", country: "FR", alt: "Français"},
|
||||
{label: "Deutsch", key: "de", country: "DE", alt: "Deutsch"},
|
||||
{label: "日本語", key: "ja", country: "JP", alt: "日本語"},
|
||||
{label: "한국어", key: "ko", country: "KR", alt: "한국어"},
|
||||
{label: "Русский", key: "ru", country: "RU", alt: "Русский"},
|
||||
];
|
||||
|
||||
export function getThemeData(organization, application) {
|
||||
if (application?.themeData?.isEnabled) {
|
||||
return application.themeData;
|
||||
@ -188,20 +199,33 @@ export const OtherProviderInfo = {
|
||||
},
|
||||
};
|
||||
|
||||
export function getCountriesData() {
|
||||
export function initCountries() {
|
||||
const countries = require("i18n-iso-countries");
|
||||
countries.registerLocale(require("i18n-iso-countries/langs/" + getLanguage() + ".json"));
|
||||
return countries;
|
||||
}
|
||||
|
||||
export function getCountryNames() {
|
||||
const data = getCountriesData().getNames(getLanguage(), {select: "official"});
|
||||
|
||||
return Object.entries(data).map(items => {
|
||||
return {code: items[0], name: items[1]};
|
||||
export function getCountriesData(countryCodes = phoneNumber.getCountries()) {
|
||||
return countryCodes?.map((countryCode) => {
|
||||
if (phoneNumber.isSupportedCountry(countryCode)) {
|
||||
const name = initCountries().getName(countryCode, getLanguage());
|
||||
return {
|
||||
code: countryCode,
|
||||
name: name || "",
|
||||
phone: phoneNumber.getCountryCallingCode(countryCode),
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function countryFlag(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") {
|
||||
@ -299,16 +323,14 @@ export function isValidEmail(email) {
|
||||
return emailRegex.test(email);
|
||||
}
|
||||
|
||||
export function isValidPhone(phone) {
|
||||
return phone !== "";
|
||||
export function isValidPhone(phone, countryCode = "") {
|
||||
if (countryCode !== "") {
|
||||
return phoneNumber.isValidPhoneNumber(phone, countryCode);
|
||||
}
|
||||
|
||||
// if (phone === "") {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// // https://learnku.com/articles/31543, `^s*$` filter empty email individually.
|
||||
// const phoneRegex = /^\s*$|^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$/;
|
||||
// return phoneRegex.test(phone);
|
||||
const phoneRegex = /[0-9]{4,15}$/;
|
||||
return phoneRegex.test(phone);
|
||||
}
|
||||
|
||||
export function isValidInvoiceTitle(invoiceTitle) {
|
||||
|
@ -29,6 +29,7 @@ import SelectRegionBox from "./SelectRegionBox";
|
||||
import WebAuthnCredentialTable from "./WebauthnCredentialTable";
|
||||
import ManagedAccountTable from "./ManagedAccountTable";
|
||||
import PropertyTable from "./propertyTable";
|
||||
import PhoneNumberInput from "./common/PhoneNumberInput";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
@ -286,11 +287,13 @@ class UserEditPage extends React.Component {
|
||||
<Col style={{paddingRight: "20px"}} span={11} >
|
||||
{Setting.isLocalAdminUser(this.props.account) ?
|
||||
(<Input value={this.state.user.email}
|
||||
style={{width: "280Px"}}
|
||||
disabled={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 => {
|
||||
@ -298,7 +301,7 @@ class UserEditPage extends React.Component {
|
||||
}} />)
|
||||
}
|
||||
</Col>
|
||||
<Col span={11} >
|
||||
<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}
|
||||
</Col>
|
||||
@ -307,24 +310,37 @@ class UserEditPage extends React.Component {
|
||||
} else if (accountItem.name === "Phone") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
<Col style={{marginTop: "5px"}} span={Setting.isMobile() ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Phone"), i18next.t("general:Phone - Tooltip"))} :
|
||||
</Col>
|
||||
<Col style={{paddingRight: "20px"}} span={11} >
|
||||
{Setting.isLocalAdminUser(this.props.account) ?
|
||||
<Input value={this.state.user.phone} addonBefore={`+${this.state.application?.organizationObj.phonePrefix}`}
|
||||
disabled={disabled}
|
||||
onChange={e => {
|
||||
this.updateUserField("phone", e.target.value);
|
||||
}} /> :
|
||||
(<Select virtual={false} value={`+${this.state.application?.organizationObj.phonePrefix} ${this.state.user.phone}`}
|
||||
options={[Setting.getItem(`+${this.state.application?.organizationObj.phonePrefix} ${this.state.user.phone}`, this.state.user.phone)]}
|
||||
<Input.Group compact style={{width: "280Px"}}>
|
||||
<PhoneNumberInput
|
||||
style={{width: "30%"}}
|
||||
value={this.state.user.countryCode}
|
||||
onChange={(value) => {
|
||||
this.updateUserField("countryCode", value);
|
||||
}}
|
||||
countryCodes={this.state.application?.organizationObj.countryCodes}
|
||||
/>
|
||||
<Input value={this.state.user.phone}
|
||||
style={{width: "70%"}}
|
||||
disabled={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={11} >
|
||||
<Col span={Setting.isMobile() ? 24 : 11} >
|
||||
{this.isSelf() ? (<ResetModal application={this.state.application} disabled={disabled} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null}
|
||||
</Col>
|
||||
</Row>
|
||||
|
@ -49,6 +49,7 @@ class UserListPage extends BaseListPage {
|
||||
avatar: `${Setting.StaticBaseUrl}/img/casbin.svg`,
|
||||
email: `${randomName}@example.com`,
|
||||
phone: Setting.getRandomNumber(),
|
||||
countryCode: this.state.organization.countryCodes?.length > 0 ? this.state.organization.countryCodes[0] : "",
|
||||
address: [],
|
||||
affiliation: "Example Inc.",
|
||||
tag: "staff",
|
||||
@ -261,7 +262,7 @@ class UserListPage extends BaseListPage {
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("region"),
|
||||
render: (text, record, index) => {
|
||||
return Setting.getCountriesData().getName(record.region, Setting.getLanguage(), {select: "official"});
|
||||
return Setting.initCountries().getName(record.region, Setting.getLanguage(), {select: "official"});
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -140,7 +140,6 @@ class ForgetPage extends React.Component {
|
||||
username: this.state.username,
|
||||
name: this.state.name,
|
||||
code: forms.step2.getFieldValue("emailCode"),
|
||||
phonePrefix: this.getApplicationObj()?.organizationObj.phonePrefix,
|
||||
type: "login",
|
||||
}, oAuthParams).then(res => {
|
||||
if (res.status === "ok") {
|
||||
|
@ -189,7 +189,6 @@ class LoginPage extends React.Component {
|
||||
} else {
|
||||
values["type"] = this.state.type;
|
||||
}
|
||||
values["phonePrefix"] = this.getApplicationObj()?.organizationObj.phonePrefix;
|
||||
|
||||
if (oAuthParams !== null) {
|
||||
values["samlRequest"] = oAuthParams.samlRequest;
|
||||
@ -204,6 +203,7 @@ class LoginPage extends React.Component {
|
||||
values["organization"] = this.getApplicationObj().organization;
|
||||
}
|
||||
}
|
||||
|
||||
postCodeLoginAction(res) {
|
||||
const application = this.getApplicationObj();
|
||||
const ths = this;
|
||||
@ -364,7 +364,8 @@ class LoginPage extends React.Component {
|
||||
title={i18next.t("application:Sign Up Error")}
|
||||
subTitle={i18next.t("application:The application does not allow to sign up new account")}
|
||||
extra={[
|
||||
<Button type="primary" key="signin" onClick={() => Setting.redirectToLoginPage(application, this.props.history)}>
|
||||
<Button type="primary" key="signin"
|
||||
onClick={() => Setting.redirectToLoginPage(application, this.props.history)}>
|
||||
{
|
||||
i18next.t("login:Sign In")
|
||||
}
|
||||
@ -384,7 +385,9 @@ class LoginPage extends React.Component {
|
||||
application: application.name,
|
||||
autoSignin: true,
|
||||
}}
|
||||
onFinish={(values) => {this.onFinish(values);}}
|
||||
onFinish={(values) => {
|
||||
this.onFinish(values);
|
||||
}}
|
||||
style={{width: "300px"}}
|
||||
size="large"
|
||||
ref={this.form}
|
||||
@ -424,7 +427,7 @@ class LoginPage extends React.Component {
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (this.state.loginMethod === "verificationCode") {
|
||||
if (this.state.email !== "" && !Setting.isValidEmail(this.state.username) && !Setting.isValidPhone(this.state.username)) {
|
||||
if (!Setting.isValidEmail(this.state.username) && !Setting.isValidPhone(this.state.username)) {
|
||||
this.setState({validEmailOrPhone: false});
|
||||
return Promise.reject(i18next.t("login:The input is not valid Email or Phone!"));
|
||||
}
|
||||
@ -444,7 +447,7 @@ class LoginPage extends React.Component {
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
id = "input"
|
||||
id="input"
|
||||
prefix={<UserOutlined className="site-form-item-icon" />}
|
||||
placeholder={(this.state.loginMethod === "verificationCode") ? i18next.t("login:Email or phone") : i18next.t("login:username, Email or phone")}
|
||||
disabled={!application.enablePassword}
|
||||
@ -774,13 +777,18 @@ class LoginPage extends React.Component {
|
||||
const items = [
|
||||
{label: i18next.t("login:Password"), key: "password"},
|
||||
];
|
||||
application.enableCodeSignin ? items.push({label: i18next.t("login:Verification Code"), key: "verificationCode"}) : null;
|
||||
application.enableCodeSignin ? items.push({
|
||||
label: i18next.t("login:Verification Code"),
|
||||
key: "verificationCode",
|
||||
}) : null;
|
||||
application.enableWebAuthn ? items.push({label: i18next.t("login:WebAuthn"), key: "webAuthn"}) : null;
|
||||
|
||||
if (application.enableCodeSignin || application.enableWebAuthn) {
|
||||
return (
|
||||
<div>
|
||||
<Tabs items={items} size={"small"} defaultActiveKey="password" onChange={(key) => {this.setState({loginMethod: key});}} centered>
|
||||
<Tabs items={items} size={"small"} defaultActiveKey="password" onChange={(key) => {
|
||||
this.setState({loginMethod: key});
|
||||
}} centered>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
@ -823,7 +831,7 @@ class LoginPage extends React.Component {
|
||||
<div dangerouslySetInnerHTML={{__html: application.formSideHtml}} />
|
||||
</div>
|
||||
<div className="login-form">
|
||||
<div >
|
||||
<div>
|
||||
<div>
|
||||
{
|
||||
Setting.renderHelmet(application)
|
||||
|
@ -26,6 +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";
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: {
|
||||
@ -68,6 +69,7 @@ class SignupPage extends React.Component {
|
||||
application: null,
|
||||
email: "",
|
||||
phone: "",
|
||||
countryCode: "",
|
||||
emailCode: "",
|
||||
phoneCode: "",
|
||||
validEmail: false,
|
||||
@ -157,7 +159,6 @@ class SignupPage extends React.Component {
|
||||
|
||||
onFinish(values) {
|
||||
const application = this.getApplicationObj();
|
||||
values.phonePrefix = application.organizationObj.phonePrefix;
|
||||
AuthBackend.signup(values)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
@ -378,35 +379,66 @@ class SignupPage extends React.Component {
|
||||
} else if (signupItem.name === "Phone") {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Form.Item
|
||||
name="phone"
|
||||
key="phone"
|
||||
label={i18next.t("general:Phone")}
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
message: i18next.t("signup:Please input your phone number!"),
|
||||
},
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (this.state.phone !== "" && !Setting.isValidPhone(this.state.phone)) {
|
||||
this.setState({validPhone: false});
|
||||
return Promise.reject(i18next.t("signup:The input is not valid Phone!"));
|
||||
}
|
||||
<Form.Item label={i18next.t("general:Phone")} required>
|
||||
<Input.Group compact>
|
||||
<Form.Item
|
||||
name="countryCode"
|
||||
key="countryCode"
|
||||
noStyle
|
||||
rules={[
|
||||
{
|
||||
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();
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
style={{
|
||||
width: "100%",
|
||||
}}
|
||||
addonBefore={`+${this.getApplicationObj()?.organizationObj.phonePrefix}`}
|
||||
onChange={e => this.setState({phone: e.target.value})}
|
||||
/>
|
||||
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"
|
||||
noStyle
|
||||
rules={[
|
||||
{
|
||||
required: required,
|
||||
message: i18next.t("signup:Please input your phone number!"),
|
||||
},
|
||||
{
|
||||
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();
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
style={{width: "65%"}}
|
||||
onChange={e => this.setState({phone: e.target.value})}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Input.Group>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="phoneCode"
|
||||
|
58
web/src/common/PhoneNumberInput.js
Normal file
58
web/src/common/PhoneNumberInput.js
Normal file
@ -0,0 +1,58 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
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";
|
||||
const countryCodes = props.countryCodes ?? [];
|
||||
|
||||
const handleOnChange = (e) => {
|
||||
onChange?.(e);
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
virtual={false}
|
||||
style={style}
|
||||
value={value}
|
||||
dropdownMatchSelectWidth={false}
|
||||
optionLabelProp={"label"}
|
||||
showSearch={showSearch}
|
||||
onChange={handleOnChange}
|
||||
filterOption={(input, option) =>
|
||||
(option?.label ?? "").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>
|
||||
))
|
||||
}
|
||||
</Select>
|
||||
);
|
||||
}
|
@ -219,8 +219,6 @@
|
||||
"Permissions - Tooltip": "Permissions - Tooltip",
|
||||
"Phone": "Telefon",
|
||||
"Phone - Tooltip": "Phone",
|
||||
"Phone prefix": "Telefonpräfix",
|
||||
"Phone prefix - Tooltip": "Mobile phone number prefix, used to distinguish countries or regions",
|
||||
"Preview": "Vorschau",
|
||||
"Preview - Tooltip": "The form in which the password is stored in the database",
|
||||
"Products": "Products",
|
||||
@ -252,6 +250,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",
|
||||
"Swagger": "Swagger",
|
||||
"Sync": "Sync",
|
||||
"Syncers": "Syncers",
|
||||
@ -311,10 +311,10 @@
|
||||
"Or sign in with another account": "Oder melden Sie sich mit einem anderen Konto an",
|
||||
"Password": "Passwort",
|
||||
"Password - Tooltip": "Passwort - Tooltip",
|
||||
"Please input your Email or Phone!": "Please input your Email or Phone!",
|
||||
"Please input your code!": "Bitte gib deinen Code ein!",
|
||||
"Please input your password!": "Bitte geben Sie Ihr Passwort ein!",
|
||||
"Please input your password, at least 6 characters!": "Bitte geben Sie Ihr Passwort ein, mindestens 6 Zeichen!",
|
||||
"Please input your username, Email or phone!": "Bitte geben Sie Ihren Benutzernamen, E-Mail oder Telefon ein!",
|
||||
"Redirecting, please wait.": "Redirecting, please wait.",
|
||||
"Sign In": "Anmelden",
|
||||
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
||||
@ -640,6 +640,7 @@
|
||||
"Please input your last name!": "Please input your last name!",
|
||||
"Please input your phone number!": "Bitte geben Sie Ihre Telefonnummer ein!",
|
||||
"Please input your real name!": "Bitte geben Sie Ihren persönlichen Namen ein!",
|
||||
"Please select your country code!": "Please select your country code!",
|
||||
"Please select your country/region!": "Bitte wählen Sie Ihr Land/Ihre Region!",
|
||||
"Terms of Use": "Nutzungsbedingungen",
|
||||
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
|
||||
@ -726,6 +727,7 @@
|
||||
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||
"Captcha Verify Success": "Captcha Verify Success",
|
||||
"Code Sent": "Code gesendet",
|
||||
"Country code": "Country code",
|
||||
"Country/Region": "Land/Region",
|
||||
"Country/Region - Tooltip": "Country/Region",
|
||||
"Edit User": "Benutzer bearbeiten",
|
||||
|
@ -219,8 +219,6 @@
|
||||
"Permissions - Tooltip": "Permissions - Tooltip",
|
||||
"Phone": "Phone",
|
||||
"Phone - Tooltip": "Phone - Tooltip",
|
||||
"Phone prefix": "Phone prefix",
|
||||
"Phone prefix - Tooltip": "Phone prefix - Tooltip",
|
||||
"Preview": "Preview",
|
||||
"Preview - Tooltip": "Preview - Tooltip",
|
||||
"Products": "Products",
|
||||
@ -252,6 +250,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",
|
||||
"Swagger": "Swagger",
|
||||
"Sync": "Sync",
|
||||
"Syncers": "Syncers",
|
||||
@ -311,10 +311,10 @@
|
||||
"Or sign in with another account": "Or sign in with another account",
|
||||
"Password": "Password",
|
||||
"Password - Tooltip": "Password - Tooltip",
|
||||
"Please input your Email or Phone!": "Please input your Email or Phone!",
|
||||
"Please input your code!": "Please input your code!",
|
||||
"Please input your password!": "Please input your password!",
|
||||
"Please input your password, at least 6 characters!": "Please input your password, at least 6 characters!",
|
||||
"Please input your username, Email or phone!": "Please input your username, Email or phone!",
|
||||
"Redirecting, please wait.": "Redirecting, please wait.",
|
||||
"Sign In": "Sign In",
|
||||
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
||||
@ -640,6 +640,7 @@
|
||||
"Please input your last name!": "Please input your last name!",
|
||||
"Please input your phone number!": "Please input your phone number!",
|
||||
"Please input your real name!": "Please input your real name!",
|
||||
"Please select your country code!": "Please select your country code!",
|
||||
"Please select your country/region!": "Please select your country/region!",
|
||||
"Terms of Use": "Terms of Use",
|
||||
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
|
||||
@ -726,6 +727,7 @@
|
||||
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||
"Captcha Verify Success": "Captcha Verify Success",
|
||||
"Code Sent": "Code Sent",
|
||||
"Country code": "Country code",
|
||||
"Country/Region": "Country/Region",
|
||||
"Country/Region - Tooltip": "Country/Region - Tooltip",
|
||||
"Edit User": "Edit User",
|
||||
|
@ -219,8 +219,6 @@
|
||||
"Permissions - Tooltip": "Permisos - Tooltip",
|
||||
"Phone": "Teléfono",
|
||||
"Phone - Tooltip": "Teléfono - Tooltip",
|
||||
"Phone prefix": "Prefijo teléfonico",
|
||||
"Phone prefix - Tooltip": "Prefijo teléfonico - Tooltip",
|
||||
"Preview": "Previsualizar",
|
||||
"Preview - Tooltip": "Previsualizar - Tooltip",
|
||||
"Products": "Productos",
|
||||
@ -252,6 +250,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",
|
||||
"Swagger": "Swagger",
|
||||
"Sync": "Sincronizador",
|
||||
"Syncers": "Sincronizadores",
|
||||
@ -311,10 +311,10 @@
|
||||
"Or sign in with another account": "O inicia sesión con otra cuenta",
|
||||
"Password": "Contraseña",
|
||||
"Password - Tooltip": "Contraseña - Tooltip",
|
||||
"Please input your Email or Phone!": "Please input your Email or Phone!",
|
||||
"Please input your code!": "¡Por favor ingrese su código!",
|
||||
"Please input your password!": "¡Por favor ingrese su contraseña!",
|
||||
"Please input your password, at least 6 characters!": "Su contraseña debe contener al menos 6 caracteres.",
|
||||
"Please input your username, Email or phone!": "¡Ingrese su nombre de usuario, correo electrónico o teléfono!",
|
||||
"Redirecting, please wait.": "Redirecting, please wait.",
|
||||
"Sign In": "Iniciar de sesión",
|
||||
"Sign in with WebAuthn": "Iniciar de sesión con WebAuthn",
|
||||
@ -640,6 +640,7 @@
|
||||
"Please input your last name!": "Por favor, ingrese su apellido!",
|
||||
"Please input your phone number!": "Por favor, ingrese su número teléfonico!",
|
||||
"Please input your real name!": "Por favor, ingrese un nombre real!",
|
||||
"Please select your country code!": "Please select your country code!",
|
||||
"Please select your country/region!": "Por favor, seleccione su pais/region!",
|
||||
"Terms of Use": "Términos de Uso",
|
||||
"The input is not invoice Tax ID!": "El valor ingresado no es un número de identificación fiscal de factura!",
|
||||
@ -726,6 +727,7 @@
|
||||
"Captcha Verify Failed": "Fallo la verificación del Captcha",
|
||||
"Captcha Verify Success": "Captcha verificado con éxito",
|
||||
"Code Sent": "Código enviado",
|
||||
"Country code": "Country code",
|
||||
"Country/Region": "Pais/Región",
|
||||
"Country/Region - Tooltip": "Pais/Región - Tooltip",
|
||||
"Edit User": "Editar usuario",
|
||||
|
@ -219,8 +219,6 @@
|
||||
"Permissions - Tooltip": "Permissions - Tooltip",
|
||||
"Phone": "Téléphone",
|
||||
"Phone - Tooltip": "Phone",
|
||||
"Phone prefix": "Préfixe du téléphone",
|
||||
"Phone prefix - Tooltip": "Mobile phone number prefix, used to distinguish countries or regions",
|
||||
"Preview": "Aperçu",
|
||||
"Preview - Tooltip": "The form in which the password is stored in the database",
|
||||
"Products": "Products",
|
||||
@ -252,6 +250,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",
|
||||
"Swagger": "Swagger",
|
||||
"Sync": "Sync",
|
||||
"Syncers": "Synchronisateurs",
|
||||
@ -311,10 +311,10 @@
|
||||
"Or sign in with another account": "Ou connectez-vous avec un autre compte",
|
||||
"Password": "Mot de passe",
|
||||
"Password - Tooltip": "Mot de passe - Info-bulle",
|
||||
"Please input your Email or Phone!": "Please input your Email or Phone!",
|
||||
"Please input your code!": "Veuillez saisir votre code !",
|
||||
"Please input your password!": "Veuillez saisir votre mot de passe !",
|
||||
"Please input your password, at least 6 characters!": "Veuillez entrer votre mot de passe, au moins 6 caractères !",
|
||||
"Please input your username, Email or phone!": "Veuillez entrer votre nom d'utilisateur, votre e-mail ou votre téléphone!",
|
||||
"Redirecting, please wait.": "Redirecting, please wait.",
|
||||
"Sign In": "Se connecter",
|
||||
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
||||
@ -640,6 +640,7 @@
|
||||
"Please input your last name!": "Please input your last name!",
|
||||
"Please input your phone number!": "Veuillez entrer votre numéro de téléphone!",
|
||||
"Please input your real name!": "Veuillez entrer votre nom personnel !",
|
||||
"Please select your country code!": "Please select your country code!",
|
||||
"Please select your country/region!": "Veuillez sélectionner votre pays/région!",
|
||||
"Terms of Use": "Conditions d'utilisation",
|
||||
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
|
||||
@ -726,6 +727,7 @@
|
||||
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||
"Captcha Verify Success": "Captcha Verify Success",
|
||||
"Code Sent": "Code envoyé",
|
||||
"Country code": "Country code",
|
||||
"Country/Region": "Pays/Région",
|
||||
"Country/Region - Tooltip": "Country/Region",
|
||||
"Edit User": "Editer l'utilisateur",
|
||||
|
@ -219,8 +219,6 @@
|
||||
"Permissions - Tooltip": "Permissions - Tooltip",
|
||||
"Phone": "電話番号",
|
||||
"Phone - Tooltip": "Phone",
|
||||
"Phone prefix": "電話プレフィクス",
|
||||
"Phone prefix - Tooltip": "Mobile phone number prefix, used to distinguish countries or regions",
|
||||
"Preview": "プレビュー",
|
||||
"Preview - Tooltip": "The form in which the password is stored in the database",
|
||||
"Products": "Products",
|
||||
@ -252,6 +250,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",
|
||||
"Swagger": "Swagger",
|
||||
"Sync": "Sync",
|
||||
"Syncers": "Syncers",
|
||||
@ -311,10 +311,10 @@
|
||||
"Or sign in with another account": "または別のアカウントでサインイン",
|
||||
"Password": "パスワード",
|
||||
"Password - Tooltip": "パスワード → ツールチップ",
|
||||
"Please input your Email or Phone!": "Please input your Email or Phone!",
|
||||
"Please input your code!": "コードを入力してください!",
|
||||
"Please input your password!": "パスワードを入力してください!",
|
||||
"Please input your password, at least 6 characters!": "6文字以上でパスワードを入力してください!",
|
||||
"Please input your username, Email or phone!": "ユーザー名、メールアドレスまたは電話番号を入力してください。",
|
||||
"Redirecting, please wait.": "Redirecting, please wait.",
|
||||
"Sign In": "サインイン",
|
||||
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
||||
@ -640,6 +640,7 @@
|
||||
"Please input your last name!": "Please input your last name!",
|
||||
"Please input your phone number!": "電話番号を入力してください!",
|
||||
"Please input your real name!": "個人名を入力してください!",
|
||||
"Please select your country code!": "Please select your country code!",
|
||||
"Please select your country/region!": "あなたの国/地域を選択してください!",
|
||||
"Terms of Use": "利用規約",
|
||||
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
|
||||
@ -726,6 +727,7 @@
|
||||
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||
"Captcha Verify Success": "Captcha Verify Success",
|
||||
"Code Sent": "コードを送信しました",
|
||||
"Country code": "Country code",
|
||||
"Country/Region": "国/地域",
|
||||
"Country/Region - Tooltip": "Country/Region",
|
||||
"Edit User": "ユーザーを編集",
|
||||
|
@ -219,8 +219,6 @@
|
||||
"Permissions - Tooltip": "Permissions - Tooltip",
|
||||
"Phone": "Phone",
|
||||
"Phone - Tooltip": "Phone",
|
||||
"Phone prefix": "Phone prefix",
|
||||
"Phone prefix - Tooltip": "Mobile phone number prefix, used to distinguish countries or regions",
|
||||
"Preview": "Preview",
|
||||
"Preview - Tooltip": "The form in which the password is stored in the database",
|
||||
"Products": "Products",
|
||||
@ -252,6 +250,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",
|
||||
"Swagger": "Swagger",
|
||||
"Sync": "Sync",
|
||||
"Syncers": "Syncers",
|
||||
@ -311,10 +311,10 @@
|
||||
"Or sign in with another account": "Or sign in with another account",
|
||||
"Password": "Password",
|
||||
"Password - Tooltip": "Password - Tooltip",
|
||||
"Please input your Email or Phone!": "Please input your Email or Phone!",
|
||||
"Please input your code!": "Please input your code!",
|
||||
"Please input your password!": "Please input your password!",
|
||||
"Please input your password, at least 6 characters!": "Please input your password, at least 6 characters!",
|
||||
"Please input your username, Email or phone!": "Please input your username, Email or phone!",
|
||||
"Redirecting, please wait.": "Redirecting, please wait.",
|
||||
"Sign In": "Sign In",
|
||||
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
||||
@ -640,6 +640,7 @@
|
||||
"Please input your last name!": "Please input your last name!",
|
||||
"Please input your phone number!": "Please input your phone number!",
|
||||
"Please input your real name!": "Please input your real name!",
|
||||
"Please select your country code!": "Please select your country code!",
|
||||
"Please select your country/region!": "Please select your country/region!",
|
||||
"Terms of Use": "Terms of Use",
|
||||
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
|
||||
@ -726,6 +727,7 @@
|
||||
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||
"Captcha Verify Success": "Captcha Verify Success",
|
||||
"Code Sent": "Code Sent",
|
||||
"Country code": "Country code",
|
||||
"Country/Region": "Country/Region",
|
||||
"Country/Region - Tooltip": "Country/Region",
|
||||
"Edit User": "Edit User",
|
||||
|
@ -219,8 +219,6 @@
|
||||
"Permissions - Tooltip": "Permissions - Tooltip",
|
||||
"Phone": "Телефон",
|
||||
"Phone - Tooltip": "Phone",
|
||||
"Phone prefix": "Префикс телефона",
|
||||
"Phone prefix - Tooltip": "Mobile phone number prefix, used to distinguish countries or regions",
|
||||
"Preview": "Предпросмотр",
|
||||
"Preview - Tooltip": "The form in which the password is stored in the database",
|
||||
"Products": "Продукты",
|
||||
@ -252,6 +250,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",
|
||||
"Swagger": "Swagger",
|
||||
"Sync": "Sync",
|
||||
"Syncers": "Синхронизаторы",
|
||||
@ -311,10 +311,10 @@
|
||||
"Or sign in with another account": "Или войти с помощью другой учетной записи",
|
||||
"Password": "Пароль",
|
||||
"Password - Tooltip": "Пароль - Подсказка",
|
||||
"Please input your Email or Phone!": "Please input your Email or Phone!",
|
||||
"Please input your code!": "Пожалуйста, введите ваш код!",
|
||||
"Please input your password!": "Пожалуйста, введите ваш пароль!",
|
||||
"Please input your password, at least 6 characters!": "Пожалуйста, введите ваш пароль, по крайней мере 6 символов!",
|
||||
"Please input your username, Email or phone!": "Пожалуйста, введите ваше имя пользователя, адрес электронной почты или телефон!",
|
||||
"Redirecting, please wait.": "Redirecting, please wait.",
|
||||
"Sign In": "Войти",
|
||||
"Sign in with WebAuthn": "Sign in with WebAuthn",
|
||||
@ -640,6 +640,7 @@
|
||||
"Please input your last name!": "Please input your last name!",
|
||||
"Please input your phone number!": "Пожалуйста, введите ваш номер телефона!",
|
||||
"Please input your real name!": "Пожалуйста, введите ваше личное имя!",
|
||||
"Please select your country code!": "Please select your country code!",
|
||||
"Please select your country/region!": "Пожалуйста, выберите вашу страну/регион!",
|
||||
"Terms of Use": "Условия использования",
|
||||
"The input is not invoice Tax ID!": "The input is not invoice Tax ID!",
|
||||
@ -726,6 +727,7 @@
|
||||
"Captcha Verify Failed": "Captcha Verify Failed",
|
||||
"Captcha Verify Success": "Captcha Verify Success",
|
||||
"Code Sent": "Код отправлен",
|
||||
"Country code": "Country code",
|
||||
"Country/Region": "Страна/регион",
|
||||
"Country/Region - Tooltip": "Country/Region",
|
||||
"Edit User": "Изменить пользователя",
|
||||
|
@ -219,8 +219,6 @@
|
||||
"Permissions - Tooltip": "权限",
|
||||
"Phone": "手机号",
|
||||
"Phone - Tooltip": "手机号",
|
||||
"Phone prefix": "手机号前缀",
|
||||
"Phone prefix - Tooltip": "移动电话号码前缀,用于区分国家或地区",
|
||||
"Preview": "预览",
|
||||
"Preview - Tooltip": "预览",
|
||||
"Products": "商品",
|
||||
@ -252,6 +250,8 @@
|
||||
"Successfully added": "添加成功",
|
||||
"Successfully deleted": "删除成功",
|
||||
"Successfully saved": "保存成功",
|
||||
"Supported country code": "支持的国家代码",
|
||||
"Supported country code - Tooltip": "支持发送短信的国家 - Tooltip",
|
||||
"Swagger": "API文档",
|
||||
"Sync": "同步",
|
||||
"Syncers": "同步器",
|
||||
@ -311,10 +311,10 @@
|
||||
"Or sign in with another account": "或者,登录其他账号",
|
||||
"Password": "密码",
|
||||
"Password - Tooltip": "密码",
|
||||
"Please input your Email or Phone!": "请输入您的Email或手机号!",
|
||||
"Please input your code!": "请输入您的验证码!",
|
||||
"Please input your password!": "请输入您的密码!",
|
||||
"Please input your password, at least 6 characters!": "请输入您的密码,不少于6位",
|
||||
"Please input your username, Email or phone!": "请输入您的用户名、Email或手机号!",
|
||||
"Redirecting, please wait.": "正在跳转, 请稍等.",
|
||||
"Sign In": "登录",
|
||||
"Sign in with WebAuthn": "WebAuthn登录",
|
||||
@ -640,6 +640,7 @@
|
||||
"Please input your last name!": "请输入您的姓氏!",
|
||||
"Please input your phone number!": "请输入您的手机号码!",
|
||||
"Please input your real name!": "请输入您的姓名!",
|
||||
"Please select your country code!": "请选择国家代码!",
|
||||
"Please select your country/region!": "请选择您的国家/地区",
|
||||
"Terms of Use": "《用户协议》",
|
||||
"The input is not invoice Tax ID!": "您输入的纳税人识别号有误!",
|
||||
@ -726,6 +727,7 @@
|
||||
"Captcha Verify Failed": "验证码校验失败",
|
||||
"Captcha Verify Success": "验证码校验成功",
|
||||
"Code Sent": "验证码已发送",
|
||||
"Country code": "国家代码",
|
||||
"Country/Region": "国家/地区",
|
||||
"Country/Region - Tooltip": "国家/地区",
|
||||
"Edit User": "编辑用户",
|
||||
|
@ -7829,6 +7829,11 @@ levn@~0.3.0:
|
||||
prelude-ls "~1.1.2"
|
||||
type-check "~0.3.2"
|
||||
|
||||
libphonenumber-js@^1.10.19:
|
||||
version "1.10.20"
|
||||
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.20.tgz#03c310adf83381eeceb4bd6830442fd14e64964d"
|
||||
integrity sha512-kQovlKNdLcVzerbTPmJ+Fx4R+7/pYXmPDIllHjg7IxL4X6MsMG7jaT5opfYrBok0uqkByVif//JUR8e11l/V7w==
|
||||
|
||||
lilconfig@2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25"
|
||||
|
Reference in New Issue
Block a user