<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kishan Srivastava</title>
    <description>The latest articles on DEV Community by Kishan Srivastava (@superman01).</description>
    <link>https://dev.to/superman01</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3308249%2F2feb1e11-75ad-4169-8340-2f781a3420f1.jpg</url>
      <title>DEV Community: Kishan Srivastava</title>
      <link>https://dev.to/superman01</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/superman01"/>
    <language>en</language>
    <item>
      <title>iptester-redesigned</title>
      <dc:creator>Kishan Srivastava</dc:creator>
      <pubDate>Mon, 30 Jun 2025 09:50:56 +0000</pubDate>
      <link>https://dev.to/superman01/iptester-redesigned-2hle</link>
      <guid>https://dev.to/superman01/iptester-redesigned-2hle</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState, useCallback } from 'react';
import { useUnleashContext, useUnleashClient } from '@unleash/proxy-client-react';
import styled, { keyframes, css } from 'styled-components';

// --- Keyframe Animations ---

const fadeIn = keyframes`
  from { 
    opacity: 0; 
    transform: translateY(20px) scale(0.95); 
  }
  to { 
    opacity: 1; 
    transform: translateY(0) scale(1); 
  }
`;

const slideIn = keyframes`
  from { 
    opacity: 0; 
    transform: translateX(-20px); 
  }
  to { 
    opacity: 1; 
    transform: translateX(0); 
  }
`;

const spin = keyframes`
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
`;

const pulse = keyframes`
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.05); }
`;

const gradientShift = keyframes`
  0% { background-position: 0% 50%; }
  50% { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
`;

const float = keyframes`
  0%, 100% { transform: translateY(0px); }
  50% { transform: translateY(-10px); }
`;

// --- Modern Styled Components ---

const Container = styled.div`
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  min-height: 100vh;
  padding: 2rem 1rem;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  position: relative;

  &amp;amp;::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: 
      radial-gradient(circle at 20% 50%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
      radial-gradient(circle at 80% 20%, rgba(255, 118, 117, 0.3) 0%, transparent 50%),
      radial-gradient(circle at 40% 80%, rgba(255, 177, 153, 0.3) 0%, transparent 50%);
    pointer-events: none;
  }
`;

const MaxWidthWrapper = styled.div`
  max-width: 1400px;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 2rem;
  position: relative;
  z-index: 1;
`;

const Card = styled.div`
  background: rgba(255, 255, 255, 0.95);
  backdrop-filter: blur(20px);
  border-radius: 24px;
  box-shadow: 
    0 32px 64px -12px rgba(0, 0, 0, 0.25),
    0 0 0 1px rgba(255, 255, 255, 0.05);
  padding: 2rem;
  border: 1px solid rgba(255, 255, 255, 0.2);
  animation: ${fadeIn} 0.6s ease-out;
  transition: all 0.3s ease;

  &amp;amp;:hover {
    transform: translateY(-2px);
    box-shadow: 
      0 40px 80px -12px rgba(0, 0, 0, 0.3),
      0 0 0 1px rgba(255, 255, 255, 0.1);
  }
`;

const Header = styled.div`
  text-align: center;
  margin-bottom: 1.5rem;
  animation: ${slideIn} 0.8s ease-out;
`;

const Title = styled.h1`
  font-size: 3rem;
  font-weight: 800;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
  background-size: 200% 200%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  margin-bottom: 0.75rem;
  animation: ${gradientShift} 4s ease infinite;
  line-height: 1.2;
`;

const Subtitle = styled.p`
  font-size: 1.25rem;
  color: #64748b;
  font-weight: 500;
  max-width: 600px;
  margin: 0 auto;
  line-height: 1.6;
`;

const SectionTitle = styled.h2`
  font-size: 1.75rem;
  font-weight: 700;
  background: linear-gradient(135deg, #1e293b, #475569);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  margin-bottom: 1.5rem;
  display: flex;
  align-items: center;
  gap: 0.75rem;

  &amp;amp;::before {
    content: '';
    width: 4px;
    height: 24px;
    background: linear-gradient(135deg, #667eea, #764ba2);
    border-radius: 2px;
  }
`;

const ConfigGrid = styled.div`
  display: grid;
  gap: 2rem;
  margin-bottom: 2rem;
`;

const InputGroup = styled.div`
  display: grid;
  grid-template-columns: 1fr 1fr auto;
  gap: 1rem;
  align-items: end;

  @media (max-width: 768px) {
    grid-template-columns: 1fr;
  }
`;

const DiscreteInputGroup = styled.div`
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 1rem;
  align-items: end;

  @media (max-width: 768px) {
    grid-template-columns: 1fr;
  }
`;

const InputField = styled.div`
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
`;

const Label = styled.label`
  font-size: 0.875rem;
  font-weight: 600;
  color: #374151;
  letter-spacing: 0.025em;
`;

const Input = styled.input`
  width: 100%;
  padding: 1rem 1.25rem;
  border: 2px solid #e2e8f0;
  border-radius: 16px;
  font-size: 1rem;
  color: #1f2937;
  background: rgba(255, 255, 255, 0.8);
  backdrop-filter: blur(10px);
  transition: all 0.3s ease;
  font-weight: 500;

  &amp;amp;::placeholder {
    color: #9ca3af;
    font-weight: 400;
  }

  &amp;amp;:focus {
    outline: none;
    border-color: #667eea;
    background: rgba(255, 255, 255, 0.95);
    box-shadow: 
      0 0 0 4px rgba(102, 126, 234, 0.1),
      0 8px 25px -5px rgba(102, 126, 234, 0.2);
    transform: translateY(-1px);
  }
`;

const Button = styled.button`
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  padding: 1rem 2rem;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  font-weight: 600;
  border-radius: 16px;
  border: none;
  cursor: pointer;
  transition: all 0.3s ease;
  white-space: nowrap;
  font-size: 0.975rem;
  letter-spacing: 0.025em;
  position: relative;
  overflow: hidden;

  &amp;amp;::before {
    content: '';
    position: absolute;
    top: 0;
    left: -100%;
    width: 100%;
    height: 100%;
    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
    transition: left 0.5s;
  }

  &amp;amp;:hover {
    transform: translateY(-2px);
    box-shadow: 0 15px 35px -5px rgba(102, 126, 234, 0.4);

    &amp;amp;::before {
      left: 100%;
    }
  }

  &amp;amp;:active {
    transform: translateY(0);
  }

  &amp;amp;:disabled {
    background: linear-gradient(135deg, #9ca3af, #6b7280);
    cursor: not-allowed;
    transform: none;
    box-shadow: none;

    &amp;amp;::before {
      display: none;
    }
  }
`;

const Spinner = styled.div`
  border: 3px solid rgba(255, 255, 255, 0.3);
  border-top: 3px solid white;
  border-radius: 50%;
  width: 1.25rem;
  height: 1.25rem;
  animation: ${spin} 1s linear infinite;
`;

const ErrorMessage = styled.div`
  background: linear-gradient(135deg, #fee2e2, #fecaca);
  color: #dc2626;
  font-size: 0.875rem;
  padding: 1rem 1.5rem;
  border-radius: 12px;
  text-align: center;
  border: 1px solid #fca5a5;
  font-weight: 500;
  animation: ${fadeIn} 0.3s ease-out;
`;

const IpCardsGrid = styled.div`
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 1.5rem;
  margin-top: 2rem;
`;

const IpCardContainer = styled.div`
  background: rgba(255, 255, 255, 0.95);
  backdrop-filter: blur(20px);
  border-radius: 20px;
  box-shadow: 
    0 20px 40px -12px rgba(0, 0, 0, 0.15),
    0 0 0 1px rgba(255, 255, 255, 0.1);
  padding: 1.5rem;
  border: 1px solid rgba(255, 255, 255, 0.2);
  display: flex;
  flex-direction: column;
  gap: 1rem;
  animation: ${fadeIn} 0.5s ease-out;
  transition: all 0.3s ease;
  position: relative;
  overflow: hidden;

  &amp;amp;::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 4px;
    background: linear-gradient(135deg, #667eea, #764ba2);
    border-radius: 20px 20px 0 0;
  }

  &amp;amp;:hover {
    transform: translateY(-4px) scale(1.02);
    box-shadow: 
      0 32px 64px -12px rgba(0, 0, 0, 0.25),
      0 0 0 1px rgba(255, 255, 255, 0.2);
  }
`;

const IpCardHeader = styled.h3`
  font-size: 1.5rem;
  font-weight: 700;
  color: #1f2937;
  margin: 0;
  font-family: 'Monaco', 'Menlo', monospace;
  background: linear-gradient(135deg, #1f2937, #374151);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
`;

const IpCardDetail = styled.div`
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 0.925rem;
  padding: 0.5rem 0;
`;

const IpCardLabel = styled.span`
  font-weight: 600;
  color: #6b7280;
  font-size: 0.875rem;
`;

const IpCardValue = styled.span`
  font-weight: 700;
  color: #1f2937;
  font-size: 0.875rem;
`;

const StatusBadge = styled.span`
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  border-radius: 50px;
  font-size: 0.875rem;
  font-weight: 600;
  transition: all 0.3s ease;

  ${(props) =&amp;gt; {
    switch (props.type) {
      case 'enabled':
        return css`
          background: linear-gradient(135deg, #d1fae5, #a7f3d0);
          color: #059669;
          border: 1px solid #6ee7b7;
          animation: ${pulse} 2s ease-in-out infinite;
        `;
      case 'disabled':
        return css`
          background: linear-gradient(135deg, #fee2e2, #fecaca);
          color: #dc2626;
          border: 1px solid #fca5a5;
        `;
      case 'loading':
        return css`
          background: linear-gradient(135deg, #eff6ff, #dbeafe);
          color: #2563eb;
          border: 1px solid #93c5fd;
        `;
      case 'error':
        return css`
          background: linear-gradient(135deg, #fee2e2, #fecaca);
          color: #dc2626;
          border: 1px solid #fca5a5;
        `;
      default:
        return css`
          background: linear-gradient(135deg, #f3f4f6, #e5e7eb);
          color: #6b7280;
          border: 1px solid #d1d5db;
        `;
    }
  }}
`;

const TestButton = styled(Button)`
  width: 100%;
  margin-top: 0.5rem;
  background: linear-gradient(135deg, #4f46e5, #7c3aed);
  font-size: 0.875rem;
  padding: 0.875rem 1.5rem;

  &amp;amp;:hover {
    box-shadow: 0 15px 35px -5px rgba(79, 70, 229, 0.4);
  }

  &amp;amp;:disabled {
    background: linear-gradient(135deg, #9ca3af, #6b7280);
  }
`;

const EmptyState = styled.div`
  text-align: center;
  padding: 4rem 2rem;
  background: rgba(255, 255, 255, 0.95);
  backdrop-filter: blur(20px);
  border-radius: 24px;
  box-shadow: 
    0 32px 64px -12px rgba(0, 0, 0, 0.15),
    0 0 0 1px rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.2);
  animation: ${fadeIn} 0.6s ease-out;
  position: relative;
  overflow: hidden;

  &amp;amp;::before {
    content: '';
    position: absolute;
    top: -50%;
    left: -50%;
    width: 200%;
    height: 200%;
    background: 
      radial-gradient(circle, rgba(102, 126, 234, 0.1) 0%, transparent 50%);
    animation: ${float} 6s ease-in-out infinite;
  }
`;

const EmptyStateIcon = styled.div`
  font-size: 4rem;
  margin-bottom: 1.5rem;
  display: block;
  background: linear-gradient(135deg, #667eea, #764ba2);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: ${float} 3s ease-in-out infinite;
`;

const EmptyStateTitle = styled.h3`
  font-size: 1.75rem;
  font-weight: 700;
  background: linear-gradient(135deg, #1f2937, #374151);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  margin-bottom: 1rem;
`;

const EmptyStateText = styled.p`
  font-size: 1.125rem;
  color: #6b7280;
  margin-bottom: 1.5rem;
  line-height: 1.6;
  max-width: 400px;
  margin-left: auto;
  margin-right: auto;
`;

const EmptyStateNote = styled.p`
  font-size: 0.925rem;
  color: #9ca3af;
  font-style: italic;
  background: rgba(102, 126, 234, 0.05);
  padding: 1rem 1.5rem;
  border-radius: 12px;
  border: 1px solid rgba(102, 126, 234, 0.1);
  max-width: 500px;
  margin: 0 auto;
`;

const StatsBar = styled.div`
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 1rem;
  margin-top: 2rem;
  padding: 1.5rem;
  background: rgba(255, 255, 255, 0.6);
  backdrop-filter: blur(10px);
  border-radius: 16px;
  border: 1px solid rgba(255, 255, 255, 0.3);
`;

const StatItem = styled.div`
  text-align: center;
  padding: 0.5rem;
`;

const StatValue = styled.div`
  font-size: 1.5rem;
  font-weight: 800;
  background: linear-gradient(135deg, #667eea, #764ba2);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
`;

const StatLabel = styled.div`
  font-size: 0.75rem;
  color: #6b7280;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-top: 0.25rem;
`;

// --- Helper Functions for IP Conversion ---
const ipToNumber = (ip) =&amp;gt; {
  return ip.split('.').reduce((acc, octet) =&amp;gt; (acc &amp;lt;&amp;lt; 8) + parseInt(octet, 10), 0) &amp;gt;&amp;gt;&amp;gt; 0;
};

const numberToIp = (num) =&amp;gt; {
  return [
    (num &amp;gt;&amp;gt;&amp;gt; 24) &amp;amp; 0xFF,
    (num &amp;gt;&amp;gt;&amp;gt; 16) &amp;amp; 0xFF,
    (num &amp;gt;&amp;gt;&amp;gt; 8) &amp;amp; 0xFF,
    num &amp;amp; 0xFF
  ].join('.');
};

// --- IP Card Component ---
const IpCard = ({ ipData, featureFlagName, onTestFlag }) =&amp;gt; {
  const { id, ipAddress, testResult } = ipData;

  const handleTestClick = () =&amp;gt; {
    onTestFlag(id, ipAddress);
  };

  return (
    &amp;lt;IpCardContainer&amp;gt;
      &amp;lt;IpCardHeader&amp;gt;🌐 {ipAddress}&amp;lt;/IpCardHeader&amp;gt;
      &amp;lt;IpCardDetail&amp;gt;
        &amp;lt;IpCardLabel&amp;gt;Status:&amp;lt;/IpCardLabel&amp;gt;
        {testResult ? (
          testResult.isLoading ? (
            &amp;lt;StatusBadge type="loading"&amp;gt;
              &amp;lt;Spinner style={{ width: '1rem', height: '1rem', borderTopColor: '#2563eb' }} /&amp;gt;
              Testing...
            &amp;lt;/StatusBadge&amp;gt;
          ) : testResult.error ? (
            &amp;lt;StatusBadge type="error"&amp;gt;❌ Error&amp;lt;/StatusBadge&amp;gt;
          ) : testResult.enabled ? (
            &amp;lt;StatusBadge type="enabled"&amp;gt;✅ Enabled&amp;lt;/StatusBadge&amp;gt;
          ) : (
            &amp;lt;StatusBadge type="disabled"&amp;gt;❌ Disabled&amp;lt;/StatusBadge&amp;gt;
          )
        ) : (
          &amp;lt;StatusBadge&amp;gt;⏳ Not Tested&amp;lt;/StatusBadge&amp;gt;
        )}
      &amp;lt;/IpCardDetail&amp;gt;
      {testResult &amp;amp;&amp;amp; testResult.timestamp &amp;amp;&amp;amp; (
        &amp;lt;IpCardDetail&amp;gt;
          &amp;lt;IpCardLabel&amp;gt;Last Test:&amp;lt;/IpCardLabel&amp;gt;
          &amp;lt;IpCardValue&amp;gt;{testResult.timestamp}&amp;lt;/IpCardValue&amp;gt;
        &amp;lt;/IpCardDetail&amp;gt;
      )}
      &amp;lt;TestButton onClick={handleTestClick} disabled={testResult?.isLoading}&amp;gt;
        {testResult?.isLoading ? (
          &amp;lt;&amp;gt;
            &amp;lt;Spinner style={{ width: '1rem', height: '1rem' }} /&amp;gt;
            Testing...
          &amp;lt;/&amp;gt;
        ) : (
          &amp;lt;&amp;gt;
            🧪 Test Flag
          &amp;lt;/&amp;gt;
        )}
      &amp;lt;/TestButton&amp;gt;
    &amp;lt;/IpCardContainer&amp;gt;
  );
};

// --- Main IP Range Simulator Component ---
const IPRangeSimulator = () =&amp;gt; {
  const [featureFlagName, setFeatureFlagName] = useState('geo-targeted-feature');
  const [startIp, setStartIp] = useState('192.168.1.1');
  const [endIp, setEndIp] = useState('192.168.1.10');
  const [discreteIp, setDiscreteIp] = useState('');
  const [ipCards, setIpCards] = useState([]);
  const [errorMessage, setErrorMessage] = useState('');

  const unleashClient = useUnleashClient();
  const updateContext = useUnleashContext();

  // Calculate stats
  const stats = {
    total: ipCards.length,
    tested: ipCards.filter(card =&amp;gt; card.testResult &amp;amp;&amp;amp; !card.testResult.isLoading).length,
    enabled: ipCards.filter(card =&amp;gt; card.testResult &amp;amp;&amp;amp; card.testResult.enabled &amp;amp;&amp;amp; !card.testResult.error).length,
    disabled: ipCards.filter(card =&amp;gt; card.testResult &amp;amp;&amp;amp; !card.testResult.enabled &amp;amp;&amp;amp; !card.testResult.error).length,
  };

  const addRangeCards = useCallback(() =&amp;gt; {
    setErrorMessage('');
    try {
      const startNum = ipToNumber(startIp);
      const endNum = ipToNumber(endIp);

      if (isNaN(startNum) || isNaN(endNum) || startNum &amp;gt; endNum) {
        setErrorMessage('Invalid IP range. Please enter valid start and end IPs.');
        return;
      }

      const newCards = [];
      for (let i = startNum; i &amp;lt;= endNum; i++) {
        newCards.push({
          id: crypto.randomUUID(),
          ipAddress: numberToIp(i),
          testResult: null,
        });
        if (newCards.length &amp;gt; 100) {
          setErrorMessage('Too many IPs in range. Max 100 cards allowed per range generation.');
          break;
        }
      }
      setIpCards(prevCards =&amp;gt; [...prevCards, ...newCards]);
      setStartIp('');
      setEndIp('');
    } catch (e) {
      setErrorMessage('Error parsing IP range. Please check format.');
      console.error(e);
    }
  }, [startIp, endIp]);

  const addDiscreteCard = useCallback(() =&amp;gt; {
    setErrorMessage('');
    if (!discreteIp || !/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(discreteIp)) {
      setErrorMessage('Please enter a valid discrete IP address.');
      return;
    }
    setIpCards(prevCards =&amp;gt; [
      ...prevCards,
      {
        id: crypto.randomUUID(),
        ipAddress: discreteIp,
        testResult: null,
      },
    ]);
    setDiscreteIp('');
  }, [discreteIp]);

  const handleTestFlag = useCallback(async (cardId, ipAddress) =&amp;gt; {
    setIpCards(prevCards =&amp;gt;
      prevCards.map(card =&amp;gt;
        card.id === cardId ? { ...card, testResult: { ...card.testResult, isLoading: true, error: false } } : card
      )
    );
    setErrorMessage('');

    try {
      await updateContext({
        remoteAddress: ipAddress,
        userId: 'simulator-user-' + ipAddress.replace(/\./g, '-'),
      });
      await new Promise((resolve) =&amp;gt; setTimeout(resolve, 100));

      const isEnabled = unleashClient.isEnabled(featureFlagName);

      setIpCards(prevCards =&amp;gt;
        prevCards.map(card =&amp;gt;
          card.id === cardId
            ? {
                ...card,
                testResult: {
                  ip: ipAddress,
                  enabled: isEnabled,
                  timestamp: new Date().toLocaleTimeString(),
                  isLoading: false,
                  error: false,
                },
              }
            : card
        )
      );
    } catch (error) {
      console.error(`Error testing flag for IP ${ipAddress}:`, error);
      setIpCards(prevCards =&amp;gt;
        prevCards.map(card =&amp;gt;
          card.id === cardId
            ? {
                ...card,
                testResult: {
                  ip: ipAddress,
                  enabled: false,
                  timestamp: new Date().toLocaleTimeString(),
                  isLoading: false,
                  error: true,
                },
              }
            : card
        )
      );
      setErrorMessage(`Failed to test flag for ${ipAddress}. Check console.`);
    }
  }, [featureFlagName, updateContext, unleashClient]);

  return (
    &amp;lt;Container&amp;gt;
      &amp;lt;MaxWidthWrapper&amp;gt;
        {/* Header */}
        &amp;lt;Card&amp;gt;
          &amp;lt;Header&amp;gt;
            &amp;lt;Title&amp;gt;🗺️ Unleash IP Strategy Simulator&amp;lt;/Title&amp;gt;
            &amp;lt;Subtitle&amp;gt;
              Generate and test feature flag status for multiple IP addresses with modern, intuitive interface.
            &amp;lt;/Subtitle&amp;gt;
          &amp;lt;/Header&amp;gt;
        &amp;lt;/Card&amp;gt;

        {/* Configuration Panel */}
        &amp;lt;Card&amp;gt;
          &amp;lt;SectionTitle&amp;gt;⚙️ Configuration &amp;amp; IP Generation&amp;lt;/SectionTitle&amp;gt;

          &amp;lt;ConfigGrid&amp;gt;
            &amp;lt;InputField&amp;gt;
              &amp;lt;Label htmlFor="flagName"&amp;gt;🚩 Feature Flag Name&amp;lt;/Label&amp;gt;
              &amp;lt;Input
                id="flagName"
                type="text"
                value={featureFlagName}
                onChange={(e) =&amp;gt; setFeatureFlagName(e.target.value)}
                placeholder="e.g., geo-targeted-feature"
              /&amp;gt;
            &amp;lt;/InputField&amp;gt;

            &amp;lt;InputGroup&amp;gt;
              &amp;lt;InputField&amp;gt;
                &amp;lt;Label htmlFor="startIp"&amp;gt;🚀 Start IP (Range)&amp;lt;/Label&amp;gt;
                &amp;lt;Input
                  id="startIp"
                  type="text"
                  value={startIp}
                  onChange={(e) =&amp;gt; setStartIp(e.target.value)}
                  placeholder="e.g., 192.168.1.1"
                /&amp;gt;
              &amp;lt;/InputField&amp;gt;
              &amp;lt;InputField&amp;gt;
                &amp;lt;Label htmlFor="endIp"&amp;gt;🏁 End IP (Range)&amp;lt;/Label&amp;gt;
                &amp;lt;Input
                  id="endIp"
                  type="text"
                  value={endIp}
                  onChange={(e) =&amp;gt; setEndIp(e.target.value)}
                  placeholder="e.g., 192.168.1.10"
                /&amp;gt;
              &amp;lt;/InputField&amp;gt;
              &amp;lt;Button onClick={addRangeCards}&amp;gt;
                📊 Generate Range
              &amp;lt;/Button&amp;gt;
            &amp;lt;/InputGroup&amp;gt;

            &amp;lt;DiscreteInputGroup&amp;gt;
              &amp;lt;InputField&amp;gt;
                &amp;lt;Label htmlFor="discreteIp"&amp;gt;🎯 Discrete IP Address&amp;lt;/Label&amp;gt;
                &amp;lt;Input

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;DiscreteInputGroup&amp;gt;
              &amp;lt;InputField&amp;gt;
                &amp;lt;Label htmlFor="discreteIp"&amp;gt;🎯 Discrete IP Address&amp;lt;/Label&amp;gt;
                &amp;lt;Input
                  id="discreteIp"
                  type="text"
                  value={discreteIp}
                  onChange={(e) =&amp;gt; setDiscreteIp(e.target.value)}
                  placeholder="e.g., 203.0.113.45"
                /&amp;gt;
              &amp;lt;/InputField&amp;gt;
              &amp;lt;Button onClick={addDiscreteCard}&amp;gt;
                ➕ Add IP
              &amp;lt;/Button&amp;gt;
            &amp;lt;/DiscreteInputGroup&amp;gt;
          &amp;lt;/ConfigGrid&amp;gt;

          {errorMessage &amp;amp;&amp;amp; (
            &amp;lt;ErrorMessage&amp;gt;{errorMessage}&amp;lt;/ErrorMessage&amp;gt;
          )}
        &amp;lt;/Card&amp;gt;

        {/* Stats Bar */}
        {ipCards.length &amp;gt; 0 &amp;amp;&amp;amp; (
          &amp;lt;StatsBar&amp;gt;
            &amp;lt;StatItem&amp;gt;
              &amp;lt;StatValue&amp;gt;{stats.total}&amp;lt;/StatValue&amp;gt;
              &amp;lt;StatLabel&amp;gt;Total IPs&amp;lt;/StatLabel&amp;gt;
            &amp;lt;/StatItem&amp;gt;
            &amp;lt;StatItem&amp;gt;
              &amp;lt;StatValue&amp;gt;{stats.tested}&amp;lt;/StatValue&amp;gt;
              &amp;lt;StatLabel&amp;gt;Tested&amp;lt;/StatLabel&amp;gt;
            &amp;lt;/StatItem&amp;gt;
            &amp;lt;StatItem&amp;gt;
              &amp;lt;StatValue&amp;gt;{stats.enabled}&amp;lt;/StatValue&amp;gt;
              &amp;lt;StatLabel&amp;gt;Enabled&amp;lt;/StatLabel&amp;gt;
            &amp;lt;/StatItem&amp;gt;
            &amp;lt;StatItem&amp;gt;
              &amp;lt;StatValue&amp;gt;{stats.disabled}&amp;lt;/StatValue&amp;gt;
              &amp;lt;StatLabel&amp;gt;Disabled&amp;lt;/StatLabel&amp;gt;
            &amp;lt;/StatItem&amp;gt;
          &amp;lt;/StatsBar&amp;gt;
        )}

        {/* IP Cards Display */}
        {ipCards.length &amp;gt; 0 ? (
          &amp;lt;IpCardsGrid&amp;gt;
            {ipCards.map((ipData) =&amp;gt; (
              &amp;lt;IpCard
                key={ipData.id}
                ipData={ipData}
                featureFlagName={featureFlagName}
                onTestFlag={handleTestFlag}
              /&amp;gt;
            ))}
          &amp;lt;/IpCardsGrid&amp;gt;
        ) : (
          &amp;lt;EmptyState&amp;gt;
            &amp;lt;EmptyStateIcon&amp;gt;🚀&amp;lt;/EmptyStateIcon&amp;gt;
            &amp;lt;EmptyStateTitle&amp;gt;Ready to Start Testing&amp;lt;/EmptyStateTitle&amp;gt;
            &amp;lt;EmptyStateText&amp;gt;
              Enter an IP range or discrete IP address above to generate cards and begin testing your feature flags.
            &amp;lt;/EmptyStateText&amp;gt;
            &amp;lt;EmptyStateNote&amp;gt;
              💡 Ensure your feature flag "{featureFlagName}" has an "IP Address" strategy configured in Unleash for accurate testing results.
            &amp;lt;/EmptyStateNote&amp;gt;
          &amp;lt;/EmptyState&amp;gt;
        )}
      &amp;lt;/MaxWidthWrapper&amp;gt;
    &amp;lt;/Container&amp;gt;
  );
};

export default IPRangeSimulator;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>iptester2</title>
      <dc:creator>Kishan Srivastava</dc:creator>
      <pubDate>Mon, 30 Jun 2025 09:38:08 +0000</pubDate>
      <link>https://dev.to/superman01/iptester2-4lhi</link>
      <guid>https://dev.to/superman01/iptester2-4lhi</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState, useCallback } from 'react';
import { useUnleashContext, useUnleashClient } from '@unleash/proxy-client-react';
import styled, { keyframes, css } from 'styled-components';

// --- Styled Components ---

const fadeIn = keyframes`
  from { opacity: 0; transform: translateY(-10px); }
  to { opacity: 1; transform: translateY(0); }
`;

const spin = keyframes`
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
`;

const Container = styled.div`
  font-family: 'Inter', sans-serif;
  background-color: #f8fafc; /* Tailwind gray-50 */
  min-height: 100vh;
  padding: 2rem;
  display: flex;
  justify-content: center;
  align-items: flex-start;
`;

const MaxWidthWrapper = styled.div`
  max-width: 1200px; /* Increased max-width for more cards */
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 1.5rem; /* gap-6 */
`;

const Card = styled.div`
  background-color: white;
  border-radius: 0.75rem; /* rounded-xl */
  box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); /* shadow-xl */
  padding: 1.5rem; /* p-6 */
  border: 1px solid #e2e8f0; /* border-gray-200 */
`;

const Header = styled.div`
  text-align: center;
  margin-bottom: 1rem; /* mb-4 */
`;

const Title = styled.h1`
  font-size: 1.875rem; /* text-3xl */
  font-weight: 700; /* font-bold */
  color: #1a202c; /* gray-900 */
  margin-bottom: 0.5rem; /* mb-2 */
`;

const Subtitle = styled.p`
  font-size: 1.125rem; /* text-lg */
  color: #4a5568; /* gray-600 */
`;

const SectionTitle = styled.h2`
  font-size: 1.5rem; /* text-2xl */
  font-weight: 600; /* font-semibold */
  color: #2d3748; /* gray-800 */
  margin-bottom: 1rem; /* mb-4 */
`;

const InputGroup = styled.div`
  display: flex;
  flex-direction: column;
  gap: 1rem; /* gap-4 */
  @media (min-width: 640px) { /* sm */
    flex-direction: row;
    align-items: flex-end;
  }
`;

const InputField = styled.div`
  flex: 1;
`;

const Label = styled.label`
  display: block;
  font-size: 0.875rem; /* text-sm */
  font-weight: 500; /* font-medium */
  color: #2d3748; /* gray-700 */
  margin-bottom: 0.25rem; /* mb-1 */
`;

const Input = styled.input`
  width: 100%;
  padding: 0.75rem 1rem; /* px-4 py-3 */
  border: 1px solid #cbd5e0; /* border-gray-300 */
  border-radius: 0.5rem; /* rounded-lg */
  font-size: 1rem; /* text-base */
  color: #2d3748; /* text-gray-800 */
  box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); /* shadow-sm */
  &amp;amp;:focus {
    outline: none;
    border-color: #3b82f6; /* border-blue-500 */
    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3); /* ring-2 ring-blue-300 */
  }
`;

const Button = styled.button`
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem; /* gap-2 */
  padding: 0.75rem 1.5rem; /* px-6 py-3 */
  background-color: #3b82f6; /* blue-500 */
  color: white;
  font-weight: 600; /* font-semibold */
  border-radius: 0.5rem; /* rounded-lg */
  border: none;
  cursor: pointer;
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); /* shadow-md */
  transition: background-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
  white-space: nowrap; /* Prevent text wrapping */

  &amp;amp;:hover {
    background-color: #2563eb; /* blue-600 */
    box-shadow: 0 6px 8px -1px rgba(0, 0, 0, 0.15), 0 4px 6px -2px rgba(0, 0, 0, 0.08);
  }

  &amp;amp;:disabled {
    background-color: #9ca3af; /* gray-400 */
    cursor: not-allowed;
    box-shadow: none;
  }
`;

const Spinner = styled.div`
  border: 4px solid rgba(255, 255, 255, 0.3);
  border-top: 4px solid white;
  border-radius: 50%;
  width: 1.5rem; /* h-6 w-6 */
  height: 1.5rem; /* h-6 w-6 */
  animation: ${spin} 1s linear infinite;
`;

const ErrorMessage = styled.p`
  color: #dc2626; /* red-600 */
  font-size: 0.875rem; /* text-sm */
  margin-top: 0.75rem; /* mt-3 */
  text-align: center;
`;

const IpCardsGrid = styled.div`
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); /* Responsive grid */
  gap: 1.5rem; /* gap-6 */
  margin-top: 1.5rem; /* mt-6 */
`;

const IpCardContainer = styled.div`
  background-color: white;
  border-radius: 0.75rem; /* rounded-xl */
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); /* shadow-md */
  padding: 1.25rem; /* p-5 */
  border: 1px solid #e2e8f0; /* border-gray-200 */
  display: flex;
  flex-direction: column;
  gap: 0.75rem; /* gap-3 */
  animation: ${fadeIn} 0.5s ease-out;
`;

const IpCardHeader = styled.h3`
  font-size: 1.25rem; /* text-xl */
  font-weight: 600; /* font-semibold */
  color: #2d3748; /* gray-800 */
  margin-bottom: 0.5rem;
`;

const IpCardDetail = styled.div`
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 0.9375rem; /* text-base */
  color: #4a5568; /* gray-600 */
`;

const IpCardLabel = styled.span`
  font-weight: 500;
`;

const IpCardValue = styled.span`
  font-weight: 700;
  color: #1a202c;
`;

const StatusBadge = styled.span`
  display: inline-flex;
  padding: 0.25rem 0.75rem; /* px-3 py-1 */
  border-radius: 9999px; /* rounded-full */
  font-size: 0.875rem; /* text-sm */
  font-weight: 600; /* font-semibold */
  ${(props) =&amp;gt; {
    switch (props.type) {
      case 'enabled':
        return css`
          background-color: #dcfce7; /* green-100 */
          color: #16a34a; /* green-700 */
        `;
      case 'disabled':
        return css`
          background-color: #fee2e2; /* red-100 */
          color: #dc2626; /* red-700 */
        `;
      case 'loading':
        return css`
          background-color: #eff6ff; /* blue-50 */
          color: #2563eb; /* blue-600 */
        `;
      case 'error':
        return css`
          background-color: #fee2e2; /* red-100 */
          color: #dc2626; /* red-700 */
        `;
      default:
        return css`
          background-color: #e2e8f0;
          color: #4a5568;
        `;
    }
  }}
`;

const TestButton = styled(Button)`
  width: 100%;
  margin-top: 1rem; /* mt-4 */
  background-color: #4f46e5; /* indigo-600 */
  &amp;amp;:hover {
    background-color: #4338ca; /* indigo-700 */
  }
`;

const EmptyState = styled.div`
  text-align: center;
  padding: 3rem; /* p-12 */
  background-color: white;
  border-radius: 0.75rem; /* rounded-xl */
  box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); /* shadow-xl */
  border: 1px solid #e2e8f0; /* border-gray-200 */
  animation: ${fadeIn} 0.5s ease-out;
`;

const EmptyStateIcon = styled.span`
  font-size: 3rem; /* text-5xl */
  margin-bottom: 1rem; /* mb-4 */
  display: block;
`;

const EmptyStateTitle = styled.h3`
  font-size: 1.5rem; /* text-2xl */
  font-weight: 600; /* font-semibold */
  color: #2d3748; /* gray-800 */
  margin-bottom: 0.5rem; /* mb-2 */
`;

const EmptyStateText = styled.p`
  font-size: 1rem; /* text-base */
  color: #4a5568; /* gray-600 */
  margin-bottom: 1rem; /* mb-4 */
`;

const EmptyStateNote = styled.p`
  font-size: 0.875rem; /* text-sm */
  color: #718096; /* gray-500 */
  font-style: italic;
`;

// --- Helper Functions for IP Conversion ---
// These are crucial for handling IP ranges

const ipToNumber = (ip) =&amp;gt; {
  return ip.split('.').reduce((acc, octet) =&amp;gt; (acc &amp;lt;&amp;lt; 8) + parseInt(octet, 10), 0) &amp;gt;&amp;gt;&amp;gt; 0; // &amp;gt;&amp;gt;&amp;gt; 0 converts to unsigned 32-bit
};

const numberToIp = (num) =&amp;gt; {
  return [
    (num &amp;gt;&amp;gt;&amp;gt; 24) &amp;amp; 0xFF,
    (num &amp;gt;&amp;gt;&amp;gt; 16) &amp;amp; 0xFF,
    (num &amp;gt;&amp;gt;&amp;gt; 8) &amp;amp; 0xFF,
    num &amp;amp; 0xFF
  ].join('.');
};

// --- IP Card Component ---
// This component represents a single IP address card

const IpCard = ({ ipData, featureFlagName, onTestFlag }) =&amp;gt; {
  const { id, ipAddress, testResult } = ipData;

  const handleTestClick = () =&amp;gt; {
    onTestFlag(id, ipAddress);
  };

  return (
    &amp;lt;IpCardContainer&amp;gt;
      &amp;lt;IpCardHeader&amp;gt;{ipAddress}&amp;lt;/IpCardHeader&amp;gt;
      &amp;lt;IpCardDetail&amp;gt;
        &amp;lt;IpCardLabel&amp;gt;Status:&amp;lt;/IpCardLabel&amp;gt;
        {testResult ? (
          testResult.isLoading ? (
            &amp;lt;StatusBadge type="loading"&amp;gt;
              &amp;lt;Spinner style={{ width: '1rem', height: '1rem', borderTopColor: '#2563eb' }} /&amp;gt; Testing...
            &amp;lt;/StatusBadge&amp;gt;
          ) : testResult.error ? (
            &amp;lt;StatusBadge type="error"&amp;gt;❌ Error&amp;lt;/StatusBadge&amp;gt;
          ) : testResult.enabled ? (
            &amp;lt;StatusBadge type="enabled"&amp;gt;✅ Enabled&amp;lt;/StatusBadge&amp;gt;
          ) : (
            &amp;lt;StatusBadge type="disabled"&amp;gt;❌ Disabled&amp;lt;/StatusBadge&amp;gt;
          )
        ) : (
          &amp;lt;StatusBadge&amp;gt;Not Tested&amp;lt;/StatusBadge&amp;gt;
        )}
      &amp;lt;/IpCardDetail&amp;gt;
      {testResult &amp;amp;&amp;amp; testResult.timestamp &amp;amp;&amp;amp; (
        &amp;lt;IpCardDetail&amp;gt;
          &amp;lt;IpCardLabel&amp;gt;Last Test:&amp;lt;/IpCardLabel&amp;gt;
          &amp;lt;IpCardValue&amp;gt;{testResult.timestamp}&amp;lt;/IpCardValue&amp;gt;
        &amp;lt;/IpCardDetail&amp;gt;
      )}
      &amp;lt;TestButton onClick={handleTestClick} disabled={testResult?.isLoading}&amp;gt;
        {testResult?.isLoading ? 'Testing...' : 'Test Flag'}
      &amp;lt;/TestButton&amp;gt;
    &amp;lt;/IpCardContainer&amp;gt;
  );
};

// --- Main IP Range Simulator Component ---

const IPRangeSimulator = () =&amp;gt; {
  const [featureFlagName, setFeatureFlagName] = useState('geo-targeted-feature');
  const [startIp, setStartIp] = useState('192.168.1.1');
  const [endIp, setEndIp] = useState('192.168.1.10');
  const [discreteIp, setDiscreteIp] = useState('');
  const [ipCards, setIpCards] = useState([]); // Array to hold all IP card data
  const [errorMessage, setErrorMessage] = useState('');

  const unleashClient = useUnleashClient();
  const updateContext = useUnleashContext();

  // Function to add IPs from a range
  const addRangeCards = useCallback(() =&amp;gt; {
    setErrorMessage('');
    try {
      const startNum = ipToNumber(startIp);
      const endNum = ipToNumber(endIp);

      if (isNaN(startNum) || isNaN(endNum) || startNum &amp;gt; endNum) {
        setErrorMessage('Invalid IP range. Please enter valid start and end IPs.');
        return;
      }

      const newCards = [];
      for (let i = startNum; i &amp;lt;= endNum; i++) {
        newCards.push({
          id: crypto.randomUUID(), // Unique ID for each card
          ipAddress: numberToIp(i),
          testResult: null, // Initial state for test result
        });
        if (newCards.length &amp;gt; 100) { // Limit to prevent too many cards
            setErrorMessage('Too many IPs in range. Max 100 cards allowed per range generation.');
            break;
        }
      }
      setIpCards(prevCards =&amp;gt; [...prevCards, ...newCards]);
      setStartIp(''); // Clear inputs after adding
      setEndIp('');
    } catch (e) {
      setErrorMessage('Error parsing IP range. Please check format.');
      console.error(e);
    }
  }, [startIp, endIp]);

  // Function to add a discrete IP
  const addDiscreteCard = useCallback(() =&amp;gt; {
    setErrorMessage('');
    if (!discreteIp || !/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(discreteIp)) {
      setErrorMessage('Please enter a valid discrete IP address.');
      return;
    }
    setIpCards(prevCards =&amp;gt; [
      ...prevCards,
      {
        id: crypto.randomUUID(),
        ipAddress: discreteIp,
        testResult: null,
      },
    ]);
    setDiscreteIp(''); // Clear input after adding
  }, [discreteIp]);

  // Function to handle testing a single IP flag (called by IpCard)
  const handleTestFlag = useCallback(async (cardId, ipAddress) =&amp;gt; {
    setIpCards(prevCards =&amp;gt;
      prevCards.map(card =&amp;gt;
        card.id === cardId ? { ...card, testResult: { ...card.testResult, isLoading: true, error: false } } : card
      )
    );
    setErrorMessage(''); // Clear general error message

    try {
      await updateContext({
        remoteAddress: ipAddress,
        userId: 'simulator-user-' + ipAddress.replace(/\./g, '-'), // Ensure unique userId per IP test
      });
      await new Promise((resolve) =&amp;gt; setTimeout(resolve, 100)); // Small delay

      const isEnabled = unleashClient.isEnabled(featureFlagName);

      setIpCards(prevCards =&amp;gt;
        prevCards.map(card =&amp;gt;
          card.id === cardId
            ? {
                ...card,
                testResult: {
                  ip: ipAddress,
                  enabled: isEnabled,
                  timestamp: new Date().toLocaleTimeString(),
                  isLoading: false,
                  error: false,
                },
              }
            : card
        )
      );
    } catch (error) {
      console.error(`Error testing flag for IP ${ipAddress}:`, error);
      setIpCards(prevCards =&amp;gt;
        prevCards.map(card =&amp;gt;
          card.id === cardId
            ? {
                ...card,
                testResult: {
                  ip: ipAddress,
                  enabled: false,
                  timestamp: new Date().toLocaleTimeString(),
                  isLoading: false,
                  error: true,
                },
              }
            : card
        )
      );
      setErrorMessage(`Failed to test flag for ${ipAddress}. Check console.`);
    }
  }, [featureFlagName, updateContext, unleashClient]);

  return (
    &amp;lt;Container&amp;gt;
      &amp;lt;MaxWidthWrapper&amp;gt;
        {/* Header */}
        &amp;lt;Card&amp;gt;
          &amp;lt;Header&amp;gt;
            &amp;lt;Title&amp;gt;🗺️ Unleash IP Strategy Simulator&amp;lt;/Title&amp;gt;
            &amp;lt;Subtitle&amp;gt;
              Generate and test feature flag status for multiple IP addresses.
            &amp;lt;/Subtitle&amp;gt;
          &amp;lt;/Header&amp;gt;
        &amp;lt;/Card&amp;gt;

        {/* Configuration Panel */}
        &amp;lt;Card&amp;gt;
          &amp;lt;SectionTitle&amp;gt;Configuration &amp;amp; IP Generation&amp;lt;/SectionTitle&amp;gt;
          &amp;lt;InputField className="mb-4"&amp;gt;
            &amp;lt;Label htmlFor="flagName"&amp;gt;Feature Flag Name&amp;lt;/Label&amp;gt;
            &amp;lt;Input
              id="flagName"
              type="text"
              value={featureFlagName}
              onChange={(e) =&amp;gt; setFeatureFlagName(e.target.value)}
              placeholder="e.g., geo-targeted-feature"
            /&amp;gt;
          &amp;lt;/InputField&amp;gt;

          &amp;lt;div className="flex flex-col gap-4 mb-4 md:flex-row"&amp;gt;
            &amp;lt;InputField&amp;gt;
              &amp;lt;Label htmlFor="startIp"&amp;gt;Start IP (Range)&amp;lt;/Label&amp;gt;
              &amp;lt;Input
                id="startIp"
                type="text"
                value={startIp}
                onChange={(e) =&amp;gt; setStartIp(e.target.value)}
                placeholder="e.g., 192.168.1.1"
              /&amp;gt;
            &amp;lt;/InputField&amp;gt;
            &amp;lt;InputField&amp;gt;
              &amp;lt;Label htmlFor="endIp"&amp;gt;End IP (Range)&amp;lt;/Label&amp;gt;
              &amp;lt;Input
                id="endIp"
                type="text"
                value={endIp}
                onChange={(e) =&amp;gt; setEndIp(e.target.value)}
                placeholder="e.g., 192.168.1.10"
              /&amp;gt;
            &amp;lt;/InputField&amp;gt;
            &amp;lt;Button onClick={addRangeCards}&amp;gt;
              Generate Range Cards
            &amp;lt;/Button&amp;gt;
          &amp;lt;/div&amp;gt;

          &amp;lt;div className="flex flex-col gap-4 md:flex-row"&amp;gt;
            &amp;lt;InputField&amp;gt;
              &amp;lt;Label htmlFor="discreteIp"&amp;gt;Discrete IP Address&amp;lt;/Label&amp;gt;
              &amp;lt;Input
                id="discreteIp"
                type="text"
                value={discreteIp}
                onChange={(e) =&amp;gt; setDiscreteIp(e.target.value)}
                placeholder="e.g., 203.0.113.45"
              /&amp;gt;
            &amp;lt;/InputField&amp;gt;
            &amp;lt;Button onClick={addDiscreteCard}&amp;gt;
              Add Discrete IP Card
            &amp;lt;/Button&amp;gt;
          &amp;lt;/div&amp;gt;

          {errorMessage &amp;amp;&amp;amp; (
            &amp;lt;ErrorMessage&amp;gt;{errorMessage}&amp;lt;/ErrorMessage&amp;gt;
          )}
        &amp;lt;/Card&amp;gt;

        {/* IP Cards Display */}
        {ipCards.length &amp;gt; 0 ? (
          &amp;lt;IpCardsGrid&amp;gt;
            {ipCards.map((ipData) =&amp;gt; (
              &amp;lt;IpCard
                key={ipData.id}
                ipData={ipData}
                featureFlagName={featureFlagName}
                onTestFlag={handleTestFlag}
              /&amp;gt;
            ))}
          &amp;lt;/IpCardsGrid&amp;gt;
        ) : (
          &amp;lt;EmptyState&amp;gt;
            &amp;lt;EmptyStateIcon&amp;gt;➕&amp;lt;/EmptyStateIcon&amp;gt;
            &amp;lt;EmptyStateTitle&amp;gt;Start by Adding IPs&amp;lt;/EmptyStateTitle&amp;gt;
            &amp;lt;EmptyStateText&amp;gt;
              Enter an IP range or a discrete IP address to generate cards and begin testing.
            &amp;lt;/EmptyStateText&amp;gt;
            &amp;lt;EmptyStateNote&amp;gt;
              Ensure your feature flag "{featureFlagName}" has an "IP Address" strategy configured in Unleash.
            &amp;lt;/EmptyStateNote&amp;gt;
          &amp;lt;/EmptyState&amp;gt;
        )}
      &amp;lt;/MaxWidthWrapper&amp;gt;
    &amp;lt;/Container&amp;gt;
  );
};

export default IPRangeSimulator;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>iptester</title>
      <dc:creator>Kishan Srivastava</dc:creator>
      <pubDate>Mon, 30 Jun 2025 08:36:20 +0000</pubDate>
      <link>https://dev.to/superman01/iptester-ee5</link>
      <guid>https://dev.to/superman01/iptester-ee5</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState, useCallback } from 'react';
import { useUnleashContext, useUnleashClient } from '@unleash/proxy-client-react';
import styled, { keyframes, css } from 'styled-components';

// --- Styled Components ---

const fadeIn = keyframes`
  from { opacity: 0; transform: translateY(-10px); }
  to { opacity: 1; transform: translateY(0); }
`;

const spin = keyframes`
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
`;

const Container = styled.div`
  font-family: 'Inter', sans-serif;
  background-color: #f8fafc; /* Tailwind gray-50 */
  min-height: 100vh;
  padding: 2rem;
  display: flex;
  justify-content: center;
  align-items: flex-start;
`;

const MaxWidthWrapper = styled.div`
  max-width: 56rem; /* max-w-xl (56rem) */
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 1.5rem; /* gap-6 */
`;

const Card = styled.div`
  background-color: white;
  border-radius: 0.75rem; /* rounded-xl */
  box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); /* shadow-xl */
  padding: 1.5rem; /* p-6 */
  border: 1px solid #e2e8f0; /* border-gray-200 */
`;

const Header = styled.div`
  text-align: center;
  margin-bottom: 1rem; /* mb-4 */
`;

const Title = styled.h1`
  font-size: 1.875rem; /* text-3xl */
  font-weight: 700; /* font-bold */
  color: #1a202c; /* gray-900 */
  margin-bottom: 0.5rem; /* mb-2 */
`;

const Subtitle = styled.p`
  font-size: 1.125rem; /* text-lg */
  color: #4a5568; /* gray-600 */
`;

const SectionTitle = styled.h2`
  font-size: 1.5rem; /* text-2xl */
  font-weight: 600; /* font-semibold */
  color: #2d3748; /* gray-800 */
  margin-bottom: 1rem; /* mb-4 */
`;

const InputGroup = styled.div`
  display: flex;
  flex-direction: column;
  gap: 1rem; /* gap-4 */
`;

const Label = styled.label`
  display: block;
  font-size: 0.875rem; /* text-sm */
  font-weight: 500; /* font-medium */
  color: #2d3748; /* gray-700 */
  margin-bottom: 0.25rem; /* mb-1 */
`;

const Input = styled.input`
  width: 100%;
  padding: 0.75rem 1rem; /* px-4 py-3 */
  border: 1px solid #cbd5e0; /* border-gray-300 */
  border-radius: 0.5rem; /* rounded-lg */
  font-size: 1rem; /* text-base */
  color: #2d3748; /* text-gray-800 */
  box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); /* shadow-sm */
  &amp;amp;:focus {
    outline: none;
    border-color: #3b82f6; /* border-blue-500 */
    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3); /* ring-2 ring-blue-300 */
  }
`;

const Button = styled.button`
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem; /* gap-2 */
  padding: 0.75rem 1.5rem; /* px-6 py-3 */
  background-color: #3b82f6; /* blue-500 */
  color: white;
  font-weight: 600; /* font-semibold */
  border-radius: 0.5rem; /* rounded-lg */
  border: none;
  cursor: pointer;
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); /* shadow-md */
  transition: background-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;

  &amp;amp;:hover {
    background-color: #2563eb; /* blue-600 */
    box-shadow: 0 6px 8px -1px rgba(0, 0, 0, 0.15), 0 4px 6px -2px rgba(0, 0, 0, 0.08);
  }

  &amp;amp;:disabled {
    background-color: #9ca3af; /* gray-400 */
    cursor: not-allowed;
    box-shadow: none;
  }
`;

const Spinner = styled.div`
  border: 4px solid rgba(255, 255, 255, 0.3);
  border-top: 4px solid white;
  border-radius: 50%;
  width: 1.5rem; /* h-6 w-6 */
  height: 1.5rem; /* h-6 w-6 */
  animation: ${spin} 1s linear infinite;
`;

const ErrorMessage = styled.p`
  color: #dc2626; /* red-600 */
  font-size: 0.875rem; /* text-sm */
  margin-top: 0.75rem; /* mt-3 */
  text-align: center;
`;

const ResultItem = styled.div`
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: 0.5rem; /* py-2 */
  padding-bottom: 0.5rem; /* py-2 */
  border-bottom: 1px solid #f3f4f6; /* border-gray-100 */
  &amp;amp;:last-child {
    border-bottom: none;
  }
`;

const ResultLabel = styled.span`
  color: #4a5568; /* gray-600 */
  font-weight: 500; /* font-medium */
`;

const ResultValue = styled.span`
  color: #1a202c; /* gray-900 */
  font-weight: 700; /* font-bold */
`;

const StatusBadge = styled.span`
  display: inline-flex;
  padding: 0.25rem 0.75rem; /* px-3 py-1 */
  border-radius: 9999px; /* rounded-full */
  font-size: 0.875rem; /* text-sm */
  font-weight: 600; /* font-semibold */
  ${(props) =&amp;gt; {
    switch (props.type) {
      case 'enabled':
        return css`
          background-color: #dcfce7; /* green-100 */
          color: #16a34a; /* green-700 */
        `;
      case 'disabled':
        return css`
          background-color: #fee2e2; /* red-100 */
          color: #dc2626; /* red-700 */
        `;
      case 'error':
        return css`
          background-color: #fee2e2; /* red-100 */
          color: #dc2626; /* red-700 */
        `;
      default:
        return css`
          background-color: #e2e8f0;
          color: #4a5568;
        `;
    }
  }}
`;

const EmptyState = styled.div`
  text-align: center;
  padding: 3rem; /* p-12 */
  background-color: white;
  border-radius: 0.75rem; /* rounded-xl */
  box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); /* shadow-xl */
  border: 1px solid #e2e8f0; /* border-gray-200 */
  animation: ${fadeIn} 0.5s ease-out;
`;

const EmptyStateIcon = styled.span`
  font-size: 3rem; /* text-5xl */
  margin-bottom: 1rem; /* mb-4 */
  display: block;
`;

const EmptyStateTitle = styled.h3`
  font-size: 1.5rem; /* text-2xl */
  font-weight: 600; /* font-semibold */
  color: #2d3748; /* gray-800 */
  margin-bottom: 0.5rem; /* mb-2 */
`;

const EmptyStateText = styled.p`
  font-size: 1rem; /* text-base */
  color: #4a5568; /* gray-600 */
  margin-bottom: 1rem; /* mb-4 */
`;

const EmptyStateNote = styled.p`
  font-size: 0.875rem; /* text-sm */
  color: #718096; /* gray-500 */
  font-style: italic;
`;

// --- Main Component ---

const IPStrategySimulator = () =&amp;gt; {
  const [featureFlagName, setFeatureFlagName] = useState('geo-targeted-feature'); // Default flag name
  const [simulatedIpAddress, setSimulatedIpAddress] = useState('192.168.1.10'); // Default example IP
  const [testResult, setTestResult] = useState(null); // Stores the result: { ip, enabled, timestamp }
  const [isLoading, setIsLoading] = useState(false);
  const [errorMessage, setErrorMessage] = useState('');

  const updateContext = useUnleashContext();
  const unleashClient = useUnleashClient();

  const testIpAddress = useCallback(async () =&amp;gt; {
    setErrorMessage('');
    setTestResult(null);
    setIsLoading(true);

    if (!simulatedIpAddress) {
      setErrorMessage('Please enter a simulated IP address.');
      setIsLoading(false);
      return;
    }
    if (!featureFlagName) {
      setErrorMessage('Please enter the feature flag name.');
      setIsLoading(false);
      return;
    }

    try {
      // Update Unleash context with the simulated IP address
      // For IP strategy, 'remoteAddress' is the key context field.
      await updateContext({
        remoteAddress: simulatedIpAddress,
        // It's good practice to also provide a userId for consistency,
        // even if the strategy doesn't explicitly use it.
        userId: 'simulator-user-' + simulatedIpAddress.replace(/\./g, '-'),
      });

      // Small delay to ensure context updates are processed
      await new Promise((resolve) =&amp;gt; setTimeout(resolve, 100));

      // Get the actual flag status based on the updated context
      const isEnabled = unleashClient.isEnabled(featureFlagName);

      setTestResult({
        ip: simulatedIpAddress,
        enabled: isEnabled,
        timestamp: new Date().toLocaleTimeString(),
      });

    } catch (error) {
      console.error("Error during IP strategy test:", error);
      setErrorMessage("An error occurred during the test. Check console for details.");
      setTestResult({
        ip: simulatedIpAddress,
        enabled: false,
        timestamp: new Date().toLocaleTimeString(),
        error: true,
      });
    } finally {
      setIsLoading(false);
    }
  }, [simulatedIpAddress, featureFlagName, updateContext, unleashClient]);

  return (
    &amp;lt;Container&amp;gt;
      &amp;lt;MaxWidthWrapper&amp;gt;
        {/* Header */}
        &amp;lt;Card&amp;gt;
          &amp;lt;Header&amp;gt;
            &amp;lt;Title&amp;gt;📍 Unleash IP Strategy Simulator&amp;lt;/Title&amp;gt;
            &amp;lt;Subtitle&amp;gt;
              Test feature flag behavior based on simulated IP addresses.
            &amp;lt;/Subtitle&amp;gt;
          &amp;lt;/Header&amp;gt;
        &amp;lt;/Card&amp;gt;

        {/* Configuration Panel */}
        &amp;lt;Card&amp;gt;
          &amp;lt;SectionTitle&amp;gt;Test Configuration&amp;lt;/SectionTitle&amp;gt;
          &amp;lt;InputGroup&amp;gt;
            &amp;lt;div&amp;gt;
              &amp;lt;Label htmlFor="flagName"&amp;gt;Feature Flag Name&amp;lt;/Label&amp;gt;
              &amp;lt;Input
                id="flagName"
                type="text"
                value={featureFlagName}
                onChange={(e) =&amp;gt; setFeatureFlagName(e.target.value)}
                placeholder="e.g., geo-targeted-feature"
              /&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div&amp;gt;
              &amp;lt;Label htmlFor="simulatedIp"&amp;gt;Simulated IP Address&amp;lt;/Label&amp;gt;
              &amp;lt;Input
                id="simulatedIp"
                type="text"
                value={simulatedIpAddress}
                onChange={(e) =&amp;gt; setSimulatedIpAddress(e.target.value)}
                placeholder="e.g., 192.168.1.10 or 203.0.113.50"
              /&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;Button onClick={testIpAddress} disabled={isLoading}&amp;gt;
              {isLoading ? (
                &amp;lt;&amp;gt;
                  &amp;lt;Spinner /&amp;gt;
                  Testing IP...
                &amp;lt;/&amp;gt;
              ) : (
                &amp;lt;&amp;gt;🌐 Test IP Address&amp;lt;/&amp;gt;
              )}
            &amp;lt;/Button&amp;gt;
          &amp;lt;/InputGroup&amp;gt;
          {errorMessage &amp;amp;&amp;amp; (
            &amp;lt;ErrorMessage&amp;gt;{errorMessage}&amp;lt;/ErrorMessage&amp;gt;
          )}
        &amp;lt;/Card&amp;gt;

        {/* Test Result Display */}
        {testResult &amp;amp;&amp;amp; (
          &amp;lt;Card style={{ animation: `${fadeIn} 0.5s ease-out` }}&amp;gt;
            &amp;lt;SectionTitle&amp;gt;Test Result&amp;lt;/SectionTitle&amp;gt;
            &amp;lt;div className="flex flex-col gap-3"&amp;gt;
              &amp;lt;ResultItem&amp;gt;
                &amp;lt;ResultLabel&amp;gt;Simulated IP:&amp;lt;/ResultLabel&amp;gt;
                &amp;lt;ResultValue&amp;gt;{testResult.ip}&amp;lt;/ResultValue&amp;gt;
              &amp;lt;/ResultItem&amp;gt;
              &amp;lt;ResultItem&amp;gt;
                &amp;lt;ResultLabel&amp;gt;Feature Flag Status:&amp;lt;/ResultLabel&amp;gt;
                {testResult.error ? (
                  &amp;lt;StatusBadge type="error"&amp;gt;❌ Error&amp;lt;/StatusBadge&amp;gt;
                ) : testResult.enabled ? (
                  &amp;lt;StatusBadge type="enabled"&amp;gt;✅ Enabled&amp;lt;/StatusBadge&amp;gt;
                ) : (
                  &amp;lt;StatusBadge type="disabled"&amp;gt;❌ Disabled&amp;lt;/StatusBadge&amp;gt;
                )}
              &amp;lt;/ResultItem&amp;gt;
              &amp;lt;ResultItem&amp;gt;
                &amp;lt;ResultLabel&amp;gt;Test Time:&amp;lt;/ResultLabel&amp;gt;
                &amp;lt;ResultValue&amp;gt;{testResult.timestamp}&amp;lt;/ResultValue&amp;gt;
              &amp;lt;/ResultItem&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/Card&amp;gt;
        )}

        {/* Initial Empty State */}
        {!testResult &amp;amp;&amp;amp; !isLoading &amp;amp;&amp;amp; !errorMessage &amp;amp;&amp;amp; (
          &amp;lt;EmptyState&amp;gt;
            &amp;lt;EmptyStateIcon&amp;gt;🧪&amp;lt;/EmptyStateIcon&amp;gt;
            &amp;lt;EmptyStateTitle&amp;gt;Ready to Test IP-Based Features&amp;lt;/EmptyStateTitle&amp;gt;
            &amp;lt;EmptyStateText&amp;gt;
              Enter a feature flag name and a simulated IP address, then click "Test IP Address" to see the result.
            &amp;lt;/EmptyStateText&amp;gt;
            &amp;lt;EmptyStateNote&amp;gt;
              Make sure your feature flag "{featureFlagName}" has an "IP Address" strategy configured in Unleash.
            &amp;lt;/EmptyStateNote&amp;gt;
          &amp;lt;/EmptyState&amp;gt;
        )}
      &amp;lt;/MaxWidthWrapper&amp;gt;
    &amp;lt;/Container&amp;gt;
  );
};

export default IPStrategySimulator;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
  </channel>
</rss>
