/ src / pages / Home.tsx
Home.tsx
   1  import { useState, useEffect } from "react";
   2  import ClientDashboard from "../components/ShieldedPool";
   3  import { DATA_URL } from "../components/lib/chart/data-url";
   4  
   5  const GovernanceDashboard = () => {
   6    const [activeTab, setActiveTab] = useState<
   7      "parameters" | "proposals" | "validator" | "charts"
   8    >("parameters");
   9    const [darkMode, setDarkMode] = useState(false);
  10    const [paramsData, setParamsData] = useState<any>(null);
  11    const [propsData, setPropsData] = useState<any>(null);
  12    const [validatorData, setValidatorData] = useState<any>(null);
  13    const [loading, setLoading] = useState({
  14      params: true,
  15      props: true,
  16      validator: true,
  17    });
  18    const [error, setError] = useState<{
  19      params: string | null;
  20      props: string | null;
  21      validator: string | null;
  22    }>({ params: null, props: null, validator: null });
  23  
  24    useEffect(() => {
  25      // Check for user's preferred color scheme
  26      const prefersDark = window.matchMedia(
  27        "(prefers-color-scheme: dark)"
  28      ).matches;
  29      setDarkMode(prefersDark);
  30  
  31      // Fetch Parameters Data
  32      const fetchParams = async () => {
  33        try {
  34          const response = await fetch(DATA_URL.protocol_parametersUrl);
  35          if (!response.ok)
  36            throw new Error(`HTTP error! status: ${response.status}`);
  37          const jsonData = await response.json();
  38          setParamsData(jsonData[0]);
  39        } catch (err) {
  40          setError((prev) => ({
  41            ...prev,
  42            params:
  43              err instanceof Error ? err.message : "An unknown error occurred",
  44          }));
  45        } finally {
  46          setLoading((prev) => ({ ...prev, params: false }));
  47        }
  48      };
  49  
  50      // Fetch Proposals Data
  51      const fetchProps = async () => {
  52        try {
  53          // const response = await fetch(
  54          //   "https://raw.githubusercontent.com/ZecHub/zechub-wiki/main/public/data/namada/props.json"
  55          // );
  56          const response = await fetch(DATA_URL.propsDetailsUrl);
  57          if (!response.ok)
  58            throw new Error(`HTTP error! status: ${response.status}`);
  59          const jsonData = await response.json();
  60          setPropsData(jsonData[0]);
  61        } catch (err) {
  62          setError((prev) => ({
  63            ...prev,
  64            props:
  65              err instanceof Error ? err.message : "An unknown error occurred",
  66          }));
  67        } finally {
  68          setLoading((prev) => ({ ...prev, props: false }));
  69        }
  70      };
  71  
  72      // Fetch Validator Data
  73      const fetchValidator = async () => {
  74        try {
  75          const response = await fetch(DATA_URL.zechubUrl);
  76          if (!response.ok)
  77            throw new Error(`HTTP error! status: ${response.status}`);
  78          const jsonData = await response.json();
  79          setValidatorData(jsonData[0]);
  80        } catch (err) {
  81          setError((prev) => ({
  82            ...prev,
  83            validator:
  84              err instanceof Error ? err.message : "An unknown error occurred",
  85          }));
  86        } finally {
  87          setLoading((prev) => ({ ...prev, validator: false }));
  88        }
  89      };
  90  
  91      fetchParams();
  92      fetchProps();
  93      fetchValidator();
  94    }, []);
  95  
  96    const toggleDarkMode = () => {
  97      setDarkMode(!darkMode);
  98    };
  99  
 100    if (loading.params && loading.props) {
 101      return (
 102        <div
 103          className={`flex justify-center items-center h-screen ${
 104            darkMode ? "bg-gray-900" : "bg-gray-100"
 105          }`}
 106        >
 107          <div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-400"></div>
 108        </div>
 109      );
 110    }
 111  
 112    return (
 113      <div
 114        className={`min-h-screen ${
 115          darkMode ? "dark bg-background" : "bg-background"
 116        }`}
 117      >
 118        <div className="min-h-screen text-foreground">
 119          {/* Header Section */}
 120          <header className="bg-card shadow-sm transition-theme fixed w-[100vw] z-[99]">
 121            <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
 122              <div className="flex justify-between items-center">
 123                <div className="flex items-center space-x-4">
 124                  <h1 className="text-2xl font-bold text-foreground">
 125                    Namada Governance Dashboard
 126                  </h1>
 127                </div>
 128                <div className="flex items-center space-x-4">
 129                  <button
 130                    onClick={toggleDarkMode}
 131                    className={`p-2 rounded-full transition-all duration-150 ${
 132                      darkMode
 133                        ? "bg-gray-700 text-yellow-300 hover:bg-gray-600"
 134                        : "bg-gray-200 text-gray-700 hover:bg-gray-300"
 135                    }`}
 136                    aria-label="Toggle dark mode"
 137                  >
 138                    {darkMode ? (
 139                      <span className="w-5 h-5 block">☀️</span>
 140                    ) : (
 141                      <span className="w-5 h-5 block">🌙</span>
 142                    )}
 143                  </button>
 144                </div>
 145              </div>
 146            </div>
 147          </header>
 148  
 149          {/* Main Content */}
 150          <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6 pt-[100px] imd:pt-[68px]">
 151            {/* Tabs Navigation */}
 152            <div className="border-b border-border mb-6 transition-theme">
 153              <nav className="-mb-px flex space-x-8 flex-col imd:flex-row">
 154                <button
 155                  onClick={() => setActiveTab("parameters")}
 156                  className={`whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm mr-0 imd:mr-8 ${
 157                    activeTab === "parameters"
 158                      ? "text-blue-400 border-blue-400"
 159                      : `border-transparent ${
 160                          darkMode
 161                            ? "hover:border-gray-300 text-gray-400 hover:text-gray-300"
 162                            : "hover:border-gray-300 text-gray-600 hover:text-gray-700"
 163                        }`
 164                  }`}
 165                >
 166                  Protocol Parameters
 167                </button>
 168                <button
 169                  onClick={() => setActiveTab("proposals")}
 170                  className={`whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm mr-0 imd:mr-8 ${
 171                    activeTab === "proposals"
 172                      ? "text-blue-400 border-blue-400"
 173                      : `border-transparent ${
 174                          darkMode
 175                            ? "hover:border-gray-300 text-gray-400 hover:text-gray-300"
 176                            : "hover:border-gray-300 text-gray-600 hover:text-gray-700"
 177                        }`
 178                  }`}
 179                >
 180                  Governance Proposals
 181                </button>
 182                <button
 183                  onClick={() => setActiveTab("validator")}
 184                  className={`whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm mr-0 imd:mr-8 ${
 185                    activeTab === "validator"
 186                      ? "text-blue-400 border-blue-400"
 187                      : `border-transparent ${
 188                          darkMode
 189                            ? "hover:border-gray-300 text-gray-400 hover:text-gray-300"
 190                            : "hover:border-gray-300 text-gray-600 hover:text-gray-700"
 191                        }`
 192                  }`}
 193                >
 194                  Validator
 195                </button>
 196                <button
 197                  onClick={() => setActiveTab("charts")}
 198                  className={`whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm ${
 199                    activeTab === "charts"
 200                      ? "text-blue-400 border-blue-400"
 201                      : `border-transparent ${
 202                          darkMode
 203                            ? "hover:border-gray-300 text-gray-400 hover:text-gray-300"
 204                            : "hover:border-gray-300 text-gray-600 hover:text-gray-700"
 205                        }`
 206                  }`}
 207                >
 208                  Charts
 209                </button>
 210              </nav>
 211            </div>
 212  
 213            {/* Tab Content */}
 214            {activeTab === "parameters" ? (
 215              <ParametersTab
 216                data={paramsData}
 217                loading={loading.params}
 218                error={error.params}
 219                darkMode={darkMode}
 220              />
 221            ) : activeTab === "proposals" ? (
 222              <ProposalsTab
 223                data={propsData}
 224                loading={loading.props}
 225                error={error.props}
 226                darkMode={darkMode}
 227              />
 228            ) : activeTab === "validator" ? (
 229              <ValidatorTab
 230                data={validatorData}
 231                loading={loading.validator}
 232                error={error.validator}
 233                darkMode={darkMode}
 234              />
 235            ) : (
 236              <ClientDashboard />
 237            )}
 238          </main>
 239        </div>
 240      </div>
 241    );
 242  };
 243  
 244  const ParametersTab = ({ data, loading, error, darkMode }: any) => {
 245    if (loading) {
 246      return (
 247        <div className="flex justify-center items-center h-64">
 248          <div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-400"></div>
 249        </div>
 250      );
 251    }
 252  
 253    if (error) {
 254      return (
 255        <div
 256          className={`p-4 rounded-lg ${
 257            darkMode ? "bg-gray-800 text-red-400" : "bg-white text-red-600"
 258          }`}
 259        >
 260          <strong>Error:</strong> {error}
 261        </div>
 262      );
 263    }
 264  
 265    if (!data) return null;
 266  
 267    return (
 268      <div className="space-y-6">
 269        {/* Governance Parameters Grid */}
 270        <div className="grid gap-6 md:grid-cols-1 lg:grid-cols-2">
 271          {/* Governance Parameters Card */}
 272          <div
 273            className={`rounded-lg shadow overflow-hidden ${
 274              darkMode ? "bg-gray-800" : "bg-white"
 275            }`}
 276          >
 277            <div
 278              className={`px-6 py-4 border-b ${
 279                darkMode ? "border-gray-700" : "border-gray-200"
 280              }`}
 281            >
 282              <h2
 283                className={`text-lg font-semibold ${
 284                  darkMode ? "text-gray-200" : "text-gray-800"
 285                }`}
 286              >
 287                Governance Parameters
 288              </h2>
 289            </div>
 290            <div className="px-6 py-4">
 291              <div className="space-y-4">
 292                {Object.entries(data.Governance_Parameters[0]).map(
 293                  ([key, value]) => (
 294                    <div key={key} className="flex justify-between">
 295                      <span
 296                        className={`text-sm ${
 297                          darkMode ? "text-gray-300" : "text-gray-600"
 298                        }`}
 299                      >
 300                        {key.replace(/_/g, " ")}
 301                      </span>
 302                      <span
 303                        className={`text-sm font-medium ${
 304                          darkMode ? "text-gray-100" : "text-gray-900"
 305                        }`}
 306                      >
 307                        {typeof value === "number"
 308                          ? value.toLocaleString()
 309                          : String(value)}
 310                      </span>
 311                    </div>
 312                  )
 313                )}
 314              </div>
 315            </div>
 316          </div>
 317  
 318          {/* Public Goods Funding Card */}
 319          <div
 320            className={`rounded-lg shadow overflow-hidden ${
 321              darkMode ? "bg-gray-800" : "bg-white"
 322            }`}
 323          >
 324            <div
 325              className={`px-6 py-4 border-b ${
 326                darkMode ? "border-gray-700" : "border-gray-200"
 327              }`}
 328            >
 329              <h2
 330                className={`text-lg font-semibold ${
 331                  darkMode ? "text-gray-200" : "text-gray-800"
 332                }`}
 333              >
 334                Public Goods Funding
 335              </h2>
 336            </div>
 337            <div className="px-6 py-4">
 338              <div className="space-y-4">
 339                {Object.entries(data.Public_Goods_Funding_Parameters[0]).map(
 340                  ([key, value]) => (
 341                    <div key={key} className="flex justify-between">
 342                      <span
 343                        className={`text-sm ${
 344                          darkMode ? "text-gray-300" : "text-gray-600"
 345                        }`}
 346                      >
 347                        {key.replace(/_/g, " ")}
 348                      </span>
 349                      <span
 350                        className={`text-sm font-medium ${
 351                          darkMode ? "text-gray-100" : "text-gray-900"
 352                        }`}
 353                      >
 354                        {typeof value === "number" ? `${value}` : String(value)}
 355                      </span>
 356                    </div>
 357                  )
 358                )}
 359              </div>
 360            </div>
 361          </div>
 362  
 363          {/* Protocol Parameters Card */}
 364          <div
 365            className={`rounded-lg shadow overflow-hidden ${
 366              darkMode ? "bg-gray-800" : "bg-white"
 367            }`}
 368          >
 369            <div
 370              className={`px-6 py-4 border-b ${
 371                darkMode ? "border-gray-700" : "border-gray-200"
 372              }`}
 373            >
 374              <h2
 375                className={`text-lg font-semibold ${
 376                  darkMode ? "text-gray-200" : "text-gray-800"
 377                }`}
 378              >
 379                Protocol Parameters
 380              </h2>
 381            </div>
 382            <div className="px-6 py-4">
 383              <div className="space-y-4">
 384                {Object.entries(data.Protocol_Parameters[0])
 385                  .filter(
 386                    ([key]) =>
 387                      ![
 388                        "VP_allowlist",
 389                        "Transactions_allowlist",
 390                        "Protocol_Parameters",
 391                        "Implicit_VP_hash",
 392                      ].includes(key)
 393                  )
 394                  .map(([key, value]) => (
 395                    <div key={key} className="flex justify-between">
 396                      <span
 397                        className={`text-sm ${
 398                          darkMode ? "text-gray-300" : "text-gray-600"
 399                        }`}
 400                      >
 401                        {key.replace(/_/g, " ")}
 402                      </span>
 403                      <span
 404                        className={`text-sm font-medium ${
 405                          darkMode ? "text-gray-100" : "text-gray-900"
 406                        }`}
 407                      >
 408                        {typeof value === "boolean"
 409                          ? value
 410                            ? "Yes"
 411                            : "No"
 412                          : String(value)}
 413                      </span>
 414                    </div>
 415                  ))}
 416              </div>
 417            </div>
 418          </div>
 419  
 420          {/* Proof of Stake Card */}
 421          <div
 422            className={`rounded-lg shadow overflow-hidden ${
 423              darkMode ? "bg-gray-800" : "bg-white"
 424            }`}
 425          >
 426            <div
 427              className={`px-6 py-4 border-b ${
 428                darkMode ? "border-gray-700" : "border-gray-200"
 429              }`}
 430            >
 431              <h2
 432                className={`text-lg font-semibold ${
 433                  darkMode ? "text-gray-200" : "text-gray-800"
 434                }`}
 435              >
 436                Proof of Stake
 437              </h2>
 438            </div>
 439            <div className="px-6 py-4">
 440              <div className="space-y-4">
 441                {Object.entries(data.Proof_Of_Stake_Parmeters[0]).map(
 442                  ([key, value]) => (
 443                    <div key={key} className="flex justify-between">
 444                      <span
 445                        className={`text-sm ${
 446                          darkMode ? "text-gray-300" : "text-gray-600"
 447                        }`}
 448                      >
 449                        {key.replace(/_/g, " ")}
 450                      </span>
 451                      <span
 452                        className={`text-sm font-medium ${
 453                          darkMode ? "text-gray-100" : "text-gray-900"
 454                        }`}
 455                      >
 456                        {typeof value === "number" && key.includes("rate")
 457                          ? `${value * 100}%`
 458                          : String(value)}
 459                      </span>
 460                    </div>
 461                  )
 462                )}
 463              </div>
 464            </div>
 465          </div>
 466        </div>
 467  
 468        {/* Allowlists Section */}
 469        <div
 470          className={`rounded-lg shadow overflow-hidden ${
 471            darkMode ? "bg-gray-800" : "bg-white"
 472          }`}
 473        >
 474          <div
 475            className={`px-6 py-4 border-b ${
 476              darkMode ? "border-gray-700" : "border-gray-200"
 477            }`}
 478          >
 479            <h2
 480              className={`text-lg font-semibold ${
 481                darkMode ? "text-gray-200" : "text-gray-800"
 482              }`}
 483            >
 484              Allowlists
 485            </h2>
 486          </div>
 487          <div className="px-6 py-4">
 488            <div className="grid gap-6 md:grid-cols-2">
 489              {/* VP Allowlist */}
 490              <div>
 491                <h3
 492                  className={`text-md font-medium mb-3 ${
 493                    darkMode ? "text-gray-300" : "text-gray-700"
 494                  }`}
 495                >
 496                  VP Allowlist
 497                </h3>
 498                <div
 499                  className={`p-4 rounded-md max-h-60 overflow-y-auto ${
 500                    darkMode ? "bg-gray-700" : "bg-gray-100"
 501                  }`}
 502                >
 503                  {data.Protocol_Parameters[0].VP_allowlist.map(
 504                    (hash: string, i: number) => (
 505                      <div
 506                        key={i}
 507                        className={`text-xs font-mono mb-1 break-all ${
 508                          darkMode ? "text-gray-300" : "text-gray-700"
 509                        }`}
 510                      >
 511                        {hash}
 512                      </div>
 513                    )
 514                  )}
 515                </div>
 516                <h3
 517                  className={`text-md font-medium mt-6 mb-3 ${
 518                    darkMode ? "text-gray-300" : "text-gray-700"
 519                  }`}
 520                >
 521                  Implicit VP hash
 522                </h3>
 523                <div
 524                  className={`p-4 rounded-md max-h-60 overflow-y-auto ${
 525                    darkMode ? "bg-gray-700" : "bg-gray-100"
 526                  }`}
 527                >
 528                  <div
 529                    className={`text-xs font-mono mb-1 break-all ${
 530                      darkMode ? "text-gray-300" : "text-gray-700"
 531                    }`}
 532                  >
 533                    {data.Protocol_Parameters[0].Implicit_VP_hash}
 534                  </div>
 535                </div>
 536              </div>
 537  
 538              {/* Transactions Allowlist */}
 539              <div>
 540                <h3
 541                  className={`text-md font-medium mb-3 ${
 542                    darkMode ? "text-gray-300" : "text-gray-700"
 543                  }`}
 544                >
 545                  Transactions Allowlist
 546                </h3>
 547                <div
 548                  className={`p-4 rounded-md max-h-60 overflow-y-auto ${
 549                    darkMode ? "bg-gray-700" : "bg-gray-100"
 550                  }`}
 551                >
 552                  {data.Protocol_Parameters[0].Transactions_allowlist.map(
 553                    (hash: string, i: number) => (
 554                      <div
 555                        key={i}
 556                        className={`text-xs font-mono mb-1 break-all ${
 557                          darkMode ? "text-gray-300" : "text-gray-700"
 558                        }`}
 559                      >
 560                        {hash}
 561                      </div>
 562                    )
 563                  )}
 564                </div>
 565              </div>
 566            </div>
 567          </div>
 568        </div>
 569      </div>
 570    );
 571  };
 572  
 573  const ProposalsTab = ({ data, loading, error, darkMode }: any) => {
 574    const [expandedId, setExpandedId] = useState<number | null>(null);
 575  
 576    const toggleExpand = (id: number) => {
 577      setExpandedId(expandedId === id ? null : id);
 578    };
 579  
 580    const parseResult = (resultString: string) => {
 581      if (!resultString) return null;
 582  
 583      const addressMatch = resultString.match(/(\d+)\s+addresses/);
 584      const yaysMatch = resultString.match(/(\d+)\s+yays/);
 585      const naysMatch = resultString.match(/(\d+)\s+nays/);
 586      const abstainsMatch = resultString.match(/(\d+)\s+abstains/);
 587  
 588      return {
 589        addresses: addressMatch ? parseInt(addressMatch[1]) : 0,
 590        yays: yaysMatch ? parseInt(yaysMatch[1]) : 0,
 591        nays: naysMatch ? parseInt(naysMatch[1]) : 0,
 592        abstains: abstainsMatch ? parseInt(abstainsMatch[1]) : 0,
 593      };
 594    };
 595  
 596    if (loading) {
 597      return (
 598        <div className="flex justify-center items-center h-64">
 599          <div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-400"></div>
 600        </div>
 601      );
 602    }
 603  
 604    if (error) {
 605      return (
 606        <div
 607          className={`p-4 rounded-lg ${
 608            darkMode ? "bg-gray-800 text-red-400" : "bg-white text-red-600"
 609          }`}
 610        >
 611          <strong>Error:</strong> {error}
 612        </div>
 613      );
 614    }
 615  
 616    if (!data) return null;
 617  
 618    return (
 619      <div className="space-y-6">
 620        {/* Current Epoch */}
 621        <div
 622          className={`rounded-lg shadow px-6 py-4 ${
 623            darkMode ? "bg-gray-800" : "bg-white"
 624          }`}
 625        >
 626          <h2
 627            className={`text-lg font-semibold ${
 628              darkMode ? "text-gray-200" : "text-gray-800"
 629            }`}
 630          >
 631            Current Epoch:{" "}
 632            <span className="font-bold">{data.Last_committed_epoch}</span>
 633          </h2>
 634        </div>
 635  
 636        {/* Proposals Table */}
 637        <div
 638          className={`rounded-lg shadow overflow-hidden ${
 639            darkMode ? "bg-gray-800" : "bg-white"
 640          }`}
 641        >
 642          <div className="overflow-x-auto">
 643            <table
 644              className={`min-w-full divide-y ${
 645                darkMode ? "divide-gray-700" : "divide-gray-200"
 646              }`}
 647            >
 648              <thead className={darkMode ? "bg-gray-700" : "bg-gray-50"}>
 649                <tr>
 650                  <th
 651                    scope="col"
 652                    className={`px-6 py-3 text-left text-xs font-medium uppercase tracking-wider ${
 653                      darkMode ? "text-gray-300" : "text-gray-500"
 654                    }`}
 655                  >
 656                    {"#"}
 657                  </th>
 658                  <th
 659                    scope="col"
 660                    className={`px-6 py-3 text-left text-xs font-medium uppercase tracking-wider ${
 661                      darkMode ? "text-gray-300" : "text-gray-500"
 662                    }`}
 663                  >
 664                    ID
 665                  </th>
 666                  <th
 667                    scope="col"
 668                    className={`px-6 py-3 text-left text-xs font-medium uppercase tracking-wider ${
 669                      darkMode ? "text-gray-300" : "text-gray-500"
 670                    }`}
 671                  >
 672                    Type
 673                  </th>
 674                  <th
 675                    scope="col"
 676                    className={`px-6 py-3 text-left text-xs font-medium uppercase tracking-wider ${
 677                      darkMode ? "text-gray-300" : "text-gray-500"
 678                    }`}
 679                  >
 680                    Author
 681                  </th>
 682                  <th
 683                    scope="col"
 684                    className={`px-6 py-3 text-left text-xs font-medium uppercase tracking-wider ${
 685                      darkMode ? "text-gray-300" : "text-gray-500"
 686                    }`}
 687                  >
 688                    Start Epoch
 689                  </th>
 690                  <th
 691                    scope="col"
 692                    className={`px-6 py-3 text-left text-xs font-medium uppercase tracking-wider ${
 693                      darkMode ? "text-gray-300" : "text-gray-500"
 694                    }`}
 695                  >
 696                    End Epoch
 697                  </th>
 698                  <th
 699                    scope="col"
 700                    className={`px-6 py-3 text-left text-xs font-medium uppercase tracking-wider ${
 701                      darkMode ? "text-gray-300" : "text-gray-500"
 702                    }`}
 703                  >
 704                    Activation Epoch
 705                  </th>
 706                </tr>
 707              </thead>
 708              <tbody
 709                className={`divide-y ${
 710                  darkMode
 711                    ? "bg-gray-800 divide-gray-700"
 712                    : "bg-white divide-gray-200"
 713                }`}
 714              >
 715                {data.Proposal.sort((a: any, b: any) => b.Id - a.Id).map(
 716                  (proposal: any, index: number) => {
 717                    const result = parseResult(proposal.Result);
 718                    const totalVotes = result
 719                      ? result.yays + result.nays + result.abstains
 720                      : 0;
 721                    const yayPercentage =
 722                      result && totalVotes > 0
 723                        ? (result.yays / totalVotes) * 100
 724                        : 0;
 725                    const nayPercentage =
 726                      result && totalVotes > 0
 727                        ? (result.nays / totalVotes) * 100
 728                        : 0;
 729                    const abstainPercentage =
 730                      result && totalVotes > 0
 731                        ? (result.abstains / totalVotes) * 100
 732                        : 0;
 733  
 734                    return (
 735                      <>
 736                        <tr
 737                          key={proposal.Id}
 738                          onClick={() => toggleExpand(proposal.Id)}
 739                          className={`cursor-pointer ${
 740                            darkMode ? "hover:bg-gray-700" : "hover:bg-gray-50"
 741                          }`}
 742                        >
 743                          <td
 744                            className={`px-6 py-4 whitespace-nowrap text-sm font-medium ${
 745                              darkMode ? "text-gray-100" : "text-gray-900"
 746                            }`}
 747                          >
 748                            {index + 1}
 749                          </td>
 750                          <td
 751                            className={`px-6 py-4 whitespace-nowrap text-sm font-medium ${
 752                              darkMode ? "text-gray-100" : "text-gray-900"
 753                            }`}
 754                          >
 755                            {proposal.Id}
 756                          </td>
 757                          <td
 758                            className={`px-6 py-4 whitespace-nowrap text-sm ${
 759                              darkMode ? "text-gray-300" : "text-gray-600"
 760                            }`}
 761                          >
 762                            {proposal.Type}
 763                          </td>
 764                          <td
 765                            className={`px-6 py-4 whitespace-nowrap text-sm font-mono ${
 766                              darkMode ? "text-gray-300" : "text-gray-600"
 767                            }`}
 768                          >
 769                            {proposal.Author}
 770                          </td>
 771                          <td
 772                            className={`px-6 py-4 whitespace-nowrap text-sm ${
 773                              darkMode ? "text-gray-300" : "text-gray-600"
 774                            }`}
 775                          >
 776                            {proposal.Start_Epoch}
 777                          </td>
 778                          <td
 779                            className={`px-6 py-4 whitespace-nowrap text-sm ${
 780                              darkMode ? "text-gray-300" : "text-gray-600"
 781                            }`}
 782                          >
 783                            {proposal.End_Epoch}
 784                          </td>
 785                          <td
 786                            className={`px-6 py-4 whitespace-nowrap text-sm ${
 787                              darkMode ? "text-gray-300" : "text-gray-600"
 788                            }`}
 789                          >
 790                            {proposal.Activation_Epoch}
 791                          </td>
 792                        </tr>
 793                        {expandedId === proposal.Id && (
 794                          <tr key={`content-${proposal.Id}`}>
 795                            <td
 796                              colSpan={7}
 797                              className={`px-6 py-6 max-w-[73rem] ${
 798                                darkMode ? "bg-gray-900" : "bg-gray-50"
 799                              }`}
 800                            >
 801                              {/* Proposal Content */}
 802                              <div className="space-y-4">
 803                                {proposal.Content &&
 804                                  Object.keys(proposal.Content)
 805                                    .reverse()
 806                                    .map((key) => (
 807                                      <div key={key}>
 808                                        <div
 809                                          className={`font-semibold text-sm uppercase mb-1 ${
 810                                            darkMode
 811                                              ? "text-gray-400"
 812                                              : "text-gray-600"
 813                                          }`}
 814                                        >
 815                                          {key.replace(/-/g, " ")}
 816                                        </div>
 817                                        <div
 818                                          className={`text-sm whitespace-pre-wrap ${
 819                                            darkMode
 820                                              ? "text-gray-300"
 821                                              : "text-gray-700"
 822                                          }`}
 823                                        >
 824                                          {proposal.Content[key]}
 825                                        </div>
 826                                      </div>
 827                                    ))}
 828                              </div>
 829                              {/* Voting Results */}
 830                              {result && (
 831                                <div
 832                                  className={`mt-6 p-4 rounded-lg ${
 833                                    darkMode ? "bg-gray-800" : "bg-white"
 834                                  }`}
 835                                >
 836                                  <h3
 837                                    className={`text-lg font-semibold mb-4 ${
 838                                      darkMode ? "text-gray-200" : "text-gray-800"
 839                                    }`}
 840                                  >
 841                                    Voting Results
 842                                  </h3>
 843  
 844                                  {/* Total Addresses */}
 845                                  <div className="mb-4">
 846                                    <span
 847                                      className={`text-sm ${
 848                                        darkMode
 849                                          ? "text-gray-400"
 850                                          : "text-gray-600"
 851                                      }`}
 852                                    >
 853                                      Total Addresses:
 854                                    </span>
 855                                    <span
 856                                      className={`ml-2 text-sm font-semibold ${
 857                                        darkMode
 858                                          ? "text-gray-200"
 859                                          : "text-gray-800"
 860                                      }`}
 861                                    >
 862                                      {result.addresses.toLocaleString()}
 863                                    </span>
 864                                  </div>
 865  
 866                                  {/* Vote Breakdown */}
 867                                  <div className="space-y-3">
 868                                    {/* Yays */}
 869                                    <div>
 870                                      <div className="flex justify-between mb-1">
 871                                        <span
 872                                          className={`text-sm font-medium ${
 873                                            darkMode
 874                                              ? "text-green-400"
 875                                              : "text-green-600"
 876                                          }`}
 877                                        >
 878                                          Yays
 879                                        </span>
 880                                        <span
 881                                          className={`text-sm font-semibold ${
 882                                            darkMode
 883                                              ? "text-green-400"
 884                                              : "text-green-600"
 885                                          }`}
 886                                        >
 887                                          {result.yays.toLocaleString()} (
 888                                          {yayPercentage.toFixed(1)}%)
 889                                        </span>
 890                                      </div>
 891                                      <div
 892                                        className={`w-full rounded-full h-2.5 ${
 893                                          darkMode ? "bg-gray-700" : "bg-gray-200"
 894                                        }`}
 895                                      >
 896                                        <div
 897                                          className="bg-green-500 h-2.5 rounded-full"
 898                                          style={{ width: `${yayPercentage}%` }}
 899                                        ></div>
 900                                      </div>
 901                                    </div>
 902  
 903                                    {/* Nays */}
 904                                    <div>
 905                                      <div className="flex justify-between mb-1">
 906                                        <span
 907                                          className={`text-sm font-medium ${
 908                                            darkMode
 909                                              ? "text-red-400"
 910                                              : "text-red-600"
 911                                          }`}
 912                                        >
 913                                          Nays
 914                                        </span>
 915                                        <span
 916                                          className={`text-sm font-semibold ${
 917                                            darkMode
 918                                              ? "text-red-400"
 919                                              : "text-red-600"
 920                                          }`}
 921                                        >
 922                                          {result.nays.toLocaleString()} (
 923                                          {nayPercentage.toFixed(1)}%)
 924                                        </span>
 925                                      </div>
 926                                      <div
 927                                        className={`w-full rounded-full h-2.5 ${
 928                                          darkMode ? "bg-gray-700" : "bg-gray-200"
 929                                        }`}
 930                                      >
 931                                        <div
 932                                          className="bg-red-500 h-2.5 rounded-full"
 933                                          style={{ width: `${nayPercentage}%` }}
 934                                        ></div>
 935                                      </div>
 936                                    </div>
 937  
 938                                    {/* Abstains */}
 939                                    <div>
 940                                      <div className="flex justify-between mb-1">
 941                                        <span
 942                                          className={`text-sm font-medium ${
 943                                            darkMode
 944                                              ? "text-gray-400"
 945                                              : "text-gray-600"
 946                                          }`}
 947                                        >
 948                                          Abstains
 949                                        </span>
 950                                        <span
 951                                          className={`text-sm font-semibold ${
 952                                            darkMode
 953                                              ? "text-gray-400"
 954                                              : "text-gray-600"
 955                                          }`}
 956                                        >
 957                                          {result.abstains.toLocaleString()} (
 958                                          {abstainPercentage.toFixed(1)}%)
 959                                        </span>
 960                                      </div>
 961                                      <div
 962                                        className={`w-full rounded-full h-2.5 ${
 963                                          darkMode ? "bg-gray-700" : "bg-gray-200"
 964                                        }`}
 965                                      >
 966                                        <div
 967                                          className={`h-2.5 rounded-full ${
 968                                            darkMode
 969                                              ? "bg-gray-500"
 970                                              : "bg-gray-400"
 971                                          }`}
 972                                          style={{
 973                                            width: `${abstainPercentage}%`,
 974                                          }}
 975                                        ></div>
 976                                      </div>
 977                                    </div>
 978                                  </div>
 979                                </div>
 980                              )}
 981                            </td>
 982                          </tr>
 983                        )}
 984                      </>
 985                    );
 986                  }
 987                )}
 988              </tbody>
 989            </table>
 990          </div>
 991        </div>
 992      </div>
 993    );
 994  };
 995  
 996  const ValidatorTab = ({ data, loading, error, darkMode }: any) => {
 997    if (loading) {
 998      return (
 999        <div className="flex justify-center items-center h-64">
1000          <div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-400"></div>
1001        </div>
1002      );
1003    }
1004  
1005    if (error) {
1006      return (
1007        <div
1008          className={`p-4 rounded-lg ${
1009            darkMode ? "bg-gray-800 text-red-400" : "bg-white text-red-600"
1010          }`}
1011        >
1012          <strong>Error:</strong> {error}
1013        </div>
1014      );
1015    }
1016  
1017    if (!data) return null;
1018  
1019    return (
1020      <div className="space-y-6">
1021        <div
1022          className={`rounded-xl shadow-2xl overflow-hidden w-full max-w-2xl ${
1023            darkMode ? "bg-gray-800" : "bg-white"
1024          }`}
1025        >
1026          {/* Header with Avatar */}
1027          <div
1028            className={`p-6 flex items-center space-x-4 ${
1029              darkMode ? "bg-gray-700" : "bg-gray-100"
1030            }`}
1031          >
1032            <img
1033              src={data.Avatar}
1034              alt="ZecHub Logo"
1035              className="w-16 h-16 rounded-full border-2 border-blue-500"
1036            />
1037            <div>
1038              <h1 className="text-2xl font-bold text-blue-400">{data.Name}</h1>
1039              <p className={darkMode ? "text-gray-300" : "text-gray-600"}>
1040                {data.Description}
1041              </p>
1042            </div>
1043          </div>
1044  
1045          {/* Main Content */}
1046          <div className="p-6 space-y-4">
1047            <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
1048              <div
1049                className={`p-4 rounded-lg ${
1050                  darkMode ? "bg-gray-700" : "bg-gray-100"
1051                }`}
1052              >
1053                <h2
1054                  className={`text-sm font-semibold uppercase tracking-wider ${
1055                    darkMode ? "text-gray-400" : "text-gray-500"
1056                  }`}
1057                >
1058                  Contact
1059                </h2>
1060                <p className="mt-2">
1061                  <span className={darkMode ? "text-gray-400" : "text-gray-500"}>
1062                    Email:
1063                  </span>{" "}
1064                  {data.Email}
1065                </p>
1066                <p className="mt-1">
1067                  <span className={darkMode ? "text-gray-400" : "text-gray-500"}>
1068                    Discord:
1069                  </span>{" "}
1070                  {data.Discord}
1071                </p>
1072                <p className="mt-1">
1073                  <span className={darkMode ? "text-gray-400" : "text-gray-500"}>
1074                    Website:
1075                  </span>
1076                  <a
1077                    href={data.Website}
1078                    target="_blank"
1079                    rel="noopener noreferrer"
1080                    className="text-blue-400 hover:underline ml-1"
1081                  >
1082                    {data.Website}
1083                  </a>
1084                </p>
1085              </div>
1086  
1087              <div
1088                className={`p-4 rounded-lg ${
1089                  darkMode ? "bg-gray-700" : "bg-gray-100"
1090                }`}
1091              >
1092                <h2
1093                  className={`text-sm font-semibold uppercase tracking-wider ${
1094                    darkMode ? "text-gray-400" : "text-gray-500"
1095                  }`}
1096                >
1097                  Staking Details
1098                </h2>
1099                <p className="mt-2">
1100                  <span className={darkMode ? "text-gray-400" : "text-gray-500"}>
1101                    Commission:
1102                  </span>{" "}
1103                  {data.Commission}
1104                </p>
1105                <p className="mt-1">
1106                  <span className={darkMode ? "text-gray-400" : "text-gray-500"}>
1107                    Max Change:
1108                  </span>{" "}
1109                  {data.Max_Change}
1110                </p>
1111                <p className="mt-1">
1112                  <span className={darkMode ? "text-gray-400" : "text-gray-500"}>
1113                    Epoch:
1114                  </span>{" "}
1115                  {data.Epoch}
1116                </p>
1117              </div>
1118            </div>
1119  
1120            <div
1121              className={`p-4 rounded-lg ${
1122                darkMode ? "bg-gray-700" : "bg-gray-100"
1123              }`}
1124            >
1125              <h2
1126                className={`text-sm font-semibold uppercase tracking-wider ${
1127                  darkMode ? "text-gray-400" : "text-gray-500"
1128                }`}
1129              >
1130                Address
1131              </h2>
1132              <p
1133                className={`mt-2 font-mono text-sm break-all p-3 rounded ${
1134                  darkMode ? "bg-gray-800" : "bg-gray-200"
1135                }`}
1136              >
1137                {data.Address}
1138              </p>
1139            </div>
1140          </div>
1141        </div>
1142      </div>
1143    );
1144  };
1145  
1146  export default GovernanceDashboard;