diff --git a/docs/config.md b/docs/config.md index d6f6e408..df77e9a7 100644 --- a/docs/config.md +++ b/docs/config.md @@ -856,8 +856,8 @@ billing-contact: "phil@example.com" ``` ## Phone calls -ntfy supports phone calls via [Twilio](https://www.twilio.com/) as a phone call provider. If phone calls are enabled, -users can verify and add a phone number, and then receive phone calls when publish a message with the `X-Call` header. +ntfy supports phone calls via [Twilio](https://www.twilio.com/) as a call provider. If phone calls are enabled, +users can verify and add a phone number, and then receive phone calls when publishing a message using the `X-Call` header. See [publishing page](publish.md#phone-calls) for more details. To enable Twilio integration, sign up with [Twilio](https://www.twilio.com/), purchase a phone number (Toll free numbers diff --git a/docs/publish.md b/docs/publish.md index 3cca6fc6..1b5957b9 100644 --- a/docs/publish.md +++ b/docs/publish.md @@ -2709,7 +2709,7 @@ You may also simply pass `yes` as a value to pick the first of your verified pho On ntfy.sh, this feature is only supported to [ntfy Pro](https://ntfy.sh/app) plans.
- ![e-mail publishing](static/img/web-phone-verify.png) + ![phone number verification](static/img/web-phone-verify.png)
Phone number verification in the web app
diff --git a/go.mod b/go.mod index 1f4c9e75..162fd943 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( require ( cloud.google.com/go v0.110.2 // indirect - cloud.google.com/go/compute v1.19.2 // indirect + cloud.google.com/go/compute v1.19.3 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.0.1 // indirect cloud.google.com/go/longrunning v0.4.2 // indirect diff --git a/go.sum b/go.sum index ff2d580f..73cd9d8f 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ cloud.google.com/go v0.110.2 h1:sdFPBr6xG9/wkBbfhmUz/JmZC7X6LavQgcrVINrKiVA= cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw= cloud.google.com/go/compute v1.19.2 h1:GbJtPo8OKVHbVep8jvM57KidbYHxeE68LOVqouNLrDY= cloud.google.com/go/compute v1.19.2/go.mod h1:5f5a+iC1IriXYauaQ0EyQmEAEq9CGRnV5xJSQSlTV08= +cloud.google.com/go/compute v1.19.3 h1:DcTwsFgGev/wV5+q8o2fzgcHOaac+DKGC91ZlvpsQds= +cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/firestore v1.9.0 h1:IBlRyxgGySXu5VuW0RgGFlTtLukSnNkpDiEOMkQkmpA= diff --git a/log/log_test.go b/log/log_test.go index ed35b495..d7ceb1c9 100644 --- a/log/log_test.go +++ b/log/log_test.go @@ -198,6 +198,30 @@ func TestLog_LevelOverride_ManyOnSameField(t *testing.T) { require.Equal(t, "", File()) } +func TestLog_FieldIf(t *testing.T) { + t.Cleanup(resetState) + + var out bytes.Buffer + SetOutput(&out) + SetLevel(DebugLevel) + SetFormat(JSONFormat) + + Time(time.Unix(11, 0).UTC()). + FieldIf("trace_field", "manager", TraceLevel). // This is not logged + Field("tag", "manager"). + Debug("trace_field is not logged") + SetLevel(TraceLevel) + Time(time.Unix(12, 0).UTC()). + FieldIf("trace_field", "manager", TraceLevel). // Now it is logged + Field("tag", "manager"). + Debug("trace_field is logged") + + expected := `{"time":"1970-01-01T00:00:11Z","level":"DEBUG","message":"trace_field is not logged","tag":"manager"} +{"time":"1970-01-01T00:00:12Z","level":"DEBUG","message":"trace_field is logged","tag":"manager","trace_field":"manager"} +` + require.Equal(t, expected, out.String()) +} + func TestLog_UsingStdLogger_JSON(t *testing.T) { t.Cleanup(resetState) diff --git a/server/errors.go b/server/errors.go index b67558db..eee916b5 100644 --- a/server/errors.go +++ b/server/errors.go @@ -108,11 +108,12 @@ var ( errHTTPBadRequestBillingSubscriptionExists = &errHTTP{40029, http.StatusBadRequest, "invalid request: billing subscription already exists", "", nil} errHTTPBadRequestTierInvalid = &errHTTP{40030, http.StatusBadRequest, "invalid request: tier does not exist", "", nil} errHTTPBadRequestUserNotFound = &errHTTP{40031, http.StatusBadRequest, "invalid request: user does not exist", "", nil} - errHTTPBadRequestPhoneCallsDisabled = &errHTTP{40032, http.StatusBadRequest, "invalid request: calling is disabled", "https://ntfy.sh/docs/publish/#phone-calls", nil} + errHTTPBadRequestPhoneCallsDisabled = &errHTTP{40032, http.StatusBadRequest, "invalid request: calling is disabled", "https://ntfy.sh/docs/config/#phone-calls", nil} errHTTPBadRequestPhoneNumberInvalid = &errHTTP{40033, http.StatusBadRequest, "invalid request: phone number invalid", "https://ntfy.sh/docs/publish/#phone-calls", nil} errHTTPBadRequestPhoneNumberNotVerified = &errHTTP{40034, http.StatusBadRequest, "invalid request: phone number not verified, or no matching verified numbers found", "https://ntfy.sh/docs/publish/#phone-calls", nil} errHTTPBadRequestAnonymousCallsNotAllowed = &errHTTP{40035, http.StatusBadRequest, "invalid request: anonymous phone calls are not allowed", "https://ntfy.sh/docs/publish/#phone-calls", nil} errHTTPBadRequestPhoneNumberVerifyChannelInvalid = &errHTTP{40036, http.StatusBadRequest, "invalid request: verification channel must be 'sms' or 'call'", "https://ntfy.sh/docs/publish/#phone-calls", nil} + errHTTPBadRequestDelayNoCall = &errHTTP{40037, http.StatusBadRequest, "delayed call notifications are not supported", "", nil} errHTTPNotFound = &errHTTP{40401, http.StatusNotFound, "page not found", "", nil} errHTTPUnauthorized = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication", nil} errHTTPForbidden = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication", nil} diff --git a/server/server.go b/server/server.go index fb448015..20b8ce03 100644 --- a/server/server.go +++ b/server/server.go @@ -937,6 +937,9 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi if email != "" { return false, false, "", "", false, errHTTPBadRequestDelayNoEmail // we cannot store the email address (yet) } + if call != "" { + return false, false, "", "", false, errHTTPBadRequestDelayNoCall // we cannot store the phone number (yet) + } delay, err := util.ParseFutureTime(delayStr, time.Now()) if err != nil { return false, false, "", "", false, errHTTPBadRequestDelayCannotParse diff --git a/server/server_account.go b/server/server_account.go index b9997ef3..a0bbaeaf 100644 --- a/server/server_account.go +++ b/server/server_account.go @@ -581,7 +581,7 @@ func (s *Server) handleAccountPhoneNumberDelete(w http.ResponseWriter, r *http.R return errHTTPBadRequestPhoneNumberInvalid } logvr(v, r).Tag(tagAccount).Field("phone_number", req.Number).Debug("Deleting phone number") - if err := s.userManager.DeletePhoneNumber(u.ID, req.Number); err != nil { + if err := s.userManager.RemovePhoneNumber(u.ID, req.Number); err != nil { return err } return s.writeJSON(w, newSuccessResponse()) diff --git a/server/server_test.go b/server/server_test.go index adf77a73..e231ab73 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -1190,7 +1190,20 @@ func TestServer_PublishDelayedEmail_Fail(t *testing.T) { "E-Mail": "test@example.com", "Delay": "20 min", }) - require.Equal(t, 400, response.Code) + require.Equal(t, 40003, toHTTPError(t, response.Body.String()).Code) +} + +func TestServer_PublishDelayedCall_Fail(t *testing.T) { + c := newTestConfig(t) + c.TwilioAccount = "AC1234567890" + c.TwilioAuthToken = "AAEAA1234567890" + c.TwilioFromNumber = "+1234567890" + s := newTestServer(t, c) + response := request(t, s, "PUT", "/mytopic", "fail", map[string]string{ + "Call": "yes", + "Delay": "20 min", + }) + require.Equal(t, 40037, toHTTPError(t, response.Body.String()).Code) } func TestServer_PublishEmailNoMailer_Fail(t *testing.T) { diff --git a/server/server_twilio_test.go b/server/server_twilio_test.go index 5b320959..642ad756 100644 --- a/server/server_twilio_test.go +++ b/server/server_twilio_test.go @@ -43,7 +43,7 @@ func TestServer_Twilio_Call_Add_Verify_Call_Delete_Success(t *testing.T) { require.Nil(t, err) require.Equal(t, "/2010-04-01/Accounts/AC1234567890/Calls.json", r.URL.Path) require.Equal(t, "Basic QUMxMjM0NTY3ODkwOkFBRUFBMTIzNDU2Nzg5MA==", r.Header.Get("Authorization")) - require.Equal(t, "From=%2B1234567890&To=%2B12223334444&Twiml=%0A%3CResponse%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay+loop%3D%223%22%3E%0A%09%09You+have+a+notification+from+notify+on+topic+mytopic.+Message%3A%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09hi+there%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09End+message.%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09This+message+was+sent+by+user+phil.+It+will+be+repeated+up+to+three+times.%0A%09%09%3Cbreak+time%3D%223s%22%2F%3E%0A%09%3C%2FSay%3E%0A%09%3CSay%3EGoodbye.%3C%2FSay%3E%0A%3C%2FResponse%3E", string(body)) + require.Equal(t, "From=%2B1234567890&To=%2B12223334444&Twiml=%0A%3CResponse%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay+loop%3D%223%22%3E%0A%09%09You+have+a+message+from+notify+on+topic+mytopic.+Message%3A%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09hi+there%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09End+of+message.%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09This+message+was+sent+by+user+phil.+It+will+be+repeated+three+times.%0A%09%09To+unsubscribe+from+calls+like+this%2C+remove+your+phone+number+in+the+notify+web+app.%0A%09%09%3Cbreak+time%3D%223s%22%2F%3E%0A%09%3C%2FSay%3E%0A%09%3CSay%3EGoodbye.%3C%2FSay%3E%0A%3C%2FResponse%3E", string(body)) called.Store(true) })) defer twilioCallsServer.Close() @@ -69,7 +69,7 @@ func TestServer_Twilio_Call_Add_Verify_Call_Delete_Success(t *testing.T) { require.Nil(t, err) // Send verification code for phone number - response := request(t, s, "PUT", "/v1/account/phone/verify", `{"number":"+12223334444"}`, map[string]string{ + response := request(t, s, "PUT", "/v1/account/phone/verify", `{"number":"+12223334444","channel":"sms"}`, map[string]string{ "authorization": util.BasicAuth("phil", "phil"), }) require.Equal(t, 200, response.Code) @@ -122,7 +122,7 @@ func TestServer_Twilio_Call_Success(t *testing.T) { require.Nil(t, err) require.Equal(t, "/2010-04-01/Accounts/AC1234567890/Calls.json", r.URL.Path) require.Equal(t, "Basic QUMxMjM0NTY3ODkwOkFBRUFBMTIzNDU2Nzg5MA==", r.Header.Get("Authorization")) - require.Equal(t, "From=%2B1234567890&To=%2B11122233344&Twiml=%0A%3CResponse%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay+loop%3D%223%22%3E%0A%09%09You+have+a+notification+from+notify+on+topic+mytopic.+Message%3A%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09hi+there%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09End+message.%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09This+message+was+sent+by+user+phil.+It+will+be+repeated+up+to+three+times.%0A%09%09%3Cbreak+time%3D%223s%22%2F%3E%0A%09%3C%2FSay%3E%0A%09%3CSay%3EGoodbye.%3C%2FSay%3E%0A%3C%2FResponse%3E", string(body)) + require.Equal(t, "From=%2B1234567890&To=%2B11122233344&Twiml=%0A%3CResponse%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay+loop%3D%223%22%3E%0A%09%09You+have+a+message+from+notify+on+topic+mytopic.+Message%3A%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09hi+there%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09End+of+message.%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09This+message+was+sent+by+user+phil.+It+will+be+repeated+three+times.%0A%09%09To+unsubscribe+from+calls+like+this%2C+remove+your+phone+number+in+the+notify+web+app.%0A%09%09%3Cbreak+time%3D%223s%22%2F%3E%0A%09%3C%2FSay%3E%0A%09%3CSay%3EGoodbye.%3C%2FSay%3E%0A%3C%2FResponse%3E", string(body)) called.Store(true) })) defer twilioServer.Close() @@ -167,7 +167,7 @@ func TestServer_Twilio_Call_Success_With_Yes(t *testing.T) { require.Nil(t, err) require.Equal(t, "/2010-04-01/Accounts/AC1234567890/Calls.json", r.URL.Path) require.Equal(t, "Basic QUMxMjM0NTY3ODkwOkFBRUFBMTIzNDU2Nzg5MA==", r.Header.Get("Authorization")) - require.Equal(t, "From=%2B1234567890&To=%2B11122233344&Twiml=%0A%3CResponse%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay+loop%3D%223%22%3E%0A%09%09You+have+a+notification+from+notify+on+topic+mytopic.+Message%3A%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09hi+there%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09End+message.%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09This+message+was+sent+by+user+phil.+It+will+be+repeated+up+to+three+times.%0A%09%09%3Cbreak+time%3D%223s%22%2F%3E%0A%09%3C%2FSay%3E%0A%09%3CSay%3EGoodbye.%3C%2FSay%3E%0A%3C%2FResponse%3E", string(body)) + require.Equal(t, "From=%2B1234567890&To=%2B11122233344&Twiml=%0A%3CResponse%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay+loop%3D%223%22%3E%0A%09%09You+have+a+message+from+notify+on+topic+mytopic.+Message%3A%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09hi+there%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09End+of+message.%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09This+message+was+sent+by+user+phil.+It+will+be+repeated+three+times.%0A%09%09To+unsubscribe+from+calls+like+this%2C+remove+your+phone+number+in+the+notify+web+app.%0A%09%09%3Cbreak+time%3D%223s%22%2F%3E%0A%09%3C%2FSay%3E%0A%09%3CSay%3EGoodbye.%3C%2FSay%3E%0A%3C%2FResponse%3E", string(body)) called.Store(true) })) defer twilioServer.Close() diff --git a/server/types.go b/server/types.go index 3b733678..1e9457be 100644 --- a/server/types.go +++ b/server/types.go @@ -318,7 +318,7 @@ type apiAccountPhoneNumberVerifyRequest struct { type apiAccountPhoneNumberAddRequest struct { Number string `json:"number"` - Code string `json:"code,omitempty"` + Code string `json:"code"` // Only set when adding a phone number } type apiAccountTier struct { diff --git a/user/manager.go b/user/manager.go index c57ede58..00407ab3 100644 --- a/user/manager.go +++ b/user/manager.go @@ -117,7 +117,6 @@ const ( PRIMARY KEY (user_id, phone_number), FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE ); - CREATE UNIQUE INDEX idx_user_phone_number ON user_phone (phone_number); CREATE TABLE IF NOT EXISTS schemaVersion ( id INT PRIMARY KEY, version INT NOT NULL @@ -420,7 +419,6 @@ const ( PRIMARY KEY (user_id, phone_number), FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE ); - CREATE UNIQUE INDEX idx_user_phone_number ON user_phone (phone_number); ` ) @@ -694,8 +692,8 @@ func (a *Manager) AddPhoneNumber(userID string, phoneNumber string) error { return nil } -// DeletePhoneNumber deletes a phone number from the user with the given user ID -func (a *Manager) DeletePhoneNumber(userID string, phoneNumber string) error { +// RemovePhoneNumber deletes a phone number from the user with the given user ID +func (a *Manager) RemovePhoneNumber(userID string, phoneNumber string) error { _, err := a.db.Exec(deletePhoneNumberQuery, userID, phoneNumber) return err } diff --git a/user/manager_test.go b/user/manager_test.go index cd2e1032..de1ad6fb 100644 --- a/user/manager_test.go +++ b/user/manager_test.go @@ -893,6 +893,38 @@ func TestManager_Tier_Change_And_Reset(t *testing.T) { require.Nil(t, a.ResetTier("phil")) } +func TestUser_PhoneNumberAddListRemove(t *testing.T) { + a := newTestManager(t, PermissionDenyAll) + + require.Nil(t, a.AddUser("phil", "phil", RoleUser)) + phil, err := a.User("phil") + require.Nil(t, err) + require.Nil(t, a.AddPhoneNumber(phil.ID, "+1234567890")) + + phoneNumbers, err := a.PhoneNumbers(phil.ID) + require.Nil(t, err) + require.Equal(t, 1, len(phoneNumbers)) + require.Equal(t, "+1234567890", phoneNumbers[0]) + + require.Nil(t, a.RemovePhoneNumber(phil.ID, "+1234567890")) + phoneNumbers, err = a.PhoneNumbers(phil.ID) + require.Nil(t, err) + require.Equal(t, 0, len(phoneNumbers)) +} + +func TestUser_PhoneNumberAdd_Multiple_Users_Same_Number(t *testing.T) { + a := newTestManager(t, PermissionDenyAll) + + require.Nil(t, a.AddUser("phil", "phil", RoleUser)) + require.Nil(t, a.AddUser("ben", "ben", RoleUser)) + phil, err := a.User("phil") + require.Nil(t, err) + ben, err := a.User("ben") + require.Nil(t, err) + require.Nil(t, a.AddPhoneNumber(phil.ID, "+1234567890")) + require.Nil(t, a.AddPhoneNumber(ben.ID, "+1234567890")) +} + func TestSqliteCache_Migration_From1(t *testing.T) { filename := filepath.Join(t.TempDir(), "user.db") db, err := sql.Open("sqlite3", filename) diff --git a/web/package-lock.json b/web/package-lock.json index f1b4785f..5457f17d 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -3134,14 +3134,14 @@ "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, "node_modules/@mui/base": { - "version": "5.0.0-beta.0", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.0.tgz", - "integrity": "sha512-ap+juKvt8R8n3cBqd/pGtZydQ4v2I/hgJKnvJRGjpSh3RvsvnDHO4rXov8MHQlH6VqpOekwgilFLGxMZjNTucA==", + "version": "5.0.0-beta.1", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.1.tgz", + "integrity": "sha512-xrkDCeu3JQE+JjJUnJnOrdQJMXwKhbV4AW+FRjMIj5i9cHK3BAuatG/iqbf1M+jklVWLk0KdbgioKwK+03aYbA==", "dependencies": { "@babel/runtime": "^7.21.0", "@emotion/is-prop-valid": "^1.2.0", "@mui/types": "^7.2.4", - "@mui/utils": "^5.12.3", + "@mui/utils": "^5.13.1", "@popperjs/core": "^2.11.7", "clsx": "^1.2.1", "prop-types": "^15.8.1", @@ -3166,9 +3166,9 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.13.0", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.13.0.tgz", - "integrity": "sha512-5nXz2k8Rv2ZjtQY6kXirJVyn2+ODaQuAJmXSJtLDUQDKWp3PFUj6j3bILqR0JGOs9R5ejgwz3crLKsl6GwjwkQ==", + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.13.1.tgz", + "integrity": "sha512-qDHtNDO72NcBQMhaWBt9EZMvNiO+OXjPg5Sdk/6LgRDw6Zr3HdEZ5n2FJ/qtYsaT/okGyCuQavQkcZCOCEVf/g==", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui" @@ -3200,16 +3200,16 @@ } }, "node_modules/@mui/material": { - "version": "5.13.0", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.13.0.tgz", - "integrity": "sha512-ckS+9tCpAzpdJdaTF+btF0b6mF9wbXg/EVKtnoAWYi0UKXoXBAVvEUMNpLGA5xdpCdf+A6fPbVUEHs9TsfU+Yw==", + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.13.1.tgz", + "integrity": "sha512-qSnbJZer8lIuDYFDv19/t3s0AXYY9SxcOdhCnGvetRSfOG4gy3TkiFXNCdW5OLNveTieiMpOuv46eXUmE3ZA6A==", "dependencies": { "@babel/runtime": "^7.21.0", - "@mui/base": "5.0.0-beta.0", - "@mui/core-downloads-tracker": "^5.13.0", - "@mui/system": "^5.12.3", + "@mui/base": "5.0.0-beta.1", + "@mui/core-downloads-tracker": "^5.13.1", + "@mui/system": "^5.13.1", "@mui/types": "^7.2.4", - "@mui/utils": "^5.12.3", + "@mui/utils": "^5.13.1", "@types/react-transition-group": "^4.4.6", "clsx": "^1.2.1", "csstype": "^3.1.2", @@ -3244,12 +3244,12 @@ } }, "node_modules/@mui/private-theming": { - "version": "5.12.3", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.12.3.tgz", - "integrity": "sha512-o1e7Z1Bp27n4x2iUHhegV4/Jp6H3T6iBKHJdLivS5GbwsuAE/5l4SnZ+7+K+e5u9TuhwcAKZLkjvqzkDe8zqfA==", + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.13.1.tgz", + "integrity": "sha512-HW4npLUD9BAkVppOUZHeO1FOKUJWAwbpy0VQoGe3McUYTlck1HezGHQCfBQ5S/Nszi7EViqiimECVl9xi+/WjQ==", "dependencies": { "@babel/runtime": "^7.21.0", - "@mui/utils": "^5.12.3", + "@mui/utils": "^5.13.1", "prop-types": "^15.8.1" }, "engines": { @@ -3301,15 +3301,15 @@ } }, "node_modules/@mui/system": { - "version": "5.12.3", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.12.3.tgz", - "integrity": "sha512-JB/6sypHqeJCqwldWeQ1MKkijH829EcZAKKizxbU2MJdxGG5KSwZvTBa5D9qiJUA1hJFYYupjiuy9ZdJt6rV6w==", + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.13.1.tgz", + "integrity": "sha512-BsDUjhiO6ZVAvzKhnWBHLZ5AtPJcdT+62VjnRLyA4isboqDKLg4fmYIZXq51yndg/soDK9RkY5lYZwEDku13Ow==", "dependencies": { "@babel/runtime": "^7.21.0", - "@mui/private-theming": "^5.12.3", + "@mui/private-theming": "^5.13.1", "@mui/styled-engine": "^5.12.3", "@mui/types": "^7.2.4", - "@mui/utils": "^5.12.3", + "@mui/utils": "^5.13.1", "clsx": "^1.2.1", "csstype": "^3.1.2", "prop-types": "^15.8.1" @@ -3353,13 +3353,13 @@ } }, "node_modules/@mui/utils": { - "version": "5.12.3", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.12.3.tgz", - "integrity": "sha512-D/Z4Ub3MRl7HiUccid7sQYclTr24TqUAQFFlxHQF8FR177BrCTQ0JJZom7EqYjZCdXhwnSkOj2ph685MSKNtIA==", + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.13.1.tgz", + "integrity": "sha512-6lXdWwmlUbEU2jUI8blw38Kt+3ly7xkmV9ljzY4Q20WhsJMWiNry9CX8M+TaP/HbtuyR8XKsdMgQW7h7MM3n3A==", "dependencies": { "@babel/runtime": "^7.21.0", "@types/prop-types": "^15.7.5", - "@types/react-is": "^16.7.1 || ^17.0.0", + "@types/react-is": "^18.2.0", "prop-types": "^15.8.1", "react-is": "^18.2.0" }, @@ -4016,9 +4016,9 @@ "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "node_modules/@types/node": { - "version": "20.1.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.4.tgz", - "integrity": "sha512-At4pvmIOki8yuwLtd7BNHl3CiWNbtclUbNtScGx4OHfBd4/oWoJC8KRCIxXwkdndzhxOsPXihrsOoydxBjlE9Q==" + "version": "20.1.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.7.tgz", + "integrity": "sha512-WCuw/o4GSwDGMoonES8rcvwsig77dGCMbZDrZr2x4ZZiNW4P/gcoZXe/0twgtobcTkmg9TuKflxYL/DuwDyJzg==" }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -4061,21 +4061,11 @@ } }, "node_modules/@types/react-is": { - "version": "17.0.4", - "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.4.tgz", - "integrity": "sha512-FLzd0K9pnaEvKz4D1vYxK9JmgQPiGk1lu23o1kqGsLeT0iPbRSF7b76+S5T9fD8aRa0B8bY7I/3DebEj+1ysBA==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-1vz2yObaQkLL7YFe/pme2cpvDsCwI1WXIfL+5eLz0MI9gFG24Re16RzUsI8t9XZn9ZWvgLNDrJBmrqXJO7GNQQ==", "dependencies": { - "@types/react": "^17" - } - }, - "node_modules/@types/react-is/node_modules/@types/react": { - "version": "17.0.59", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.59.tgz", - "integrity": "sha512-gSON5zWYIGyoBcycCE75E9+r6dCC2dHdsrVkOEiIYNU5+Q28HcBAuqvDuxHcCbMfHBHdeT5Tva/AFn3rnMKE4g==", - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" + "@types/react": "*" } }, "node_modules/@types/react-transition-group": { @@ -4175,14 +4165,14 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.59.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.5.tgz", - "integrity": "sha512-feA9xbVRWJZor+AnLNAr7A8JRWeZqHUf4T9tlP+TN04b05pFVhO5eN7/O93Y/1OUlLMHKbnJisgDURs/qvtqdg==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.6.tgz", + "integrity": "sha512-sXtOgJNEuRU5RLwPUb1jxtToZbgvq3M6FPpY4QENxoOggK+UpTxUBpj6tD8+Qh2g46Pi9We87E+eHnUw8YcGsw==", "dependencies": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.5", - "@typescript-eslint/type-utils": "5.59.5", - "@typescript-eslint/utils": "5.59.5", + "@typescript-eslint/scope-manager": "5.59.6", + "@typescript-eslint/type-utils": "5.59.6", + "@typescript-eslint/utils": "5.59.6", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -4208,11 +4198,11 @@ } }, "node_modules/@typescript-eslint/experimental-utils": { - "version": "5.59.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.59.5.tgz", - "integrity": "sha512-ArcSSBifznsKNA/p4h2w3Olt/T8AZf3bNglxD8OnuTsSDJbRpjPPmI8qpr6ijyvk1J/T3GMJHwRIluS/Kuz9kA==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.59.6.tgz", + "integrity": "sha512-UIVfEaaHggOuhgqdpFlFQ7IN9UFMCiBR/N7uPBUyUlwNdJzYfAu9m4wbOj0b59oI/HSPW1N63Q7lsvfwTQY13w==", "dependencies": { - "@typescript-eslint/utils": "5.59.5" + "@typescript-eslint/utils": "5.59.6" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -4226,13 +4216,13 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.59.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.5.tgz", - "integrity": "sha512-NJXQC4MRnF9N9yWqQE2/KLRSOLvrrlZb48NGVfBa+RuPMN6B7ZcK5jZOvhuygv4D64fRKnZI4L4p8+M+rfeQuw==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.6.tgz", + "integrity": "sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA==", "dependencies": { - "@typescript-eslint/scope-manager": "5.59.5", - "@typescript-eslint/types": "5.59.5", - "@typescript-eslint/typescript-estree": "5.59.5", + "@typescript-eslint/scope-manager": "5.59.6", + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/typescript-estree": "5.59.6", "debug": "^4.3.4" }, "engines": { @@ -4252,12 +4242,12 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.5.tgz", - "integrity": "sha512-jVecWwnkX6ZgutF+DovbBJirZcAxgxC0EOHYt/niMROf8p4PwxxG32Qdhj/iIQQIuOflLjNkxoXyArkcIP7C3A==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.6.tgz", + "integrity": "sha512-gLbY3Le9Dxcb8KdpF0+SJr6EQ+hFGYFl6tVY8VxLPFDfUZC7BHFw+Vq7bM5lE9DwWPfx4vMWWTLGXgpc0mAYyQ==", "dependencies": { - "@typescript-eslint/types": "5.59.5", - "@typescript-eslint/visitor-keys": "5.59.5" + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/visitor-keys": "5.59.6" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -4268,12 +4258,12 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.59.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.5.tgz", - "integrity": "sha512-4eyhS7oGym67/pSxA2mmNq7X164oqDYNnZCUayBwJZIRVvKpBCMBzFnFxjeoDeShjtO6RQBHBuwybuX3POnDqg==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.6.tgz", + "integrity": "sha512-A4tms2Mp5yNvLDlySF+kAThV9VTBPCvGf0Rp8nl/eoDX9Okun8byTKoj3fJ52IJitjWOk0fKPNQhXEB++eNozQ==", "dependencies": { - "@typescript-eslint/typescript-estree": "5.59.5", - "@typescript-eslint/utils": "5.59.5", + "@typescript-eslint/typescript-estree": "5.59.6", + "@typescript-eslint/utils": "5.59.6", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -4294,9 +4284,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.59.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.5.tgz", - "integrity": "sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.6.tgz", + "integrity": "sha512-tH5lBXZI7T2MOUgOWFdVNUILsI02shyQvfzG9EJkoONWugCG77NDDa1EeDGw7oJ5IvsTAAGVV8I3Tk2PNu9QfA==", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -4306,12 +4296,12 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.5.tgz", - "integrity": "sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.6.tgz", + "integrity": "sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA==", "dependencies": { - "@typescript-eslint/types": "5.59.5", - "@typescript-eslint/visitor-keys": "5.59.5", + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/visitor-keys": "5.59.6", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -4332,16 +4322,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.59.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.5.tgz", - "integrity": "sha512-sCEHOiw+RbyTii9c3/qN74hYDPNORb8yWCoPLmB7BIflhplJ65u2PBpdRla12e3SSTJ2erRkPjz7ngLHhUegxA==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.6.tgz", + "integrity": "sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg==", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.59.5", - "@typescript-eslint/types": "5.59.5", - "@typescript-eslint/typescript-estree": "5.59.5", + "@typescript-eslint/scope-manager": "5.59.6", + "@typescript-eslint/types": "5.59.6", + "@typescript-eslint/typescript-estree": "5.59.6", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, @@ -4377,11 +4367,11 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.5", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.5.tgz", - "integrity": "sha512-qL+Oz+dbeBRTeyJTIy0eniD3uvqU7x+y1QceBismZ41hd4aBSRh8UAw4pZP0+XzLuPZmx4raNMq/I+59W2lXKA==", + "version": "5.59.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.6.tgz", + "integrity": "sha512-zEfbFLzB9ETcEJ4HZEEsCR9HHeNku5/Qw1jSS5McYJv5BR+ftYXwFFAH5Al+xkGaZEqowMwl7uoJjQb1YSPF8Q==", "dependencies": { - "@typescript-eslint/types": "5.59.5", + "@typescript-eslint/types": "5.59.6", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -4956,9 +4946,9 @@ } }, "node_modules/axe-core": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.0.tgz", - "integrity": "sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.1.tgz", + "integrity": "sha512-sCXXUhA+cljomZ3ZAwb8i1p3oOlkABzPy08ZDAoGcYuvtBPlQ1Ytde129ArXyHWDhfeewq7rlx9F+cUx2SSlkg==", "engines": { "node": ">=4" } @@ -5511,9 +5501,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001487", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001487.tgz", - "integrity": "sha512-83564Z3yWGqXsh2vaH/mhXfEM0wX+NlBCm1jYHOb97TrTWJEmPTccZgeLTPBUUb0PNVo+oomb7wkimZBIERClA==", + "version": "1.0.30001488", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001488.tgz", + "integrity": "sha512-NORIQuuL4xGpIy6iCCQGN4iFjlBXtfKWIenlUuyZJumLRIindLb7wXM+GO8erEhb7vXfcnf4BAg2PrSDN5TNLQ==", "funding": [ { "type": "opencollective", @@ -6749,9 +6739,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.394", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.394.tgz", - "integrity": "sha512-0IbC2cfr8w5LxTz+nmn2cJTGafsK9iauV2r5A5scfzyovqLrxuLoxOHE5OBobP3oVIggJT+0JfKnw9sm87c8Hw==" + "version": "1.4.397", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.397.tgz", + "integrity": "sha512-jwnPxhh350Q/aMatQia31KAIQdhEsYS0fFZ0BQQlN9tfvOEwShu6ZNwI4kL/xBabjcB/nTy6lSt17kNIluJZ8Q==" }, "node_modules/emittery": { "version": "0.8.1", @@ -9146,9 +9136,9 @@ } }, "node_modules/is-core-module": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", - "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", "dependencies": { "has": "^1.0.3" }, @@ -13879,9 +13869,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.12.tgz", - "integrity": "sha512-NdxGCAZdRrwVI1sy59+Wzrh+pMMHxapGnpfenDVlMEXoOcvt4pGE0JLK9YY2F5dLxcFYA/YbVQKhcGU+FtSYQg==", + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -15976,9 +15966,9 @@ } }, "node_modules/terser": { - "version": "5.17.3", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.3.tgz", - "integrity": "sha512-AudpAZKmZHkG9jueayypz4duuCFJMMNGRMwaPvQKWfxKedh8Z2x3OCoDqIIi1xx5+iwx1u6Au8XQcc9Lke65Yg==", + "version": "5.17.4", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.4.tgz", + "integrity": "sha512-jcEKZw6UPrgugz/0Tuk/PVyLAPfMBJf5clnGueo45wTweoV8yh7Q7PEkhkJ5uuUbC7zAxEcG3tqNr1bstkQ8nw==", "dependencies": { "@jridgewell/source-map": "^0.3.2", "acorn": "^8.5.0", diff --git a/web/src/app/AccountApi.js b/web/src/app/AccountApi.js index 8908f306..915e3bb8 100644 --- a/web/src/app/AccountApi.js +++ b/web/src/app/AccountApi.js @@ -1,14 +1,16 @@ import { accountBillingPortalUrl, accountBillingSubscriptionUrl, - accountPasswordUrl, accountPhoneUrl, accountPhoneVerifyUrl, + accountPasswordUrl, + accountPhoneUrl, + accountPhoneVerifyUrl, accountReservationSingleUrl, accountReservationUrl, accountSettingsUrl, - accountSubscriptionSingleUrl, accountSubscriptionUrl, accountTokenUrl, - accountUrl, maybeWithBearerAuth, + accountUrl, + maybeWithBearerAuth, tiersUrl, withBasicAuth, withBearerAuth @@ -18,7 +20,7 @@ import subscriptionManager from "./SubscriptionManager"; import i18n from "i18next"; import prefs from "./Prefs"; import routes from "../components/routes"; -import {fetchOrThrow, throwAppError, UnauthorizedError} from "./errors"; +import {fetchOrThrow, UnauthorizedError} from "./errors"; const delayMillis = 45000; // 45 seconds const intervalMillis = 900000; // 15 minutes diff --git a/web/src/components/Account.js b/web/src/components/Account.js index b480ea6b..83ef0b7d 100644 --- a/web/src/components/Account.js +++ b/web/src/components/Account.js @@ -1,13 +1,17 @@ import * as React from 'react'; import {useContext, useState} from 'react'; import { - Alert, ButtonGroup, + Alert, CardActions, - CardContent, Chip, - FormControl, FormControlLabel, InputLabel, + CardContent, + Chip, + FormControl, + FormControlLabel, LinearProgress, Link, - Portal, Radio, RadioGroup, + Portal, + Radio, + RadioGroup, Select, Snackbar, Stack, @@ -47,14 +51,12 @@ import {AccountContext} from "./App"; import DialogFooter from "./DialogFooter"; import {Paragraph} from "./styles"; import CloseIcon from "@mui/icons-material/Close"; -import {Check, ContentCopy, DeleteForever, Public} from "@mui/icons-material"; +import {ContentCopy, Public} from "@mui/icons-material"; import MenuItem from "@mui/material/MenuItem"; import DialogContentText from "@mui/material/DialogContentText"; import {IncorrectPasswordError, UnauthorizedError} from "../app/errors"; import {ProChip} from "./SubscriptionPopup"; import AddIcon from "@mui/icons-material/Add"; -import ListItemIcon from "@mui/material/ListItemIcon"; -import ListItemText from "@mui/material/ListItemText"; const Account = () => { if (!session.exists()) { @@ -427,6 +429,7 @@ const AddPhoneNumberDialog = (props) => { const handleCancel = () => { if (verificationCodeSent) { setVerificationCodeSent(false); + setCode(""); } else { props.onClose(); }