Skip to content

Commit

Permalink
add feature flags concept
Browse files Browse the repository at this point in the history
  • Loading branch information
nothing9537 committed Oct 26, 2023
1 parent 6882390 commit 7276754
Show file tree
Hide file tree
Showing 7 changed files with 47 additions and 3 deletions.
16 changes: 16 additions & 0 deletions json-server/db.json
Original file line number Diff line number Diff line change
Expand Up @@ -1196,6 +1196,10 @@
"ADMIN",
"USER"
],
"features": {
"isArticleRatingEnabled": true,
"isCounterEnabled": true
},
"avatar": "https://source.boringavatars.com/pixel/120/Stefan?colors=26a653,2a1d8f,79646a"
},
{
Expand All @@ -1205,6 +1209,10 @@
"roles": [
"USER"
],
"features": {
"isArticleRatingEnabled": false,
"isCounterEnabled": true
},
"avatar": "https://cdn.discordapp.com/attachments/341701512155758594/1136485169189105714/Main_avatar.png"
},
{
Expand All @@ -1215,6 +1223,10 @@
"MANAGER",
"USER"
],
"features": {
"isArticleRatingEnabled": false,
"isCounterEnabled": false
},
"avatar": "https://cdn.discordapp.com/attachments/341701512155758594/1136485169189105714/Main_avatar.png"
},
{
Expand All @@ -1224,6 +1236,10 @@
"roles": [
"USER"
],
"features": {
"isArticleRatingEnabled": true,
"isCounterEnabled": false
},
"avatar": "https://source.boringavatars.com/pixel/120/Stefan?colors=26a653,2a1d8f,79646a"
}
],
Expand Down
6 changes: 5 additions & 1 deletion src/entities/User/model/slice/userSlice.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { AUTH_TOKEN_KEY } from '@/shared/consts/localStorage';
import { User, UserSchema } from '../types/user';
import { setFeatureFlags } from '@/shared/lib/features';

const initialState: UserSchema = {
_mounted: false,
Expand All @@ -13,12 +14,15 @@ export const userSlice = createSlice({
setAuthData: (state, action: PayloadAction<User>) => {
localStorage.setItem(AUTH_TOKEN_KEY, JSON.stringify(action.payload));
state.authData = action.payload;
setFeatureFlags(action.payload?.features);
},
initAuthData: (state) => {
const authData = localStorage.getItem(AUTH_TOKEN_KEY);

if (authData) {
state.authData = JSON.parse(authData);
const parsedUser = JSON.parse(authData) as User;
state.authData = parsedUser;
setFeatureFlags(parsedUser?.features);
}

state._mounted = true;
Expand Down
2 changes: 2 additions & 0 deletions src/entities/User/model/types/user.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { FeatureFlags } from '@/shared/types/featureFlags';
import { UserRole } from '../consts';

export interface User {
id: string;
username: string;
avatar?: string;
roles?: UserRole[];
features?: FeatureFlags;
}

export interface UserSchema {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { FC, memo } from 'react';
import { useParams } from 'react-router-dom';
import { DynamicModuleWrapper, ReducersList } from '@/shared/lib/components/DynamicModuleWrapper';
import { classNames } from '@/shared/lib/classNames/classNames';
import { PageWrapper } from '@/widgets/PageWrapper';
import { getFeatureFlags } from '@/shared/lib/features';
import { ArticleDetails } from '@/entities/Article';
import { ArticleRating } from '@/features/ArticleRating';
import { ArticleRecommendations } from '@/features/ArticleRecommendations';
import { PageWrapper } from '@/widgets/PageWrapper';
import { ArticleDetailsComments, articleDetailsCommentsReducer } from '../ArticleDetailsComments';
import { ArticleDetailsPageHeader } from '../ArticleDetailsPageHeader/ArticleDetailsPageHeader';
import cls from './ArticleDetailsPage.module.scss';
import { Counter } from '@/entities/Counter';

const reducers: ReducersList = {
articleDetailsComments: articleDetailsCommentsReducer,
Expand All @@ -19,12 +21,16 @@ interface ArticleDetailsPageProps {
const ArticleDetailsPage: FC<ArticleDetailsPageProps> = ({ className }) => {
const { id } = useParams<{ id: string }>();

const isArticleRatingFeatureEnabled = getFeatureFlags('isArticleRatingEnabled');
const isCounterFeatureEnabled = getFeatureFlags('isCounterEnabled');

return (
<DynamicModuleWrapper reducers={reducers}>
<PageWrapper className={classNames(cls.ArticleDetailsPage, {}, [className])}>
<ArticleDetailsPageHeader id={id} />
<ArticleDetails id={id!} />

Check warning on line 31 in src/pages/ArticleDetailsPage/ui/ArticleDetailsPage/ArticleDetailsPage.tsx

View workflow job for this annotation

GitHub Actions / checks (18.x)

Forbidden non-null assertion
<ArticleRating id={id!} />
{isCounterFeatureEnabled && <Counter />}
{isArticleRatingFeatureEnabled && <ArticleRating id={id!} />}

Check warning on line 33 in src/pages/ArticleDetailsPage/ui/ArticleDetailsPage/ArticleDetailsPage.tsx

View workflow job for this annotation

GitHub Actions / checks (18.x)

Forbidden non-null assertion
<ArticleRecommendations />
<ArticleDetailsComments id={id} />
</PageWrapper>
Expand Down
11 changes: 11 additions & 0 deletions src/shared/lib/features/featureFlagsHandlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { FeatureFlags } from '@/shared/types/featureFlags';

let featureFlags: FeatureFlags;

export const setFeatureFlags = (newFeatureFlags?: FeatureFlags) => {
if (newFeatureFlags) {
featureFlags = newFeatureFlags;
}
};

export const getFeatureFlags = (flag: keyof FeatureFlags) => featureFlags[flag];
1 change: 1 addition & 0 deletions src/shared/lib/features/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './featureFlagsHandlers';
4 changes: 4 additions & 0 deletions src/shared/types/featureFlags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface FeatureFlags {
isArticleRatingEnabled?: boolean;
isCounterEnabled?: boolean;
}

0 comments on commit 7276754

Please sign in to comment.