igniteui-cli 15.2.2 → 15.3.1-beta.1

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 (134) hide show
  1. package/lib/commands/build.js +7 -12
  2. package/package.json +7 -7
  3. package/templates/blazor/igb/projects/ai-config/files/skills/AGENTS.md +0 -5
  4. package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-components/SKILL.md +2 -0
  5. package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-components/references/charts.md +7 -35
  6. package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-components/references/data-display.md +0 -54
  7. package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-components/references/feedback.md +0 -38
  8. package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-components/references/form-controls.md +0 -68
  9. package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-components/references/layout-manager.md +1 -124
  10. package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-components/references/layout.md +0 -62
  11. package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-grids/references/grid-migration.md +322 -0
  12. package/templates/blazor/igb/projects/ai-config/files/skills/igniteui-blazor-theming/SKILL.md +1 -1
  13. package/templates/react/igr-ts/projects/_base/files/package.json +1 -0
  14. package/templates/react/igr-ts/projects/_base/files/src/app/app.tsx +4 -2
  15. package/templates/react/igr-ts/projects/_base/files/src/setupTests.ts +12 -0
  16. package/templates/react/igr-ts/projects/_base/files/styles.css +6 -0
  17. package/templates/react/igr-ts/projects/_base_with_home/files/index.html +2 -1
  18. package/templates/react/igr-ts/projects/_base_with_home/files/src/app/home/home.tsx +60 -10
  19. package/templates/react/igr-ts/projects/_base_with_home/files/src/app/home/style.module.css +79 -20
  20. package/templates/react/igr-ts/projects/ai-config/files/skills/grid-lite-to-igr-grid-migration/SKILL.md +274 -0
  21. package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-components/SKILL.md +0 -8
  22. package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-components/reference/CHARTS-GRIDS.md +6 -36
  23. package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-components/reference/COMPONENT-CATALOGUE.md +8 -142
  24. package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-components/reference/EVENT-HANDLING.md +2 -0
  25. package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-customize-theme/SKILL.md +7 -14
  26. package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-customize-theme/reference/CSS-THEMING.md +2 -0
  27. package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-customize-theme/reference/MCP-SERVER.md +0 -8
  28. package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-generate-from-image-design/SKILL.md +2 -2
  29. package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-generate-from-image-design/reference/component-mapping.md +60 -74
  30. package/templates/react/igr-ts/projects/ai-config/files/skills/igniteui-react-generate-from-image-design/reference/gotchas.md +2 -2
  31. package/templates/react/igr-ts/projects/empty/index.js +2 -2
  32. package/templates/react/igr-ts/projects/side-nav/files/src/app/app-routes.tsx +5 -0
  33. package/templates/react/igr-ts/projects/side-nav/files/src/app/app.css +82 -0
  34. package/templates/react/igr-ts/projects/side-nav/files/src/app/app.tsx +104 -0
  35. package/templates/react/igr-ts/projects/side-nav/files/src/app/home/home.tsx +69 -0
  36. package/templates/react/igr-ts/projects/side-nav/files/src/app/home/style.module.css +105 -0
  37. package/templates/react/igr-ts/projects/{top-nav → side-nav}/index.d.ts +2 -2
  38. package/templates/react/igr-ts/projects/{top-nav → side-nav}/index.js +7 -7
  39. package/templates/react/igr-ts/projects/side-nav-auth/files/index.html +19 -0
  40. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/app-routes.tsx +24 -0
  41. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/app.css +84 -0
  42. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/app.tsx +124 -0
  43. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/AuthContext.tsx +73 -0
  44. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/AuthGuard.tsx +14 -0
  45. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/Login.module.css +93 -0
  46. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/Login.tsx +69 -0
  47. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/LoginBar.module.css +42 -0
  48. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/LoginBar.tsx +44 -0
  49. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/LoginDialog.module.css +14 -0
  50. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/LoginDialog.tsx +49 -0
  51. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/Register.module.css +74 -0
  52. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/components/Register.tsx +67 -0
  53. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/models/external-login.ts +10 -0
  54. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/models/login.ts +4 -0
  55. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/models/register-info.ts +6 -0
  56. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/models/user.ts +19 -0
  57. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/pages/Profile.module.css +87 -0
  58. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/pages/Profile.tsx +42 -0
  59. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/pages/RedirectFacebook.tsx +44 -0
  60. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/pages/RedirectGoogle.tsx +40 -0
  61. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/pages/RedirectMicrosoft.tsx +40 -0
  62. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/services/authentication.ts +37 -0
  63. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/services/external-auth-config.ts +44 -0
  64. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/services/externalAuth.ts +272 -0
  65. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/services/fakeBackend.ts +88 -0
  66. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/services/jwtUtil.ts +10 -0
  67. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/services/pkce.ts +29 -0
  68. package/templates/react/igr-ts/projects/side-nav-auth/files/src/app/authentication/services/userStore.ts +39 -0
  69. package/templates/react/igr-ts/projects/side-nav-auth/index.d.ts +15 -0
  70. package/templates/react/igr-ts/projects/side-nav-auth/index.js +46 -0
  71. package/templates/react/igr-ts/projects/side-nav-mini/files/src/app/app-routes.tsx +5 -0
  72. package/templates/react/igr-ts/projects/side-nav-mini/files/src/app/app.css +109 -0
  73. package/templates/react/igr-ts/projects/side-nav-mini/files/src/app/app.test.tsx +20 -0
  74. package/templates/react/igr-ts/projects/side-nav-mini/files/src/app/app.tsx +81 -0
  75. package/templates/react/igr-ts/projects/side-nav-mini/files/src/app/home/home.tsx +69 -0
  76. package/templates/react/igr-ts/projects/side-nav-mini/files/src/app/home/style.module.css +105 -0
  77. package/templates/react/igr-ts/projects/side-nav-mini/index.d.ts +15 -0
  78. package/templates/react/igr-ts/projects/side-nav-mini/index.js +46 -0
  79. package/templates/react/igr-ts/projects/side-nav-mini-auth/files/src/app/app.css +106 -0
  80. package/templates/react/igr-ts/projects/side-nav-mini-auth/files/src/app/app.tsx +101 -0
  81. package/templates/react/igr-ts/projects/side-nav-mini-auth/index.d.ts +15 -0
  82. package/templates/react/igr-ts/projects/side-nav-mini-auth/index.js +50 -0
  83. package/templates/webcomponents/igc-ts/projects/_base/files/src/app/app.ts +6 -1
  84. package/templates/webcomponents/igc-ts/projects/_base/files/styles.css +1 -0
  85. package/templates/webcomponents/igc-ts/projects/_base_with_home/files/index.html +2 -0
  86. package/templates/webcomponents/igc-ts/projects/_base_with_home/files/src/app/home/home.ts +103 -9
  87. package/templates/webcomponents/igc-ts/projects/_base_with_home/files/src/assets/wc.png +0 -0
  88. package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-choose-components/SKILL.md +122 -160
  89. package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-customize-component-theme/SKILL.md +83 -311
  90. package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-customize-component-theme/references/mcp-setup.md +69 -0
  91. package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-generate-from-image-design/SKILL.md +4 -1
  92. package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-generate-from-image-design/references/component-mapping.md +60 -61
  93. package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-generate-from-image-design/references/gotchas.md +15 -11
  94. package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-migrate-grid-lite-to-premium/SKILL.md +446 -0
  95. package/templates/webcomponents/igc-ts/projects/ai-config/files/skills/igniteui-wc-optimize-bundle-size/SKILL.md +23 -274
  96. package/templates/webcomponents/igc-ts/projects/empty/index.js +1 -1
  97. package/templates/webcomponents/igc-ts/projects/side-nav/files/index.html +21 -0
  98. package/templates/webcomponents/igc-ts/projects/side-nav/files/src/app/app-routing.ts +9 -0
  99. package/templates/webcomponents/igc-ts/projects/side-nav/files/src/app/app.ts +192 -22
  100. package/templates/webcomponents/igc-ts/projects/side-nav/files/src/app/home/home.ts +175 -0
  101. package/templates/webcomponents/igc-ts/projects/side-nav/index.js +1 -1
  102. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/index.html +25 -0
  103. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/app-routing.ts +37 -0
  104. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/app.ts +251 -0
  105. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/login-bar/login-bar.ts +124 -0
  106. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/login-dialog/login-dialog.ts +253 -0
  107. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/models/external-login.ts +10 -0
  108. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/models/login.ts +4 -0
  109. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/models/register-info.ts +6 -0
  110. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/models/user.ts +19 -0
  111. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/services/authentication.ts +37 -0
  112. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/services/external-auth-config.ts +44 -0
  113. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/services/externalAuth.ts +272 -0
  114. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/services/fakeBackend.ts +88 -0
  115. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/services/jwtUtil.ts +10 -0
  116. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/services/pkce.ts +29 -0
  117. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/authentication/services/userStore.ts +39 -0
  118. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/profile/profile.ts +142 -0
  119. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/redirect/redirect-facebook.ts +57 -0
  120. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/redirect/redirect-google.ts +53 -0
  121. package/templates/webcomponents/igc-ts/projects/side-nav-auth/files/src/app/redirect/redirect-microsoft.ts +53 -0
  122. package/templates/webcomponents/igc-ts/projects/side-nav-auth/index.d.ts +15 -0
  123. package/templates/webcomponents/igc-ts/projects/side-nav-auth/index.js +46 -0
  124. package/templates/webcomponents/igc-ts/projects/side-nav-mini/files/src/app/app-routing.ts +13 -0
  125. package/templates/webcomponents/igc-ts/projects/side-nav-mini/files/src/app/app.ts +238 -0
  126. package/templates/webcomponents/igc-ts/projects/side-nav-mini/index.d.ts +14 -0
  127. package/templates/webcomponents/igc-ts/projects/side-nav-mini/index.js +45 -0
  128. package/templates/webcomponents/igc-ts/projects/side-nav-mini-auth/files/src/app/app.ts +258 -0
  129. package/templates/webcomponents/igc-ts/projects/side-nav-mini-auth/index.d.ts +15 -0
  130. package/templates/webcomponents/igc-ts/projects/side-nav-mini-auth/index.js +50 -0
  131. package/templates/react/igr-ts/projects/top-nav/files/src/app/app.css +0 -62
  132. package/templates/react/igr-ts/projects/top-nav/files/src/app/app.tsx +0 -18
  133. package/templates/react/igr-ts/projects/top-nav/files/src/components/navigation-header/index.tsx +0 -19
  134. /package/templates/react/igr-ts/projects/{top-nav → side-nav}/files/src/app/app.test.tsx +0 -0
@@ -0,0 +1,88 @@
1
+ // ⚠ DEVELOPMENT ONLY — simulates POST /login, /register, /extlogin using localStorage.
2
+ // Before going to production: remove this interceptor and replace with calls to your real API.
3
+ import type { Login } from '../models/login.js';
4
+ import type { RegisterInfo } from '../models/register-info.js';
5
+ import type { ExternalLogin } from '../models/external-login.js';
6
+
7
+ const USERS_KEY = '_fake_users';
8
+
9
+ interface StoredUser {
10
+ given_name: string;
11
+ family_name: string;
12
+ email: string;
13
+ passwordHash: string;
14
+ externalId?: string;
15
+ }
16
+
17
+ function getUsers(): StoredUser[] {
18
+ try {
19
+ return JSON.parse(localStorage.getItem(USERS_KEY) ?? '[]');
20
+ } catch {
21
+ return [];
22
+ }
23
+ }
24
+
25
+ function saveUsers(users: StoredUser[]): void {
26
+ localStorage.setItem(USERS_KEY, JSON.stringify(users));
27
+ }
28
+
29
+ async function hashPassword(password: string): Promise<string> {
30
+ const data = new TextEncoder().encode(password);
31
+ const digest = await crypto.subtle.digest('SHA-256', data);
32
+ return Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, '0')).join('');
33
+ }
34
+
35
+ function makeJwt(payload: object): string {
36
+ const header = btoa(JSON.stringify({ alg: 'none', typ: 'JWT' }));
37
+ const body = btoa(JSON.stringify({ exp: Date.now() / 1000 + 3600, ...payload }));
38
+ return `${header}.${body}.`;
39
+ }
40
+
41
+ export async function fakeLogin(data: Login): Promise<string> {
42
+ const users = getUsers();
43
+ const passwordHash = await hashPassword(data.password);
44
+ const user = users.find(u => u.email === data.email && u.passwordHash === passwordHash);
45
+ if (!user) {
46
+ throw new Error('Invalid email or password.');
47
+ }
48
+ return makeJwt({ name: `${user.given_name} ${user.family_name}`, given_name: user.given_name, family_name: user.family_name, email: user.email });
49
+ }
50
+
51
+ export async function fakeRegister(data: RegisterInfo): Promise<string> {
52
+ const users = getUsers();
53
+ if (users.find(u => u.email === data.email)) {
54
+ throw new Error('An account with this email already exists.');
55
+ }
56
+ const newUser: StoredUser = {
57
+ given_name: data.given_name,
58
+ family_name: data.family_name,
59
+ email: data.email,
60
+ passwordHash: await hashPassword(data.password)
61
+ };
62
+ saveUsers([...users, newUser]);
63
+ return makeJwt({ name: `${data.given_name} ${data.family_name}`, given_name: data.given_name, family_name: data.family_name, email: data.email });
64
+ }
65
+ /** Upsert a user from a social (external) auth provider and return a JWT. */
66
+ export function fakeExtLogin(data: ExternalLogin): string {
67
+ const users = getUsers();
68
+ const existing = users.find(u => u.email === data.email && data.email != null)
69
+ ?? users.find(u => u.externalId === data.id);
70
+ const given_name = data.given_name ?? data.name?.split(' ')[0] ?? '';
71
+ const family_name = data.family_name ?? data.name?.split(' ').slice(1).join(' ') ?? '';
72
+ // Resolve email: prefer what the provider returned, fall back to what we stored previously.
73
+ const email = data.email ?? existing?.email;
74
+ if (existing) {
75
+ // Update profile fields from provider (name/picture may change).
76
+ // Also store externalId if this user was originally created by email (first social login).
77
+ existing.given_name = given_name;
78
+ existing.family_name = family_name;
79
+ if (!existing.externalId) existing.externalId = data.id;
80
+ saveUsers(users);
81
+ } else {
82
+ if (!email) {
83
+ throw new Error('Cannot create an account without an email address.');
84
+ }
85
+ saveUsers([...users, { given_name, family_name, email, passwordHash: '', externalId: data.id }]);
86
+ }
87
+ return makeJwt({ name: data.name, given_name, family_name, email, picture: data.picture });
88
+ }
@@ -0,0 +1,10 @@
1
+ import type { UserJWT } from '../models/user.js';
2
+
3
+ /** Parse the payload of a JWT string into a UserJWT object. */
4
+ export function parseUser(token: string): UserJWT & { token: string } {
5
+ const base64url = token.split('.')[1];
6
+ const base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');
7
+ const padded = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, '=');
8
+ const decoded = JSON.parse(atob(padded));
9
+ return { ...decoded, token };
10
+ }
@@ -0,0 +1,29 @@
1
+ // PKCE (Proof Key for Code Exchange) utilities for OAuth 2.0 Authorization Code Flow.
2
+ // https://tools.ietf.org/html/rfc7636
3
+
4
+ function base64UrlEncode(bytes: Uint8Array): string {
5
+ return btoa(String.fromCharCode(...bytes))
6
+ .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
7
+ }
8
+
9
+ /** Generate a cryptographically random PKCE code verifier (43–128 chars, URL-safe). */
10
+ export function generateCodeVerifier(): string {
11
+ const bytes = crypto.getRandomValues(new Uint8Array(32));
12
+ return base64UrlEncode(bytes);
13
+ }
14
+
15
+ /** Compute the S256 code challenge from a code verifier. */
16
+ export async function generateCodeChallenge(verifier: string): Promise<string> {
17
+ const data = new TextEncoder().encode(verifier);
18
+ const digest = await crypto.subtle.digest('SHA-256', data);
19
+ return base64UrlEncode(new Uint8Array(digest));
20
+ }
21
+
22
+ /** Build a URL with query parameters from a plain object. */
23
+ export function buildAuthUrl(endpoint: string, params: Record<string, string>): string {
24
+ const url = new URL(endpoint);
25
+ for (const [k, v] of Object.entries(params)) {
26
+ url.searchParams.set(k, v);
27
+ }
28
+ return url.toString();
29
+ }
@@ -0,0 +1,39 @@
1
+ import type { User } from '../models/user.js';
2
+
3
+ const USER_KEY = 'currentUser';
4
+
5
+ /**
6
+ * Simple localStorage-backed user store.
7
+ *
8
+ * NOTE: Storing the full user object in localStorage can be susceptible to XSS attacks.
9
+ * Consider additional security measures before going to production.
10
+ */
11
+ export const UserStore = {
12
+ getUser(): User | null {
13
+ try {
14
+ const raw = localStorage.getItem(USER_KEY);
15
+ if (!raw) return null;
16
+ const parsed: User = JSON.parse(raw);
17
+ // Discard expired tokens so a stale session is never silently restored.
18
+ if (parsed.exp && Date.now() / 1000 > parsed.exp) {
19
+ localStorage.removeItem(USER_KEY);
20
+ return null;
21
+ }
22
+ return parsed;
23
+ } catch {
24
+ return null;
25
+ }
26
+ },
27
+
28
+ setUser(user: User): void {
29
+ localStorage.setItem(USER_KEY, JSON.stringify(user));
30
+ },
31
+
32
+ clearUser(): void {
33
+ localStorage.removeItem(USER_KEY);
34
+ },
35
+
36
+ getInitials(user: User): string {
37
+ return (user.given_name.charAt(0) + (user.family_name?.charAt(0) ?? '')).toUpperCase();
38
+ }
39
+ };
@@ -0,0 +1,142 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { customElement } from 'lit/decorators.js';
3
+ import { defineComponents, IgcAvatarComponent } from 'igniteui-webcomponents';
4
+ import { UserStore } from '../authentication/services/userStore.js';
5
+
6
+ defineComponents(IgcAvatarComponent);
7
+
8
+ @customElement('app-profile')
9
+ export default class ProfilePage extends LitElement {
10
+ static styles = css`
11
+ :host {
12
+ display: flex;
13
+ justify-content: center;
14
+ padding: 48px 16px;
15
+ width: 100%;
16
+ box-sizing: border-box;
17
+ }
18
+
19
+ .card {
20
+ align-self: flex-start;
21
+ width: 100%;
22
+ max-width: 640px;
23
+ background: #fff;
24
+ border-radius: 8px;
25
+ box-shadow: 0 2px 12px rgba(0, 0, 0, .08);
26
+ padding: 32px;
27
+ box-sizing: border-box;
28
+ }
29
+
30
+ .header {
31
+ display: flex;
32
+ align-items: center;
33
+ gap: 20px;
34
+ padding-bottom: 24px;
35
+ border-bottom: 1px solid #d7eaf8;
36
+ }
37
+
38
+ .avatar {
39
+ flex: 0 0 auto;
40
+ --ig-avatar-background: #e0f2ff;
41
+ --ig-avatar-color: #0075d2;
42
+ --ig-avatar-size: 4rem;
43
+ }
44
+
45
+ .intro {
46
+ min-width: 0;
47
+ }
48
+
49
+ .status {
50
+ margin: 0 0 4px;
51
+ color: #000;
52
+ font-size: .875rem;
53
+ font-weight: 700;
54
+ text-transform: uppercase;
55
+ }
56
+
57
+ .name {
58
+ margin: 0;
59
+ overflow-wrap: anywhere;
60
+ color: #09f;
61
+ font-size: 2rem;
62
+ font-weight: 600;
63
+ line-height: 1.2;
64
+ }
65
+
66
+ .description {
67
+ margin: 8px 0 0;
68
+ color: #000;
69
+ font-size: 1rem;
70
+ line-height: 1.5;
71
+ }
72
+
73
+ .details {
74
+ margin: 28px 0 0;
75
+ padding: 0;
76
+ }
77
+
78
+ .row {
79
+ display: grid;
80
+ grid-template-columns: 140px minmax(0, 1fr);
81
+ gap: 24px;
82
+ padding: 14px 0;
83
+ border-bottom: 1px solid #eef3f7;
84
+ }
85
+
86
+ dt {
87
+ color: rgba(0, 0, 0, .62);
88
+ font-size: .875rem;
89
+ font-weight: 600;
90
+ margin: 0;
91
+ }
92
+
93
+ dd {
94
+ margin: 0;
95
+ font-size: 1rem;
96
+ color: #2d2d2d;
97
+ }
98
+ `;
99
+
100
+ render() {
101
+ const user = UserStore.getUser();
102
+ const initials = user ? UserStore.getInitials(user) : 'U';
103
+
104
+ return html`
105
+ <div class="card">
106
+ <div class="header">
107
+ <igc-avatar
108
+ class="avatar"
109
+ shape="circle"
110
+ src=${user?.picture ?? ''}
111
+ initials=${user?.picture ? '' : initials}
112
+ ></igc-avatar>
113
+ <div class="intro">
114
+ <p class="status">Signed in</p>
115
+ <h1 class="name">${user?.name || 'Your profile'}</h1>
116
+ <p class="description">Your account details are available on this protected route.</p>
117
+ </div>
118
+ </div>
119
+ <dl class="details">
120
+ <div class="row">
121
+ <dt>First name</dt>
122
+ <dd>${user?.given_name || 'Not provided'}</dd>
123
+ </div>
124
+ <div class="row">
125
+ <dt>Last name</dt>
126
+ <dd>${user?.family_name || 'Not provided'}</dd>
127
+ </div>
128
+ <div class="row">
129
+ <dt>Email</dt>
130
+ <dd>${user?.email || 'No email available'}</dd>
131
+ </div>
132
+ </dl>
133
+ </div>
134
+ `;
135
+ }
136
+ }
137
+
138
+ declare global {
139
+ interface HTMLElementTagNameMap {
140
+ 'app-profile': ProfilePage;
141
+ }
142
+ }
@@ -0,0 +1,57 @@
1
+ import { LitElement, html } from 'lit';
2
+ import { customElement, state } from 'lit/decorators.js';
3
+ import { Router } from '@vaadin/router';
4
+ import { ExternalAuth } from '../authentication/services/externalAuth.js';
5
+ import { Authentication } from '../authentication/services/authentication.js';
6
+ import { UserStore } from '../authentication/services/userStore.js';
7
+ import type { User } from '../authentication/models/user.js';
8
+
9
+ /**
10
+ * Handles the Facebook login redirect.
11
+ * Facebook uses a popup (JS SDK) instead of PKCE, so this page reads the profile
12
+ * that was stored in sessionStorage during the FB.login() callback.
13
+ */
14
+ @customElement('app-redirect-facebook')
15
+ export class RedirectFacebookElement extends LitElement {
16
+ @state() private error = '';
17
+
18
+ connectedCallback() {
19
+ super.connectedCallback();
20
+ this._handleRedirect();
21
+ }
22
+
23
+ private async _handleRedirect() {
24
+ try {
25
+ const externalUser = await ExternalAuth.handleRedirect('facebook');
26
+ const result = await Authentication.loginWith(externalUser);
27
+ if (result.user) {
28
+ UserStore.setUser(result.user as User);
29
+ this.dispatchEvent(new CustomEvent('auth-change', { bubbles: true, composed: true }));
30
+ Router.go('/auth/profile');
31
+ } else {
32
+ this.error = result.error ?? 'Facebook sign-in failed.';
33
+ }
34
+ } catch (e: any) {
35
+ console.error('Facebook sign-in failed:', e);
36
+ this.error = 'Facebook sign-in failed. Please try again.';
37
+ }
38
+ }
39
+
40
+ render() {
41
+ if (this.error) {
42
+ return html`
43
+ <div style="padding:2rem;color:#d32f2f">
44
+ <p>${this.error}</p>
45
+ <button @click=${() => Router.go('/')}>Back to Home</button>
46
+ </div>
47
+ `;
48
+ }
49
+ return html`<p style="padding:2rem">Signing in with Facebook…</p>`;
50
+ }
51
+ }
52
+
53
+ declare global {
54
+ interface HTMLElementTagNameMap {
55
+ 'app-redirect-facebook': RedirectFacebookElement;
56
+ }
57
+ }
@@ -0,0 +1,53 @@
1
+ import { LitElement, html } from 'lit';
2
+ import { customElement, state } from 'lit/decorators.js';
3
+ import { Router } from '@vaadin/router';
4
+ import { ExternalAuth } from '../authentication/services/externalAuth.js';
5
+ import { Authentication } from '../authentication/services/authentication.js';
6
+ import { UserStore } from '../authentication/services/userStore.js';
7
+ import type { User } from '../authentication/models/user.js';
8
+
9
+ /** Handles the OAuth redirect from Google. Exchanges the authorization code for a user profile. */
10
+ @customElement('app-redirect-google')
11
+ export class RedirectGoogleElement extends LitElement {
12
+ @state() private error = '';
13
+
14
+ connectedCallback() {
15
+ super.connectedCallback();
16
+ this._handleRedirect();
17
+ }
18
+
19
+ private async _handleRedirect() {
20
+ try {
21
+ const externalUser = await ExternalAuth.handleRedirect('google');
22
+ const result = await Authentication.loginWith(externalUser);
23
+ if (result.user) {
24
+ UserStore.setUser(result.user as User);
25
+ this.dispatchEvent(new CustomEvent('auth-change', { bubbles: true, composed: true }));
26
+ Router.go('/auth/profile');
27
+ } else {
28
+ this.error = result.error ?? 'Google sign-in failed.';
29
+ }
30
+ } catch (e: any) {
31
+ console.error('Google sign-in failed:', e);
32
+ this.error = 'Google sign-in failed. Please try again.';
33
+ }
34
+ }
35
+
36
+ render() {
37
+ if (this.error) {
38
+ return html`
39
+ <div style="padding:2rem;color:#d32f2f">
40
+ <p>${this.error}</p>
41
+ <button @click=${() => Router.go('/')}>Back to Home</button>
42
+ </div>
43
+ `;
44
+ }
45
+ return html`<p style="padding:2rem">Signing in with Google…</p>`;
46
+ }
47
+ }
48
+
49
+ declare global {
50
+ interface HTMLElementTagNameMap {
51
+ 'app-redirect-google': RedirectGoogleElement;
52
+ }
53
+ }
@@ -0,0 +1,53 @@
1
+ import { LitElement, html } from 'lit';
2
+ import { customElement, state } from 'lit/decorators.js';
3
+ import { Router } from '@vaadin/router';
4
+ import { ExternalAuth } from '../authentication/services/externalAuth.js';
5
+ import { Authentication } from '../authentication/services/authentication.js';
6
+ import { UserStore } from '../authentication/services/userStore.js';
7
+ import type { User } from '../authentication/models/user.js';
8
+
9
+ /** Handles the OAuth redirect from Microsoft. Exchanges the authorization code for a user profile. */
10
+ @customElement('app-redirect-microsoft')
11
+ export class RedirectMicrosoftElement extends LitElement {
12
+ @state() private error = '';
13
+
14
+ connectedCallback() {
15
+ super.connectedCallback();
16
+ this._handleRedirect();
17
+ }
18
+
19
+ private async _handleRedirect() {
20
+ try {
21
+ const externalUser = await ExternalAuth.handleRedirect('microsoft');
22
+ const result = await Authentication.loginWith(externalUser);
23
+ if (result.user) {
24
+ UserStore.setUser(result.user as User);
25
+ this.dispatchEvent(new CustomEvent('auth-change', { bubbles: true, composed: true }));
26
+ Router.go('/auth/profile');
27
+ } else {
28
+ this.error = result.error ?? 'Microsoft sign-in failed.';
29
+ }
30
+ } catch (e: any) {
31
+ console.error('Microsoft sign-in failed:', e);
32
+ this.error = 'Microsoft sign-in failed. Please try again.';
33
+ }
34
+ }
35
+
36
+ render() {
37
+ if (this.error) {
38
+ return html`
39
+ <div style="padding:2rem;color:#d32f2f">
40
+ <p>${this.error}</p>
41
+ <button @click=${() => Router.go('/')}>Back to Home</button>
42
+ </div>
43
+ `;
44
+ }
45
+ return html`<p style="padding:2rem">Signing in with Microsoft…</p>`;
46
+ }
47
+ }
48
+
49
+ declare global {
50
+ interface HTMLElementTagNameMap {
51
+ 'app-redirect-microsoft': RedirectMicrosoftElement;
52
+ }
53
+ }
@@ -0,0 +1,15 @@
1
+ import { ProjectTemplate } from "@igniteui/cli-core";
2
+ import { SideNavProject } from "../side-nav";
3
+ export declare class SideNavAuthIgcProject extends SideNavProject implements ProjectTemplate {
4
+ id: string;
5
+ name: string;
6
+ description: string;
7
+ dependencies: string[];
8
+ framework: string;
9
+ projectType: string;
10
+ hasExtraConfiguration: boolean;
11
+ isHidden: boolean;
12
+ get templatePaths(): string[];
13
+ }
14
+ declare const _default: SideNavAuthIgcProject;
15
+ export default _default;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.SideNavAuthIgcProject = void 0;
27
+ const path = __importStar(require("path"));
28
+ const side_nav_1 = require("../side-nav");
29
+ class SideNavAuthIgcProject extends side_nav_1.SideNavProject {
30
+ constructor() {
31
+ super(...arguments);
32
+ this.id = "side-nav-auth";
33
+ this.name = "Side navigation + login";
34
+ this.description = "Side navigation extended with user authentication module";
35
+ this.dependencies = [];
36
+ this.framework = "webcomponents";
37
+ this.projectType = "igc-ts";
38
+ this.hasExtraConfiguration = false;
39
+ this.isHidden = true;
40
+ }
41
+ get templatePaths() {
42
+ return [...super.templatePaths, path.join(__dirname, "files")];
43
+ }
44
+ }
45
+ exports.SideNavAuthIgcProject = SideNavAuthIgcProject;
46
+ exports.default = new SideNavAuthIgcProject();
@@ -0,0 +1,13 @@
1
+ import { type Route } from '@vaadin/router';
2
+ import './home/home.js';
3
+ import './not-found/not-found.js';
4
+
5
+ export interface AppRoute extends Route {
6
+ icon?: string;
7
+ }
8
+
9
+ export const routes: AppRoute[] = [
10
+ { path: '/', component: 'app-home', name: 'Home', icon: 'home' },
11
+ // The fallback route should always be after other alternatives.
12
+ { path: '(.*)', component: 'app-not-found' },
13
+ ];