sumba 1.0.19 → 1.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/bajo/config.json +13 -8
  2. package/bajo/hook/dobo.sumba-user@after-record-create.js +12 -0
  3. package/bajo/hook/dobo.sumba-user@after-record-update.js +13 -0
  4. package/bajo/hook/dobo.sumba-user@before-record-validation.js +1 -1
  5. package/bajo/intl/en-US.json +13 -5
  6. package/bajo/intl/id.json +15 -6
  7. package/bajo/method/create-excerpt.js +11 -0
  8. package/dobo/feature/category.js +16 -0
  9. package/dobo/fixture/contact-form-cat.json +21 -0
  10. package/dobo/fixture/trouble-cat.json +13 -0
  11. package/dobo/schema/contact-form-cat.json +3 -0
  12. package/dobo/schema/contact-form.json +1 -1
  13. package/dobo/schema/ticket-cat.json +3 -0
  14. package/dobo/schema/ticket-detail.json +7 -0
  15. package/dobo/schema/ticket.json +17 -0
  16. package/lib/check-site-id.js +2 -2
  17. package/lib/password-rule.js +8 -7
  18. package/package.json +1 -1
  19. package/sumba/waibu-mpa@secure-routes.json +1 -1
  20. package/waibuMpa/partial/_mail/user-activation-success.html +9 -0
  21. package/waibuMpa/partial/_mail/user-activation-success.id.html +10 -0
  22. package/waibuMpa/partial/_mail/user-forgot-password-changed.html +4 -0
  23. package/waibuMpa/partial/_mail/user-forgot-password-changed.id.html +4 -0
  24. package/waibuMpa/partial/_mail/user-forgot-password-link.html +9 -0
  25. package/waibuMpa/partial/_mail/user-forgot-password-link.id.html +9 -0
  26. package/waibuMpa/partial/_mail/user-signup-success.html +16 -0
  27. package/waibuMpa/partial/_mail/user-signup-success.id.html +17 -0
  28. package/waibuMpa/partial/help/contact-form/form.html +3 -5
  29. package/waibuMpa/partial/help/trouble-tickets/add.html +21 -0
  30. package/waibuMpa/partial/help/trouble-tickets/details.html +26 -0
  31. package/waibuMpa/partial/help/trouble-tickets/list.html +10 -0
  32. package/waibuMpa/partial/list-item/member-links.html +3 -0
  33. package/waibuMpa/partial/user/forgot-password-nomail.id.md +4 -0
  34. package/waibuMpa/partial/user/forgot-password.html +4 -0
  35. package/waibuMpa/partial/user/fpl-invalid.html +1 -0
  36. package/waibuMpa/partial/user/fpl-invalid.id.md +3 -0
  37. package/waibuMpa/partial/user/fpl-invalid.md +3 -0
  38. package/waibuMpa/partial/user/fpl.html +5 -0
  39. package/waibuMpa/partial/user/signup/success-mail.id.md +5 -0
  40. package/waibuMpa/partial/user/signup/success-mail.md +5 -0
  41. package/waibuMpa/partial/user/signup/success-nomail.id.md +5 -0
  42. package/waibuMpa/partial/user/signup/success-nomail.md +5 -0
  43. package/waibuMpa/partial/user/signup/success.html +5 -3
  44. package/waibuMpa/route/help/contact-form.js +3 -2
  45. package/waibuMpa/route/help/trouble-tickets/add.js +24 -0
  46. package/waibuMpa/route/help/trouble-tickets/details/@id.js +41 -0
  47. package/waibuMpa/route/help/trouble-tickets/list.js +22 -0
  48. package/waibuMpa/route/my-stuff/change-password.js +3 -3
  49. package/waibuMpa/route/user/activation.js +5 -3
  50. package/waibuMpa/route/user/forgot-password/@fpl.js +59 -0
  51. package/waibuMpa/route/user/forgot-password.js +27 -1
  52. package/waibuMpa/template/_mail/user-activation-success.html +3 -0
  53. package/waibuMpa/template/_mail/user-forgot-password-changed.html +3 -0
  54. package/waibuMpa/template/_mail/user-forgot-password-link.html +3 -0
  55. package/waibuMpa/template/_mail/user-signup-success.html +3 -0
  56. package/waibuMpa/template/help/trouble-tickets/add.html +4 -0
  57. package/waibuMpa/template/help/trouble-tickets/details.html +4 -0
  58. package/waibuMpa/template/user/fpl-invalid.html +3 -0
  59. package/waibuMpa/template/user/fpl.html +3 -0
  60. package/dobo/i18n/en-US.json +0 -7
  61. package/dobo/i18n/id.json +0 -7
  62. package/waibuMpa/route/help/trouble-tickets.js +0 -24
package/bajo/config.json CHANGED
@@ -12,7 +12,9 @@
12
12
  "/my-stuff": "sumba:/my-stuff/profile",
13
13
  "/info": "sumba:/info/about-us",
14
14
  "/user": "sumba:/my-stuff/profile",
15
- "/db/export": "sumba:/db/export/list"
15
+ "/db/export": "sumba:/db/export/list",
16
+ "/help": "sumba:/help/contact-form",
17
+ "/help/trouble-tickets": "sumba:/help/trouble-tickets/list"
16
18
  }
17
19
  },
18
20
  "waibuAdmin": {
@@ -56,12 +58,15 @@
56
58
  "signout": "sumba:/signout",
57
59
  "afterSignout": "/"
58
60
  },
59
- "userPassword": {
60
- "minUppercase": 1,
61
- "minLowercase": 1,
62
- "minSpecialChar": 1,
63
- "minNumeric": 1,
64
- "noWhitespace": false,
65
- "latinOnlyChars": false
61
+ "siteSetting": {
62
+ "forgotPasswordExpDur": "5m",
63
+ "userPassword": {
64
+ "minUppercase": 1,
65
+ "minLowercase": 1,
66
+ "minSpecialChar": 1,
67
+ "minNumeric": 1,
68
+ "noWhitespace": false,
69
+ "latinOnlyChars": false
70
+ }
66
71
  }
67
72
  }
@@ -0,0 +1,12 @@
1
+ async function afterRecordCreate (body, options = {}, rec) {
2
+ if (!(this.app.masohi && this.app.masohiMail)) return
3
+ options.tpl = 'sumba.template:/_mail/user-signup-success.html'
4
+ const { data } = rec
5
+ const to = `${data.firstName} ${data.lastName} <${data.email}>`
6
+ const subject = options.req.t('newUserSignup')
7
+ try {
8
+ await this.app.masohi.send({ to, subject, message: data, options })
9
+ } catch (err) {}
10
+ }
11
+
12
+ export default afterRecordCreate
@@ -0,0 +1,13 @@
1
+ async function afterRecordUpdate (id, body, options = {}, rec) {
2
+ if (!(this.app.masohi && this.app.masohiMail)) return
3
+ const { data, oldData } = rec
4
+ if (!(oldData.status === 'UNVERIFIED' && data.status === 'ACTIVE')) return
5
+ options.tpl = 'sumba.template:/_mail/user-activation-success.html'
6
+ const to = `${data.firstName} ${data.lastName} <${data.email}>`
7
+ const subject = options.req.t('userActivation')
8
+ try {
9
+ await this.app.masohi.send({ to, subject, message: data, options })
10
+ } catch (err) {}
11
+ }
12
+
13
+ export default afterRecordUpdate
@@ -2,7 +2,7 @@ import passwordRule from '../../lib/password-rule.js'
2
2
 
3
3
  async function doboSumbaUserBeforeRecordValidation (body, options = {}) {
4
4
  const { set } = this.app.bajo.lib._
5
- const password = await passwordRule.call(this)
5
+ const password = await passwordRule.call(this, options.req)
6
6
  const rule = { password }
7
7
  set(options, 'validation.params.rule', rule)
8
8
  }
@@ -24,7 +24,7 @@
24
24
  "signin": "Signin",
25
25
  "yourStuff": "Your Stuff",
26
26
  "forgotPassword": "Forgot Password",
27
- "userActivation": "User Activation",
27
+ "userActivation": "Account Activation",
28
28
  "signup": "Signup",
29
29
  "newUserSignup": "New User Signup",
30
30
  "updateProfile": "Update Profile",
@@ -58,15 +58,22 @@
58
58
  "help": "Help",
59
59
  "troubleTickets": "Trouble Tickets",
60
60
  "guest": "Guest",
61
- "formSubmitted": "Form submitted successfully",
62
- "formSubmittedInfo": "Please allow us to read your submission thoroughly and we'll get back to you rightaway",
63
- "thankYou": "Thank you!",
64
61
  "warningMemberOnly%s": "Please authenticate yourself first, because the <a href=\"%s\">page<a> your're trying to access is a member only page",
65
62
  "collectingRouteGuards": "Collecting route guards:",
66
63
  "secureRoutes%d": "- Secure routes: %d",
67
64
  "secureInvRoutes%d": "- Secure, inverted routes: %d",
68
65
  "anonRoutes%d": "- Anonymous routes: %d",
69
66
  "anonInvRoutes%d": "- Anonymous, inverted routes: %d",
67
+ "replies": "Replies",
68
+ "compose": "Compose",
69
+ "yourReply": "Your Reply",
70
+ "you": "You",
71
+ "us": "Us",
72
+ "support": "Support",
73
+ "unknownUsernameEmailOrInactive": "Unknown username/email or account is inactive",
74
+ "emailSent": "An email has been sent to your address",
75
+ "forgotPasswordLink": "Forgot Password Link",
76
+ "forgotPasswordChanged": "Password Successfully Changed",
70
77
  "field": {
71
78
  "currentPassword": "Current Password",
72
79
  "newPassword": "New Password",
@@ -76,7 +83,8 @@
76
83
  "hostname": "Hostname",
77
84
  "iconset": "Iconset",
78
85
  "subject": "Subject",
79
- "message": "Message"
86
+ "message": "Message",
87
+ "cat": "Category"
80
88
  },
81
89
  "validation": {
82
90
  "password.minOfUppercase": "Contains at leaset {{min}} uppercase characters",
package/bajo/intl/id.json CHANGED
@@ -24,7 +24,7 @@
24
24
  "signin": "Masuk",
25
25
  "yourStuff": "Perkakas Anda",
26
26
  "forgotPassword": "Lupa Kata Sandi",
27
- "userActivation": "Aktivasi Pengguna",
27
+ "userActivation": "Aktivasi Akun",
28
28
  "signup": "Pendaftaran",
29
29
  "newUserSignup": "Pendaftaran Pengguna Baru",
30
30
  "updateProfile": "Perbarui Profil",
@@ -52,21 +52,29 @@
52
52
  "cookiePolicy": "Kebijakan Cookie",
53
53
  "privacy": "Privasi",
54
54
  "privacyStatement": "Pernyataan Privasi",
55
- "terms": "Terms",
55
+ "terms": "Ketentuan",
56
56
  "termsConditions": "Term dan Kondisi",
57
57
  "legalInfo": "Info Legal",
58
58
  "help": "Bantuan",
59
59
  "troubleTickets": "Tiket Masalah",
60
60
  "guest": "Tamu",
61
- "formSubmitted": "Formulir sukses dikirim",
62
- "formSubmittedInfo": "Kami akan mereview sebentar kiriman Anda dan akan menghubungi Anda kembali secepatnya",
63
- "thankYou": "Thank you!",
64
61
  "warningMemberOnly%s": "Silahkan melalukan otentikasi terlebih dahulu karena <a href=\"%s\">halaman<a> yang akan Anda akses adalah salah satu halaman khusus anggota saja",
65
62
  "collectingRouteGuards": "Mengoleksi pertahanan jalur:",
66
63
  "secureRoutes%d": "- Jalur aman: %d",
67
64
  "secureInvRoutes%d": "- Jalur aman, terbalik: %d",
68
65
  "anonRoutes%d": "- Jalur anonim: %d",
69
66
  "anonInvRoutes%d": "- Jalur anonim, terbalik: %d",
67
+ "conversation": "Percakapan",
68
+ "replies": "Balas Berbalas",
69
+ "compose": "Tulis",
70
+ "yourReply": "Balasan Anda",
71
+ "you": "Anda",
72
+ "us": "Kami",
73
+ "support": "Bantuan",
74
+ "unknownUsernameEmailOrInactive": "Nama pengguna/surel tidak dikenal atau akun belum aktif",
75
+ "emailSent": "Sebuah surel telah dikirim ke alamat Anda",
76
+ "forgotPasswordLink": "Tautan Lupa Kata Sandi",
77
+ "forgotPasswordChanged": "Kata Sandi Sukses Diubah",
70
78
  "field": {
71
79
  "currentPassword": "Kata Sandi Saat Ini",
72
80
  "newPassword": "Kata Sandi Baru",
@@ -77,7 +85,8 @@
77
85
  "hostname": "Nama Host",
78
86
  "iconset": "Iconset",
79
87
  "subject": "Judul",
80
- "message": "Pesan"
88
+ "message": "Pesan",
89
+ "cat": "Kategori"
81
90
  },
82
91
  "validation": {
83
92
  "password.minOfUppercase": "Mengandung setidaknya {{min}} huruf besar",
@@ -0,0 +1,11 @@
1
+ // based on: https://medium.com/@paulohfev/problem-solving-how-to-create-an-excerpt-fdb048687928
2
+
3
+ function createExcerpt (content, maxWords = 100, trailChars = '...') {
4
+ const listOfWords = content.trim().split(' ')
5
+ const truncatedContent = listOfWords.slice(0, maxWords).join(' ')
6
+ const excerpt = truncatedContent + trailChars
7
+ const output = listOfWords.length > maxWords ? excerpt : content
8
+ return output
9
+ }
10
+
11
+ export default createExcerpt
@@ -0,0 +1,16 @@
1
+ async function category (opts = {}) {
2
+ return {
3
+ properties: [{
4
+ name: 'name',
5
+ type: 'string',
6
+ maxLength: 50,
7
+ index: true
8
+ }, {
9
+ name: 'level',
10
+ type: 'integer',
11
+ index: true
12
+ }]
13
+ }
14
+ }
15
+
16
+ export default category
@@ -0,0 +1,21 @@
1
+ [{
2
+ "name": "QUESTION",
3
+ "level": 2,
4
+ "siteId": "?:SumbaSite::alias:default"
5
+ }, {
6
+ "name": "INQUIRY",
7
+ "level": 1,
8
+ "siteId": "?:SumbaSite::alias:default"
9
+ }, {
10
+ "name": "BILLING",
11
+ "level": 4,
12
+ "siteId": "?:SumbaSite::alias:default"
13
+ }, {
14
+ "name": "API",
15
+ "level": 3,
16
+ "siteId": "?:SumbaSite::alias:default"
17
+ }, {
18
+ "name": "MISC",
19
+ "level": 5,
20
+ "siteId": "?:SumbaSite::alias:default"
21
+ }]
@@ -0,0 +1,13 @@
1
+ [{
2
+ "name": "TECHNICAL",
3
+ "siteId": "?:SumbaSite::alias:default"
4
+ }, {
5
+ "name": "BILLING",
6
+ "siteId": "?:SumbaSite::alias:default"
7
+ }, {
8
+ "name": "API",
9
+ "siteId": "?:SumbaSite::alias:default"
10
+ }, {
11
+ "name": "MISC",
12
+ "siteId": "?:SumbaSite::alias:default"
13
+ }]
@@ -0,0 +1,3 @@
1
+ {
2
+ "feature": ["sumba.category", "sumba.siteId"]
3
+ }
@@ -4,7 +4,7 @@
4
4
  "firstName::50:true:true",
5
5
  "lastName::50:true:true",
6
6
  "email::50:true:true",
7
- "category::50:true",
7
+ "cat::50:true",
8
8
  "subject::255::true",
9
9
  "message:text"
10
10
  ],
@@ -0,0 +1,3 @@
1
+ {
2
+ "feature": ["sumba.category", "sumba.siteId"]
3
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "properties": [
3
+ "masterId::255:true:true",
4
+ "message:text"
5
+ ],
6
+ "feature": ["createdAt", "sumba.userId", "sumba.siteId"]
7
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "properties": [
3
+ "subject::255:true:true",
4
+ "cat::50:true",
5
+ "message:text:::true"
6
+ ],
7
+ "feature": {
8
+ "createdAt": true,
9
+ "updatedAt": true,
10
+ "sumba.siteId": true,
11
+ "sumba.userId": true,
12
+ "sumba.status": {
13
+ "default": "OPEN",
14
+ "values": ["OPEN", "CLOSED"]
15
+ }
16
+ }
17
+ }
@@ -1,5 +1,5 @@
1
1
  async function mergeSetting (req) {
2
- const { defaultsDeep } = this.app.bajo
2
+ const { defaultsDeep, parseObject } = this.app.bajo
3
3
  const { trim, get, filter } = this.app.bajo.lib._
4
4
  const { recordFind } = this.app.dobo
5
5
  const defSetting = {}
@@ -21,7 +21,7 @@ async function mergeSetting (req) {
21
21
  nsSetting[ns][item.key] = value
22
22
  }
23
23
  }
24
- req.site.setting = defaultsDeep({}, nsSetting, defSetting)
24
+ req.site.setting = parseObject(defaultsDeep({}, nsSetting, defSetting))
25
25
  }
26
26
 
27
27
  const omitted = ['status']
@@ -1,6 +1,6 @@
1
1
  import { joiPasswordExtendCore } from 'joi-password'
2
2
 
3
- async function passwordRule () {
3
+ async function passwordRule (req) {
4
4
  const { importPkg } = this.app.bajo
5
5
  const joi = await importPkg('dobo:joi')
6
6
  const joiPassword = joi.extend(joiPasswordExtendCore)
@@ -9,12 +9,13 @@ async function passwordRule () {
9
9
  .min(8)
10
10
  .max(100)
11
11
  .required()
12
- if (this.config.userPassword.minUppercase) password = password.minOfUppercase(this.config.userPassword.minUppercase)
13
- if (this.config.userPassword.minLowercase) password = password.minOfLowercase(this.config.userPassword.minLowercase)
14
- if (this.config.userPassword.minSpecialChar) password = password.minOfSpecialCharacters(this.config.userPassword.minSpecialChar)
15
- if (this.config.userPassword.minNumeric) password = password.minOfNumeric(this.config.userPassword.minNumeric)
16
- if (this.config.userPassword.noWhitespace) password = password.noWhiteSpaces()
17
- if (this.config.userPassword.latinOnlyChars) password = password.onlyLatinCharacters()
12
+ const cfg = req.site.setting.sumba.userPassword
13
+ if (cfg.minUppercase) password = password.minOfUppercase(cfg.minUppercase)
14
+ if (cfg.minLowercase) password = password.minOfLowercase(cfg.minLowercase)
15
+ if (cfg.minSpecialChar) password = password.minOfSpecialCharacters(cfg.minSpecialChar)
16
+ if (cfg.minNumeric) password = password.minOfNumeric(cfg.minNumeric)
17
+ if (cfg.noWhitespace) password = password.noWhiteSpaces()
18
+ if (cfg.latinOnlyChars) password = password.onlyLatinCharacters()
18
19
  return password
19
20
  }
20
21
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sumba",
3
- "version": "1.0.19",
3
+ "version": "1.0.21",
4
4
  "description": "Bajo Framework's Biz Suite",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "/my-stuff/*": ["*"],
3
3
  "/signout": ["*"],
4
- "/help/trouble-tickets": ["*"]
4
+ "/help/trouble-tickets/*": ["*"]
5
5
  }
@@ -0,0 +1,9 @@
1
+ <p>Congratulations, your account with username <strong><%= username %></strong>
2
+ has been successfully activated. Thus you can now access the Members-only pages
3
+ according to the permissions set by your Admin.</p>
4
+
5
+ <p>If you still have questions, please don't hesitate to
6
+ fill out the form on the <strong>Help</strong> page.
7
+ We will be happy to answer all your questions.</p>
8
+
9
+ <p>Thank you!</p>
@@ -0,0 +1,10 @@
1
+ <p>Selamat, akun Anda dengan nama pengguna <strong><%= username %></strong>
2
+ telah berhasil diaktivkan. Dengan demikian Anda telah bisa mengakses
3
+ halaman-halaman khusus Anggota sesuai dengan permisi yang ditetapkan oleh
4
+ Admin Anda.</p>
5
+
6
+ <p>Jika Anda masih memiliki pertanyaan-pertanyaan, silahkan untuk tidak segan
7
+ mengisi formulir di halaman <strong>Bantuan</strong>. Dengan senang hati
8
+ kami akan menjawab semua pertanyaan Anda.</p>
9
+
10
+ <p>Terima kasih!</p>
@@ -0,0 +1,4 @@
1
+ <p>Your password has been successfully changed via the <b>Forgot Password</b> page.
2
+ Therefore, please use a new password because the old one is no longer valid.</p>
3
+
4
+ <p>Please contact us if you are still having trouble, thank you!</p>
@@ -0,0 +1,4 @@
1
+ <p>Kata sandi Anda telah sukses diganti via fasilitas <b>Lupa Kata Sandi</b>. Untuk itu
2
+ silahkan menggunakan kata sandi yang baru karena yang lama sudah tidak berlaku lagi.</p>
3
+
4
+ <p>Silahkan hubungi kami jika Anda masih mengalami kendala, terima kasih!</p>
@@ -0,0 +1,9 @@
1
+ <p>Here is a special password change link for you:</p>
2
+
3
+ <% const url = 'http://' + _meta.hostHeader + _routePath('sumba:/user/forgot-password') + '/' + fpToken %>
4
+ <p><a href="<%= url %>"><%= url %></a></p>
5
+
6
+ <p>Please make the change immediately because after <strong>5 minutes</strong> the link above
7
+ is no longer valid.</p>
8
+
9
+ <p>Please contact us if you still have questions, thank you!</p>
@@ -0,0 +1,9 @@
1
+ <p>Berikut adalah tautan khusus pergantian password untuk Anda:</p>
2
+
3
+ <% const url = 'http://' + _meta.hostHeader + _routePath('sumba:/user/forgot-password') + '/' + fpToken %>
4
+ <p><a href="<%= url %>"><%= url %></a></p>
5
+
6
+ <p>Silahkan melakukan pergantian segera karena setelah <strong>5 menit</strong> tautan diatas
7
+ tidak berlaku kembali.</p>
8
+
9
+ <p>Silahkan hubungi kami jika Anda masih memiliki pertanyaan, terima kasih!</p>
@@ -0,0 +1,16 @@
1
+ <p>Welcome and thank your for joining us in <%= _meta.site.title %>.
2
+ Please click the following link to activate your account:</p>
3
+
4
+ <p><% const url = _meta.hostHeader + _routePath("sumba:/user/activation") + "?key=" + token %>
5
+ <a href="http://<%= url %>">http://<%= url %></a></p>
6
+
7
+ <p>If you are unable to click the link above, please visit our website
8
+ at http://<%= _meta.hostHeader %>, then visit the <strong>Account Activation</strong> from
9
+ the link available in the footer.</p>
10
+
11
+ <p>Please copy the key below, then enter it in the input box on the page and press
12
+ the <strong>Submit</strong> button:</p>
13
+
14
+ <strong><%= token %></strong>
15
+
16
+ <p>Once again, thank you for joining us. We hope our services will benefit you.<p>
@@ -0,0 +1,17 @@
1
+ <p>Selamat datang dan telah bergabung di <%= _meta.site.title %>. Selanjutnya,
2
+ silahkan mengklik link dibawah ini untuk melakukan aktivasi akun anda:</p>
3
+
4
+ <p><% const url = _meta.hostHeader + _routePath("sumba:/user/activation") + "?key=" + token %>
5
+ <a href="http://<%= url %>">http://<%= url %></a></p>
6
+
7
+ <p>Jika Ada tidak bisa mengklik link diatas, silahkan kunjungi situs kami
8
+ di http://<%= _meta.hostHeader %>, kemudian kunjungi di halaman <strong>Aktivasi
9
+ Akun</strong> dari link yang tersedia di catatan kaki.</p>
10
+
11
+ <p>Silahkan kopi kunci dibawah ini, kemudian masukkan di kotak masukan yang
12
+ ada di halaman tersebut dan tekan tombol <strong>Kirim</strong>:</p>
13
+
14
+ <strong><%= token %></strong>
15
+
16
+ <p>Sekali lagi terimakasih telah bergabung bersama kami, semoga layanan kami
17
+ membawa manfaat untuk Anda.<p>
@@ -3,11 +3,9 @@
3
3
  <c:form-input name="lastName" col="6-lg" label-floating <%= _meta.user ? 'readonly' : '' %> />
4
4
  <c:form-input type="email" col="6-lg" name="email" label-floating <%= _meta.user ? 'readonly' : '' %> />
5
5
  <c:form-select name="category" col="6-lg" label-floating>
6
- <c:option value="QUESTION" t:content="Question" />
7
- <c:option value="INQUIRY" t:content="Inquiry" />
8
- <c:option value="BILLING" t:content="Billing" />
9
- <c:option value="API" t:content="API Integration" />
10
- <c:option value="MISC" t:content="Misc" />
6
+ <% for (const cat of cats) { %>
7
+ <c:option value="<%= cat.name %>" t:content="<%= _.camelCase('cat ' + cat.name) %>" />
8
+ <% } %>
11
9
  </c:form-select>
12
10
  <c:form-input name="subject" label-floating />
13
11
  <c:form-textarea name="message" label-floating style="height:10em"/>
@@ -0,0 +1,21 @@
1
+ <c:form reset-validation grid-gutter="2">
2
+ <c:form-input name="subject" col="8-lg 6-md" label-floating />
3
+ <c:form-select name="cat" col="4-lg 6-md" label-floating>
4
+ <% for (const cat of cats) { %>
5
+ <c:option value="<%= cat.name %>" <%= form.cat === cat.name ? 'selected' : '' %> t:content="<%= _.camelCase('cat ' + cat.name) %>" />
6
+ <% } %>
7
+ </c:form-select>
8
+ <c:form-textarea name="message" label-floating style="height:10em"/>
9
+ <c:div flex="justify-content:between" margin="top-4">
10
+ <c:div>
11
+ <c:btn color="primary" icon="arrowStart" t:content="Back" href="sumba:/help/trouble-tickets/list" />
12
+ <% if (error) { %>
13
+ <c:a margin="start-2" icon="remove" t:content="Clear" x-data @click="wbs.invalidateForm" />
14
+ <% } %>
15
+ </c:div>
16
+ <c:div>
17
+ <c:btn type="reset" color="link" t:content="Reset" />
18
+ <c:btn type="submit" color="primary" t:content="Submit" />
19
+ </c:div>
20
+ </c:div>
21
+ </c:form>
@@ -0,0 +1,26 @@
1
+ <c:card margin="bottom-4">
2
+ <c:card-body shadow>
3
+ <c:card-title><%= master.subject %></c:card-title>
4
+ <c:card-subtitle>
5
+ <%= _format(master.createdAt, 'datetime') %>
6
+ <c:badge margin="start-2" background="color:primary"><%= master.status %></c:badge>
7
+ </c:card-subtitle>
8
+ <c:card-text>
9
+ <%= _parseMarkdown(master.message) %>
10
+ </c:card-text>
11
+ </c:card-body>
12
+ </c:card>
13
+ <c:grid-row>
14
+ <c:grid-col col="8-lg 6-md">
15
+ <c:wdb-table responsive body-divider strip hover no-details-click />
16
+ <c:div>
17
+ <c:wdb-pagination />
18
+ </c:div>
19
+ </c:grid-col>
20
+ <c:grid-col col="4-lg 6-md">
21
+ <c:form button reset-validation>
22
+ <c:form-textarea t:label="yourReply" name="message" label-floating style="height:10em"/>
23
+ </c:form>
24
+ </c:grid-col>
25
+ </c:grid-row>
26
+
@@ -0,0 +1,10 @@
1
+ <c:grid-row gutter="3" margin="bottom-3">
2
+ <c:grid-col col="4-lg">
3
+ <c:wdb-query />
4
+ </c:grid-col>
5
+ <c:grid-col col="8-lg" flex="justify-content:end-lg">
6
+ <c:wdb-btn-add href="sumba:/help/trouble-tickets/add"/>
7
+ </c:grid-col>
8
+ </c:grid-row>
9
+ <c:wdb-table responsive body-divider strip hover details-href="sumba:/help/trouble-tickets/details/:id" />
10
+ <c:include resource="waibuDb.partial:/crud/_list-footer.html" />
@@ -8,4 +8,7 @@
8
8
  <c:list-item href="sumba:/signin" t:content="signin" />
9
9
  <c:list-item href="sumba:/user/signup" t:content="signup" />
10
10
  <c:list-item href="sumba:/user/forgot-password" t:content="forgotPassword" />
11
+ <% if (_hasPlugin('masohi') && _hasPlugin('masohiMail')) { %>
12
+ <c:list-item href="sumba:/user/activation" t:content="userActivation" />
13
+ <% } %>
11
14
  <% } %>
@@ -0,0 +1,4 @@
1
+ Anda lupa dengan kata sandi Anda? Tidak masalah, silahkan hubungi kami
2
+ segera!
3
+
4
+ Dengan senang hati kami akan membantu Anda!
@@ -1,6 +1,10 @@
1
+ <% if (_hasPlugin('masohiMail')) { %>
1
2
  <c:form button reset-validation>
2
3
  <c:form-input name="usernameEmail" label-floating wrapper-margin="bottom-2" t:hint="linkPasswordWillbeSent"/>
3
4
  </c:form>
5
+ <% } else { %>
6
+ <c:include resource="sumba.partial:/user/forgot-password-nomail.md" />
7
+ <% } %>
4
8
  <% if (!page.noLinks) { %>
5
9
  <c:list type="unstyled" margin="start-3">
6
10
  <c:include resource="sumba.partial:/list-item/signin.html" />
@@ -0,0 +1 @@
1
+ <c:include resource="sumba.partial:/user/fpl-invalid.md" />
@@ -0,0 +1,3 @@
1
+ Link lupa kata sandi yang Anda masukkan salah atau sudah kadaluarsa dan tidak berlaku lagi. Silahkan membuatnya sekali lagi jika Anda masih menginginkannya.
2
+
3
+ Untuk itu selalu pastikan bahwa Anda mengkliknya dari link yang dikirim melalui email, terima kasih!
@@ -0,0 +1,3 @@
1
+ The forgot password link that you entered is incorrect or has expired and is no longer valid. Please make it again if you still want it.
2
+
3
+ Therefore always make sure that you click on it from the link sent via email, thank you!
@@ -0,0 +1,5 @@
1
+ <c:form button reset-validation referer>
2
+ <c:form-plaintext name="username" label-floating wrapper-margin="bottom-2" />
3
+ <c:form-password name="newPassword" label-floating wrapper-margin="bottom-2" />
4
+ <c:form-password name="verifyNewPassword" label-floating wrapper-margin="bottom-3" />
5
+ </c:form>
@@ -0,0 +1,5 @@
1
+ Formulir sukses dikirim.
2
+
3
+ Sebuah email telah dikirimkan ke alamat email Anda berisikan link aktivasi akun Anda. Mohon untuk dicek dan melakukan aktivasi secepatnya.
4
+
5
+ Terima kasih!
@@ -0,0 +1,5 @@
1
+ Form submitted successfully.
2
+
3
+ An email has been sent to your email address containing your account activation link. Please check and activate as soon as possible.
4
+
5
+ Thank You!
@@ -0,0 +1,5 @@
1
+ Formulir sukses dikirim.
2
+
3
+ Kami akan segera melakukan review kiriman Anda dan akan menghubungi Anda kembali secepatnya.
4
+
5
+ Thank you!
@@ -0,0 +1,5 @@
1
+ Form submitted successfully.
2
+
3
+ Please allow us to read your submission thoroughly and we'll get back to you as soon as possible.
4
+
5
+ Thank you!
@@ -1,6 +1,8 @@
1
- <c:p t:content="formSubmitted" />
2
- <c:p t:content="formSubmittedInfo" />
3
- <c:p t:content="thankYou" />
1
+ <% if (_hasPlugin('masohiMail')) { %>
2
+ <c:include resource="sumba.partial:/user/signup/success-mail.md" />
3
+ <% } else { %>
4
+ <c:include resource="sumba.partial:/user/signup/success-nomail.md" />
5
+ <% } %>
4
6
  <% if (!page.noLinks) { %>
5
7
  <c:list type="unstyled" margin="start-3">
6
8
  <c:list-item t:content="gotoHome" href="/" icon="house"/>
@@ -3,7 +3,7 @@ const contactForm = {
3
3
  handler: async function (req, reply) {
4
4
  const { defaultsDeep } = this.app.bajo
5
5
  const { pick } = this.app.bajo.lib._
6
- const { recordCreate } = this.app.waibuDb
6
+ const { recordCreate, recordFind } = this.app.waibuDb
7
7
 
8
8
  const def = req.user ? pick(req.user, ['firstName', 'lastName', 'email']) : {}
9
9
  const form = defaultsDeep(req.body, def)
@@ -17,7 +17,8 @@ const contactForm = {
17
17
  error = err
18
18
  }
19
19
  }
20
- return reply.view('sumba.template:/help/contact-form/form.html', { form, error })
20
+ const cats = await recordFind({ model: 'SumbaContactFormCat', req, options: { sort: 'level:1+name:1', limit: -1, dataOnly: true } })
21
+ return reply.view('sumba.template:/help/contact-form/form.html', { form, error, cats })
21
22
  }
22
23
  }
23
24
 
@@ -0,0 +1,24 @@
1
+ const model = 'SumbaTicket'
2
+
3
+ const add = {
4
+ method: ['GET', 'POST'],
5
+ handler: async function (req, reply) {
6
+ const { recordCreate, recordFind } = this.app.waibuDb
7
+ const { defaultsDeep } = this.app.bajo.lib._
8
+ const options = {}
9
+ const form = defaultsDeep(req.body, {})
10
+ let error
11
+ if (req.method === 'POST') {
12
+ try {
13
+ await recordCreate({ model, req, reply, options })
14
+ return reply.redirectTo('sumba:/help/trouble-tickets/list')
15
+ } catch (err) {
16
+ error = err
17
+ }
18
+ }
19
+ const cats = await recordFind({ model: 'SumbaTicketCat', req, options: { sort: 'level:1+name:1', limit: -1, dataOnly: true } })
20
+ return reply.view('sumba.template:/help/trouble-tickets/add.html', { form, error, cats })
21
+ }
22
+ }
23
+
24
+ export default add
@@ -0,0 +1,41 @@
1
+ const model = 'SumbaTicketDetail'
2
+
3
+ const id = {
4
+ method: ['GET', 'POST'],
5
+ handler: async function (req, reply) {
6
+ const { cloneDeep } = this.app.bajo.lib._
7
+ const { recordCreate, recordFind, getSchemaExt } = this.app.waibuDb
8
+ const { schema } = await getSchemaExt(model, 'list')
9
+
10
+ const master = (await recordFind({ model: 'SumbaTicket', req, options: { dataOnly: true, query: { id: req.params.id }, limit: 1 } }))[0]
11
+ if (!master) throw this.error('_notFound')
12
+ const form = cloneDeep(req.body)
13
+ let error
14
+ if (req.method === 'POST') {
15
+ try {
16
+ form.masterId = master.id + ''
17
+ console.log(form, master)
18
+ await recordCreate({ model: 'SumbaTicketDetail', req, body: form })
19
+ return reply.redirectTo('sumba:/help/trouble-tickets/list')
20
+ } catch (err) {
21
+ error = err
22
+ }
23
+ }
24
+ const query = { masterId: req.params.id + '' }
25
+ const options = { count: true, query }
26
+ const list = await recordFind({ model, req, options })
27
+ schema.view.disabled = ['update', 'remove']
28
+ schema.view.fields = ['createdAt']
29
+ schema.view.label = { createdAt: 'conversation' }
30
+ schema.view.formatter = {
31
+ createdAt: (val, rec) => {
32
+ const message = this.app.bajoMarkdown ? this.app.bajoMarkdown.parseContent(rec.message) : rec.message
33
+ const isMe = rec.userId === req.user.id
34
+ return `<c:div margin="bottom-3"><c:badge background="color:${isMe ? 'primary' : 'secondary'}" t:content="${isMe ? 'you' : 'us'}" /> <small>${req.format(val, 'datetime')}</small></c:div> ${message}`
35
+ }
36
+ }
37
+ return reply.view('sumba.template:/help/trouble-tickets/details.html', { list, schema, master, form, error })
38
+ }
39
+ }
40
+
41
+ export default id
@@ -0,0 +1,22 @@
1
+ const model = 'SumbaTicket'
2
+
3
+ const list = {
4
+ method: ['GET'],
5
+ handler: async function (req, reply) {
6
+ const { recordFind, getSchemaExt } = this.app.waibuDb
7
+ const { schema } = await getSchemaExt(model, 'list')
8
+ const options = { count: true }
9
+ const list = await recordFind({ model, req, options })
10
+ schema.view.disabled = ['update', 'remove']
11
+ schema.view.fields = ['createdAt', 'subject', 'status']
12
+ schema.view.label = { subject: 'Your Message' }
13
+ schema.view.formatter = {
14
+ subject: (val, rec) => {
15
+ return `<strong>${val}</strong><br />${this.createExcerpt(rec.message, 35)}`
16
+ }
17
+ }
18
+ return reply.view('sumba.template:/help/trouble-tickets/list.html', { list, schema })
19
+ }
20
+ }
21
+
22
+ export default list
@@ -12,7 +12,7 @@ const profile = {
12
12
  let error
13
13
  if (req.method === 'POST') {
14
14
  try {
15
- const newPassword = await passwordRule.call(this)
15
+ const newPassword = await passwordRule.call(this, req)
16
16
  const schema = Joi.object({
17
17
  currentPassword: Joi.string().min(8).max(50).required(),
18
18
  newPassword,
@@ -26,11 +26,11 @@ const profile = {
26
26
  const rec = await recordGet(model, req.user.id)
27
27
  const verified = await bcrypt.compare(req.body.currentPassword, rec.password)
28
28
  if (!verified) throw this.error('invalidCurrentPassword', { details: [{ field: 'currentPassword', error: 'invalidPassword' }], statusCode: 400 })
29
- await recordUpdate(model, req.user.id, { password: req.body.newPassword })
29
+ await recordUpdate(model, req.user.id, { password: req.body.newPassword }, { req, noFlash: true })
30
30
  // signout and redirect to signin
31
31
  req.session.user = null
32
32
  req.flash('notify', req.t('passwordChangedReSignin'))
33
- return reply.redirectTo('sumba:/signin')
33
+ return reply.redirectTo(this.config.redirect.signin)
34
34
  } catch (err) {
35
35
  error = err
36
36
  }
@@ -1,16 +1,18 @@
1
+ const model = 'SumbaUser'
2
+
1
3
  const userActivation = {
2
4
  method: ['GET', 'POST'],
3
5
  handler: async function (req, reply) {
4
6
  const { defaultsDeep } = this.app.bajo
5
- const { recordFind, recordUpdate } = this.app.dobo
7
+ const { recordFind, recordUpdate } = this.app.waibuDb
6
8
  const form = defaultsDeep(req.body, { key: req.query.key })
7
9
  let error
8
10
  if (req.method === 'POST') {
9
11
  try {
10
12
  const query = { status: 'UNVERIFIED', token: req.body.key }
11
- const result = await recordFind('SumbaUser', { query }, { noHook: true })
13
+ const result = await recordFind({ model, req, reply, options: { dataOnly: true, query, limit: 1, noHook: true } })
12
14
  if (result.length === 0) throw this.error('validationError', { details: [{ field: 'key', error: 'invalidActivationKey' }] })
13
- await recordUpdate('SumbaUser', result[0].id, { status: 'ACTIVE' }, { noHook: true, noValidation: true, noFlash: true })
15
+ await recordUpdate({ model, req, reply, id: result[0].id, body: { status: 'ACTIVE' }, options: { noValidation: true, noFlash: true } })
14
16
  req.flash('notify', req.t('userActivated'))
15
17
  return reply.redirectTo(this.config.redirect.signin, req)
16
18
  } catch (err) {
@@ -0,0 +1,59 @@
1
+ import passwordRule from '../../../../lib/password-rule.js'
2
+ const model = 'SumbaUser'
3
+
4
+ async function getUser (req, reply) {
5
+ const { dayjs } = this.app.bajo.lib
6
+ const { recordFind } = this.app.waibuDb
7
+ const invalidFpl = 'sumba.template:/user/fpl-invalid.html'
8
+ if (Buffer.from(req.params.fpl, 'base64').toString('base64') !== req.params.fpl) return invalidFpl
9
+ const fpToken = Buffer.from(req.params.fpl, 'base64').toString()
10
+ const [token, sec] = fpToken.split(':')
11
+ if (dayjs().unix() > Number(sec)) return invalidFpl
12
+ const query = { token, status: 'ACTIVE' }
13
+ const users = await recordFind({ model, req, reply, options: { query, limit: 1, dataOnly: true, noHook: true } })
14
+ if (users.length === 0) return invalidFpl
15
+ return users[0]
16
+ }
17
+
18
+ const forgotPasswordLink = {
19
+ method: ['GET', 'POST'],
20
+ handler: async function (req, reply) {
21
+ const { defaultsDeep, importPkg } = this.app.bajo
22
+ const { isString } = this.app.bajo.lib._
23
+ const { recordUpdate } = this.app.dobo
24
+ const Joi = await importPkg('dobo:joi')
25
+
26
+ const form = defaultsDeep(req.body, {})
27
+ const user = await getUser.call(this, req, reply)
28
+ if (isString(user)) return reply.view(user)
29
+ form.username = user.username
30
+ let error
31
+ if (req.method === 'POST') {
32
+ try {
33
+ const newPassword = await passwordRule.call(this, req)
34
+ const schema = Joi.object({
35
+ newPassword,
36
+ verifyNewPassword: Joi.ref('newPassword')
37
+ })
38
+ try {
39
+ await schema.validateAsync(req.body, this.app.dobo.config.validationParams)
40
+ } catch (err) {
41
+ throw this.error('validationError', { details: err.details, values: err.values, ns: this.name, statusCode: 422, code: 'DB_VALIDATION' })
42
+ }
43
+ await recordUpdate(model, user.id, { password: req.body.newPassword }, { req, noFlash: true })
44
+ const to = `${user.firstName} ${user.lastName} <${user.email}>`
45
+ const subject = req.t('forgotPasswordChanged')
46
+ const options = { req, reply, tpl: 'sumba.template:/_mail/user-forgot-password-changed.html' }
47
+ await this.app.masohi.send({ to, subject, message: user, options })
48
+ req.flash('notify', req.t('passwordChangedReSignin'))
49
+ return reply.redirectTo(this.config.redirect.signin)
50
+ } catch (err) {
51
+ console.log(err)
52
+ error = err
53
+ }
54
+ }
55
+ return reply.view('sumba.template:/user/fpl.html', { form, error })
56
+ }
57
+ }
58
+
59
+ export default forgotPasswordLink
@@ -1,7 +1,33 @@
1
+ const model = 'SumbaUser'
2
+
1
3
  const profile = {
2
4
  method: ['GET', 'POST'],
3
5
  handler: async function (req, reply) {
4
- return reply.view('sumba.template:/user/forgot-password.html')
6
+ if (!this.app.masohiMail) return reply.view('sumba.template:/user/forgot-password.html')
7
+ const { defaultsDeep } = this.app.bajo
8
+ const { dayjs } = this.app.bajo.lib
9
+ const { recordFind } = this.app.dobo
10
+ const form = defaultsDeep(req.body, {})
11
+ let error
12
+ if (req.method === 'POST') {
13
+ try {
14
+ const query = { status: 'ACTIVE', $or: [{ username: req.body.usernameEmail }, { email: req.body.usernameEmail }] }
15
+ const result = await recordFind(model, { query, limit: 1 }, { dataOnly: true, noHook: true, forceNoHidden: true })
16
+ if (result.length === 0) throw this.error('validationError', { details: [{ field: 'usernameEmail', error: 'unknownUsernameEmailOrInactive' }] })
17
+ const data = result[0]
18
+ const to = `${data.firstName} ${data.lastName} <${data.email}>`
19
+ const subject = req.t('forgotPasswordLink')
20
+ const options = { req, reply, tpl: 'sumba.template:/_mail/user-forgot-password-link.html' }
21
+ const exp = req.site.setting.sumba.forgotPasswordExpDur
22
+ data.fpToken = Buffer.from(`${data.token}:${dayjs().add(exp, 'ms').unix()}`).toString('base64')
23
+ await this.app.masohi.send({ to, subject, message: data, options })
24
+ req.flash('notify', req.t('emailSent'))
25
+ return reply.redirectTo(this.config.redirect.signin)
26
+ } catch (err) {
27
+ error = err
28
+ }
29
+ }
30
+ return reply.view('sumba.template:/user/forgot-password.html', { form, error })
5
31
  }
6
32
  }
7
33
 
@@ -0,0 +1,3 @@
1
+ ---
2
+ layout: waibuMpa.layout:/email.html
3
+ ---
@@ -0,0 +1,3 @@
1
+ ---
2
+ layout: waibuMpa.layout:/email.html
3
+ ---
@@ -0,0 +1,3 @@
1
+ ---
2
+ layout: waibuMpa.layout:/email.html
3
+ ---
@@ -0,0 +1,3 @@
1
+ ---
2
+ layout: waibuMpa.layout:/email.html
3
+ ---
@@ -0,0 +1,4 @@
1
+ ---
2
+ t:title: addTroubleTickets
3
+ layout: sumba.layout:/wide.html
4
+ ---
@@ -0,0 +1,4 @@
1
+ ---
2
+ t:title: troubleTicketsDetails
3
+ layout: sumba.layout:/wide.html
4
+ ---
@@ -0,0 +1,3 @@
1
+ ---
2
+ t:title: forgotPassword
3
+ ---
@@ -0,0 +1,3 @@
1
+ ---
2
+ t:title: forgotPassword
3
+ ---
@@ -1,7 +0,0 @@
1
- {
2
- "field": {
3
- "hostname": "Hostname",
4
- "allowUserTheme": "Theme by User",
5
- "expires": "Expiration"
6
- }
7
- }
package/dobo/i18n/id.json DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "field": {
3
- "hostname": "Nama host",
4
- "allowUserTheme": "Tema oleh Pengguna",
5
- "expires": "Kadaluarsa"
6
- }
7
- }
@@ -1,24 +0,0 @@
1
- const troubleTicketsForm = {
2
- method: ['GET', 'POST'],
3
- handler: async function (req, reply) {
4
- const { defaultsDeep } = this.app.bajo
5
- const { pick } = this.app.bajo.lib._
6
- const { recordCreate } = this.app.waibuDb
7
-
8
- const def = req.user ? pick(req.user, ['firstName', 'lastName', 'email']) : {}
9
- const form = defaultsDeep(req.body, def)
10
- let error
11
- if (req.method === 'POST') {
12
- try {
13
- const { data } = await recordCreate({ model: 'SumbaContactForm', req, reply, options: { noFlash: true } })
14
- req.flash('notify', 'Contact form successfully submitted')
15
- return reply.view('sumba.template:/help/trouble-tickets/success.html', { form: req.body, data })
16
- } catch (err) {
17
- error = err
18
- }
19
- }
20
- return reply.view('sumba.template:/help/trouble-tickets/list.html', { form, error })
21
- }
22
- }
23
-
24
- export default troubleTicketsForm