Skip to content

Commit 92bb0b6

Browse files
Conditional navigation display for Teams vs Browser contexts (#133)
* feat: Hide navigation menu in Microsoft Teams integration - Created useTeamsContext hook to detect Teams environment - Updated FooterComponent to conditionally hide navigation in Teams - Navigation now shows in standalone web app and hides in Teams bot Resolves #124 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: akash2017sky <akash2017sky@users.noreply.github.com> * Updated the Menu display conditionally. left the power by KnowAll ai * Highlighted the selected menu * fixed the code review comments * Code review comments fixed for the header menu navigation * Medium code review comments fixed * Removed wiki handle duplicate navigation --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: akash2017sky <akash2017sky@users.noreply.github.com>
1 parent d6fcc5d commit 92bb0b6

File tree

6 files changed

+210
-12
lines changed

6 files changed

+210
-12
lines changed

tabs/src/components/FooterComponent.module.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@
3636
color: #84cc16;
3737
}
3838

39+
.navigation a.active {
40+
color: #84cc16;
41+
font-weight: 600;
42+
}
43+
3944
.attribution {
4045
display: flex;
4146
align-items: center;

tabs/src/components/FooterComponent.tsx

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,35 @@
11
import React from 'react';
2-
import { Link } from 'react-router-dom';
32
import styles from './FooterComponent.module.css';
43
import { KNOWALL_CONSTANTS } from '../constants/branding';
4+
import { useTeamsAuth } from '../hooks/useTeamsAuth';
5+
import NavigationLinks from './NavigationLinks';
56

67
type FooterComponentProps = {
78
hidden: boolean;
89
};
910

1011
const FooterComponent: React.FC<FooterComponentProps> = ({ hidden }) => {
12+
const { isInTeams } = useTeamsAuth();
1113

1214
if (hidden) {
1315
return null;
14-
}
16+
}
17+
1518
return (
1619
<footer className={styles.footer}>
17-
<div className={styles.navigation}>
18-
<Link to="/feed">Feed</Link>
19-
<Link to="/users">Users</Link>
20-
<Link to="/rewards">Rewards</Link>
21-
<Link to="/wallet">Wallet</Link>
22-
<Link to="/settings">Settings</Link>
23-
</div>
20+
{/* Show navigation links ONLY in Teams context */}
21+
{isInTeams && (
22+
<nav className={styles.navigation} aria-label="Primary navigation">
23+
<NavigationLinks
24+
linkClassName=""
25+
activeLinkClassName={styles.active}
26+
/>
27+
</nav>
28+
)}
29+
{/* Always show Powered by KnowAll AI */}
2430
<div className={styles.attribution}>
2531
<span className={styles.poweredBy}>Powered by</span>
26-
<a
32+
<a
2733
href={KNOWALL_CONSTANTS.website}
2834
target="_blank"
2935
rel="noopener noreferrer"
@@ -34,7 +40,6 @@ const FooterComponent: React.FC<FooterComponentProps> = ({ hidden }) => {
3440
</a>
3541
</div>
3642
</footer>
37-
3843
);
3944
};
4045

tabs/src/components/HeaderComponent.module.css

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,41 @@
4141
transform: scale(1.05);
4242
}
4343

44+
/* ========================================
45+
Navigation Styles
46+
======================================== */
47+
.navigation {
48+
display: flex;
49+
align-items: center;
50+
gap: 8px;
51+
}
52+
53+
.navLink {
54+
color: rgba(255, 255, 255, 0.8);
55+
text-decoration: none;
56+
font-size: 14px;
57+
font-weight: 500;
58+
padding: 8px 16px;
59+
border-radius: 6px;
60+
transition: all 0.2s ease;
61+
}
62+
63+
.navLink:hover {
64+
color: #84cc16;
65+
background: rgba(132, 204, 22, 0.1);
66+
}
67+
68+
.navLinkActive {
69+
color: #84cc16;
70+
background: rgba(132, 204, 22, 0.15);
71+
font-weight: 600;
72+
}
73+
74+
.navLink:focus {
75+
outline: 2px solid #84cc16;
76+
outline-offset: 2px;
77+
}
78+
4479
.rightSection {
4580
display: flex;
4681
align-items: center;
@@ -197,6 +232,10 @@
197232
font-size: 18px;
198233
}
199234

235+
.navigation {
236+
display: none;
237+
}
238+
200239
.userDetails {
201240
display: none;
202241
}
@@ -229,6 +268,15 @@
229268
}
230269

231270
/* Loading skeleton styles */
271+
.navLinkSkeleton {
272+
width: 50px;
273+
height: 14px;
274+
border-radius: 4px;
275+
background: linear-gradient(90deg, rgba(255, 255, 255, 0.1) 25%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.1) 75%);
276+
background-size: 200% 100%;
277+
animation: shimmer 1.5s infinite;
278+
}
279+
232280
.userInfoSkeleton {
233281
display: flex;
234282
align-items: center;

tabs/src/components/HeaderComponent.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { InteractionStatus } from '@azure/msal-browser';
44
import { Link } from 'react-router-dom';
55
import styles from './HeaderComponent.module.css';
66
import { useTeamsAuth } from '../hooks/useTeamsAuth';
7+
import NavigationLinks from './NavigationLinks';
78

89
const HeaderComponent: React.FC = () => {
910
const { accounts, inProgress } = useMsal();
@@ -16,7 +17,7 @@ const HeaderComponent: React.FC = () => {
1617
const dropdownButtonRef = useRef<HTMLDivElement>(null);
1718

1819
// Use shared Teams auth hook
19-
const { handleLogout, isLoggingOut, isTeamsInitializing } = useTeamsAuth();
20+
const { handleLogout, isLoggingOut, isTeamsInitializing, isInTeams } = useTeamsAuth();
2021

2122
useEffect(() => {
2223
if (account) {
@@ -99,6 +100,11 @@ const HeaderComponent: React.FC = () => {
99100
// Show loading skeleton during authentication initialization
100101
const isLoading = inProgress !== InteractionStatus.None || isTeamsInitializing;
101102

103+
// Hide header when running inside Microsoft Teams for cleaner integration
104+
if (isInTeams) {
105+
return null;
106+
}
107+
102108
if (isLoading) {
103109
return (
104110
<header className={styles.header}>
@@ -108,6 +114,13 @@ const HeaderComponent: React.FC = () => {
108114
<span className={styles.appName}>Zaplie</span>
109115
</Link>
110116
</div>
117+
<nav className={styles.navigation} aria-hidden="true">
118+
<div className={styles.navLinkSkeleton} />
119+
<div className={styles.navLinkSkeleton} />
120+
<div className={styles.navLinkSkeleton} />
121+
<div className={styles.navLinkSkeleton} />
122+
<div className={styles.navLinkSkeleton} />
123+
</nav>
111124
<div className={styles.rightSection}>
112125
<div className={styles.userInfoSkeleton}>
113126
<div className={styles.avatarSkeleton} />
@@ -146,6 +159,13 @@ const HeaderComponent: React.FC = () => {
146159
</Link>
147160
</div>
148161

162+
<nav className={styles.navigation} aria-label="Primary navigation">
163+
<NavigationLinks
164+
linkClassName={styles.navLink}
165+
activeLinkClassName={styles.navLinkActive}
166+
/>
167+
</nav>
168+
149169
<div className={styles.rightSection}>
150170
<div className={styles.userInfoWrapper} ref={dropdownRef}>
151171
<div
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import React from 'react';
2+
import { Link, useLocation } from 'react-router-dom';
3+
import { isActivePath } from '../utils/navigation';
4+
5+
/**
6+
* Navigation link configuration
7+
*/
8+
interface NavLink {
9+
path: string;
10+
label: string;
11+
}
12+
13+
/**
14+
* Props for the NavigationLinks component
15+
*/
16+
interface NavigationLinksProps {
17+
/**
18+
* CSS class name for each link
19+
*/
20+
linkClassName?: string;
21+
/**
22+
* CSS class name for active links
23+
*/
24+
activeLinkClassName?: string;
25+
}
26+
27+
/**
28+
* Navigation links configuration - single source of truth for all navigation items
29+
*/
30+
const NAV_LINKS: NavLink[] = [
31+
{ path: '/feed', label: 'Feed' },
32+
{ path: '/users', label: 'Users' },
33+
{ path: '/rewards', label: 'Rewards' },
34+
{ path: '/wallet', label: 'Wallet' },
35+
{ path: '/settings', label: 'Settings' },
36+
];
37+
38+
/**
39+
* Shared navigation links component used by both Header and Footer.
40+
* Provides consistent navigation across the application with proper accessibility support.
41+
*
42+
* @param props - Component props
43+
* @returns Navigation links fragment
44+
*/
45+
const NavigationLinks: React.FC<NavigationLinksProps> = ({
46+
linkClassName = '',
47+
activeLinkClassName = '',
48+
}) => {
49+
const location = useLocation();
50+
const isActive = (path: string) => isActivePath(location.pathname, path);
51+
52+
return (
53+
<>
54+
{NAV_LINKS.map(({ path, label }) => (
55+
<Link
56+
key={path}
57+
to={path}
58+
className={`${linkClassName} ${isActive(path) ? activeLinkClassName : ''}`.trim()}
59+
aria-current={isActive(path) ? 'page' : undefined}
60+
>
61+
{label}
62+
</Link>
63+
))}
64+
</>
65+
);
66+
};
67+
68+
export default NavigationLinks;

tabs/src/utils/navigation.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Shared navigation utilities for route matching.
3+
* These utilities are used by HeaderComponent and FooterComponent
4+
* to determine active navigation states.
5+
*/
6+
7+
/**
8+
* Checks if a given path is currently active based on the current location.
9+
*
10+
* This function uses precise matching to avoid false positives:
11+
* - Exact matches always return true (e.g., '/settings' matches '/settings')
12+
* - Nested routes are matched when the current path starts with targetPath + '/'
13+
* (e.g., '/settings/profile' matches '/settings', but '/settings-advanced' does NOT)
14+
* - The root path '/' only matches exactly to prevent it from matching all paths
15+
* - Trailing slashes are normalized to handle edge cases like '/settings/' matching '/settings'
16+
*
17+
* @param currentPath - The current location pathname (e.g., location.pathname)
18+
* @param targetPath - The navigation target path to check against (e.g., '/settings')
19+
* @returns true if the targetPath should be considered active for the currentPath
20+
*
21+
* @example
22+
* isActivePath('/settings', '/settings') // true - exact match
23+
* isActivePath('/settings/', '/settings') // true - trailing slash normalized
24+
* isActivePath('/settings/profile', '/settings') // true - nested route
25+
* isActivePath('/settings-advanced', '/settings') // false - different route
26+
* isActivePath('/feed', '/') // false - root only matches exactly
27+
*/
28+
export const isActivePath = (currentPath: string, targetPath: string): boolean => {
29+
// Validate inputs to prevent runtime errors
30+
if (!currentPath || !targetPath) {
31+
return false;
32+
}
33+
34+
// Normalize paths by removing trailing slashes (but keep root '/' intact)
35+
const normalizedCurrent = currentPath.replace(/\/+$/, '') || '/';
36+
const normalizedTarget = targetPath.replace(/\/+$/, '') || '/';
37+
38+
// Exact match
39+
if (normalizedCurrent === normalizedTarget) {
40+
return true;
41+
}
42+
43+
// For nested routes: must start with path followed by a slash
44+
// This prevents /settings from matching /settings-advanced
45+
// But allows /settings to match /settings/profile
46+
if (normalizedTarget !== '/') {
47+
return normalizedCurrent.startsWith(normalizedTarget + '/');
48+
}
49+
50+
// Special case for root path - only exact match
51+
return false;
52+
};

0 commit comments

Comments
 (0)