@social-mail/social-mail-client 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 (49) hide show
  1. package/dist/admin/AdminAppIndex.pack.js +225 -150
  2. package/dist/admin/AdminAppIndex.pack.js.map +1 -1
  3. package/dist/admin/AdminAppIndex.pack.min.js +12 -5
  4. package/dist/admin/AdminAppIndex.pack.min.js.map +1 -1
  5. package/dist/admin/drawer/AdminDrawer.js +3 -3
  6. package/dist/admin/drawer/AdminDrawer.js.map +1 -1
  7. package/dist/admin/pages/domains/services/AdminDomainService.js +1 -1
  8. package/dist/admin/pages/domains/services/AdminDomainService.js.map +1 -1
  9. package/dist/admin/pages/users/EditUserPage.js +65 -69
  10. package/dist/admin/pages/users/EditUserPage.js.map +1 -1
  11. package/dist/admin/pages/users/UserListPage.js +26 -4
  12. package/dist/admin/pages/users/UserListPage.js.map +1 -1
  13. package/dist/common/controls/password/PasswordStrength.js +59 -0
  14. package/dist/common/controls/password/PasswordStrength.js.map +1 -0
  15. package/dist/common/pages/user/LoginPage.js +44 -7
  16. package/dist/common/pages/user/LoginPage.js.map +1 -1
  17. package/dist/common/validators/Validators.js +27 -0
  18. package/dist/common/validators/Validators.js.map +1 -0
  19. package/dist/model/model.js +4 -2
  20. package/dist/model/model.js.map +1 -1
  21. package/dist/style/OverrideAppStyle.js +6 -0
  22. package/dist/style/OverrideAppStyle.js.map +1 -1
  23. package/dist/tsconfig.tsbuildinfo +1 -1
  24. package/dist/web/AppIndex.pack.js +151 -13
  25. package/dist/web/AppIndex.pack.js.map +1 -1
  26. package/dist/web/AppIndex.pack.min.js +12 -5
  27. package/dist/web/AppIndex.pack.min.js.map +1 -1
  28. package/dist/web/drawer/AppDrawer.js +2 -2
  29. package/dist/web/drawer/AppDrawer.js.map +1 -1
  30. package/dist/web/page/mails/MailsPage.js +1 -1
  31. package/dist/web/page/mails/MailsPage.js.map +1 -1
  32. package/node_modules/@web-atoms/web-controls/dist/mobile-app/MobileApp.d.ts.map +1 -1
  33. package/node_modules/@web-atoms/web-controls/dist/mobile-app/MobileApp.js +1 -0
  34. package/node_modules/@web-atoms/web-controls/dist/mobile-app/MobileApp.js.map +1 -1
  35. package/node_modules/@web-atoms/web-controls/dist/tsconfig.tsbuildinfo +1 -1
  36. package/node_modules/@web-atoms/web-controls/package.json +1 -1
  37. package/node_modules/@web-atoms/web-controls/src/mobile-app/MobileApp.tsx +1 -0
  38. package/package.json +2 -2
  39. package/src/admin/drawer/AdminDrawer.tsx +3 -3
  40. package/src/admin/pages/domains/services/AdminDomainService.ts +2 -1
  41. package/src/admin/pages/users/EditUserPage.tsx +58 -57
  42. package/src/admin/pages/users/UserListPage.tsx +15 -2
  43. package/src/common/controls/password/PasswordStrength.tsx +43 -0
  44. package/src/common/pages/user/LoginPage.tsx +52 -3
  45. package/src/common/validators/Validators.ts +27 -0
  46. package/src/model/model.ts +6 -5
  47. package/src/style/OverrideAppStyle.ts +6 -0
  48. package/src/web/drawer/AppDrawer.tsx +2 -2
  49. package/src/web/page/mails/MailsPage.tsx +1 -1
@@ -121,6 +121,7 @@ import Route from "@web-atoms/core/dist/core/Route";
121
121
  grid-row-start: 1;
122
122
  grid-column-start: 2;
123
123
  overflow: hidden;
124
+ overflow-y: visible;
124
125
  white-space: nowrap;
125
126
  text-overflow: ellipsis;
126
127
  color: var(--accent-text-color, black);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@social-mail/social-mail-client",
3
- "version": "1.0.19",
3
+ "version": "1.0.21",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -19,7 +19,7 @@
19
19
  "@web-atoms/font-awesome": "^1.0.37",
20
20
  "@web-atoms/hybrid-clr": "^1.0.21",
21
21
  "@web-atoms/module-loader": "^2.1.32",
22
- "@web-atoms/web-controls": "^2.3.249",
22
+ "@web-atoms/web-controls": "^2.3.250",
23
23
  "reflect-metadata": "^0.1.13"
24
24
  },
25
25
  "devDependencies": {
@@ -33,17 +33,17 @@ export default class AdminDrawer extends Drawer {
33
33
  commandLink={AdminCommands.openHome.displayRoute({})}
34
34
  />
35
35
  <MenuItem
36
- icon="fas fa-home"
36
+ icon="fas fa-folder"
37
37
  label="Domains"
38
38
  commandLink={AdminCommands.openDomainList.displayRoute({})}
39
39
  />
40
40
  <MenuItem
41
- icon="fas fa-home"
41
+ icon="fas fa-user"
42
42
  label="Users"
43
43
  commandLink={AdminCommands.openUserList.displayRoute({})}
44
44
  />
45
45
  <MenuItem
46
- icon="fas fa-home"
46
+ icon="fas fa-envelope"
47
47
  label="Mailboxes"
48
48
  commandLink={AdminCommands.openMailboxList.displayRoute({})}
49
49
  />
@@ -18,7 +18,8 @@ export default class AdminDomainService {
18
18
 
19
19
  public save(domain: IHostedDomain) {
20
20
  const clone = this.entityService.cloner(domain)
21
- .include((x) => [x.domainUsers,x.domain,x.domainKeys]);
21
+ .include((x) =>x.domainUsers)
22
+ .include((x) => x.domain);
22
23
  return this.entityService.save(clone.copy);
23
24
  }
24
25
 
@@ -1,65 +1,73 @@
1
- import { ContentPage } from "@web-atoms/web-controls/dist/mobile-app/MobileApp";
2
- import { DomainUser, IUser, User } from "../../../model/model";
1
+ import { PopupWindowPage } from "@web-atoms/web-controls/dist/mobile-app/MobileApp";
2
+ import { IUser } from "../../../model/model";
3
3
  import XNode from "@web-atoms/core/dist/core/XNode";
4
4
  import Form from "@web-atoms/web-controls/dist/basic/Form";
5
5
  import FormField from "@web-atoms/web-controls/dist/basic/FormField";
6
+ import PasswordBox from "@web-atoms/web-controls/dist/basic/PasswordBox";
6
7
  import Bind from "@web-atoms/core/dist/core/Bind";
7
- import RequiredFormField, { PasswordFormField, TextFormField } from "../../../common/controls/form/RequiredFormField";
8
+ import { IPasswordValidation, Validators } from "../../../common/validators/Validators";
9
+ import PasswordStrength from "../../../common/controls/password/PasswordStrength";
8
10
  import Action from "@web-atoms/core/dist/view-model/Action";
9
- import AtomRepeater from "@web-atoms/web-controls/dist/basic/AtomRepeater";
10
- import PageNavigator from "@web-atoms/web-controls/dist/PageNavigator";
11
- import ChooseDomainPage from "../domains/choose/ChooseDomainPage";
12
- import AdminUserService from "./services/AdminUserService";
13
- import InjectProperty from "@web-atoms/core/dist/core/InjectProperty";
11
+ import EntityService from "../../../services/EntityService";
12
+ import DateTime from "@web-atoms/date-time/dist/DateTime";
13
+ import Checkbox from "@web-atoms/web-controls/dist/basic/Checkbox";
14
14
 
15
- export default class EditUserPage extends ContentPage {
15
+ export default class EditUserPage extends PopupWindowPage {
16
16
 
17
- public parameters: { model?: IUser};
17
+ public parameters: IUser;
18
18
 
19
- public model: IUser & { password?: string };
19
+ public model: IUser;
20
20
 
21
- @InjectProperty
22
- private adminUserService: AdminUserService;
21
+ private password: string = "";
23
22
 
24
23
  async init() {
25
24
 
26
- this.model = this.parameters.model ?? {
27
- ... User.create({
28
- domainUsers: []
29
- }),
30
- password: ""
25
+ this.model = this.parameters;
26
+
27
+ this.model.changePassword ??= {
28
+ newPassword: "",
29
+ forceChangePasswordOnLogin: true
31
30
  };
32
31
 
33
- this.renderer = <div data-layout="center-page-900px">
34
- <Form>
35
- <TextFormField label="Display Name" autofocus={true} required={true} value={() => this.model.displayName}/>
36
- <TextFormField label="Username" required={true} value={() => this.model.userName}/>
37
- { this.model.userID
38
- ? <PasswordFormField
39
- label="Password"
40
- required={true}
41
- value={() => this.model.password}/>
42
- : <PasswordFormField
43
- label="Password"
44
- placeholder="Leave empty..."
45
- value={() => this.model.password}/>
46
- }
32
+ this.title = `Reset Password for ${this.model.displayName} (${this.model.userID}, ${this.model.userName})` ;
47
33
 
48
- <div>
49
- <h5>Domains <i data-click-event="add-domain" class="fas fa-plus"/></h5>
50
- <AtomRepeater
51
- for="table"
52
- items={this.model.domainUsers}
53
- itemRenderer={(item) => <tr>
54
- <td>{item.hostedDomain.domain.name}</td>
55
- </tr>}
34
+ this.renderer = <div>
35
+ <Form>
36
+ { !this.model.userID && <FormField label="Display Name">
37
+ <input value={Bind.twoWaysImmediate(() => this.model.displayName)}/>
38
+ </FormField>}
39
+ { !this.model.userID && <FormField label="Username">
40
+ <input value={Bind.twoWaysImmediate(() => this.model.userName)}/>
41
+ </FormField>}
42
+ <FormField
43
+ label="Password"
44
+ required={true}
45
+ error={Bind.oneWay(() => Validators.isValidPassword(this.password) ? "" : "Password is invalid" )}
46
+ >
47
+ <PasswordBox
48
+ value={Bind.twoWaysImmediate(() => this.password)}
56
49
  />
57
- </div>
58
-
50
+ </FormField>
51
+ <PasswordStrength
52
+ password={Bind.oneWay(() => this.password)}
53
+ />
54
+ <FormField
55
+ label="Password again"
56
+ required={true}
57
+ error={Bind.oneWay(() => this.password !== this.model.changePassword.newPassword ? "Passwords do not match" : "")}>
58
+ <PasswordBox
59
+ value={Bind.twoWaysImmediate(() => this.model.changePassword.newPassword)}
60
+ />
61
+ </FormField>
62
+ <FormField label="Force Change Password">
63
+ <Checkbox
64
+ checked={Bind.twoWaysImmediate(() => this.model.changePassword.forceChangePasswordOnLogin)}
65
+ text="Force user to change password on next login."
66
+ />
67
+ </FormField>
59
68
  <div data-layout="command-row">
60
69
  <button
61
- data-submit-event="save-user"
62
- data-layout="accept-button"
70
+ data-submit-event="reset-password"
63
71
  text="Save"
64
72
  />
65
73
  </div>
@@ -67,19 +75,12 @@ export default class EditUserPage extends ContentPage {
67
75
  </div>;
68
76
  }
69
77
 
70
- @Action({ onEvent: "add-domain"})
71
- async addDomain() {
72
- const hostedDomain = await PageNavigator.pushPageForResult(ChooseDomainPage);
73
- this.model.domainUsers.add(DomainUser.create({
74
- domainID: hostedDomain.domainID,
75
- hostedDomain
76
- }));
77
- }
78
-
79
- @Action({ onEvent: "save-user", close: true })
80
- async saveUser() {
81
- await this.adminUserService.save(this.model);
82
- return this.model;
78
+ @Action({ onEvent: "reset-password", success: "Information saved successfully.", close: true})
79
+ async resetPassword() {
80
+ const entityService = this.resolve(EntityService);
81
+ this.model.dateUpdated = DateTime.utcNow;
82
+ const clone = entityService.cloner(this.model).include((x) => x.changePassword).copy;
83
+ await entityService.save(clone);
83
84
  }
84
85
 
85
86
  }
@@ -4,7 +4,7 @@ import PageNavigator from "@web-atoms/web-controls/dist/PageNavigator";
4
4
  import AtomRepeater from "@web-atoms/web-controls/dist/basic/AtomRepeater";
5
5
  import { ContentPage } from "@web-atoms/web-controls/dist/mobile-app/MobileApp";
6
6
  import EditDomainPage from "../domains/edit/EditDomainPage";
7
- import { IUser } from "../../../model/model";
7
+ import { IUser, User } from "../../../model/model";
8
8
  import AdminUserService from "./services/AdminUserService";
9
9
  import InjectProperty from "@web-atoms/core/dist/core/InjectProperty";
10
10
  import InfiniteRepeater from "../../../common/controls/repeater/InfiniteRepeater";
@@ -39,6 +39,7 @@ export default class UserListPage extends ContentPage {
39
39
  <td>{user.userID}</td>
40
40
  <td>{user.userName}</td>
41
41
  <td>{user.displayName}</td>
42
+ <td><i data-cursor="pointer" class="fas fa-key" title="Change Password" data-click-event="user-change-password"/></td>
42
43
  </tr> }
43
44
  />
44
45
  </div>;
@@ -46,8 +47,20 @@ export default class UserListPage extends ContentPage {
46
47
 
47
48
  @Action({ onEvent: "add-user"})
48
49
  async addDomain() {
49
- await PageNavigator.pushPageForResult(EditUserPage, {});
50
+ await PageNavigator.pushPageForResult(EditUserPage, User.create({
51
+ userName: "",
52
+ displayName: "",
53
+ changePassword: {
54
+ forceChangePasswordOnLogin: true,
55
+ newPassword: ""
56
+ }
57
+ }) );
50
58
  this.version++;
51
59
  }
52
60
 
61
+ @Action({ onEvent: "user-change-password"})
62
+ async userChangePassword(model: IUser) {
63
+ PageNavigator.pushPage(EditUserPage, model);
64
+ }
65
+
53
66
  }
@@ -0,0 +1,43 @@
1
+ import Bind from "@web-atoms/core/dist/core/Bind";
2
+ import XNode from "@web-atoms/core/dist/core/XNode";
3
+ import { AtomControl } from "@web-atoms/core/dist/web/controls/AtomControl";
4
+ import { IPasswordValidation, Validators } from "../../validators/Validators";
5
+
6
+ export default class PasswordStrength extends AtomControl {
7
+
8
+ public password: string = "";
9
+
10
+ public validation: IPasswordValidation = {
11
+ hasMinimumLength: false,
12
+ hasNumber: false,
13
+ hasSymbol: false,
14
+ isValid: false
15
+ };
16
+
17
+ protected preCreate(): void {
18
+ this.render(<div data-layout="vertical-flex" validation={Bind.oneWay(() => Validators.checkPassword(this.password))}>
19
+
20
+ <div data-layout="row">
21
+ <i
22
+ data-color={Bind.oneWay(() => this.validation.hasMinimumLength ? "green" : "red")}
23
+ class={Bind.oneWay(() => this.validation.hasMinimumLength ? "fas fa-check" : "fas fa-times")}/>
24
+ <span text="Password is at least 7 characters long."/>
25
+ </div>
26
+
27
+ <div data-layout="row">
28
+ <i
29
+ data-color={Bind.oneWay(() => this.validation.hasNumber ? "green" : "red")}
30
+ class={Bind.oneWay(() => this.validation.hasNumber ? "fas fa-check" : "fas fa-times")}/>
31
+ <span text="Password has a number."/>
32
+ </div>
33
+
34
+ <div data-layout="row">
35
+ <i
36
+ data-color={Bind.oneWay(() => this.validation.hasSymbol ? "green" : "red")}
37
+ class={Bind.oneWay(() => this.validation.hasSymbol ? "fas fa-check" : "fas fa-times")}/>
38
+ <span text="Password has a symbol."/>
39
+ </div>
40
+ </div>);
41
+ }
42
+
43
+ }
@@ -8,6 +8,10 @@ import PasswordBox from "@web-atoms/web-controls/dist/basic/PasswordBox";
8
8
  import Action from "@web-atoms/core/dist/view-model/Action";
9
9
  import InjectProperty from "@web-atoms/core/dist/core/InjectProperty";
10
10
  import UserService from "../../../services/UserService";
11
+ import { PasswordFormField } from "../../controls/form/RequiredFormField";
12
+ import sleep from "@web-atoms/core/dist/core/sleep";
13
+ import PasswordStrength from "../../controls/password/PasswordStrength";
14
+ import { Validators } from "../../validators/Validators";
11
15
 
12
16
  export default class LoginPage extends ContentPage {
13
17
 
@@ -16,13 +20,15 @@ export default class LoginPage extends ContentPage {
16
20
 
17
21
  private model: ILoginSession;
18
22
 
23
+ private password?: string = "";
24
+
19
25
  async init() {
20
26
  this.model = LoginSession.create({});
21
27
  this.model.userName = "";
22
28
  this.model.checkPassword = "";
23
29
  this.render(<div>
24
30
  <div data-layout="center-all">
25
- <Form data-submit-event="login">
31
+ <Form>
26
32
  <FormField label="Username">
27
33
  <input value={Bind.twoWaysImmediate(() => this.model.userName)}/>
28
34
  </FormField>
@@ -34,7 +40,7 @@ export default class LoginPage extends ContentPage {
34
40
  <div>
35
41
  <button
36
42
  data-layout="accent-button"
37
- data-type="submit"
43
+ data-submit-event="login"
38
44
  text="Login"
39
45
  />
40
46
  </div>
@@ -45,7 +51,50 @@ export default class LoginPage extends ContentPage {
45
51
 
46
52
  @Action({ onEvent: "login" })
47
53
  async login() {
48
- await this.userService.login(this.model);
54
+ try {
55
+ await this.userService.login(this.model);
56
+ return;
57
+ } catch (error) {
58
+ if(!/change the password/i.test(error.stack ?? error)) {
59
+ throw error;
60
+ }
61
+ }
62
+
63
+ this.model.newPassword = "";
64
+
65
+ this.renderer = <div>
66
+ <div data-layout="center-all">
67
+ <Form>
68
+ <div>Update your password, your one time password is expired.</div>
69
+ <FormField
70
+ label="Password"
71
+ required={true}
72
+ error={Bind.oneWay(() => Validators.isValidPassword(this.password)
73
+ ? ""
74
+ : "Password must contain one symbol, one number and should be minimum 7 characters" )}>
75
+ <PasswordBox
76
+ value={Bind.twoWaysImmediate(() => this.password)}
77
+ />
78
+ </FormField>
79
+ <PasswordStrength
80
+ password={Bind.oneWay(() => this.password)}
81
+ />
82
+ <FormField
83
+ label="Password Again"
84
+ required={true}
85
+ error={Bind.oneWay(() => this.model.newPassword !== this.password ? "Passwords do not match" : "")}>
86
+ <PasswordBox
87
+ value={Bind.twoWaysImmediate(() => this.model.newPassword)}
88
+ />
89
+ </FormField>
90
+ <div data-layout="command-row">
91
+ <button
92
+ data-submit-event="login"
93
+ text="Change Password & Login"/>
94
+ </div>
95
+ </Form>
96
+ </div>
97
+ </div>;
49
98
  }
50
99
 
51
100
  }
@@ -0,0 +1,27 @@
1
+ export interface IPasswordValidation {
2
+ hasMinimumLength: boolean;
3
+ hasNumber: boolean;
4
+ hasSymbol: boolean;
5
+ isValid: boolean;
6
+ }
7
+
8
+ export const Validators = {
9
+
10
+ checkPassword: (password: string) => {
11
+ const hasMinimumLength = password.length >= 7;
12
+ const hasNumber = /\d/.test(password);
13
+ const hasSymbol = /[\@\#\$\%\^\&\*\!]/.test(password);
14
+ return {
15
+ hasMinimumLength,
16
+ hasNumber,
17
+ hasSymbol,
18
+ isValid: hasMinimumLength && hasNumber && hasSymbol
19
+ };
20
+ },
21
+
22
+ isValidPassword: (password: string) => {
23
+ return Validators.checkPassword(password).isValid;
24
+ }
25
+
26
+ };
27
+
@@ -219,12 +219,13 @@ export interface IMailboxThread extends IClrEntity {
219
219
  lastEmailID?: number;
220
220
  dateUpdated?: DateTime;
221
221
  unread?: number;
222
+ total?: number;
222
223
  mailbox?: IMailbox;
223
224
  emailAddress?: IEmailAddress;
224
225
  lastEmail?: IEmail;
225
226
  }
226
227
 
227
- export const MailboxThread: IModel<IMailboxThread> = new Model<IMailboxThread>("MailboxThread", { mailboxID: 0,emailAddressID: 0,lastEmailID: 0,dateUpdated: new DateTime(),unread: 0 });
228
+ export const MailboxThread: IModel<IMailboxThread> = new Model<IMailboxThread>("MailboxThread", { mailboxID: 0,emailAddressID: 0,lastEmailID: 0,dateUpdated: new DateTime(),unread: 0,total: 0 });
228
229
 
229
230
  export interface IMailboxThreadEmail extends IClrEntity {
230
231
  mailboxID?: number;
@@ -240,17 +241,16 @@ export interface IUser extends IClrEntity {
240
241
  userID?: number;
241
242
  userName?: string;
242
243
  displayName?: string;
243
- /** Used only while adding new User, not used for changing password. */
244
- password?: string;
244
+ dateUpdated?: DateTime;
245
245
  /** Save this to change password */
246
- changePassword?: { oldPassword?: string;newPassword?: string };
246
+ changePassword?: { oldPassword?: string;newPassword?: string;forceChangePasswordOnLogin?: boolean };
247
247
  domainUsers?: ICollection<IDomainUser>;
248
248
  roles?: ICollection<IUserRole>;
249
249
  userMailboxes?: ICollection<IUserMailbox>;
250
250
  sessions?: ICollection<ILoginSession>;
251
251
  }
252
252
 
253
- export const User: IModel<IUser> = new Model<IUser>("User", { userName: "",displayName: "" });
253
+ export const User: IModel<IUser> = new Model<IUser>("User", { userName: "",displayName: "",dateUpdated: new DateTime() });
254
254
 
255
255
  export interface IUserRole extends IClrEntity {
256
256
  userID?: number;
@@ -278,6 +278,7 @@ export interface ILoginSession extends IClrEntity {
278
278
  invalid?: boolean;
279
279
  userName?: string;
280
280
  checkPassword?: string;
281
+ newPassword?: string;
281
282
  user?: IUser;
282
283
  }
283
284
 
@@ -6,4 +6,10 @@ import styled from "@web-atoms/core/dist/style/styled";
6
6
  width: 100%;
7
7
  }
8
8
 
9
+ div[data-base-page=base-page] {
10
+ & > [data-page-element=title] {
11
+ overflow: visible;
12
+ }
13
+ }
14
+
9
15
  `.withId("default-app-style").withOrder("medium").installGlobal();
@@ -34,12 +34,12 @@ export default class AppDrawer extends Drawer {
34
34
  commandLink={AppCommands.openHome.displayRoute({})}
35
35
  />
36
36
  <MenuItem
37
- icon="fas fa-home"
37
+ icon="fas fa-envelope"
38
38
  label="Mails"
39
39
  commandLink={AppCommands.openMails.displayRoute({})}
40
40
  />
41
41
  <MenuItem
42
- icon="fas fa-home"
42
+ icon="fas fa-folder"
43
43
  label="Mailboxes"
44
44
  commandLink={AppCommands.openMailboxList.displayRoute({})}
45
45
  />
@@ -36,7 +36,7 @@ export default class MailsPage extends MasterDetailPage {
36
36
 
37
37
  const first = mailboxes[0];
38
38
  this.currentMailboxID = first.mailboxID;
39
- this.headerRenderer = () => <div data-layout="row">
39
+ this.titleRenderer = () => <div data-layout="row">
40
40
  <DropDown
41
41
  items={mailboxes}
42
42
  labelPath={(item) => item.emailAddress.emailAddress}