iptester



This content originally appeared on DEV Community and was authored by Kishan Srivastava

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 */
  &: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;

  &: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);
  }

  &: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 */
  &: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) => {
    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 = () => {
  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 () => {
    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) => 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 (
    <Container>
      <MaxWidthWrapper>
        {/* Header */}
        <Card>
          <Header>
            <Title>📍 Unleash IP Strategy Simulator</Title>
            <Subtitle>
              Test feature flag behavior based on simulated IP addresses.
            </Subtitle>
          </Header>
        </Card>

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

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

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

export default IPStrategySimulator;



This content originally appeared on DEV Community and was authored by Kishan Srivastava