igniteui-cli 15.2.2-alpha.3 → 15.3.0

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 (175) hide show
  1. package/lib/PromptSession.js +1 -1
  2. package/lib/commands/ai-config.d.ts +9 -2
  3. package/lib/commands/ai-config.js +49 -14
  4. package/lib/commands/build.js +7 -12
  5. package/lib/commands/new.js +4 -1
  6. package/package.json +4 -4
  7. package/templates/blazor/igb/projects/ai-config/files/skills/AGENTS.md +0 -5
  8. package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-components/SKILL.md +3 -1
  9. package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-components/references/charts.md +7 -35
  10. package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-components/references/data-display.md +1 -54
  11. package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-components/references/feedback.md +0 -38
  12. package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-components/references/form-controls.md +0 -68
  13. package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-components/references/layout-manager.md +1 -124
  14. package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-components/references/layout.md +0 -62
  15. package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-generate-from-image-design/references/gotchas.md +29 -6
  16. package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-theming/SKILL.md +2 -2
  17. package/templates/react/igr-ts/accordion/default/index.js +2 -1
  18. package/templates/react/igr-ts/avatar/default/index.js +2 -1
  19. package/templates/react/igr-ts/badge/default/index.js +2 -1
  20. package/templates/react/igr-ts/banner/default/index.js +2 -1
  21. package/templates/react/igr-ts/button/default/index.js +2 -1
  22. package/templates/react/igr-ts/button-group/default/index.js +2 -1
  23. package/templates/react/igr-ts/calendar/default/index.js +2 -1
  24. package/templates/react/igr-ts/card/default/index.js +2 -1
  25. package/templates/react/igr-ts/checkbox/default/index.js +2 -1
  26. package/templates/react/igr-ts/chip/default/index.js +2 -1
  27. package/templates/react/igr-ts/circular-progress/default/index.js +2 -1
  28. package/templates/react/igr-ts/constants.d.ts +2 -0
  29. package/templates/react/igr-ts/constants.js +5 -0
  30. package/templates/react/igr-ts/custom-templates/subscription-form/index.js +2 -1
  31. package/templates/react/igr-ts/date-picker/default/index.js +2 -1
  32. package/templates/react/igr-ts/divider/default/index.js +2 -1
  33. package/templates/react/igr-ts/dropdown/default/index.js +2 -1
  34. package/templates/react/igr-ts/expansion-panel/default/index.js +2 -1
  35. package/templates/react/igr-ts/form/default/index.js +2 -1
  36. package/templates/react/igr-ts/grid/basic/index.js +2 -1
  37. package/templates/react/igr-ts/icon/default/index.js +2 -1
  38. package/templates/react/igr-ts/icon-button/default/index.js +2 -1
  39. package/templates/react/igr-ts/input/default/index.js +2 -1
  40. package/templates/react/igr-ts/linear-progress/default/index.js +2 -1
  41. package/templates/react/igr-ts/list/default/index.js +2 -1
  42. package/templates/react/igr-ts/navbar/default/index.js +2 -1
  43. package/templates/react/igr-ts/projects/_base/files/package.json +2 -1
  44. package/templates/react/igr-ts/projects/_base/files/src/app/app.tsx +4 -2
  45. package/templates/react/igr-ts/projects/_base/files/src/setupTests.ts +12 -0
  46. package/templates/react/igr-ts/projects/_base/files/styles.css +6 -0
  47. package/templates/react/igr-ts/projects/_base_with_home/files/index.html +2 -1
  48. package/templates/react/igr-ts/projects/_base_with_home/files/src/app/home/home.tsx +60 -10
  49. package/templates/react/igr-ts/projects/_base_with_home/files/src/app/home/style.module.css +79 -20
  50. package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-components/SKILL.md +0 -8
  51. package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-components/reference/CHARTS-GRIDS.md +6 -36
  52. package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-components/reference/COMPONENT-CATALOGUE.md +8 -142
  53. package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-components/reference/EVENT-HANDLING.md +2 -0
  54. package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-customize-theme/SKILL.md +7 -14
  55. package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-customize-theme/reference/CSS-THEMING.md +2 -0
  56. package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-customize-theme/reference/MCP-SERVER.md +0 -8
  57. package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-generate-from-image-design/SKILL.md +2 -2
  58. package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-generate-from-image-design/reference/component-mapping.md +60 -74
  59. package/templates/react/igr-ts/projects/empty/index.js +2 -2
  60. package/templates/react/igr-ts/projects/side-nav/files/src/app/app-routes.tsx +5 -0
  61. package/templates/react/igr-ts/projects/side-nav/files/src/app/app.css +82 -0
  62. package/templates/react/igr-ts/projects/side-nav/files/src/app/app.tsx +104 -0
  63. package/templates/react/igr-ts/projects/side-nav/files/src/app/home/home.tsx +69 -0
  64. package/templates/react/igr-ts/projects/side-nav/files/src/app/home/style.module.css +105 -0
  65. package/templates/react/igr-ts/projects/{top-nav → side-nav}/index.d.ts +2 -2
  66. package/templates/react/igr-ts/projects/{top-nav → side-nav}/index.js +7 -7
  67. package/templates/react/igr-ts/projects/side-nav-auth/files/index.html +19 -0
  68. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/app-routes.tsx +24 -0
  69. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/app.css +84 -0
  70. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/app.tsx +124 -0
  71. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/AuthContext.tsx +73 -0
  72. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/AuthGuard.tsx +14 -0
  73. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/Login.module.css +93 -0
  74. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/Login.tsx +69 -0
  75. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/LoginBar.module.css +42 -0
  76. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/LoginBar.tsx +44 -0
  77. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/LoginDialog.module.css +14 -0
  78. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/LoginDialog.tsx +49 -0
  79. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/Register.module.css +74 -0
  80. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/Register.tsx +67 -0
  81. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/models/external-login.ts +10 -0
  82. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/models/login.ts +4 -0
  83. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/models/register-info.ts +6 -0
  84. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/models/user.ts +19 -0
  85. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/pages/Profile.module.css +87 -0
  86. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/pages/Profile.tsx +42 -0
  87. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/pages/RedirectFacebook.tsx +44 -0
  88. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/pages/RedirectGoogle.tsx +40 -0
  89. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/pages/RedirectMicrosoft.tsx +40 -0
  90. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/services/authentication.ts +37 -0
  91. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/services/external-auth-config.ts +44 -0
  92. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/services/externalAuth.ts +272 -0
  93. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/services/fakeBackend.ts +88 -0
  94. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/services/jwtUtil.ts +10 -0
  95. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/services/pkce.ts +29 -0
  96. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/services/userStore.ts +39 -0
  97. package/templates/react/igr-ts/projects/side-nav-auth/index.d.ts +15 -0
  98. package/templates/react/igr-ts/projects/side-nav-auth/index.js +46 -0
  99. package/templates/react/igr-ts/projects/side-nav-mini/files/src/app/app-routes.tsx +5 -0
  100. package/templates/react/igr-ts/projects/side-nav-mini/files/src/app/app.css +109 -0
  101. package/templates/react/igr-ts/projects/side-nav-mini/files/src/app/app.test.tsx +20 -0
  102. package/templates/react/igr-ts/projects/side-nav-mini/files/src/app/app.tsx +81 -0
  103. package/templates/react/igr-ts/projects/side-nav-mini/files/src/app/home/home.tsx +69 -0
  104. package/templates/react/igr-ts/projects/side-nav-mini/files/src/app/home/style.module.css +105 -0
  105. package/templates/react/igr-ts/projects/side-nav-mini/index.d.ts +15 -0
  106. package/templates/react/igr-ts/projects/side-nav-mini/index.js +46 -0
  107. package/templates/react/igr-ts/projects/side-nav-mini-auth/files/src/app/app.css +106 -0
  108. package/templates/react/igr-ts/projects/side-nav-mini-auth/files/src/app/app.tsx +101 -0
  109. package/templates/react/igr-ts/projects/side-nav-mini-auth/index.d.ts +15 -0
  110. package/templates/react/igr-ts/projects/side-nav-mini-auth/index.js +50 -0
  111. package/templates/react/igr-ts/radio-group/default/index.js +2 -1
  112. package/templates/react/igr-ts/rating/default/index.js +2 -1
  113. package/templates/react/igr-ts/ripple/default/index.js +2 -1
  114. package/templates/react/igr-ts/slider/default/index.js +2 -1
  115. package/templates/react/igr-ts/switch/default/index.js +2 -1
  116. package/templates/react/igr-ts/tabs/default/index.js +2 -1
  117. package/templates/react/igr-ts/text-area/default/index.js +2 -1
  118. package/templates/react/igr-ts/tree/default/index.js +2 -1
  119. package/templates/webcomponents/igc-ts/grid/default/index.js +1 -1
  120. package/templates/webcomponents/igc-ts/grid/grid-editing/index.js +1 -1
  121. package/templates/webcomponents/igc-ts/grid/grid-summaries/index.js +1 -1
  122. package/templates/webcomponents/igc-ts/projects/_base/files/package.json +1 -1
  123. package/templates/webcomponents/igc-ts/projects/_base/files/src/app/app.ts +6 -1
  124. package/templates/webcomponents/igc-ts/projects/_base/files/styles.css +1 -0
  125. package/templates/webcomponents/igc-ts/projects/_base_with_home/files/index.html +2 -0
  126. package/templates/webcomponents/igc-ts/projects/_base_with_home/files/package.json +2 -2
  127. package/templates/webcomponents/igc-ts/projects/_base_with_home/files/src/app/home/home.ts +103 -9
  128. package/templates/webcomponents/igc-ts/projects/_base_with_home/files/src/assets/wc.png +0 -0
  129. package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-choose-components/SKILL.md +122 -160
  130. package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-customize-component-theme/SKILL.md +83 -311
  131. package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-customize-component-theme/references/mcp-setup.md +69 -0
  132. package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-generate-from-image-design/SKILL.md +4 -1
  133. package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-generate-from-image-design/references/component-mapping.md +60 -61
  134. package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-generate-from-image-design/references/gotchas.md +15 -11
  135. package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-optimize-bundle-size/SKILL.md +23 -274
  136. package/templates/webcomponents/igc-ts/projects/empty/index.js +1 -1
  137. package/templates/webcomponents/igc-ts/projects/side-nav/files/index.html +21 -0
  138. package/templates/webcomponents/igc-ts/projects/side-nav/files/src/app/app-routing.ts +9 -0
  139. package/templates/webcomponents/igc-ts/projects/side-nav/files/src/app/app.ts +192 -22
  140. package/templates/webcomponents/igc-ts/projects/side-nav/files/src/app/home/home.ts +175 -0
  141. package/templates/webcomponents/igc-ts/projects/side-nav/index.js +1 -1
  142. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/index.html +25 -0
  143. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/app-routing.ts +37 -0
  144. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/app.ts +251 -0
  145. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/login-bar/login-bar.ts +124 -0
  146. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/login-dialog/login-dialog.ts +253 -0
  147. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/models/external-login.ts +10 -0
  148. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/models/login.ts +4 -0
  149. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/models/register-info.ts +6 -0
  150. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/models/user.ts +19 -0
  151. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/services/authentication.ts +37 -0
  152. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/services/external-auth-config.ts +44 -0
  153. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/services/externalAuth.ts +272 -0
  154. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/services/fakeBackend.ts +88 -0
  155. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/services/jwtUtil.ts +10 -0
  156. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/services/pkce.ts +29 -0
  157. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/services/userStore.ts +39 -0
  158. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/profile/profile.ts +142 -0
  159. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/redirect/redirect-facebook.ts +57 -0
  160. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/redirect/redirect-google.ts +53 -0
  161. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/redirect/redirect-microsoft.ts +53 -0
  162. package/templates/webcomponents/igc-ts/projects/side-nav-auth/index.d.ts +15 -0
  163. package/templates/webcomponents/igc-ts/projects/side-nav-auth/index.js +46 -0
  164. package/templates/webcomponents/igc-ts/projects/side-nav-mini/files/src/app/app-routing.ts +13 -0
  165. package/templates/webcomponents/igc-ts/projects/side-nav-mini/files/src/app/app.ts +238 -0
  166. package/templates/webcomponents/igc-ts/projects/side-nav-mini/index.d.ts +14 -0
  167. package/templates/webcomponents/igc-ts/projects/side-nav-mini/index.js +45 -0
  168. package/templates/webcomponents/igc-ts/projects/side-nav-mini-auth/files/src/app/app.ts +258 -0
  169. package/templates/webcomponents/igc-ts/projects/side-nav-mini-auth/index.d.ts +15 -0
  170. package/templates/webcomponents/igc-ts/projects/side-nav-mini-auth/index.js +50 -0
  171. package/templates/webcomponents/igc-ts/tree/default/index.js +1 -1
  172. package/templates/react/igr-ts/projects/top-nav/files/src/app/app.css +0 -62
  173. package/templates/react/igr-ts/projects/top-nav/files/src/app/app.tsx +0 -18
  174. package/templates/react/igr-ts/projects/top-nav/files/src/components/navigation-header/index.tsx +0 -19
  175. /package/templates/react/igr-ts/projects/{top-nav → side-nav}/files/src/app/app.test.tsx +0 -0
@@ -0,0 +1,93 @@
1
+ .form {
2
+ display: flex;
3
+ flex-flow: column;
4
+ gap: 16px;
5
+ padding: 8px 0 0;
6
+ }
7
+
8
+ .form igc-input {
9
+ width: 100%;
10
+ --ig-input-group-focused-secondary-color: #239ef0;
11
+ --ig-input-group-focused-border-color: #239ef0;
12
+ --ig-input-group-filled-text-color: #2d2d2d;
13
+ }
14
+
15
+ .form igc-input igc-icon {
16
+ color: #0075d2;
17
+ --ig-icon-size: 1.50rem;
18
+ }
19
+
20
+ .error {
21
+ margin: 0;
22
+ font-size: .875rem;
23
+ color: #d32f2f;
24
+ }
25
+
26
+ .actions {
27
+ display: flex;
28
+ flex-flow: column;
29
+ gap: 8px;
30
+ padding-top: 4px;
31
+ }
32
+
33
+ .submitBtn {
34
+ display: block;
35
+ }
36
+
37
+ .submitBtn::part(base) {
38
+ width: 100%;
39
+ min-height: 40px;
40
+ font-weight: 600;
41
+ text-transform: uppercase;
42
+ }
43
+
44
+ .submitBtn:not([disabled])::part(base) {
45
+ background: #239ef0;
46
+ color: #fff;
47
+ }
48
+
49
+ .submitBtn:not([disabled])::part(base):hover {
50
+ background: #1a8fd8;
51
+ }
52
+
53
+ .submitBtn[disabled]::part(base) {
54
+ background: #e0e0e0;
55
+ color: #767676;
56
+ }
57
+
58
+ .linkBtn {
59
+ align-self: center;
60
+ color: #0075d2;
61
+ font-size: .875rem;
62
+ cursor: pointer;
63
+ text-decoration: underline;
64
+ text-transform: none;
65
+ }
66
+
67
+ .linkBtn:hover,
68
+ .linkBtn:focus-visible {
69
+ color: #005da8;
70
+ }
71
+
72
+ .socialLogin {
73
+ display: grid;
74
+ gap: 8px;
75
+ padding-top: 16px;
76
+ border-top: 1px solid #d7d7d7;
77
+ }
78
+
79
+ .socialBtn {
80
+ display: block;
81
+ }
82
+
83
+ .socialBtn::part(base) {
84
+ width: 100%;
85
+ min-height: 40px;
86
+ color: #fff;
87
+ font-weight: 600;
88
+ text-transform: uppercase;
89
+ }
90
+
91
+ .google::part(base) { background: rgb(255, 19, 74); }
92
+ .facebook::part(base) { background: rgb(19, 119, 213); }
93
+ .microsoft::part(base){ background: rgb(27, 158, 245); }
@@ -0,0 +1,69 @@
1
+ import { useState, type FormEvent } from 'react';
2
+ import { IgrButton, IgrIcon, IgrInput } from 'igniteui-react';
3
+ import { useAuth } from '../AuthContext';
4
+ import { ExternalAuth } from '../services/externalAuth';
5
+ import type { Login as LoginData } from '../models/login';
6
+ import styles from './Login.module.css';
7
+
8
+ interface LoginProps {
9
+ onRegister: () => void;
10
+ onSuccess: () => void;
11
+ }
12
+
13
+ export function Login({ onRegister, onSuccess }: LoginProps) {
14
+ const { login } = useAuth();
15
+ const [email, setEmail] = useState('');
16
+ const [password, setPassword] = useState('');
17
+ const [error, setError] = useState('');
18
+
19
+ const canSubmit = email.trim() !== '' && password !== '';
20
+
21
+ const handleSubmit = async (e: FormEvent) => {
22
+ e.preventDefault();
23
+ setError('');
24
+ const data: LoginData = { email, password };
25
+ const err = await login(data);
26
+ if (err) {
27
+ setError(err);
28
+ } else {
29
+ setPassword('');
30
+ onSuccess();
31
+ }
32
+ };
33
+
34
+ return (
35
+ <form className={styles.form} onSubmit={handleSubmit} noValidate>
36
+ <IgrInput outlined type="email" name="email" label="Email" autoComplete="email" required={true}
37
+ value={email} onInput={(e: any) => setEmail(e.detail ?? '')}>
38
+ <IgrIcon slot="suffix" name="account_circle" collection="material" />
39
+ </IgrInput>
40
+ <IgrInput outlined type="password" name="password" label="Password" autoComplete="current-password" required={true}
41
+ value={password} onInput={(e: any) => setPassword(e.detail ?? '')}>
42
+ <IgrIcon slot="suffix" name="lock" collection="material" />
43
+ </IgrInput>
44
+ {error && <p className={styles.error}>{error}</p>}
45
+ <div className={styles.actions}>
46
+ <IgrButton variant="contained" type="submit" className={styles.submitBtn} disabled={!canSubmit}>
47
+ <span>Log In</span>
48
+ </IgrButton>
49
+ <a className={styles.linkBtn} onClick={onRegister} role="button" tabIndex={0}>Create new account</a>
50
+ </div>
51
+ {ExternalAuth.hasProvider() && (
52
+ <div className={styles.socialLogin}>
53
+ {ExternalAuth.hasProvider('google') && (
54
+ <IgrButton variant="contained" type="button" className={`${styles.socialBtn} ${styles.google}`}
55
+ onClick={() => ExternalAuth.login('google')}><span>Sign in with Google</span></IgrButton>
56
+ )}
57
+ {ExternalAuth.hasProvider('facebook') && (
58
+ <IgrButton variant="contained" type="button" className={`${styles.socialBtn} ${styles.facebook}`}
59
+ onClick={() => ExternalAuth.login('facebook')}><span>Sign in with Facebook</span></IgrButton>
60
+ )}
61
+ {ExternalAuth.hasProvider('microsoft') && (
62
+ <IgrButton variant="contained" type="button" className={`${styles.socialBtn} ${styles.microsoft}`}
63
+ onClick={() => ExternalAuth.login('microsoft')}><span>Sign in with Microsoft</span></IgrButton>
64
+ )}
65
+ </div>
66
+ )}
67
+ </form>
68
+ );
69
+ }
@@ -0,0 +1,42 @@
1
+ .loginBtn::part(base) {
2
+ color: #0075d2;
3
+ background: #fff;
4
+ border-color: rgba(0, 117, 210, 0.35);
5
+ font-weight: 600;
6
+ white-space: nowrap;
7
+ transition: background .15s;
8
+ }
9
+
10
+ .loginBtn::part(base):hover {
11
+ background: #e8f3fc;
12
+ }
13
+
14
+ .profileAvatar {
15
+ cursor: pointer;
16
+ color: #0075d2;
17
+ --ig-avatar-background: #fff;
18
+ --ig-avatar-color: #0075d2;
19
+ }
20
+
21
+ :global(igc-dropdown-item:hover),
22
+ :global(igc-dropdown-item[active]:hover) {
23
+ background: #e8f3fc;
24
+ color: #0075d2;
25
+ }
26
+
27
+ :global(igc-dropdown-item[active]) {
28
+ background: #e8f3fc;
29
+ color: #0075d2;
30
+ }
31
+
32
+ :global(igc-dropdown-item[selected]),
33
+ :global(igc-dropdown-item[selected]:hover),
34
+ :global(igc-dropdown-item[selected][active]) {
35
+ background: #e8f3fc;
36
+ color: #0075d2;
37
+ }
38
+
39
+ .profileAvatar:focus-visible {
40
+ outline: 2px solid #fff;
41
+ outline-offset: 2px;
42
+ }
@@ -0,0 +1,44 @@
1
+ import { useState } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { IgrAvatar, IgrButton, IgrDropdown, IgrDropdownItem } from 'igniteui-react';
4
+ import { useAuth } from '../AuthContext';
5
+ import { LoginDialog } from './LoginDialog';
6
+ import styles from './LoginBar.module.css';
7
+
8
+ export function LoginBar() {
9
+ const { currentUser, initials, logout } = useAuth();
10
+ const navigate = useNavigate();
11
+ const [dialogOpen, setDialogOpen] = useState(false);
12
+
13
+ if (!currentUser) {
14
+ return (
15
+ <>
16
+ <IgrButton variant="outlined" className={styles.loginBtn} onClick={() => setDialogOpen(true)}>
17
+ <span>Log In</span>
18
+ </IgrButton>
19
+ <LoginDialog open={dialogOpen} onClose={() => setDialogOpen(false)} />
20
+ </>
21
+ );
22
+ }
23
+
24
+ return (
25
+ <IgrDropdown placement="bottom-end">
26
+ <IgrAvatar
27
+ slot="target"
28
+ className={styles.profileAvatar}
29
+ shape="circle"
30
+ size="small"
31
+ initials={initials}
32
+ src={currentUser.picture ?? ''}
33
+ tabIndex={0}
34
+ aria-label="Open profile menu"
35
+ />
36
+ <IgrDropdownItem onClick={() => navigate('/auth/profile')}>
37
+ <span>Profile</span>
38
+ </IgrDropdownItem>
39
+ <IgrDropdownItem onClick={() => { logout(); navigate('/'); }}>
40
+ <span>Log Out</span>
41
+ </IgrDropdownItem>
42
+ </IgrDropdown>
43
+ );
44
+ }
@@ -0,0 +1,14 @@
1
+ .dialog::part(base) {
2
+ max-width: 24rem;
3
+ width: calc(100vw - 48px);
4
+ }
5
+
6
+ .dialog::part(title) {
7
+ font-size: 1.125rem;
8
+ font-weight: 600;
9
+ color: #2d2d2d;
10
+ }
11
+
12
+ .body {
13
+ padding: 4px 0;
14
+ }
@@ -0,0 +1,49 @@
1
+ import { useState, useEffect, useRef } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { IgrDialog } from 'igniteui-react';
4
+ import { Login } from './Login';
5
+ import { Register } from './Register';
6
+ import styles from './LoginDialog.module.css';
7
+
8
+ interface LoginDialogProps {
9
+ open: boolean;
10
+ onClose: () => void;
11
+ }
12
+
13
+ export function LoginDialog({ open, onClose }: LoginDialogProps) {
14
+ const [showLogin, setShowLogin] = useState(true);
15
+ const dialogRef = useRef<IgrDialog>(null);
16
+ const navigate = useNavigate();
17
+
18
+ useEffect(() => {
19
+ if (open) {
20
+ setShowLogin(true);
21
+ dialogRef.current?.show();
22
+ } else {
23
+ dialogRef.current?.hide();
24
+ }
25
+ }, [open]);
26
+
27
+ const handleSuccess = () => {
28
+ dialogRef.current?.hide();
29
+ navigate('/auth/profile');
30
+ };
31
+
32
+ return (
33
+ <IgrDialog
34
+ ref={dialogRef}
35
+ className={styles.dialog}
36
+ title={showLogin ? 'Login' : 'Register'}
37
+ closeOnOutsideClick={true}
38
+ onClosed={onClose}
39
+ >
40
+ <div className={styles.body}>
41
+ {showLogin
42
+ ? <Login onRegister={() => setShowLogin(false)} onSuccess={handleSuccess} />
43
+ : <Register onLogin={() => setShowLogin(true)} onSuccess={handleSuccess} />
44
+ }
45
+ </div>
46
+ <span slot="footer" />
47
+ </IgrDialog>
48
+ );
49
+ }
@@ -0,0 +1,74 @@
1
+ .form {
2
+ display: flex;
3
+ flex-flow: column;
4
+ gap: 16px;
5
+ padding: 8px 0 0;
6
+ }
7
+
8
+ .form igc-input {
9
+ width: 100%;
10
+ --ig-input-group-focused-secondary-color: #239ef0;
11
+ --ig-input-group-focused-border-color: #239ef0;
12
+ --ig-input-group-filled-text-color: #2d2d2d;
13
+ }
14
+
15
+ .form igc-input igc-icon {
16
+ color: #0075d2;
17
+ --ig-icon-size: 1.50rem;
18
+ }
19
+
20
+ .error {
21
+ margin: 0;
22
+ font-size: .875rem;
23
+ color: #d32f2f;
24
+ }
25
+
26
+ .actions {
27
+ display: flex;
28
+ flex-flow: column;
29
+ gap: 8px;
30
+ padding-top: 4px;
31
+ }
32
+
33
+ .submitBtn {
34
+ display: block;
35
+ }
36
+
37
+ .submitBtn::part(base) {
38
+ width: 100%;
39
+ min-height: 40px;
40
+ font-weight: 600;
41
+ text-transform: uppercase;
42
+ }
43
+
44
+ .submitBtn:not([disabled])::part(base) {
45
+ background: #239ef0;
46
+ color: #fff;
47
+ }
48
+
49
+ .submitBtn:not([disabled])::part(base):hover {
50
+ background: #1a8fd8;
51
+ }
52
+
53
+ .submitBtn[disabled]::part(base) {
54
+ background: #e0e0e0;
55
+ color: #767676;
56
+ }
57
+
58
+ .linkBtn {
59
+ align-self: center;
60
+ color: #0075d2;
61
+ font-size: .875rem;
62
+ cursor: pointer;
63
+ text-decoration: underline;
64
+ text-transform: none;
65
+ }
66
+
67
+ .linkBtn:hover,
68
+ .linkBtn:focus-visible {
69
+ color: #005da8;
70
+ }
71
+
72
+ .linkBtn::part(base):hover {
73
+ color: #005da8;
74
+ }
@@ -0,0 +1,67 @@
1
+ import { useState, type FormEvent } from 'react';
2
+ import { IgrButton, IgrIcon, IgrInput } from 'igniteui-react';
3
+ import { useAuth } from '../AuthContext';
4
+ import type { RegisterInfo } from '../models/register-info';
5
+ import styles from './Register.module.css';
6
+
7
+ interface RegisterProps {
8
+ onLogin: () => void;
9
+ onSuccess: () => void;
10
+ }
11
+
12
+ export function Register({ onLogin, onSuccess }: RegisterProps) {
13
+ const { register } = useAuth();
14
+ const [givenName, setGivenName] = useState('');
15
+ const [familyName, setFamilyName] = useState('');
16
+ const [email, setEmail] = useState('');
17
+ const [password, setPassword] = useState('');
18
+ const [error, setError] = useState('');
19
+
20
+ const canSubmit = givenName.trim() !== '' && email.trim() !== '' && password !== '';
21
+
22
+ const handleSubmit = async (e: FormEvent) => {
23
+ e.preventDefault();
24
+ setError('');
25
+ const data: RegisterInfo = {
26
+ given_name: givenName,
27
+ family_name: familyName,
28
+ email,
29
+ password
30
+ };
31
+ const err = await register(data);
32
+ if (err) {
33
+ setError(err);
34
+ } else {
35
+ setPassword('');
36
+ onSuccess();
37
+ }
38
+ };
39
+
40
+ return (
41
+ <form className={styles.form} onSubmit={handleSubmit} noValidate>
42
+ <IgrInput outlined type="text" name="given_name" label="First Name" autoComplete="given-name" required={true}
43
+ value={givenName} onInput={(e: any) => setGivenName(e.detail ?? '')}>
44
+ <IgrIcon slot="suffix" name="assignment_ind" collection="material" />
45
+ </IgrInput>
46
+ <IgrInput outlined type="text" name="family_name" label="Last Name" autoComplete="family-name"
47
+ value={familyName} onInput={(e: any) => setFamilyName(e.detail ?? '')}>
48
+ <IgrIcon slot="suffix" name="assignment_ind" collection="material" />
49
+ </IgrInput>
50
+ <IgrInput outlined type="email" name="email" label="Email" autoComplete="email" required={true}
51
+ value={email} onInput={(e: any) => setEmail(e.detail ?? '')}>
52
+ <IgrIcon slot="suffix" name="account_circle" collection="material" />
53
+ </IgrInput>
54
+ <IgrInput outlined type="password" name="password" label="Password" autoComplete="new-password" required={true}
55
+ value={password} onInput={(e: any) => setPassword(e.detail ?? '')}>
56
+ <IgrIcon slot="suffix" name="lock" collection="material" />
57
+ </IgrInput>
58
+ {error && <p className={styles.error}>{error}</p>}
59
+ <div className={styles.actions}>
60
+ <IgrButton variant="contained" type="submit" className={styles.submitBtn} disabled={!canSubmit}>
61
+ <span>Sign Up</span>
62
+ </IgrButton>
63
+ <a className={styles.linkBtn} onClick={onLogin} role="button" tabIndex={0}>Have an account?</a>
64
+ </div>
65
+ </form>
66
+ );
67
+ }
@@ -0,0 +1,10 @@
1
+ /** User profile returned by a social (external) auth provider. */
2
+ export interface ExternalLogin {
3
+ id: string;
4
+ name: string;
5
+ email?: string; // not always present use id as fallback key
6
+ given_name?: string;
7
+ family_name?: string;
8
+ picture?: string;
9
+ externalToken: string;
10
+ }
@@ -0,0 +1,4 @@
1
+ export interface Login {
2
+ email: string;
3
+ password: string;
4
+ }
@@ -0,0 +1,6 @@
1
+ export interface RegisterInfo {
2
+ given_name: string;
3
+ family_name: string;
4
+ email: string;
5
+ password: string;
6
+ }
@@ -0,0 +1,19 @@
1
+ /** Data transfer model expected from backend API JWT-s */
2
+ export interface UserJWT {
3
+ exp: number;
4
+ name: string;
5
+ given_name: string;
6
+ family_name: string;
7
+ email: string;
8
+ picture?: string;
9
+ }
10
+
11
+ /** Client user model */
12
+ export interface User extends UserJWT {
13
+ token: string;
14
+ }
15
+
16
+ export interface LoginResult {
17
+ user?: User;
18
+ error?: string;
19
+ }
@@ -0,0 +1,87 @@
1
+ .container {
2
+ display: flex;
3
+ justify-content: center;
4
+ padding: 48px 16px;
5
+ width: 100%;
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ .card {
10
+ align-self: flex-start;
11
+ width: 100%;
12
+ max-width: 640px;
13
+ background: #fff;
14
+ border-radius: 8px;
15
+ box-shadow: 0 2px 12px rgba(0, 0, 0, .08);
16
+ padding: 32px;
17
+ box-sizing: border-box;
18
+ }
19
+
20
+ .header {
21
+ display: flex;
22
+ align-items: center;
23
+ gap: 20px;
24
+ padding-bottom: 24px;
25
+ border-bottom: 1px solid #d7eaf8;
26
+ }
27
+
28
+ .avatar {
29
+ flex: 0 0 auto;
30
+ --ig-avatar-background: #e0f2ff;
31
+ --ig-avatar-color: #0075d2;
32
+ --ig-avatar-size: 4rem;
33
+ }
34
+
35
+ .intro {
36
+ min-width: 0;
37
+ }
38
+
39
+ .status {
40
+ margin: 0 0 4px;
41
+ color: #000;
42
+ font-size: .875rem;
43
+ font-weight: 700;
44
+ text-transform: uppercase;
45
+ }
46
+
47
+ .name {
48
+ margin: 0;
49
+ overflow-wrap: anywhere;
50
+ color: #09f;
51
+ font-size: 2rem;
52
+ font-weight: 600;
53
+ line-height: 1.2;
54
+ }
55
+
56
+ .description {
57
+ margin: 8px 0 0;
58
+ color: #000;
59
+ font-size: 1rem;
60
+ line-height: 1.5;
61
+ }
62
+
63
+ .details {
64
+ margin: 28px 0 0;
65
+ padding: 0;
66
+ }
67
+
68
+ .row {
69
+ display: grid;
70
+ grid-template-columns: 140px minmax(0, 1fr);
71
+ gap: 24px;
72
+ padding: 14px 0;
73
+ border-bottom: 1px solid #eef3f7;
74
+ }
75
+
76
+ .dt {
77
+ color: rgba(0, 0, 0, .62);
78
+ font-size: .875rem;
79
+ font-weight: 600;
80
+ margin: 0;
81
+ }
82
+
83
+ .dd {
84
+ margin: 0;
85
+ font-size: 1rem;
86
+ color: #2d2d2d;
87
+ }
@@ -0,0 +1,42 @@
1
+ import { IgrAvatar } from 'igniteui-react';
2
+ import { useAuth } from '../AuthContext';
3
+ import styles from './Profile.module.css';
4
+
5
+ export default function Profile() {
6
+ const { currentUser } = useAuth();
7
+ const initials = ((currentUser?.given_name?.[0] ?? '') + (currentUser?.family_name?.[0] ?? '')).toUpperCase() || 'U';
8
+
9
+ return (
10
+ <div className={styles.container}>
11
+ <div className={styles.card}>
12
+ <div className={styles.header}>
13
+ <IgrAvatar
14
+ className={styles.avatar}
15
+ shape="circle"
16
+ initials={initials}
17
+ src={currentUser?.picture ?? ''}
18
+ />
19
+ <div className={styles.intro}>
20
+ <p className={styles.status}>Signed in</p>
21
+ <h1 className={styles.name}>{currentUser?.name || 'Your profile'}</h1>
22
+ <p className={styles.description}>Your account details are available on this protected route.</p>
23
+ </div>
24
+ </div>
25
+ <dl className={styles.details}>
26
+ <div className={styles.row}>
27
+ <dt className={styles.dt}>First name</dt>
28
+ <dd className={styles.dd}>{currentUser?.given_name || 'Not provided'}</dd>
29
+ </div>
30
+ <div className={styles.row}>
31
+ <dt className={styles.dt}>Last name</dt>
32
+ <dd className={styles.dd}>{currentUser?.family_name || 'Not provided'}</dd>
33
+ </div>
34
+ <div className={styles.row}>
35
+ <dt className={styles.dt}>Email</dt>
36
+ <dd className={styles.dd}>{currentUser?.email || 'No email available'}</dd>
37
+ </div>
38
+ </dl>
39
+ </div>
40
+ </div>
41
+ );
42
+ }
@@ -0,0 +1,44 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { ExternalAuth } from '../services/externalAuth';
4
+ import { useAuth } from '../AuthContext';
5
+
6
+ /**
7
+ * Handles the Facebook login redirect.
8
+ * Facebook uses a popup (JS SDK) instead of PKCE, so this page reads the profile
9
+ * that was stored in sessionStorage during the FB.login() callback.
10
+ */
11
+ export default function RedirectFacebook() {
12
+ const { loginWith } = useAuth();
13
+ const navigate = useNavigate();
14
+ const [error, setError] = useState('');
15
+
16
+ useEffect(() => {
17
+ (async () => {
18
+ try {
19
+ const externalUser = await ExternalAuth.handleRedirect('facebook');
20
+ const err = await loginWith(externalUser);
21
+ if (err) {
22
+ setError(err);
23
+ } else {
24
+ navigate('/auth/profile');
25
+ }
26
+ } catch (e: any) {
27
+ console.error('Facebook sign-in failed:', e);
28
+ setError('Facebook sign-in failed. Please try again.');
29
+ }
30
+ })();
31
+ // eslint-disable-next-line react-hooks/exhaustive-deps
32
+ }, []);
33
+
34
+ if (error) {
35
+ return (
36
+ <div style={{ padding: '2rem', color: '#d32f2f' }}>
37
+ <p>{error}</p>
38
+ <button onClick={() => navigate('/')}>Back to Home</button>
39
+ </div>
40
+ );
41
+ }
42
+
43
+ return <p style={{ padding: '2rem' }}>Signing in with Facebook…</p>;
44
+ }