Skip to content

Commit 0abf66f

Browse files
georgianastasovivanvpetrovCopilotkdinevCopilot
authored
fix(cli:templates): improve side nav auth template UI and selection flow (#1725)
* feat(igx-templates): add side-nav-mini project template with collapsible mini nav * fix(cli:templates): align angular side-nav template layout * fix(cli:templates): align web components side-nav template layout * fix(cli:templates): align react side-nav template layout * fix(templates): add navigation role to resources container Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * feat(templates): add side-nav-mini template (collapsible mini navigation) for React and WC * fix(cli:templates): align side-nav resources layout responsiveness * feat(templates): side-nav-mini template for React and WC now match the improved defalt side-nav * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * feat(templates): fix side-nav-mini animation * fix(cli:templates): update icons * feat(templates): fix side-nav-mini icon size * feat(templates): fix some code according to base PR * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix(cli:templates): polish angular side nav auth template UI * feat(cli:templates): add auth selection step for navigation templates * test(cli:templates): restore register selector in auth template Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix(cli:templates): handle missing project template error and improve auth variant selection * chore(templates): name option changed for improved readability * fix(styles): change text to uppercase for submit buttons * fix(templates): css variables redundancy fixed * fix(templates): css variables fixed * feat(templates): add auth template option for React and WC * feat(templates): auth template for React and WC now working * feat(templates): visual improvements * feat(auth): update styles for react and web components auth templates * feat(templates): password clear after login * fix(auth): avoid storing plaintext fake passwords * feat(templates): implement external OAuth Google, Facebook, and Microsoft for React and WC * refactor(auth): update plain element with igniteui components * test(templates): update prompt session specs for auth template * refactor(auth): remove unnecessary border from login dialog and update dialog behavior * feat(templates): converted components to igniteui. Fixed differences * refactor(auth): update login and registration components styling * refactor(auth): update social login buttons for consistency * fix(templates): inputs for Reacts mini side nav * feat(auth): improve OAuth providers and enhance authentication flow * fix(auth): add OidcSecurityService import for authentication functionality * refactor(auth): update external login model and enhance user session management * refactor(auth): unify import statements and type usage in Login and Register * feat(auth): enhance user profile handling in fake backend * fix(templates): login button text * fix(templates): font set to WC template * fix(templates): menu items and theme fix * fix(templates): core rewiew and polish --------- Co-authored-by: INFRAGISTICS\IPetrov <IPetrov@infragistics.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Konstantin Dinev <kdinev@bellumgens.com> Co-authored-by: ivanvpetrov <110455887+ivanvpetrov@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent b70f03a commit 0abf66f

112 files changed

Lines changed: 4549 additions & 156 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Ignite UI for React</title>
8+
<link href="https://fonts.googleapis.com/css?family=Titillium+Web:300,400,600,700" rel="stylesheet">
9+
<link rel="stylesheet" href="./styles.css">
10+
<!-- Facebook JS SDK — required only when facebook is configured in external-auth-config.ts.
11+
Remove this script if you are not using Facebook login. -->
12+
<script async defer crossorigin="anonymous"
13+
src="https://connect.facebook.net/en_US/sdk.js"></script>
14+
</head>
15+
<body>
16+
<div id="root"></div>
17+
</body>
18+
<script type="module" src="/src/main.tsx"></script>
19+
</html>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import Home from './home/home';
2+
import Profile from './authentication/pages/Profile';
3+
import RedirectGoogle from './authentication/pages/RedirectGoogle';
4+
import RedirectMicrosoft from './authentication/pages/RedirectMicrosoft';
5+
import RedirectFacebook from './authentication/pages/RedirectFacebook';
6+
import { AuthGuard } from './authentication/AuthGuard';
7+
8+
export const routes = [
9+
{ path: '/', element: <Home />, text: 'Home', icon: 'home' },
10+
{
11+
path: '/auth/profile',
12+
element: (
13+
<AuthGuard>
14+
<Profile />
15+
</AuthGuard>
16+
),
17+
text: 'Profile',
18+
icon: 'account_circle',
19+
requiresAuth: true
20+
},
21+
{ path: '/auth/redirect-google', element: <RedirectGoogle /> },
22+
{ path: '/auth/redirect-microsoft', element: <RedirectMicrosoft /> },
23+
{ path: '/auth/redirect-facebook', element: <RedirectFacebook /> },
24+
];
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
.app {
2+
display: flex;
3+
flex-flow: column nowrap;
4+
height: 100%;
5+
overflow: hidden;
6+
}
7+
8+
.app__navbar {
9+
display: flex;
10+
align-items: center;
11+
flex: 0 0 auto;
12+
height: 56px;
13+
padding: 0 16px;
14+
background: #239ef0;
15+
box-shadow: 0 2px 4px rgba(0, 0, 0, .24);
16+
box-sizing: border-box;
17+
position: relative;
18+
z-index: 10;
19+
}
20+
21+
.app__navbar-spacer {
22+
flex: 1 1 auto;
23+
}
24+
25+
.app__title {
26+
margin: 0 0 0 16px;
27+
font-size: 1.25rem;
28+
font-weight: 600;
29+
line-height: 1;
30+
color: #000;
31+
}
32+
33+
.app__menu-button {
34+
display: inline-flex;
35+
align-items: center;
36+
justify-content: center;
37+
width: 40px;
38+
height: 40px;
39+
padding: 0;
40+
color: #000;
41+
border: 0;
42+
background: transparent;
43+
cursor: pointer;
44+
}
45+
46+
.app__menu-button igc-icon {
47+
font-size: 24px;
48+
}
49+
50+
.app__body {
51+
display: flex;
52+
flex: 1 1 auto;
53+
min-height: 0;
54+
}
55+
56+
.app__drawer {
57+
flex: 0 0 auto;
58+
height: 100%;
59+
--menu-full-width: 280px;
60+
}
61+
62+
igc-nav-drawer-item::part(base) {
63+
min-height: 48px;
64+
color: #2d2d2d;
65+
}
66+
67+
igc-nav-drawer-item[active]::part(base) {
68+
background: #e0f2ff;
69+
color: #0075d2;
70+
}
71+
72+
igc-nav-drawer-item[active] igc-icon {
73+
color: #0075d2;
74+
}
75+
76+
.app__content {
77+
flex: 1 1 auto;
78+
display: flex;
79+
flex-flow: row nowrap;
80+
justify-content: center;
81+
align-items: stretch;
82+
min-width: 0;
83+
overflow: auto;
84+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { useEffect, useMemo, useState } from "react";
2+
import { Outlet, useLocation, useNavigate } from "react-router-dom";
3+
import {
4+
IgrIcon,
5+
IgrNavDrawer,
6+
IgrNavDrawerItem,
7+
registerIcon,
8+
} from "igniteui-react";
9+
import { configureTheme } from "igniteui-webcomponents";
10+
import { AuthProvider, useAuth } from "./authentication/AuthContext";
11+
import { LoginBar } from "./authentication/components/LoginBar";
12+
import { routes } from "./app-routes";
13+
import "igniteui-webcomponents/themes/light/material.css";
14+
import "./app.css";
15+
16+
configureTheme('material', 'light');
17+
18+
const materialIcons = [
19+
['home', 'action/svg/production/ic_home_24px.svg'],
20+
['menu', 'navigation/svg/production/ic_menu_24px.svg'],
21+
['apps', 'navigation/svg/production/ic_apps_24px.svg'],
22+
['code', 'action/svg/production/ic_code_24px.svg'],
23+
['build', 'action/svg/production/ic_build_24px.svg'],
24+
['palette', 'image/svg/production/ic_palette_24px.svg'],
25+
['account_circle', 'action/svg/production/ic_account_circle_24px.svg'],
26+
['lock', 'action/svg/production/ic_lock_24px.svg'],
27+
['assignment_ind', 'action/svg/production/ic_assignment_ind_24px.svg'],
28+
] as const;
29+
30+
materialIcons.forEach(([name, path]) =>
31+
registerIcon(name, `https://unpkg.com/material-design-icons@3.0.1/${path}`, "material")
32+
);
33+
34+
function AppContent() {
35+
const name = "$(name)";
36+
const location = useLocation();
37+
const navigate = useNavigate();
38+
const { currentUser } = useAuth();
39+
const [drawerOpen, setDrawerOpen] = useState(true);
40+
const [drawerPosition, setDrawerPosition] = useState<"relative" | "start">("relative");
41+
42+
const visibleRoutes = useMemo(() => {
43+
return routes.filter((route) => {
44+
if (!route.path || !route.text) return false;
45+
if ((route as any).requiresAuth && !currentUser) return false;
46+
return true;
47+
});
48+
}, [currentUser]);
49+
50+
useEffect(() => {
51+
const mediaQuery = window.matchMedia("(min-width: 1025px)");
52+
const updateDrawerState = () => {
53+
setDrawerOpen(mediaQuery.matches);
54+
setDrawerPosition(mediaQuery.matches ? "relative" : "start");
55+
};
56+
57+
updateDrawerState();
58+
mediaQuery.addEventListener("change", updateDrawerState);
59+
60+
return () => mediaQuery.removeEventListener("change", updateDrawerState);
61+
}, []);
62+
63+
const handleRouteClick = (path: string) => {
64+
navigate(path);
65+
66+
if (window.matchMedia("(max-width: 1024px)").matches) {
67+
setDrawerOpen(false);
68+
}
69+
};
70+
71+
return (
72+
<div className="app">
73+
<header className="app__navbar">
74+
<button
75+
className="app__menu-button"
76+
type="button"
77+
aria-label="Toggle navigation"
78+
onClick={() => setDrawerOpen((open) => !open)}
79+
>
80+
<IgrIcon name="menu" collection="material" />
81+
</button>
82+
<h1 className="app__title">{name}</h1>
83+
<div className="app__navbar-spacer" />
84+
<LoginBar />
85+
</header>
86+
<div className="app__body">
87+
<IgrNavDrawer
88+
className="app__drawer"
89+
open={drawerOpen}
90+
position={drawerPosition}
91+
>
92+
{visibleRoutes.map((route) => (
93+
<IgrNavDrawerItem
94+
key={route.path}
95+
active={location.pathname === route.path}
96+
onClick={() => handleRouteClick(route.path)}
97+
>
98+
<IgrIcon
99+
slot="icon"
100+
name={route.icon || "home"}
101+
collection="material"
102+
style={{
103+
color: location.pathname === route.path ? "#0075D2" : "#2d2d2d",
104+
}}
105+
/>
106+
<span slot="content">{route.text}</span>
107+
</IgrNavDrawerItem>
108+
))}
109+
</IgrNavDrawer>
110+
<main className="app__content">
111+
<Outlet />
112+
</main>
113+
</div>
114+
</div>
115+
);
116+
}
117+
118+
export default function App() {
119+
return (
120+
<AuthProvider>
121+
<AppContent />
122+
</AuthProvider>
123+
);
124+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { createContext, useContext, useState, useCallback, type ReactNode } from 'react';
2+
import type { User } from './models/user';
3+
import type { Login } from './models/login';
4+
import type { RegisterInfo } from './models/register-info';
5+
import type { ExternalLogin } from './models/external-login';
6+
import { Authentication } from './services/authentication';
7+
import { UserStore } from './services/userStore';
8+
import { ExternalAuth } from './services/externalAuth';
9+
10+
interface AuthContextType {
11+
currentUser: User | null;
12+
initials: string | null;
13+
login: (data: Login) => Promise<string | null>;
14+
register: (data: RegisterInfo) => Promise<string | null>;
15+
loginWith: (data: ExternalLogin) => Promise<string | null>;
16+
logout: () => void;
17+
}
18+
19+
const AuthContext = createContext<AuthContextType | null>(null);
20+
21+
export function AuthProvider({ children }: { children: ReactNode }) {
22+
const [currentUser, setCurrentUser] = useState<User | null>(() => UserStore.getUser());
23+
24+
const initials = currentUser ? UserStore.getInitials(currentUser) : null;
25+
26+
const login = useCallback(async (data: Login): Promise<string | null> => {
27+
const result = await Authentication.login(data);
28+
if (result.user) {
29+
UserStore.setUser(result.user);
30+
setCurrentUser(result.user);
31+
return null;
32+
}
33+
return result.error ?? 'Login failed';
34+
}, []);
35+
36+
const register = useCallback(async (data: RegisterInfo): Promise<string | null> => {
37+
const result = await Authentication.register(data);
38+
if (result.user) {
39+
UserStore.setUser(result.user);
40+
setCurrentUser(result.user);
41+
return null;
42+
}
43+
return result.error ?? 'Registration failed';
44+
}, []);
45+
46+
const loginWith = useCallback(async (data: ExternalLogin): Promise<string | null> => {
47+
const result = await Authentication.loginWith(data);
48+
if (result.user) {
49+
UserStore.setUser(result.user);
50+
setCurrentUser(result.user);
51+
return null;
52+
}
53+
return result.error ?? 'Social login failed';
54+
}, []);
55+
56+
const logout = useCallback(() => {
57+
ExternalAuth.logout();
58+
UserStore.clearUser();
59+
setCurrentUser(null);
60+
}, []);
61+
62+
return (
63+
<AuthContext.Provider value={{ currentUser, initials, login, register, loginWith, logout }}>
64+
{children}
65+
</AuthContext.Provider>
66+
);
67+
}
68+
69+
export function useAuth() {
70+
const ctx = useContext(AuthContext);
71+
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
72+
return ctx;
73+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Navigate, useLocation } from 'react-router-dom';
2+
import { useAuth } from './AuthContext';
3+
import type { ReactNode } from 'react';
4+
5+
export function AuthGuard({ children }: { children: ReactNode }) {
6+
const { currentUser } = useAuth();
7+
const location = useLocation();
8+
9+
if (!currentUser) {
10+
return <Navigate to="/" state={{ returnUrl: location.pathname }} replace />;
11+
}
12+
13+
return <>{children}</>;
14+
}

0 commit comments

Comments
 (0)