diff --git a/pkg/api/authorizations.go b/pkg/api/authorizations.go index 9719c84c..0a4a9c7e 100644 --- a/pkg/api/authorizations.go +++ b/pkg/api/authorizations.go @@ -233,9 +233,8 @@ func (a *AuthorizationsApi) TwoFactorAuthorizeByRecoveryCodeHandler(c *core.Cont func (a *AuthorizationsApi) getAuthResponse(token string, need2FA bool, user *models.User) *models.AuthResponse { return &models.AuthResponse{ - Token: token, - Need2FA: need2FA, - NeedVerifyEmail: false, - User: user.ToUserBasicInfo(), + Token: token, + Need2FA: need2FA, + User: user.ToUserBasicInfo(), } } diff --git a/pkg/api/transaction_categories.go b/pkg/api/transaction_categories.go index 82c03a64..c7cc9f79 100644 --- a/pkg/api/transaction_categories.go +++ b/pkg/api/transaction_categories.go @@ -3,6 +3,8 @@ package api import ( "sort" + "github.com/gin-gonic/gin/binding" + "github.com/mayswind/ezbookkeeping/pkg/core" "github.com/mayswind/ezbookkeeping/pkg/errs" "github.com/mayswind/ezbookkeeping/pkg/log" @@ -134,7 +136,7 @@ func (a *TransactionCategoriesApi) CategoryCreateHandler(c *core.Context) (inter // CategoryCreateBatchHandler saves some new transaction category by request parameters for current user func (a *TransactionCategoriesApi) CategoryCreateBatchHandler(c *core.Context) (interface{}, *errs.Error) { var categoryCreateBatchReq models.TransactionCategoryCreateBatchRequest - err := c.ShouldBindJSON(&categoryCreateBatchReq) + err := c.ShouldBindBodyWith(&categoryCreateBatchReq, binding.JSON) if err != nil { log.WarnfWithRequestId(c, "[transaction_categories.CategoryCreateBatchHandler] parse request failed, because %s", err.Error()) @@ -143,53 +145,12 @@ func (a *TransactionCategoriesApi) CategoryCreateBatchHandler(c *core.Context) ( uid := c.GetCurrentUid() - categoryTypeMaxOrderMap := make(map[models.TransactionCategoryType]int32) - categoriesMap := make(map[*models.TransactionCategory][]*models.TransactionCategory) - categoriesMap[nil] = make([]*models.TransactionCategory, len(categoryCreateBatchReq.Categories)) - totalCount := 0 - - for i := 0; i < len(categoryCreateBatchReq.Categories); i++ { - categoryCreateReq := categoryCreateBatchReq.Categories[i] - var maxOrderId, exists = categoryTypeMaxOrderMap[categoryCreateReq.Type] - - if !exists { - maxOrderId, err = a.categories.GetMaxDisplayOrder(c, uid, categoryCreateReq.Type) - - if err != nil { - log.ErrorfWithRequestId(c, "[transaction_categories.CategoryCreateBatchHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error()) - return nil, errs.Or(err, errs.ErrOperationFailed) - } - } - - category := a.createNewCategoryModel(uid, &models.TransactionCategoryCreateRequest{ - Name: categoryCreateReq.Name, - Type: categoryCreateReq.Type, - Icon: categoryCreateReq.Icon, - Color: categoryCreateReq.Color, - }, maxOrderId+1) - - categoriesMap[category] = make([]*models.TransactionCategory, len(categoryCreateReq.SubCategories)) - - for j := int32(0); j < int32(len(categoryCreateReq.SubCategories)); j++ { - subCategory := a.createNewCategoryModel(uid, categoryCreateReq.SubCategories[j], j+1) - categoriesMap[category][j] = subCategory - totalCount++ - } - - categoriesMap[nil][i] = category - categoryTypeMaxOrderMap[categoryCreateReq.Type] = maxOrderId + 1 - totalCount++ - } - - categories, err := a.categories.CreateCategories(c, uid, categoriesMap) + categories, err := a.createBatchCategories(c, uid, &categoryCreateBatchReq) if err != nil { - log.ErrorfWithRequestId(c, "[transaction_categories.CategoryCreateBatchHandler] failed to create categories for user \"uid:%d\", because %s", uid, err.Error()) return nil, errs.Or(err, errs.ErrOperationFailed) } - log.InfofWithRequestId(c, "[transaction_categories.CategoryCreateBatchHandler] user \"uid:%d\" has created categoroies successfully", uid) - return a.getTransactionCategoryListByTypeResponse(categories, 0) } @@ -325,6 +286,58 @@ func (a *TransactionCategoriesApi) CategoryDeleteHandler(c *core.Context) (inter return true, nil } +func (a *TransactionCategoriesApi) createBatchCategories(c *core.Context, uid int64, categoryCreateBatchReq *models.TransactionCategoryCreateBatchRequest) ([]*models.TransactionCategory, error) { + var err error + categoryTypeMaxOrderMap := make(map[models.TransactionCategoryType]int32) + categoriesMap := make(map[*models.TransactionCategory][]*models.TransactionCategory) + categoriesMap[nil] = make([]*models.TransactionCategory, len(categoryCreateBatchReq.Categories)) + totalCount := 0 + + for i := 0; i < len(categoryCreateBatchReq.Categories); i++ { + categoryCreateReq := categoryCreateBatchReq.Categories[i] + var maxOrderId, exists = categoryTypeMaxOrderMap[categoryCreateReq.Type] + + if !exists { + maxOrderId, err = a.categories.GetMaxDisplayOrder(c, uid, categoryCreateReq.Type) + + if err != nil { + log.ErrorfWithRequestId(c, "[transaction_categories.CategoryCreateBatchHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error()) + return nil, errs.Or(err, errs.ErrOperationFailed) + } + } + + category := a.createNewCategoryModel(uid, &models.TransactionCategoryCreateRequest{ + Name: categoryCreateReq.Name, + Type: categoryCreateReq.Type, + Icon: categoryCreateReq.Icon, + Color: categoryCreateReq.Color, + }, maxOrderId+1) + + categoriesMap[category] = make([]*models.TransactionCategory, len(categoryCreateReq.SubCategories)) + + for j := int32(0); j < int32(len(categoryCreateReq.SubCategories)); j++ { + subCategory := a.createNewCategoryModel(uid, categoryCreateReq.SubCategories[j], j+1) + categoriesMap[category][j] = subCategory + totalCount++ + } + + categoriesMap[nil][i] = category + categoryTypeMaxOrderMap[categoryCreateReq.Type] = maxOrderId + 1 + totalCount++ + } + + categories, err := a.categories.CreateCategories(c, uid, categoriesMap) + + if err != nil { + log.ErrorfWithRequestId(c, "[transaction_categories.createBatchCategories] failed to create categories for user \"uid:%d\", because %s", uid, err.Error()) + return nil, errs.Or(err, errs.ErrOperationFailed) + } + + log.InfofWithRequestId(c, "[transaction_categories.createBatchCategories] user \"uid:%d\" has created categories successfully", uid) + + return categories, nil +} + func (a *TransactionCategoriesApi) createNewCategoryModel(uid int64, categoryCreateReq *models.TransactionCategoryCreateRequest, order int32) *models.TransactionCategory { return &models.TransactionCategory{ Uid: uid, diff --git a/pkg/api/users.go b/pkg/api/users.go index 6c965415..66bcef9a 100644 --- a/pkg/api/users.go +++ b/pkg/api/users.go @@ -4,6 +4,8 @@ import ( "strings" "time" + "github.com/gin-gonic/gin/binding" + "github.com/mayswind/ezbookkeeping/pkg/core" "github.com/mayswind/ezbookkeeping/pkg/errs" "github.com/mayswind/ezbookkeeping/pkg/log" @@ -36,7 +38,7 @@ func (a *UsersApi) UserRegisterHandler(c *core.Context) (interface{}, *errs.Erro } var userRegisterReq models.UserRegisterRequest - err := c.ShouldBindJSON(&userRegisterReq) + err := c.ShouldBindBodyWith(&userRegisterReq, binding.JSON) if err != nil { log.WarnfWithRequestId(c, "[users.UserRegisterHandler] parse request failed, because %s", err.Error()) @@ -72,10 +74,23 @@ func (a *UsersApi) UserRegisterHandler(c *core.Context) (interface{}, *errs.Erro log.InfofWithRequestId(c, "[users.UserRegisterHandler] user \"%s\" has registered successfully, uid is %d", user.Username, user.Uid) - authResp := &models.AuthResponse{ - Need2FA: false, - NeedVerifyEmail: settings.Container.Current.EnableUserForceVerifyEmail, - User: user.ToUserBasicInfo(), + presetCategoriesSaved := false + + if len(userRegisterReq.Categories) > 0 { + _, err = TransactionCategories.createBatchCategories(c, user.Uid, &userRegisterReq.TransactionCategoryCreateBatchRequest) + + if err == nil { + presetCategoriesSaved = true + } + } + + authResp := &models.RegisterResponse{ + AuthResponse: models.AuthResponse{ + Need2FA: false, + User: user.ToUserBasicInfo(), + }, + NeedVerifyEmail: settings.Container.Current.EnableUserForceVerifyEmail, + PresetCategoriesSaved: presetCategoriesSaved, } if settings.Container.Current.EnableUserVerifyEmail && settings.Container.Current.EnableSMTP { diff --git a/pkg/models/auth_response.go b/pkg/models/auth_response.go index 0d634cc9..ec959d91 100644 --- a/pkg/models/auth_response.go +++ b/pkg/models/auth_response.go @@ -2,8 +2,14 @@ package models // AuthResponse returns a view-object of user authorization type AuthResponse struct { - Token string `json:"token"` - Need2FA bool `json:"need2FA"` - NeedVerifyEmail bool `json:"needVerifyEmail"` - User *UserBasicInfo `json:"user"` + Token string `json:"token"` + Need2FA bool `json:"need2FA"` + User *UserBasicInfo `json:"user"` +} + +// RegisterResponse returns a view-object of user register response +type RegisterResponse struct { + AuthResponse + NeedVerifyEmail bool `json:"needVerifyEmail"` + PresetCategoriesSaved bool `json:"presetCategoriesSaved"` } diff --git a/pkg/models/user.go b/pkg/models/user.go index 681d0ecc..5dc1d1fa 100644 --- a/pkg/models/user.go +++ b/pkg/models/user.go @@ -107,6 +107,7 @@ type UserRegisterRequest struct { Language string `json:"language" binding:"required,min=2,max=16"` DefaultCurrency string `json:"defaultCurrency" binding:"required,len=3,validCurrency"` FirstDayOfWeek WeekDay `json:"firstDayOfWeek" binding:"min=0,max=6"` + TransactionCategoryCreateBatchRequest } // UserVerifyEmailRequest represents all parameters of user verify email request diff --git a/src/lib/services.js b/src/lib/services.js index e51a0edf..13a7d4ee 100644 --- a/src/lib/services.js +++ b/src/lib/services.js @@ -91,7 +91,7 @@ export default { } }); }, - register: ({ username, email, nickname, password, language, defaultCurrency, firstDayOfWeek }) => { + register: ({ username, email, nickname, password, language, defaultCurrency, firstDayOfWeek, categories }) => { return axios.post('register.json', { username, email, @@ -99,7 +99,8 @@ export default { password, language, defaultCurrency, - firstDayOfWeek + firstDayOfWeek, + categories }, { timeout: api.requestVerifyEmailTimeout }); diff --git a/src/locales/en.js b/src/locales/en.js index dd96446d..37096165 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -1056,8 +1056,11 @@ export default { 'Use Preset Transaction Categories': 'Use Preset Transaction Categories', 'Preset Categories': 'Preset Categories', 'Set Whether You Use The Preset Transaction Categories': 'Set Whether You Use The Preset Transaction Categories', + 'Complete': 'Complete', + 'Registration Complete': 'Registration Complete', 'You have been successfully registered': 'You have been successfully registered', 'You have been successfully registered, but something wrong with adding preset categories. You can re-add preset categories in settings page anytime.': 'You have been successfully registered, but something wrong with adding preset categories. You can re-add preset categories in settings page anytime.', + 'You have been successfully registered. Account activation link has been sent to your email address, please activate your account first.': 'You have been successfully registered. Account activation link has been sent to your email address, please activate your account first.', 'Unable to sign up': 'Unable to sign up', 'User registration is disabled': 'User registration is disabled', 'Unable to get user profile': 'Unable to get user profile', diff --git a/src/locales/zh_Hans.js b/src/locales/zh_Hans.js index dde31e74..a1d476f9 100644 --- a/src/locales/zh_Hans.js +++ b/src/locales/zh_Hans.js @@ -1056,8 +1056,11 @@ export default { 'Use Preset Transaction Categories': '使用预设交易分类', 'Preset Categories': '预设分类', 'Set Whether You Use The Preset Transaction Categories': '设置是否使用预设交易分类', + 'Complete': '完成', + 'Registration Complete': '注册完成', 'You have been successfully registered': '注册成功', - 'You have been successfully registered, but something wrong with adding preset categories. You can re-add preset categories in settings page anytime.': '注册成功,但是添加预设分类时出错。您可以随时在设置页面中重新添加预设分类。', + 'You have been successfully registered, but something wrong with adding preset categories. You can re-add preset categories in settings page anytime.': '您已经注册成功,但是添加预设分类时出错。您可以随时在设置页面中重新添加预设分类。', + 'You have been successfully registered. Account activation link has been sent to your email address, please activate your account first.': '您已经注册成功。账号激活链接已经发送到您的邮箱地址,请先激活您的账号。', 'Unable to sign up': '无法注册', 'User registration is disabled': '用户注册已禁用', 'Unable to get user profile': '无法获取用户信息', diff --git a/src/stores/index.js b/src/stores/index.js index 0e5f1205..2676f7e7 100644 --- a/src/stores/index.js +++ b/src/stores/index.js @@ -159,7 +159,7 @@ export const useRootStore = defineStore('root', { }); }); }, - register({ user }) { + register({ user, presetCategories }) { const settingsStore = useSettingsStore(); return new Promise((resolve, reject) => { @@ -170,7 +170,8 @@ export const useRootStore = defineStore('root', { nickname: user.nickname, language: user.language, defaultCurrency: user.defaultCurrency, - firstDayOfWeek: user.firstDayOfWeek + firstDayOfWeek: user.firstDayOfWeek, + categories: presetCategories }).then(response => { const data = response.data; diff --git a/src/views/desktop/SignupPage.vue b/src/views/desktop/SignupPage.vue index d1b7413c..1685de47 100644 --- a/src/views/desktop/SignupPage.vue +++ b/src/views/desktop/SignupPage.vue @@ -20,18 +20,7 @@ - + @@ -47,7 +36,7 @@ type="text" autocomplete="username" clearable - :disabled="submitting" + :disabled="submitting || navigateToHomePage" :label="$t('Username')" :placeholder="$t('Your username')" v-model="user.username" @@ -59,7 +48,7 @@ type="text" autocomplete="nickname" clearable - :disabled="submitting" + :disabled="submitting || navigateToHomePage" :label="$t('Nickname')" :placeholder="$t('Your nickname')" v-model="user.nickname" @@ -72,7 +61,7 @@ type="email" autocomplete="email" clearable - :disabled="submitting" + :disabled="submitting || navigateToHomePage" :label="$t('E-mail')" :placeholder="$t('Your email address')" v-model="user.email" @@ -84,7 +73,7 @@ @@ -170,7 +159,7 @@ @@ -186,7 +175,7 @@ -
+

{{ getCategoryTypeName(categoryType) }}

@@ -217,33 +206,41 @@
+ + +
{{ $t('Registration Complete') }}
+

{{ finalResultMessage }}

+
- {{ $t('Previous') }} - {{ $t('Next') }} + v-if="currentStep === 'basicSetting'">{{ $t('Next') }} {{ $t('Submit') }} + {{ $t('Continue') }}
- +
@@ -281,6 +278,8 @@ export default { isConfirmPasswordVisible: false, submitting: false, usePresetCategories: false, + finalResultMessage: null, + navigateToHomePage: false, icons: { previous: mdiArrowLeft, next: mdiArrowRight, @@ -341,6 +340,30 @@ export default { return languageInfo.displayName; }, + allSteps() { + const allSteps = [ + { + name: 'basicSetting', + title: this.$t('User Information'), + subTitle: this.$t('Basic Information') + }, + { + name: 'presetCategories', + title: this.$t('Transaction Categories'), + subTitle: this.$t('Preset Categories') + } + ]; + + if (this.finalResultMessage) { + allSteps.push({ + name: 'finalResult', + title: this.$t('Complete'), + subTitle: this.$t('Registration Complete') + }); + } + + return allSteps; + }, inputIsEmpty() { return !!this.inputEmptyProblemMessage; }, @@ -381,7 +404,7 @@ export default { }, methods: { switchToTab(tabName) { - if (this.submitting) { + if (this.submitting || this.currentStep === 'finalResult' || this.navigateToHomePage) { return; } @@ -414,27 +437,33 @@ export default { return; } + self.navigateToHomePage = false; self.submitting = true; - let submitCategories = []; + let presetCategories = []; if (self.usePresetCategories) { - submitCategories = categorizedArrayToPlainArray(self.allPresetCategories); + presetCategories = categorizedArrayToPlainArray(self.allPresetCategories); } self.rootStore.register({ - user: self.user + user: self.user, + presetCategories: presetCategories }).then(response => { if (!self.$user.isUserLogined()) { self.submitting = false; - if (self.usePresetCategories) { - self.$refs.snackbar.showMessage('You have been successfully registered, but something wrong with adding preset categories. You can re-add preset categories in settings page anytime.'); + if (self.usePresetCategories && !response.presetCategoriesSaved) { + self.finalResultMessage = self.$t('You have been successfully registered, but something wrong with adding preset categories. You can re-add preset categories in settings page anytime.'); + self.currentStep = 'finalResult'; + } else if (response.needVerifyEmail) { + self.finalResultMessage = self.$t('You have been successfully registered. Account activation link has been sent to your email address, please activate your account first.'); + self.currentStep = 'finalResult'; } else { self.$refs.snackbar.showMessage('You have been successfully registered'); + self.navigateToHomePage = true; } - self.$router.replace('/'); return; } @@ -447,27 +476,15 @@ export default { self.exchangeRatesStore.getLatestExchangeRates({ silent: true, force: false }); } - if (!self.usePresetCategories) { - self.submitting = false; + self.submitting = false; + if (self.usePresetCategories && !response.presetCategoriesSaved) { + self.$refs.snackbar.showMessage('You have been successfully registered, but something wrong with adding preset categories. You can re-add preset categories in settings page anytime.'); + } else { self.$refs.snackbar.showMessage('You have been successfully registered'); - self.$router.replace('/'); - return; } - self.transactionCategoriesStore.addCategories({ - categories: submitCategories - }).then(() => { - self.submitting = false; - - self.$refs.snackbar.showMessage('You have been successfully registered'); - self.$router.replace('/'); - }).catch(() => { - self.submitting = false; - - self.$refs.snackbar.showMessage('You have been successfully registered, but something wrong with adding preset categories. You can re-add preset categories in settings page anytime.'); - self.$router.replace('/'); - }); + self.navigateToHomePage = true; }).catch(error => { self.submitting = false; @@ -476,6 +493,14 @@ export default { } }); }, + navigateToLogin() { + this.$router.push('/'); + }, + onSnackbarShowStateChanged(newValue) { + if (!newValue && this.navigateToHomePage) { + this.$router.replace('/'); + } + }, getCategoryTypeName(categoryType) { switch (categoryType) { case categoryConstants.allCategoryTypes.Income.toString(): diff --git a/src/views/mobile/SignupPage.vue b/src/views/mobile/SignupPage.vue index fd498193..317a04af 100644 --- a/src/views/mobile/SignupPage.vue +++ b/src/views/mobile/SignupPage.vue @@ -295,21 +295,24 @@ export default { self.submitting = true; self.$showLoading(() => self.submitting); - let submitCategories = []; + let presetCategories = []; if (self.usePresetCategories) { - submitCategories = categorizedArrayToPlainArray(self.allPresetCategories); + presetCategories = categorizedArrayToPlainArray(self.allPresetCategories); } self.rootStore.register({ - user: self.user + user: self.user, + presetCategories: presetCategories }).then(response => { if (!self.$user.isUserLogined()) { self.submitting = false; self.$hideLoading(); - if (self.usePresetCategories) { - self.$toast('You have been successfully registered, but something wrong with adding preset categories. You can re-add preset categories in settings page anytime.'); + if (self.usePresetCategories && !response.presetCategoriesSaved) { + self.$toast('You have been successfully registered, but something wrong with adding preset categories. You can re-add preset categories in settings page anytime.', 5000); + } else if (response.needVerifyEmail) { + self.$toast('You have been successfully registered. Account activation link has been sent to your email address, please activate your account first.', 5000); } else { self.$toast('You have been successfully registered'); } @@ -327,30 +330,16 @@ export default { self.exchangeRatesStore.getLatestExchangeRates({ silent: true, force: false }); } - if (!self.usePresetCategories) { - self.submitting = false; - self.$hideLoading(); + self.submitting = false; + self.$hideLoading(); + if (self.usePresetCategories && !response.presetCategoriesSaved) { + self.$toast('You have been successfully registered, but something wrong with adding preset categories. You can re-add preset categories in settings page anytime.'); + } else { self.$toast('You have been successfully registered'); - router.navigate('/'); - return; } - self.transactionCategoriesStore.addCategories({ - categories: submitCategories - }).then(() => { - self.submitting = false; - self.$hideLoading(); - - self.$toast('You have been successfully registered'); - router.navigate('/'); - }).catch(() => { - self.submitting = false; - self.$hideLoading(); - - self.$toast('You have been successfully registered, but something wrong with adding preset categories. You can re-add preset categories in settings page anytime.'); - router.navigate('/'); - }); + router.navigate('/'); }).catch(error => { self.submitting = false; self.$hideLoading();