Skip to content

Commit

Permalink
add: feature flag listing page
Browse files Browse the repository at this point in the history
  • Loading branch information
MehulKChaudhari committed Nov 25, 2024
1 parent 95822dd commit e4da311
Show file tree
Hide file tree
Showing 10 changed files with 551 additions and 4 deletions.
1 change: 1 addition & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ VITE_REACT_RDS_BACKEND_BASE_URL='https://staging-api.realdevsquad.com'
VITE_REACT_STATUS_SITE_URL='https://staging-status.realdevsquad.com'
VITE_REACT_MEMBERS_SITE_URL='https://staging-members.realdevsquad.com'
VITE_REACT_WELCOME_SITE_URL='https://welcome.realdevsquad.com'
VITE_REACT_RDS_BACKEND_BASE_URL='https://localhost:4000'
9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"dev:server": "vite --port 4000",
"dev:reverse-ssl": "local-ssl-proxy --source 443 --target 4000",
"dev": "run-p dev:*",
"build": "tsc -b && vite build",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
Expand All @@ -16,9 +18,12 @@
"test:coverage": "vitest run --coverage"
},
"dependencies": {
"axios": "^1.7.7",
"framer-motion": "^11.11.17",
"install": "^0.13.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.3.0",
"react-router-dom": "^6.28.0"
},
"devDependencies": {
Expand Down Expand Up @@ -46,6 +51,8 @@
"eslint-plugin-react-refresh": "^0.4.14",
"globals": "^15.11.0",
"jsdom": "^25.0.1",
"local-ssl-proxy": "^2.0.5",
"npm-run-all": "^4.1.5",
"postcss": "^8.4.47",
"prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.8",
Expand Down
360 changes: 360 additions & 0 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

64 changes: 64 additions & 0 deletions src/components/FeatureFlagCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from 'react';
import { FaRegCalendarAlt } from 'react-icons/fa';
import { MdUpdate } from 'react-icons/md';
import { formatTimestamp } from '../utils/format-timestamp';

interface FeatureFlag {
id: string;
name: string;
description: string;
status: 'ENABLED' | 'DISABLED';
createdAt: number;
createdBy: string;
updatedAt: number;
updatedBy: string;
}

interface FeatureFlagCardProps {
flag: FeatureFlag;
onCardClick: (id: string) => void;
}

const FeatureFlagCard: React.FC<FeatureFlagCardProps> = ({ flag, onCardClick }) => {

Check failure on line 22 in src/components/FeatureFlagCard.tsx

View workflow job for this annotation

GitHub Actions / lint_test

Replace `·flag,·onCardClick·` with `⏎··flag,⏎··onCardClick,⏎`
return (
<div

Check failure on line 24 in src/components/FeatureFlagCard.tsx

View workflow job for this annotation

GitHub Actions / lint_test

Delete `·`
onClick={() => onCardClick(flag.id)}
className="bg-white rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 overflow-hidden cursor-pointer hover:-translate-y-1"

Check failure on line 26 in src/components/FeatureFlagCard.tsx

View workflow job for this annotation

GitHub Actions / lint_test

Replace `bg-white·rounded-xl·shadow-lg·hover:shadow-xl·transition-all·duration-300·overflow-hidden·cursor-pointer·hover:-translate-y-1` with `cursor-pointer·overflow-hidden·rounded-xl·bg-white·shadow-lg·transition-all·duration-300·hover:-translate-y-1·hover:shadow-xl`
>
<div className="p-6">
<div className="flex justify-between items-start mb-4">

Check failure on line 29 in src/components/FeatureFlagCard.tsx

View workflow job for this annotation

GitHub Actions / lint_test

Replace `flex·justify-between·items-start·mb-4` with `mb-4·flex·items-start·justify-between`
<h2 className="text-xl font-bold text-gray-800 hover:text-primary-600 transition-colors">

Check failure on line 30 in src/components/FeatureFlagCard.tsx

View workflow job for this annotation

GitHub Actions / lint_test

Replace `text-xl·font-bold·text-gray-800·hover:text-primary-6` with `hover:text-primary-600·text-xl·font-bold·text-gray-8`
{flag.name}
</h2>
<div className="flex items-center">
<span className="text-sm font-medium text-gray-600 mr-2">Status:</span>

Check failure on line 34 in src/components/FeatureFlagCard.tsx

View workflow job for this annotation

GitHub Actions / lint_test

Replace `text-sm·font-medium·text-gray-600·mr-2">Status:` with `mr-2·text-sm·font-medium·text-gray-600">⏎··············Status:⏎············`
<span className={`px-3 py-1 rounded-full text-sm font-semibold

Check failure on line 35 in src/components/FeatureFlagCard.tsx

View workflow job for this annotation

GitHub Actions / lint_test

Replace `·className={`px-3·py-1·rounded-full·text-sm·font-semibold·` with `⏎··············className={`rounded-full·px-3·py-1·text-sm·font-semibold·${`
${flag.status === 'ENABLED'

Check failure on line 36 in src/components/FeatureFlagCard.tsx

View workflow job for this annotation

GitHub Actions / lint_test

Replace `${flag.status·===·'ENABLED'·⏎················?·'bg-green-100·text-green-700'·⏎················:·'bg-gray-100·text-gray-700'` with `··flag.status·===·'ENABLED'⏎··················?·'bg-green-100·text-green-700'⏎··················:·'bg-gray-100·text-gray-700'⏎··············`
? 'bg-green-100 text-green-700'
: 'bg-gray-100 text-gray-700'}`}
>
{flag.status}
</span>
</div>
</div>
<p className="text-gray-600 mb-6 line-clamp-2">

Check failure on line 44 in src/components/FeatureFlagCard.tsx

View workflow job for this annotation

GitHub Actions / lint_test

Replace `text-gray-600·mb-6·line-clamp-2">⏎··········{flag.description}⏎········` with `mb-6·line-clamp-2·text-gray-600">{flag.description}`
{flag.description}
</p>
<div className="border-t pt-4 mt-auto">

Check failure on line 47 in src/components/FeatureFlagCard.tsx

View workflow job for this annotation

GitHub Actions / lint_test

Replace `border-t·pt-4·mt-auto` with `mt-auto·border-t·pt-4`
<div className="text-sm text-gray-500 space-y-2">
<div className="flex items-center">
<FaRegCalendarAlt className="w-4 h-4 mr-2" />
<span>Created: {formatTimestamp(flag.createdAt)}</span>
</div>
<div className="flex items-center">
<MdUpdate className="w-4 h-4 mr-2" />
<span>Updated: {formatTimestamp(flag.updatedAt)}</span>
</div>
</div>
</div>
</div>
</div>
);
};

export default FeatureFlagCard;
13 changes: 13 additions & 0 deletions src/components/Spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';

const Spinner: React.FC = () => {
return (
<div className="flex items-center justify-center h-full">
<div
className="w-10 h-10 border-4 border-primary border-t-transparent rounded-full animate-spin"
/>
</div>
);
};

export default Spinner;
1 change: 1 addition & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const config: Config = {
welcomeSiteUrl: import.meta.env.VITE_REACT_WELCOME_SITE_URL,
membersSiteUrl: import.meta.env.VITE_REACT_MEMBERS_SITE_URL,
statusSiteUrl: import.meta.env.VITE_REACT_STATUS_SITE_URL,
rdsBackendBaseUrl: import.meta.env.VITE_REACT_RDS_BACKEND_BASE_URL,
};

export const getConfig = () => {
Expand Down
59 changes: 56 additions & 3 deletions src/pages/FeatureFlagList.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,61 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import FeatureFlagCard from '../components/FeatureFlagCard';
import Spinner from '../components/Spinner';
import { getAllFeatureFlags, FeatureFlag } from '../services/api';

const FeatureFlagList: React.FC = () => {
const [featureFlags, setFeatureFlags] = useState<FeatureFlag[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const navigate = useNavigate();

useEffect(() => {
const fetchFeatureFlags = async () => {
try {
setIsLoading(true);
const data = await getAllFeatureFlags();
setFeatureFlags(data);
setError(null);
} catch (err) {
setError('Failed to fetch feature flags');
} finally {
setIsLoading(false);
}
};

fetchFeatureFlags();
}, []);

const handleCardClick = (id: string) => {
navigate(`/featureFlag/${id}`);
};

return (
<div className="container mx-auto mt-20 p-4">
<h1 className="mb-4 text-2xl font-bold">Feature Flags</h1>
<div className="container mx-auto mt-20 px-4">
<div className="text-center mb-12">
<h1 className="text-4xl font-bold text-gray-900 mb-2">Feature Flags</h1>
</div>

{error && (
<div className="text-center text-red-600 mb-8 p-4 bg-red-50 rounded-lg">
{error}
</div>
)}

{isLoading ? (
<Spinner />
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{featureFlags.map((flag) => (
<FeatureFlagCard
key={flag.id}
flag={flag}
onCardClick={handleCardClick}
/>
))}
</div>
)}
</div>
);
};
Expand Down
27 changes: 27 additions & 0 deletions src/services/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { getConfig } from '../config';
import axios from 'axios';

export interface FeatureFlag {
id: string;
name: string;
description: string;
status: 'ENABLED' | 'DISABLED';
createdAt: number;
createdBy: string;
updatedAt: number;
updatedBy: string;
}

const apiClient = axios.create({
baseURL: getConfig().rdsBackendBaseUrl,
headers: {
'Content-Type': 'application/json',
credentials: 'include',
},
withCredentials: true,
});

export const getAllFeatureFlags = async () => {
const response = await apiClient.get<{data: FeatureFlag[]}>('/feature-flag/getAllFeatureFlags');
return response.data.data;
};
11 changes: 11 additions & 0 deletions src/utils/format-timestamp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const formatTimestamp = (timestamp: number): string => {
const date = new Date(timestamp * 1000);
return date.toLocaleString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: true
});
};
10 changes: 10 additions & 0 deletions tests/unit/format-timestamp.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { formatTimestamp } from '../../src/utils/format-timestamp';

describe('formatTimestamp', () => {
it('should format timestamp correctly', () => {
const timestamp = 1718140371;
const formattedDate = formatTimestamp(timestamp);

expect(formattedDate).toBe('June 12, 2024 at 02:42 AM');
});
});

0 comments on commit e4da311

Please sign in to comment.