Skip to content

Commit 8832202

Browse files
committed
auth path + feat flags
1 parent 64d387d commit 8832202

File tree

7 files changed

+156
-95
lines changed

7 files changed

+156
-95
lines changed

src/components/templates/Header/Header.tsx

Lines changed: 84 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -11,85 +11,104 @@ import logo from '../../../assets/logo/safety-logo-white.png';
1111
import { RootState, AppDispatch } from '../../../stores/store';
1212
import { setHeaderExpanded } from '../../../stores';
1313
import { useStore } from '../../../stores/storeConfig';
14+
import { FeatureFlags, isFeatureEnabled } from '../../../utils/featureFlags';
1415
import '../../../styles/tabs.css';
1516
import './header.css';
1617

1718
interface IProps {
18-
title: string;
19+
title: string;
1920
}
2021

2122
const Header: React.FC<IProps> = observer(({ title }) => {
22-
const { t } = useTranslation();
23-
const dispatch = useDispatch<AppDispatch>();
24-
const { isHeaderExpanded } = useSelector((state: RootState) => state.appUi);
25-
const { userStore } = useStore();
26-
const { isAuthenticated, user } = userStore;
23+
const { t } = useTranslation();
24+
const dispatch = useDispatch<AppDispatch>();
25+
const { isHeaderExpanded } = useSelector((state: RootState) => state.appUi);
26+
const { userStore } = useStore();
27+
const { isAuthenticated, user } = userStore;
2728

28-
const toggleHeaderExpanded = () => {
29-
dispatch(setHeaderExpanded(!isHeaderExpanded));
30-
};
29+
const toggleHeaderExpanded = () => {
30+
dispatch(setHeaderExpanded(!isHeaderExpanded));
31+
};
3132

32-
const onLoginHandler = () => {
33-
if (isAuthenticated) {
34-
userStore.logout();
35-
} else {
36-
userStore.login();
37-
}
38-
};
33+
const onLoginHandler = () => {
34+
if (isAuthenticated) {
35+
userStore.logout();
36+
} else {
37+
userStore.login();
38+
}
39+
};
3940

40-
const UserIcon = () => (
41-
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" className="bi bi-person-circle" viewBox="0 0 16 16" style={{ marginInlineStart: '8px' }}>
42-
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
43-
<path fillRule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
44-
</svg>
45-
);
41+
const UserIcon = () => (
42+
<svg
43+
xmlns='http://www.w3.org/2000/svg'
44+
width='18'
45+
height='18'
46+
fill='currentColor'
47+
className='bi bi-person-circle'
48+
viewBox='0 0 16 16'
49+
style={{ marginInlineStart: '8px' }}
50+
>
51+
<path d='M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z' />
52+
<path
53+
fillRule='evenodd'
54+
d='M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z'
55+
/>
56+
</svg>
57+
);
4658

47-
const loginText = isAuthenticated ? t('Logout') : t('Login');
59+
const loginText = isAuthenticated ? t('Logout') : t('Login');
4860

49-
return (
50-
<header className="header">
51-
<Navbar className="navbar" expand="lg" expanded={isHeaderExpanded} onToggle={toggleHeaderExpanded}>
52-
<div className="container-fluid">
53-
<img src={logo} alt={`${title} logo`} height="25" width="150" />
54-
<Navbar.Toggle aria-controls="basic-navbar-nav" className="text-light" data-testid="navbar-toggle" />
55-
<Navbar.Collapse id="basic-navbar-nav">
56-
<div className="navbar-nav">
57-
<NavigationList />
58-
</div>
61+
return (
62+
<header className='header'>
63+
<Navbar className='navbar' expand='lg' expanded={isHeaderExpanded} onToggle={toggleHeaderExpanded}>
64+
<div className='container-fluid'>
65+
<img src={logo} alt={`${title} logo`} height='25' width='150' />
66+
<Navbar.Toggle aria-controls='basic-navbar-nav' className='text-light' data-testid='navbar-toggle' />
67+
<Navbar.Collapse id='basic-navbar-nav'>
68+
<div className='navbar-nav'>
69+
<NavigationList />
70+
</div>
5971

60-
<div className="d-flex align-items-center ms-auto">
61-
<div className="me-3">
62-
{isAuthenticated && (
63-
<Link
64-
to="/profile"
65-
className="text-white text-decoration-none small d-none d-md-inline hover-opacity me-3"
66-
style={{ fontWeight: 500 }}
67-
>
68-
{t('Welcome')}, {user?.name || t('User')}
69-
</Link>
70-
)}
71-
{isAuthenticated && <div className="vr d-none d-md-block text-white opacity-50 me-3" style={{ height: '20px', display: 'inline-block' }}></div>}
72+
<div className='d-flex align-items-center ms-auto'>
73+
{isFeatureEnabled(FeatureFlags.AUTH) && (
74+
<div className='me-3'>
75+
{isAuthenticated && (
76+
<Link
77+
to='/profile'
78+
className='text-white text-decoration-none small d-none d-md-inline hover-opacity me-3'
79+
style={{ fontWeight: 500 }}
80+
>
81+
{t('Welcome')}, {user?.name || t('User')}
82+
</Link>
83+
)}
84+
{isAuthenticated && (
85+
<div
86+
className='vr d-none d-md-block text-white opacity-50 me-3'
87+
style={{ height: '20px', display: 'inline-block' }}
88+
></div>
89+
)}
7290

73-
<Button
74-
variant="outline-light"
75-
size="sm"
76-
className="d-flex align-items-center px-3"
77-
onClick={onLoginHandler}
78-
style={{ borderRadius: '20px', fontWeight: 500 }}
79-
>
80-
{loginText}
81-
<UserIcon />
82-
</Button>
83-
</div>
84-
<div style={{marginTop: '8px'}}>
85-
<LanguageSelector />
86-
</div>
87-
</div>
88-
</Navbar.Collapse>
89-
</div>
90-
</Navbar>
91-
</header>
92-
);
91+
<Button
92+
variant='outline-light'
93+
size='sm'
94+
className='d-flex align-items-center px-3'
95+
onClick={onLoginHandler}
96+
style={{ borderRadius: '20px', fontWeight: 500 }}
97+
>
98+
{loginText}
99+
<UserIcon />
100+
</Button>
101+
</div>
102+
)}
103+
<div style={{ marginTop: '8px' }}>
104+
<LanguageSelector />
105+
</div>
106+
</div>
107+
</Navbar.Collapse>
108+
</div>
109+
</Navbar>
110+
</header>
111+
);
93112
});
94113

95114
export default Header;

src/components/templates/TabsTemplate.tsx

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
import React, { FunctionComponent, lazy, Suspense, } from 'react';
1+
import React, { FunctionComponent, lazy, Suspense } from 'react';
22
import { useTranslation } from 'react-i18next';
33
import { observer } from 'mobx-react';
44
import Tabs from 'react-bootstrap/Tabs';
55
import Tab from 'react-bootstrap/Tab';
6-
import {ErrorBoundary, Loader, SmallCard} from '../common/';
6+
import { ErrorBoundary, Loader, SmallCard } from '../common/';
77
import { useStore } from '../../stores/storeConfig';
8-
import { useDispatch, useSelector, } from 'react-redux';
8+
import { useDispatch, useSelector } from 'react-redux';
99
import { AppDispatch, RootState } from '../../stores/store';
10-
import {setCurrentTab} from '../../stores';
10+
import { setCurrentTab } from '../../stores';
11+
import { FeatureFlags, isFeatureEnabled } from '../../utils/featureFlags';
1112

1213
// import MapPage from '../../pages/MapPage';
1314
interface IProps {
14-
type: string;
15+
type: string;
1516
}
1617

1718
const GroupByGraphsPanel = lazy(() => import('../chart/GroupByGraphsPanel'));
@@ -22,17 +23,17 @@ const MyImageGallery = lazy(() => import('../organisms/MyImageGallery'));
2223
const RiskHotspotModel = lazy(() => import('../organisms/RiskHotspotModel'));
2324

2425
const styles = {
25-
tab: { marginTop: '0.5rem' },
26-
tabMap: { marginTop: '0.1rem' },
27-
mapCard: { width: '150px', height: '35px' }
26+
tab: { marginTop: '0.5rem' },
27+
tabMap: { marginTop: '0.1rem' },
28+
mapCard: { width: '150px', height: '35px' },
2829
};
2930

3031
export const TabsTemplate: FunctionComponent<IProps> = observer(({ type }) => {
31-
const { t } = useTranslation();
32-
const dispatch = useDispatch<AppDispatch>();
33-
const { mapStore } = useStore();
34-
const { currentTab } = useSelector((state: RootState) => state.appUi);
35-
return (
32+
const { t } = useTranslation();
33+
const dispatch = useDispatch<AppDispatch>();
34+
const { mapStore } = useStore();
35+
const { currentTab } = useSelector((state: RootState) => state.appUi);
36+
return (
3637
<Tabs
3738
mountOnEnter
3839
activeKey={currentTab}
@@ -86,16 +87,18 @@ export const TabsTemplate: FunctionComponent<IProps> = observer(({ type }) => {
8687
</Suspense>
8788
</ErrorBoundary>
8889
</Tab>
89-
<Tab style={styles.tab} eventKey='riskModel' title={t('RiskModel')}>
90-
<ErrorBoundary>
91-
<Suspense fallback={<Loader />}>
92-
<div className='col-auto'>
93-
<RiskHotspotModel />
94-
</div>
95-
</Suspense>
96-
</ErrorBoundary>
97-
</Tab>
90+
{isFeatureEnabled(FeatureFlags.RISK_MODEL) && (
91+
<Tab style={styles.tab} eventKey='riskModel' title={t('RiskModel')}>
92+
<ErrorBoundary>
93+
<Suspense fallback={<Loader />}>
94+
<div className='col-auto'>
95+
<RiskHotspotModel />
96+
</div>
97+
</Suspense>
98+
</ErrorBoundary>
99+
</Tab>
100+
)}
98101
</Tabs>
99-
);
102+
);
100103
});
101104
export default TabsTemplate;

src/services/AuthService.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import axios from 'axios';
1+
import axios, { AxiosResponse } from 'axios';
22
import { API_ANYWAY_URL } from '../utils/globalEnvs';
3+
import { IUserLoggedIn } from '../types/User';
34

45
class AuthService {
56
apiUrl = API_ANYWAY_URL;
67

78
// Safety Data session-based endpoints
8-
isLoggedIn = async () => {
9+
isLoggedIn = async (): Promise<AxiosResponse<IUserLoggedIn>> => {
910
return axios.get(`${this.apiUrl}/sd-user/is_user_logged_in`, { withCredentials: true });
1011
};
1112

@@ -31,4 +32,4 @@ class AuthService {
3132
};
3233
}
3334

34-
export default AuthService;
35+
export default AuthService;

src/stores/user/UserStore.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { makeAutoObservable, runInAction } from 'mobx';
22
import RootStore from '../RootStore';
33
import AuthService from '../../services/AuthService';
4+
import { isFeatureEnabled, FeatureFlags } from '../../utils/featureFlags';
45

56
export interface IUser {
67
id: string;
@@ -27,6 +28,9 @@ export default class UserStore {
2728

2829
async checkAuthStatus() {
2930
this.isLoading = true;
31+
if (!isFeatureEnabled(FeatureFlags.AUTH)) {
32+
return;
33+
}
3034
try {
3135
const response = await this.authService.isLoggedIn();
3236
if (response.data.is_user_logged_in) {
@@ -76,7 +80,7 @@ export default class UserStore {
7680
const top = window.screenY + (window.outerHeight - height) / 2;
7781

7882
// Use the current origin as the redirect URL for the backend to send the user back to
79-
const redirectUrl = window.location.origin + '/public/close-popup.html';
83+
const redirectUrl = window.location.origin + '/close-popup.html';
8084
const authUrl = this.authService.getAuthorizeUrl(redirectUrl);
8185

8286
const popup = window.open(

src/types/User.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export interface IUserLoggedIn {
2+
is_user_logged_in: boolean;
3+
}

src/utils/featureFlags.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Feature Flags Utility
3+
* This utility manages the visibility of new or experimental features.
4+
*/
5+
6+
// Check if we are running in production
7+
const isProd = import.meta.env.PROD;
8+
9+
export enum FeatureFlags {
10+
RISK_MODEL = 'RISK_MODEL',
11+
AUTH = 'AUTH',
12+
}
13+
14+
export const FEATURE_FLAGS = {
15+
/**
16+
* Risk Hotspot Model feature
17+
* Disabled in production for now.
18+
*/
19+
RISK_MODEL: !isProd,
20+
21+
/**
22+
* New Redirect-based Authentication flow
23+
* Disabled in production for now.
24+
*/
25+
AUTH: true,
26+
};
27+
28+
/**
29+
* Helper to check if a specific feature is enabled.
30+
* Usage: if (isFeatureEnabled('RISK_MODEL')) { ... }
31+
*/
32+
export const isFeatureEnabled = (feature: FeatureFlags): boolean => {
33+
return FEATURE_FLAGS[feature] ?? false;
34+
};

src/utils/globalEnvs.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,2 @@
11
export const API_URL = import.meta.env.VITE_API_URL as string;
22
export const API_ANYWAY_URL = import.meta.env.VITE_API_ANYWAY_URL as string;
3-
4-
console.log('🚀 ~ API_URL:', API_URL);
5-
console.log('🚀 ~ API_ANYWAY_URL:', API_ANYWAY_URL);

0 commit comments

Comments
 (0)