Skip to content

Instantly share code, notes, and snippets.

@deanrather
Last active November 10, 2025 06:17
Show Gist options
  • Select an option

  • Save deanrather/6d63e9dcdf823957b171 to your computer and use it in GitHub Desktop.

Select an option

Save deanrather/6d63e9dcdf823957b171 to your computer and use it in GitHub Desktop.
Free Hosting on GitHub

Free Hosting on GitHub

This guide will take you from zero webserver knowledge, to having your own free site hosted on GitHub. The only non-free bit is registering a domain name, which can be as cheap as $5/year.

This guide uses CrazyDomains as the Registrar, FreeDNS as the DNS, and GitHub as the Webserver.


1) Domain Registration and Setup

The Domain Name Registrar is where you register your domain name, This is the only not free step. Your registrar needs to know which DNS Server will associate the Domain Name with a WebServer. I've got mine with CrazyDomains, as they offer the cheapest .com.au domains.

  1. Create an account, chuck in your payment details, etc.
  2. Register your domain(s)
  3. Set your domains Name Servers to ns1.afraid.org, ns2, ns3, and ns4.

2) Setup your DNS

Edit: Ignore this section and use CloudFlare instead. It's free, it's a DNS, It's also a CDN, and it's also easier.

I use FreeDNS, cause it's free, and doesn't look too dodgy.

  1. Set up an account, login
  2. Go to Domains -> Add a Domain to FreeDNS Domain: yourdomain.com Shared State: Shared: Public
  3. Go to Domains -> yourdomain.com -> Manage
  4. Click the Add button at the top-right Type: A Subdomain: blank Domain: yourdomain.com (public)(broken!) Destination: 192.30.252.153 Wildcard: ticked
  5. Click the Add button next to yourdomain.com again, same settings as last time, but this time use the ip: 192.30.252.154

3) Set Up your Website on GitHub

  1. go to GitHub.com, create an account, and login
  2. Create a new Repository, mame it yourdomain.com, leave it as Public, and tick `Initialize this repository with a README``
  3. Click Settings (in the right-middle -- for the repo, not for your account)
  4. Under Github Pages click Automatic page generator
  5. Make any changes you like, put in a Google Analytics ID if you want awesome free stats, click Continue to layouts
  6. Choose a theme, or just go with the default and change it later. Click Publish Page
  7. Your page can now be viewed at http://<your github username>.github.io/<your repo name>/
  8. Back on your repo's page, click Branch: master and change to the gh-pages branch
  9. Next to the Branch selector, it will show yourdomain.com, followed by a / and a +. Click the + to create a new file.
  10. Name the file CNAME and put yourdomain.com inside as the contents, click Commit new file to save it.

--

That's it! Everything's set up!

It can take up to 24 hours for the Domain Name to propogate, sometimes it only takes a few minutes thoughu. Until then browsing to yourdomain.com will just return an error. If you're really impatient you can add it to your computer's hosts file, otherwise just wait it out.


Tips

  • Settings -> Default branch: gh-pages
@cforbin1
Copy link

cforbin1 commented May 6, 2021

You didn't mention that you're assuming the user wants to version control the source code for a website.
I was halfway into doing all the steps before I realized this.
Because of it, I put my company's IT department through some hoop jumping to get me a test domain that I won't end up needing.

Bottom line, you need to warn the reader early on:
There is NO NEED for a domain name if you are trying out GitHub but are not maintaining a website!

("Set up your website on GitHub" doesn't mean setting up some views of your project in GitHub. The O.P. means literally "you have a website, now put the source for it in GitHub.")

@786-Barbershop
Copy link

Hey this is so good and worked well but i have a query is that can we place our own web pages in it rather than theme?
Thanks!

Yeah same question here plz someone Answer as soon as possible....

@alechash
Copy link

alechash commented May 22, 2021

Hey this is so good and worked well but i have a query is that can we place our own web pages in it rather than theme?
Thanks!

Yeah same question here plz someone Answer as soon as possible....

Hey! Yeah, you can. Just put an index.html in the root as the main page.

For 404 pages, you can create a 404.html for all 404 pages.

For anything else like other html, css, js, img, and other files, just create those and link them relatively and you’ll be all set!

Thanks,
If you have anymore questions, just ask, you can also mention me or other people!

@mjon
Copy link

mjon commented Aug 31, 2021

Thanks. This was all super helpful. I got CrazyDomains + Cloudflare + Github Pages up and running, just with index.html (I'll add on a Jekyll theme later). The big thing to note is that "It can take up to 24 hours for the Domain Name to propagate". I had to wait overnight before all the pieces clicked into place for mine.

I was confused for a while about the "Enforce HTTPS" check box on the Github's Pages Settings. In the end, I read that that I could safely keep that unchecked because Cloudflare takes care of that.

@alechash
Copy link

alechash commented Sep 1, 2021

Awesome! I use Cloudflare for all my domains and the checkbox is disabled on all of them

@KruZira
Copy link

KruZira commented Nov 11, 2021

Todo: use CloudFlare instead of FreeDNS

bro please do you know where i can get a free domain like example.com or example.net for free without any verification , 🙏

@Sputnik771100
Copy link

Sputnik771100 commented Dec 3, 2021

Lots of interesting ideas here, and thanks Dean for getting this convo started. But what i am thinking is...Is there any way to get something like this up and running on github? https://www.esgthereport.com

@CoderDood
Copy link

freenom and 000webhost is good and $0 dollars unlimited

@CoderDood
Copy link

My Domain:
inventionmaster.tk
Im not done with my website

@CoderDood
Copy link

dont click css buttons or it will show red screen!!

@CoderDood
Copy link

@Sputnik771100 https://www.esgthereport.com is already registered did u go to that page yourself yet??

@JehadZug
Copy link

CrazyDomains have crazy prices, I would use any other providers, google is one of the best, run away from CrazyDomains.

@Arnavraj-web
Copy link

<title>Rahul Mods</title> <script src="https://cdn.tailwindcss.com"></script> <style> @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
    /* === 1. ROOT COLORS (For Static Button Glows) === */
    :root {
        --glow-color-blue: #3b82f6; --glow-shadow-blue-hover: 0 0 15px 2px var(--glow-color-blue);
        --glow-color-green: #22c55e; --glow-shadow-green-hover: 0 0 15px 2px var(--glow-color-green);
        --glow-color-indigo: #6366f1; --glow-shadow-indigo-hover: 0 0 15px 2px var(--glow-color-indigo);
        --glow-color-purple: #a855f7; --glow-shadow-purple-hover: 0 0 15px 2px var(--glow-color-purple);
        --glow-color-pink: #ec4899; --glow-shadow-pink-hover: 0 0 15px 2px var(--glow-color-pink);
        --glow-color-red: #ef4444; --glow-shadow-red-hover: 0 0 15px 2px var(--glow-color-red);
    }

    html, body {
        height: 100%; margin: 0; padding: 0; overflow: hidden;
        font-family: 'Inter', sans-serif;
        color: #e5e7eb;
        
        /* === ANIMATED PAGE BACKGROUND === */
        background: linear-gradient(-45deg, #FF6B6B, #F9C74F, #8DDE7F, #5CB3FF, #DA8FFB, #FF6B6B);
        background-size: 400% 400%;
        animation: animated-rgb-glow-bg 20s ease infinite;
    }

    @keyframes animated-rgb-glow-bg {
        0% { background-position: 0% 50%; }
        50% { background-position: 100% 50%; }
        100% { background-position: 0% 50%; }
    }

    .loader {
        border: 4px solid #4b5563; border-top: 4px solid #3b82f6;
        border-radius: 50%; width: 24px; height: 24px;
        animation: spin 1s linear infinite;
    }
    @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }

    /* === 2. ANIMATED "RGB" GLOW EFFECT (FOR CARDS/INPUTS) === */
    
    @keyframes animated-rgb-glow-border { /* Border Animation */
        0% { border-color: #ec4899; box-shadow: 0 0 10px #ec4899; }
        20% { border-color: #a855f7; box-shadow: 0 0 10px #a855f7; }
        40% { border-color: #3b82f6; box-shadow: 0 0 10px #3b82f6; }
        60% { border-color: #22c55e; box-shadow: 0 0 10px #22c55e; }
        80% { border-color: #f59e0b; box-shadow: 0 0 10px #f59e0b; }
        100% { border-color: #ec4899; box-shadow: 0 0 10px #ec4899; }
    }

    /* NAYA CHANGE YAHAN HAI: Card/Input ka background bhi ab animate hoga */
    @keyframes animated-rgb-bg-dark {
        0% { background-color: rgba(30, 41, 59, 0.7); } /* slate-800 */
        25% { background-color: rgba(76, 29, 149, 0.7); } /* deep-purple */
        50% { background-color: rgba(126, 34, 206, 0.7); } /* purple-700 */
        75% { background-color: rgba(29, 78, 216, 0.7); } /* blue-700 */
        100% { background-color: rgba(30, 41, 59, 0.7); } /* slate-800 */
    }
    
    .rgb-card {
        /* background-color: rgba(31, 41, 55, 0.7); */ /* PURANA STATIC BG */
        backdrop-filter: blur(10px);
        border: 2px solid;
        border-radius: 0.75rem;
        padding: 1.5rem;
        /* Yahan dono animation ek saath chal rahe hain */
        animation: 
            animated-rgb-bg-dark 10s linear infinite, /* Naya BG animation */
            animated-rgb-glow-border 5s linear infinite; /* Purana Border animation */
    }

    .rgb-input {
        /* background-color: #374151; */ /* PURANA STATIC BG */
        color: #f3f4f5;
        border: 2px solid;
        border-radius: 0.375rem;
        padding: 0.75rem;
        width: 100%;
        backdrop-filter: blur(5px);
        /* Yahan bhi dono animation ek saath */
        animation: 
            animated-rgb-bg-dark 10s linear infinite reverse, /* Naya BG animation (reverse) */
            animated-rgb-glow-border 5s linear infinite; /* Purana Border animation */
    }
    .rgb-input:focus {
        outline: none;
        /* Focus par border animation tez hoga */
        animation: 
            animated-rgb-bg-dark 10s linear infinite reverse,
            animated-rgb-glow-border 2s linear infinite;
    }
    /* Tailwind classes ko override karne ke liye */
    input.rgb-input { padding-left: 0.75rem; padding-right: 0.75rem; }
    select.rgb-input { padding: 0.75rem; }


    .glowing-btn {
        transition: box-shadow 0.3s ease, transform 0.2s ease;
    }
    .glowing-btn:hover {
        transform: translateY(-1px);
    }
    .glowing-btn-blue:hover { box-shadow: var(--glow-shadow-blue-hover); }
    .glowing-btn-green:hover { box-shadow: var(--glow-shadow-green-hover); }
    .glowing-btn-indigo:hover { box-shadow: var(--glow-shadow-indigo-hover); }
    .glowing-btn-purple:hover { box-shadow: var(--glow-shadow-purple-hover); }
    .glowing-btn-pink:hover { box-shadow: var(--glow-shadow-pink-hover); }

    /* === 3. UPDATED COMPONENTS FOR DARK MODE === */
    
    .table-container tr:nth-child(even) { background-color: rgba(55, 65, 81, 0.3); }
    .table-container tr:hover { background-color: rgba(55, 65, 81, 0.6); }
    .table-container th { background-color: rgba(31, 41, 55, 0.8); padding: 12px 16px; text-align: left; font-size: 0.875rem; font-weight: 600; color: #d1d5db; }
    .table-container td { padding: 12px 16px; border-bottom: 1px solid #374151; }
    .table-container { border: 1px solid #374151; }

    .nav-btn.active { color: #60a5fa; background-color: #1f2937; }
    .nav-btn.active i { color: #60a5fa; }
    .nav-btn { transition: all 0.2s ease-in-out; }
    
    .status-active { color: #057a55; background-color: #def7ec; padding: 2px 8px; border-radius: 99px; font-size: 0.8rem; font-weight: 600;}
    .status-expired { color: #c81e1e; background-color: #fde8e8; padding: 2px 8px; border-radius: 99px; font-size: 0.8rem; font-weight: 600;}

    #toast-container { position: fixed; top: 20px; right: 20px; z-index: 100; display: flex; flex-direction: column; gap: 10px; }
    .toast { display: flex; align-items: center; padding: 12px 16px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); color: white; opacity: 0; transform: translateX(100%); transition: all 0.3s ease; min-width: 250px; }
    .toast.show { opacity: 1; transform: translateX(0); }
    .toast-success { background-color: #059669; }
    .toast-error { background-color: #dc2626; }
    .toast i { margin-right: 10px; font-size: 1.2rem; }

    .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.7); display: flex; align-items: center; justify-content: center; z-index: 50; opacity: 0; pointer-events: none; transition: opacity 0.2s ease; }
    .modal.show { opacity: 1; pointer-events: auto; }
    .modal-content {
        background-color: #1f2937; color: #d1d5db;
        border: 2px solid;
        animation: animated-rgb-glow-border 4s linear infinite 1s; /* Sirf border animation */
        padding: 25px; border-radius: 10px; width: 90%; max-width: 400px;
        box-shadow: 0 0 20px 5px rgba(59, 130, 246, 0.4);
        transform: scale(0.9); transition: transform 0.2s ease;
    }
    .modal.show .modal-content { transform: scale(1); }
    .modal-title { font-size: 1.25rem; font-weight: 600; margin-bottom: 10px; color: #fff; }
    .modal-body { margin-bottom: 20px; color: #d1d5db; }
    .modal-input { width: 100%; padding: 8px; border-radius: 6px; margin-top: 10px; background-color: #374151; border: 1px solid #4b5563; color: white; }
    .modal-footer { display: flex; justify-content: flex-end; gap: 10px; }
    .modal-content-dark { background-color: #1f2937; color: #d1d5db; border: 1px solid #4b5563; }
    .modal-content-dark label { color: #9ca3af; }

    input[type="date"].rgb-input:required:invalid::-webkit-datetime-edit { color: transparent; }
    input[type="date"].rgb-input:required:invalid::before { content: "Select Date"; color: #9ca3af; margin-right: 0.5em; }
    input[type="date"].rgb-input:required:invalid { color: #9ca3af; }
    input[type="date"].rgb-input:required:valid { color: #e5e7eb; }

    .dashboard-card {
         display: flex; align-items: center; gap: 1rem;
    }
    .dashboard-card i { font-size: 1.8rem; width: 40px; text-align: center; }
    .dashboard-card-value { font-size: 1.8rem; font-weight: 700; color: #f9fafb; line-height: 1; }
    .dashboard-card-label { font-size: 0.9rem; color: #9ca3af; }
    .dashboard-loader { width: 20px; height: 20px; border-width: 2px; }

</style>
<div id="toast-container"></div>
<div id="customModal" class="modal"> <div class="modal-content"> <h3 id="modalTitle" class="modal-title">Confirmation</h3> <p id="modalBody" class="modal-body">Are you sure?</p> <input type="text" id="modalInput" class="modal-input" style="display: none;"> <div class="modal-footer"> <button id="modalCancel" class="bg-gray-600 text-gray-100 px-4 py-2 rounded-md hover:bg-gray-500 font-medium">Cancel</button> <button id="modalConfirm" class="bg-red-600 text-white px-4 py-2 rounded-md hover:bg-red-700 font-medium glowing-btn" style="--glow-shadow-red-hover: 0 0 15px 2px #ef4444;">Confirm</button> </div> </div> </div>

<div id="loginPage" class="h-full flex items-center justify-center text-white"> <div class="p-8 rounded-lg shadow-xl w-full max-w-sm m-4 rgb-card"> <h1 class="text-3xl font-bold text-center mb-6">Admin Login</h1> <div class="space-y-4"> <div><label for="loginUser" class="block text-sm font-medium text-gray-300 mb-1">Username</label> <input type="text" id="loginUser" class="rgb-input" placeholder="Username" value=""></div> <div><label for="loginPassword" class="block text-sm font-medium text-gray-300 mb-1">Password</label> <input type="password" id="loginPassword" class="rgb-input" placeholder="password" value=""></div> <button id="loginBtn" class="w-full bg-blue-600 font-bold py-3 rounded-md hover:bg-blue-700 transition flex items-center justify-center glowing-btn glowing-btn-blue"><span id="loginBtnText">Login</span><div id="loginLoader" class="loader hidden"></div></button> <div id="loginError" class="text-red-400 text-center hidden p-2"></div> <div id="loginSuccess" class="text-green-400 text-center hidden p-2"></div> </div> <p class="text-center text-sm text-gray-400 mt-6">Don't have an account? <a href="#" id="showRegisterLink" class="font-medium text-blue-400 hover:text-blue-300">Register here</a></p> </div> </div>
<div id="registerPage" class="h-full flex items-center justify-center text-white" style="display: none;"> <div class="p-8 rounded-lg shadow-xl w-full max-w-sm m-4 rgb-card"> <h1 class="text-3xl font-bold text-center mb-6">Register Admin</h1> <div class="space-y-4"> <div><label for="registerUser" class="block text-sm font-medium text-gray-300 mb-1">New Username</label> <input type="text" id="registerUser" class="rgb-input" placeholder="New username"></div> <div><label for="registerPassword" class="block text-sm font-medium text-gray-300 mb-1">New Password</label> <input type="password" id="registerPassword" class="rgb-input" placeholder="••••••••"></div> <div><label for="registerReferralCode" class="block text-sm font-medium text-gray-300 mb-1">Referral Code</label> <input type="text" id="registerReferralCode" class="rgb-input" placeholder="Enter referral code"></div> <button id="registerBtn" class="w-full bg-green-600 font-bold py-3 rounded-md hover:bg-green-700 transition flex items-center justify-center glowing-btn glowing-btn-green"><span id="registerBtnText">Register</span><div id="registerLoader" class="loader hidden"></div></button> <div id="registerError" class="text-red-400 text-center hidden p-2"></div> </div> <p class="text-center text-sm text-gray-400 mt-6">Already have an account? <a href="#" id="showLoginLink" class="font-medium text-blue-400 hover:text-blue-300">Login here</a></p> </div> </div>


<div id="generatorPage" class="flex flex-col h-full" style="display: none;">

    <nav class="backdrop-blur-md shadow-lg border-b-2" style="animation: animated-rgb-bg-dark 10s linear infinite, animated-rgb-glow-border 6s linear infinite;"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div class="flex justify-between h-16"> <div class="flex items-center"><span id="navUsername" class="text-xl md:text-2xl font-bold text-blue-500">RAHUL MODS</span></div> <div class="flex items-center space-x-4"> <button id="navRefreshBtn" title="Refresh Data" class="text-gray-400 hover:text-blue-500 focus:outline-none"><i class="fas fa-sync-alt text-lg"></i></button> <button id="navLogoutBtn" title="Logout" class="text-gray-400 hover:text-red-500 focus:outline-none"><i class="fas fa-sign-out-alt text-lg"></i></button> </div> </div> </div> </nav>

    <main class="flex-grow overflow-y-auto p-4 md:p-8">

        <div id="dashboardPage" style="display: none;"> <div class="max-w-4xl mx-auto"> <h2 class="text-2xl font-semibold text-gray-100 mb-6">Dashboard Overview</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-6"> <div class="dashboard-card rgb-card"><i class="fas fa-key text-blue-500"></i><div><div id="dashboardTotalKeys" class="dashboard-card-value"><div class="loader dashboard-loader"></div></div><div class="dashboard-card-label">Total Keys</div></div></div> <div class="dashboard-card rgb-card" style="animation-delay: -1s;"><i class="fas fa-check-circle text-green-500"></i><div><div id="dashboardActiveKeys" class="dashboard-card-value"><div class="loader dashboard-loader"></div></div><div class="dashboard-card-label">Active Keys</div></div></div> <div class="dashboard-card rgb-card" style="animation-delay: -2s;"><i class="fas fa-users text-indigo-500"></i><div><div id="dashboardTotalResellers" class="dashboard-card-value"><div class="loader dashboard-loader"></div></div><div class="dashboard-card-label">Total Resellers</div></div></div> <div class="dashboard-card rgb-card" style="animation-delay: -3s;"><i class="fas fa-coins text-yellow-500"></i><div><div id="dashboardYourCredits" class="dashboard-card-value"><div class="loader dashboard-loader"></div></div><div class="dashboard-card-label">Your Credits</div></div></div> </div> </div> </div>

        <div id="createClientKeyPage" style="display: none;"> <div class="rgb-card p-6 md:p-8 rounded-lg max-w-4xl mx-auto"> <h2 class="text-2xl font-semibold text-gray-100 mb-4">Generate Client Key(s)</h2> <div class="space-y-4"> <div><label for="select_game" class="block text-sm font-medium text-gray-300 mb-1">Game</label><select id="select_game" class="rgb-input"><option value="Free Fire">Free Fire</option><option value="PUBG">PUBG</option><option value="Call of Duty">Call of Duty</option><option value="Other">Other</option></select></div> <input type="text" id="buyer_name" placeholder="Buyer Name" class="rgb-input"> <input type="date" id="expiry_date" class="rgb-input" required> <input type="number" id="device_limit" placeholder="Device Limit (e.g., 1)" class="rgb-input"> <div><label for="number_of_keys" class="block text-sm font-medium text-gray-300 mb-1">Number of Keys</label><input type="number" id="number_of_keys" value="1" min="1" max="100" class="rgb-input"></div> <button id="generateBtn" class="w-full bg-green-600 text-white font-bold py-3 rounded-md shadow-lg hover:bg-green-700 transition flex items-center justify-center glowing-btn glowing-btn-green"><i class="fas fa-magic mr-2"></i><span id="genBtnText">Generate Key(s)</span><div id="genLoader" class="loader hidden"></div></button> </div> </div> <div id="resultArea" class="mt-6 rgb-card p-6 rounded-lg max-w-4xl mx-auto hidden"> <h2 class="text-2xl font-semibold text-gray-100 mb-4">Generated Key(s)</h2> <div class="bg-gray-900/50 p-4 rounded-md"><p class="text-sm text-gray-400 mb-2">New Keys:</p><pre id="generatedKeysList" class="text-sm font-mono text-green-300 break-all whitespace-pre-wrap max-h-60 overflow-y-auto"></pre><p id="generatedDetails" class="text-sm text-gray-400 mt-2"></p></div> </div> </div>

        <div id="manageClientKeyPage" style="display: none;"> <div class="rgb-card p-6 md:p-8 rounded-lg max-w-4xl mx-auto"> <h2 class="text-2xl font-semibold text-gray-100 mb-4">Manage Client Keys</h2> <button id="loadKeysBtn" class="w-full bg-blue-600 text-white font-bold py-3 rounded-md shadow-lg hover:bg-blue-700 transition flex items-center justify-center mb-4 glowing-btn glowing-btn-blue"><i class="fas fa-sync-alt mr-2"></i><span id="loadKeysBtnText">Load All Keys</span><div id="loadKeysLoader" class="loader hidden"></div></button> <div id="keysTableContainer" class="table-container overflow-x-auto rounded-lg"></div> </div> </div>

        <div id="resellersPage" style="display: none;"> <div class="rgb-card p-6 md:p-8 rounded-lg max-w-4xl mx-auto"> <h2 class="text-2xl font-semibold text-gray-100 mb-4">Manage Resellers</h2> <button id="loadResellersBtn" class="w-full bg-indigo-600 text-white font-bold py-3 rounded-md shadow-lg hover:bg-indigo-700 transition flex items-center justify-center mb-4 glowing-btn glowing-btn-indigo"><i class="fas fa-users mr-2"></i><span id="loadResellersBtnText">Load All Resellers</span><div id="loadResellersLoader" class="loader hidden"></div></button> <div id="resellersTableContainer" class="table-container overflow-x-auto rounded-lg"></div> </div> </div>

        <div id="profilePage" style="display: none;"> <div class="rgb-card p-6 md:p-8 rounded-lg max-w-4xl mx-auto"> <h2 class="text-2xl font-semibold text-gray-100 mb-4">My Profile</h2> <div class="space-y-4"> <div><p class="text-sm text-gray-400">Username</p><p id="profileUsername" class="text-lg font-medium text-gray-100"></p></div> <div><p class="text-sm text-gray-400">Available Credits</p><p id="profileCredits" class="text-lg font-bold text-blue-500"></p></div> </div> </div> <div id="superAdminSection" class="mt-8 rgb-card p-6 md:p-8 rounded-lg max-w-4xl mx-auto" style="animation-delay: -1s;"> <h2 class="text-2xl font-semibold text-red-500 mb-4">Super Admin: Referral Codes</h2> <p class="text-gray-300 mb-4">Generate one-time referral codes.</p> <div class="space-y-4"> <div><label for="creditsToGrant" class="block text-sm font-medium text-gray-300">Credits</label><input type="number" id="creditsToGrant" class="w-full rgb-input" placeholder="e.g., 1000"></div> <button id="generateReferralBtn" class="w-full bg-pink-600 text-white font-bold py-3 rounded-md shadow-lg hover:bg-pink-700 transition flex items-center justify-center glowing-btn glowing-btn-pink"><i class="fas fa-gift mr-2"></i><span id="referralBtnText">Generate Code</span><div id="referralLoader" class="loader hidden"></div></button> </div> <div id="referralResultArea" class="mt-6 bg-gray-900/50 p-4 rounded-md hidden"><p class="text-sm text-gray-400">New Code:</p><code id="newReferralCode" class="text-lg font-mono text-green-300 break-all"></code></div> <hr class="my-8 border-gray-700"> <h2 class="text-2xl font-semibold text-red-500 mb-4">Unused Codes</h2> <button id="loadCodesBtn" class="w-full bg-purple-600 text-white font-bold py-3 rounded-md shadow-lg hover:bg-purple-700 transition flex items-center justify-center mb-4 glowing-btn glowing-btn-purple"><i class="fas fa-list-ul mr-2"></i><span id="loadCodesBtnText">Load Unused</span><div id="loadCodesLoader" class="loader hidden"></div></button> <div id="codesTableContainer" class="table-container overflow-x-auto rounded-lg"></div> </div> </div>

    </main>

    <nav class="backdrop-blur-md shadow-inner flex justify-around p-2 border-t-2" style="animation: animated-rgb-bg-dark 10s linear infinite, animated-rgb-glow-border 6s linear infinite; animation-direction: reverse;"> 
        <button id="navDashboardBtn" class="nav-btn flex flex-col items-center text-gray-400 p-2 rounded-lg w-1/5">
            <i class="fas fa-tachometer-alt text-xl text-blue-400"></i><span class="text-xs mt-1">Dashboard</span>
        </button> 
        <button id="navCreateBtn" class="nav-btn flex flex-col items-center text-gray-400 p-2 rounded-lg w-1/5">
            <i class="fas fa-plus-circle text-xl text-green-400"></i><span class="text-xs mt-1">Create</span>
        </button> 
        <button id="navManageBtn" class="nav-btn flex flex-col items-center text-gray-400 p-2 rounded-lg w-1/5">
            <i class="fas fa-key text-xl text-yellow-400"></i><span class="text-xs mt-1">Keys</span>
        </button> 
        <button id="navResellersBtn" class="nav-btn flex flex-col items-center text-gray-400 p-2 rounded-lg w-1/5" style="display: none;">
            <i class="fas fa-users text-xl text-indigo-400"></i><span class="text-xs mt-1">Resellers</span>
        </button> 
        <button id="navProfileBtn" class="nav-btn flex flex-col items-center text-gray-400 p-2 rounded-lg w-1/5">
            <i class="fas fa-user-circle text-xl text-pink-400"></i><span class="text-xs mt-1">Profile</span>
        </button> 
    </nav>
</div>

<div id="editKeyModal" class="modal"> <div class="modal-content modal-content-dark" style="animation: animated-rgb-glow-border 4s linear infinite 2s;"> <h2 class="text-xl font-semibold mb-4 text-white">Edit Client Key</h2><input type="hidden" id="editKeyId"> <div class="space-y-3"> <div><label for="editBuyerName" class="block text-sm font-medium">Buyer Name</label><input type="text" id="editBuyerName" class="w-full rgb-input"></div> <div><label for="editExpiryDate" class="block text-sm font-medium">Expiry Date</label><input type="date" id="editExpiryDate" class="w-full rgb-input"></div> <div><label for="editDeviceLimit" class="block text-sm font-medium">Device Limit</label><input type="number" id="editDeviceLimit" class="w-full rgb-input"></div> </div> <div class="mt-5 flex justify-end space-x-3"><button id="cancelEditBtn" class="bg-gray-600 text-gray-100 px-4 py-2 rounded-md hover:bg-gray-500 font-medium">Cancel</button><button id="saveEditBtn" class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 font-medium glowing-btn glowing-btn-blue">Save Changes</button></div> </div> </div>
<script type="module"> // --- 1. SERVER SETUP --- const API_URL = "https://rahul-mods-server.onrender.com"; let allKeysData = []; // --- Toast & Modal Functions --- // [JAVASCRIPT - NO CHANGES] function showToast(message, isError = false) { const container = document.getElementById("toast-container"); const toast = document.createElement("div"); toast.className = `toast ${isError ? 'toast-error' : 'toast-success'}`; const icon = isError ? 'fa-times-circle' : 'fa-check-circle'; toast.innerHTML = ` ${message}`; container.appendChild(toast); setTimeout(() => { toast.classList.add("show"); }, 100); setTimeout(() => { toast.classList.remove("show"); setTimeout(() => { if (container.contains(toast)) container.removeChild(toast); }, 300); }, 3000); } const modal = document.getElementById("customModal"); const modalTitle = document.getElementById("modalTitle"); const modalBody = document.getElementById("modalBody"); const modalInput = document.getElementById("modalInput"); const modalCancel = document.getElementById("modalCancel"); const modalConfirm = document.getElementById("modalConfirm"); let modalResolve; function showConfirmation({ title, body, confirmText = "Confirm", confirmColor = "bg-red-600", needsInput = false, inputPlaceholder = "", matchText = null }) { modalTitle.textContent = title; modalBody.textContent = body; modalConfirm.textContent = confirmText; modalConfirm.className = `text-white px-4 py-2 rounded-md font-medium ${confirmColor} hover:opacity-90`; modalInput.style.display = needsInput ? 'block' : 'none'; modalInput.value = ''; modalInput.placeholder = inputPlaceholder; modal.classList.add("show"); return new Promise((resolve) => { modalResolve = resolve; if (matchText) { modalConfirm.disabled = true; modalConfirm.classList.add('opacity-50', 'cursor-not-allowed'); modalInput.onkeyup = () => { const enabled = modalInput.value === matchText; modalConfirm.disabled = !enabled; modalConfirm.classList.toggle('opacity-50', !enabled); modalConfirm.classList.toggle('cursor-not-allowed', !enabled); }; } else { modalConfirm.disabled = false; modalConfirm.classList.remove('opacity-50', 'cursor-not-allowed'); modalInput.onkeyup = null; } }); } modalCancel.onclick = () => { modal.classList.remove("show"); if(modalResolve) modalResolve(false); }; modalConfirm.onclick = () => { modal.classList.remove("show"); const inputValue = modalInput.value; if(modalResolve) modalResolve(inputValue || true); }; // --- Page Elements --- // [JAVASCRIPT - NO CHANGES] const loginPage = document.getElementById("loginPage"); const registerPage = document.getElementById("registerPage"); const generatorPage = document.getElementById("generatorPage"); const dashboardPage = document.getElementById("dashboardPage"); const createClientKeyPage = document.getElementById("createClientKeyPage"); const manageClientKeyPage = document.getElementById("manageClientKeyPage"); const resellersPage = document.getElementById("resellersPage"); const profilePage = document.getElementById("profilePage"); const superAdminSection = document.getElementById("superAdminSection"); const navDashboardBtn = document.getElementById("navDashboardBtn"); const navCreateBtn = document.getElementById("navCreateBtn"); const navManageBtn = document.getElementById("navManageBtn"); const navResellersBtn = document.getElementById("navResellersBtn"); const navProfileBtn = document.getElementById("navProfileBtn"); const showLoginLink = document.getElementById("showLoginLink"); const showRegisterLink = document.getElementById("showRegisterLink"); const loginBtn = document.getElementById("loginBtn"); const loginBtnText = document.getElementById("loginBtnText"); const loginLoader = document.getElementById("loginLoader"); const loginError = document.getElementById("loginError"); const loginSuccess = document.getElementById("loginSuccess"); const registerBtn = document.getElementById("registerBtn"); const registerBtnText = document.getElementById("registerBtnText"); const registerLoader = document.getElementById("registerLoader"); const registerError = document.getElementById("registerError"); const profileUsername = document.getElementById("profileUsername"); const profileCredits = document.getElementById("profileCredits"); const loadKeysBtn = document.getElementById("loadKeysBtn"); const loadKeysBtnText = document.getElementById("loadKeysBtnText"); const loadKeysLoader = document.getElementById("loadKeysLoader"); const keysTableContainer = document.getElementById("keysTableContainer"); const generateBtn = document.getElementById("generateBtn"); const genBtnText = document.getElementById("genBtnText"); const genLoader = document.getElementById("genLoader"); const resultArea = document.getElementById("resultArea"); const generatedKeysList = document.getElementById("generatedKeysList"); const generatedDetails = document.getElementById("generatedDetails"); const loadResellersBtn = document.getElementById("loadResellersBtn"); const loadResellersBtnText = document.getElementById("loadResellersBtnText"); const loadResellersLoader = document.getElementById("loadResellersLoader"); const resellersTableContainer = document.getElementById("resellersTableContainer"); const generateReferralBtn = document.getElementById("generateReferralBtn"); const referralBtnText = document.getElementById("referralBtnText"); const referralLoader = document.getElementById("referralLoader"); const referralResultArea = document.getElementById("referralResultArea"); const newReferralCode = document.getElementById("newReferralCode"); const loadCodesBtn = document.getElementById("loadCodesBtn"); const loadCodesBtnText = document.getElementById("loadCodesBtnText"); const loadCodesLoader = document.getElementById("loadCodesLoader"); const codesTableContainer = document.getElementById("codesTableContainer"); const editKeyModal = document.getElementById("editKeyModal"); const editKeyIdInput = document.getElementById("editKeyId"); const editBuyerNameInput = document.getElementById("editBuyerName"); const editExpiryDateInput = document.getElementById("editExpiryDate"); const editDeviceLimitInput = document.getElementById("editDeviceLimit"); const cancelEditBtn = document.getElementById("cancelEditBtn"); const saveEditBtn = document.getElementById("saveEditBtn"); const dashboardTotalKeys = document.getElementById("dashboardTotalKeys"); const dashboardActiveKeys = document.getElementById("dashboardActiveKeys"); const dashboardTotalResellers = document.getElementById("dashboardTotalResellers"); const dashboardYourCredits = document.getElementById("dashboardYourCredits"); const navRefreshBtn = document.getElementById("navRefreshBtn"); const navLogoutBtn = document.getElementById("navLogoutBtn"); // --- Helper: isDateExpired Function --- // [JAVASCRIPT - NO CHANGES] function isDateExpired(dateString) { if (!dateString) return false; try { const parts = dateString.split('-'); if (parts.length !== 3) return false; const year = parseInt(parts[0]); const month = parseInt(parts[1]) - 1; const day = parseInt(parts[2]); const expiryDateLocal = new Date(year, month, day); expiryDateLocal.setHours(23, 59, 59, 999); const today = new Date(); return expiryDateLocal < today; } catch (e) { return false; } } // --- Page Navigation --- // [JAVASCRIPT - NO CHANGES] function showPage(pageId) { loginPage.style.display = 'none'; registerPage.style.display = 'none'; generatorPage.style.display = 'none'; if (pageId === 'loginPage') loginPage.style.display = 'flex'; else if (pageId === 'registerPage') registerPage.style.display = 'flex'; else if (pageId === 'generatorPage') { generatorPage.style.display = 'flex'; const isSuper = sessionStorage.getItem("isSuperAdmin") === "true"; navResellersBtn.style.display = isSuper ? 'flex' : 'none'; showAdminTab('dashboardPage'); loadDashboardData(); } } // --- Dashboard Data Loading --- // [JAVASCRIPT - NO CHANGES] async function loadDashboardData() { const loaderHtml = '
'; dashboardTotalKeys.innerHTML = loaderHtml; dashboardActiveKeys.innerHTML = loaderHtml; dashboardTotalResellers.innerHTML = loaderHtml; dashboardYourCredits.innerHTML = loaderHtml; const currentAdminUsername = sessionStorage.getItem("adminUsername"); const isSuper = sessionStorage.getItem("isSuperAdmin") === "true"; try { const keysResponse = await fetch(`${API_URL}/keys?username=${currentAdminUsername}&isSuper=true`); let resellersResponse = { ok: true, json: async () => [] }; if (isSuper) { resellersResponse = await fetch(`${API_URL}/resellers`); } if (!keysResponse.ok || !resellersResponse.ok) { throw new Error("API error"); } const keysArray = await keysResponse.json(); const resellersArray = await resellersResponse.json(); const totalKeys = keysArray.length; const activeKeys = keysArray.filter(key => !isDateExpired(key.expiry_date)).length; const totalResellers = resellersArray.filter(r => !r.isSuperAdmin).length; const yourCredits = sessionStorage.getItem("adminCredits") || '0'; dashboardTotalKeys.textContent = totalKeys; dashboardActiveKeys.textContent = activeKeys; dashboardTotalResellers.textContent = totalResellers; dashboardYourCredits.textContent = yourCredits; } catch (error) { console.error("Dashboard Error:", error); showToast("Failed to load dashboard.", true); dashboardTotalKeys.textContent = '-'; dashboardActiveKeys.textContent = '-'; dashboardTotalResellers.textContent = '-'; dashboardYourCredits.textContent = '-'; } } // [JAVASCRIPT - NO CHANGES] function showAdminTab(tabId) { dashboardPage.style.display = 'none'; createClientKeyPage.style.display = 'none'; manageClientKeyPage.style.display = 'none'; resellersPage.style.display = 'none'; profilePage.style.display = 'none'; superAdminSection.style.display = 'none'; navDashboardBtn.classList.remove('active'); navCreateBtn.classList.remove('active'); navManageBtn.classList.remove('active'); navResellersBtn.classList.remove('active'); navProfileBtn.classList.remove('active'); const pageToShow = document.getElementById(tabId); if(pageToShow) pageToShow.style.display = 'block'; if (tabId === 'dashboardPage') navDashboardBtn.classList.add('active'); else if (tabId === 'createClientKeyPage') navCreateBtn.classList.add('active'); else if (tabId === 'manageClientKeyPage') navManageBtn.classList.add('active'); else if (tabId === 'resellersPage') navResellersBtn.classList.add('active'); else if (tabId === 'profilePage') { navProfileBtn.classList.add('active'); profileUsername.textContent = sessionStorage.getItem("adminUsername") || 'Admin'; profileCredits.textContent = sessionStorage.getItem("adminCredits") || '0'; if (sessionStorage.getItem("isSuperAdmin") === "true") superAdminSection.style.display = 'block'; } } // --- Nav Button Listeners --- // [JAVASCRIPT - NO CHANGES] navDashboardBtn.addEventListener('click', () => showAdminTab('dashboardPage')); navCreateBtn.addEventListener('click', () => showAdminTab('createClientKeyPage')); navManageBtn.addEventListener('click', () => showAdminTab('manageClientKeyPage')); navResellersBtn.addEventListener('click', () => showAdminTab('resellersPage')); navProfileBtn.addEventListener('click', () => showAdminTab('profilePage')); showRegisterLink.addEventListener("click", (e) => { e.preventDefault(); showPage("registerPage"); }); showLoginLink.addEventListener("click", (e) => { e.preventDefault(); showPage("loginPage"); }); navRefreshBtn.addEventListener('click', () => { const currentTab = document.querySelector('.nav-btn.active'); if(!currentTab) return; const tabId = currentTab.id; if(tabId === 'navDashboardBtn') loadDashboardData(); else if(tabId === 'navManageBtn') loadAllClientKeys(); else if(tabId === 'navResellersBtn') loadAllResellers(); else if(tabId === 'navProfileBtn' && superAdminSection.style.display === 'block') loadUnusedReferralCodes(); showToast("Data refreshed!"); }); navLogoutBtn.addEventListener('click', () => { sessionStorage.clear(); showPage("loginPage"); window.location.reload(); }); // --- LOGIN / REGISTER LOGIC --- // [JAVASCRIPT - NO CHANGES] loginBtn.addEventListener("click", async () => { const username = document.getElementById("loginUser").value; const password = document.getElementById("loginPassword").value; loginBtnText.classList.add("hidden"); loginLoader.classList.remove("hidden"); loginBtn.disabled = true; loginError.classList.add("hidden"); loginSuccess.classList.add("hidden"); try { const response = await fetch(API_URL + '/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }) }); const data = await response.json(); if (!response.ok) { showToast(data.message || "Invalid Login!", true); loginError.textContent = data.message || "Invalid Login!"; loginError.classList.remove("hidden"); } else { const adminData = data.admin; sessionStorage.setItem("adminLoggedIn", "true"); sessionStorage.setItem("adminDocId", adminData._id); sessionStorage.setItem("adminCredits", adminData.credits); sessionStorage.setItem("adminUsername", adminData.username); sessionStorage.setItem("isSuperAdmin", adminData.isSuperAdmin); showPage("generatorPage"); } } catch (e) { console.error("Login Error: ", e); showToast("Server error.", true); loginError.textContent = "Server error."; loginError.classList.remove("hidden"); } finally { loginBtnText.classList.remove("hidden"); loginLoader.classList.add("hidden"); loginBtn.disabled = false; } }); registerBtn.addEventListener("click", async () => { const username = document.getElementById("registerUser").value; const password = document.getElementById("registerPassword").value; const referralCode = document.getElementById("registerReferralCode").value.trim(); if (!username || !password || !referralCode) { registerError.textContent = "Fill all fields."; registerError.classList.remove("hidden"); return; } registerBtnText.classList.add("hidden"); registerLoader.classList.remove("hidden"); registerBtn.disabled = true; registerError.classList.add("hidden"); try { const response = await fetch(API_URL + '/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password, referralCode }) }); const data = await response.json(); if (!response.ok) { showToast(data.message, true); registerError.textContent = data.message; registerError.classList.remove("hidden"); } else { showToast("Registered! Please login."); loginSuccess.textContent = "Registered! Please login."; loginSuccess.classList.remove("hidden"); showPage("loginPage"); } } catch (e) { console.error("Reg Error: ", e); showToast("Server error.", true); registerError.textContent = "Server error."; registerError.classList.remove("hidden"); } finally { registerBtnText.classList.remove("hidden"); registerLoader.classList.add("hidden"); registerBtn.disabled = false; } }); if (sessionStorage.getItem("adminLoggedIn") === "true") showPage("generatorPage"); else showPage("loginPage"); // --- CREATE CLIENT KEY LOGIC --- // [JAVASCRIPT - NO CHANGES] generateBtn.addEventListener("click", async () => { const numberOfKeys = parseInt(document.getElementById("number_of_keys").value); let currentCredits = parseInt(sessionStorage.getItem("adminCredits") || 0); const currentAdminUsername = sessionStorage.getItem("adminUsername"); if (isNaN(numberOfKeys) || numberOfKeys <= 0) { showToast("Enter valid keys number.", true); return; } const isSuper = sessionStorage.getItem("isSuperAdmin") === "true"; if (currentCredits < numberOfKeys && !isSuper) { showToast(`Not enough credits (${currentCredits}).`, true); return; } const selectedGame = document.getElementById("select_game").value; const buyerName = document.getElementById("buyer_name").value; const expiryDate = document.getElementById("expiry_date").value; const deviceLimit = parseInt(document.getElementById("device_limit").value); if (!expiryDate) { showToast("Select date.", true); return; } if (!currentAdminUsername) { showToast("User not found.", true); return; } if (!selectedGame || !buyerName || isNaN(deviceLimit) || deviceLimit <= 0) { showToast("Fill fields correctly.", true); return; } genBtnText.classList.add("hidden"); genLoader.classList.remove("hidden"); generateBtn.disabled = true; resultArea.classList.add("hidden"); generatedKeysList.textContent = ''; try { const response = await fetch(API_URL + '/keys/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ numberOfKeys, game: selectedGame, buyer_name: buyerName, expiry_date: expiryDate, device_limit: deviceLimit, adminUsername: currentAdminUsername }) }); const data = await response.json(); if (!response.ok) { showToast("Error: " + data.message, true); } else { const newCreditCount = data.newCreditCount; if (!isSuper) sessionStorage.setItem("adminCredits", newCreditCount); generatedKeysList.textContent = data.keys.join('\n'); generatedDetails.textContent = `${data.keys.length} key(s) generated. (Credits: ${isSuper ? '∞' : newCreditCount})`; resultArea.classList.remove("hidden"); showToast(`${data.keys.length} key(s) generated!`); if (isSuper) sessionStorage.setItem("adminCredits", 999999); loadDashboardData(); } } catch (e) { console.error("Key Gen Error: ", e); showToast("Error: " + e.message, true); } finally { genBtnText.classList.remove("hidden"); genLoader.classList.add("hidden"); generateBtn.disabled = false; } }); // --- MANAGE CLIENT KEY LOGIC --- // [JAVASCRIPT - NO CHANGES] loadKeysBtn.addEventListener('click', loadAllClientKeys); async function loadAllClientKeys() { loadKeysBtnText.classList.add("hidden"); loadKeysLoader.classList.remove("hidden"); loadKeysBtn.disabled = true; const currentAdminUsername = sessionStorage.getItem("adminUsername"); const isSuper = sessionStorage.getItem("isSuperAdmin") === "true"; if (!currentAdminUsername) { keysTableContainer.innerHTML = `

User not found.

`; loadKeysBtnText.classList.remove("hidden"); loadKeysLoader.classList.add("hidden"); loadKeysBtn.disabled = false; return; } try { const response = await fetch(`${API_URL}/keys?username=${currentAdminUsername}&isSuper=${isSuper}`); const keysArray = await response.json(); allKeysData = keysArray; if (keysArray.length === 0) { const msg = isSuper ? "No keys found." : "You haven't created keys."; keysTableContainer.innerHTML = `

${msg}

`; return; } let html = ``; if (isSuper) html += ``; html += ``; keysArray.forEach(data => { const keyVal = data.key_value || 'N/A'; const buyerVal = data.buyer_name || 'N/A'; const expiryVal = data.expiry_date || 'N/A'; const createdByVal = data.created_by || 'N/A'; const expired = isDateExpired(expiryVal); const statusClass = expired ? 'status-expired' : 'status-active'; const statusText = expired ? 'Expired' : 'Active'; html += ``; if (isSuper) html += ``; html += ``; }); html += `
KeyBuyerExpiresStatusCreatorAction
${keyVal}${buyerVal}${expiryVal}${statusText}${createdByVal}
`; keysTableContainer.innerHTML = html; } catch (e) { console.error("Key Load Error: ", e); showToast("Error loading keys.", true); keysTableContainer.innerHTML = `

Error: ${e.message}

`; } finally { loadKeysBtnText.classList.remove("hidden"); loadKeysLoader.classList.add("hidden"); loadKeysBtn.disabled = false; } } keysTableContainer.addEventListener('click', async (e) => { const target = e.target.closest('button'); if (!target) return; const keyId = target.dataset.id; if (!keyId) return; if (target.classList.contains('delete-key-btn')) { const confirmed = await showConfirmation({ title: "Delete Key?", body: "Are you sure?", confirmText: "Delete", confirmColor: "bg-red-600" }); if (!confirmed) return; try { const response = await fetch(`${API_URL}/keys/${keyId}`, { method: 'DELETE' }); const data = await response.json(); if (!response.ok) showToast("Error: " + data.message, true); else { showToast("Key deleted!"); loadAllClientKeys(); loadDashboardData(); } } catch (e) { console.error("Key Del Error: ", e); showToast("Error: " + e.message, true); } } else if (target.classList.contains('edit-key-btn')) { openEditModal(keyId); } }); async function openEditModal(keyId) { const keyData = allKeysData.find(k => k._id === keyId); if (keyData) { editKeyIdInput.value = keyId; editBuyerNameInput.value = keyData.buyer_name || ''; editExpiryDateInput.value = keyData.expiry_date || ''; editDeviceLimitInput.value = keyData.device_limit || ''; editKeyModal.classList.add('show'); } else { showToast("Key not found!", true); } } function closeEditModal() { editKeyModal.classList.remove('show'); } cancelEditBtn.addEventListener('click', closeEditModal); saveEditBtn.addEventListener('click', async () => { const keyId = editKeyIdInput.value; const newBuyerName = editBuyerNameInput.value; const newExpiryDate = editExpiryDateInput.value; const newDeviceLimit = parseInt(editDeviceLimitInput.value); if (!keyId || !newBuyerName || !newExpiryDate || isNaN(newDeviceLimit)) { showToast("Fill fields correctly.", true); return; } saveEditBtn.textContent = 'Saving...'; saveEditBtn.disabled = true; try { const response = await fetch(`${API_URL}/keys/${keyId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ buyer_name: newBuyerName, expiry_date: newExpiryDate, device_limit: newDeviceLimit }) }); const data = await response.json(); if (!response.ok) showToast("Error: " + data.message, true); else { showToast("Key updated!"); closeEditModal(); loadAllClientKeys(); loadDashboardData(); } } catch (e) { console.error("Key Edit Error:", e); showToast("Error: " + e.message, true); } finally { saveEditBtn.textContent = 'Save Changes'; saveEditBtn.disabled = false; } }); // --- MANAGE RESELLERS LOGIC --- // [JAVASCRIPT - NO CHANGES] loadResellersBtn.addEventListener('click', loadAllResellers); async function loadAllResellers() { loadResellersBtnText.classList.add("hidden"); loadResellersLoader.classList.remove("hidden"); loadResellersBtn.disabled = true; try { const response = await fetch(`${API_URL}/resellers`); const resellersArray = await response.json(); if (resellersArray.length === 0) { resellersTableContainer.innerHTML = `

No resellers found.

`; return; } let html = ``; resellersArray.forEach(data => { const username = data.username || 'N/A'; const credits = data.credits || 0; html += ``; }); html += `
UserCreditsAddAction
${username}${credits}+
`; resellersTableContainer.innerHTML = html; } catch (e) { console.error("Reseller Load Error: ", e); showToast("Error loading resellers.", true); } finally { loadResellersBtnText.classList.remove("hidden"); loadResellersLoader.classList.add("hidden"); loadResellersBtn.disabled = false; } } resellersTableContainer.addEventListener('click', async (e) => { const target = e.target.closest('button'); if (!target) return; const resellerId = target.dataset.id; const resellerUsername = target.dataset.username; if (target.classList.contains('add-credits-btn')) { const inputElement = resellersTableContainer.querySelector(`.add-credits-input[data-id="${resellerId}"]`); const amountToAdd = parseInt(inputElement.value); if (!resellerId || isNaN(amountToAdd) || amountToAdd <= 0) { showToast("Enter valid amount.", true); return; } target.disabled = true; target.textContent = '...'; try { const response = await fetch(`${API_URL}/resellers/add-credits`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ resellerId, amountToAdd }) }); if (!response.ok) { const data = await response.json(); showToast("Error: " + data.message, true); target.disabled = false; target.textContent = '+'; } else { showToast("Credits added!"); loadAllResellers(); loadDashboardData();} } catch (err) { console.error("Credit Add Error:", err); target.disabled = false; target.textContent = '+'; } } else if (target.classList.contains('delete-reseller-btn')) { if (!resellerId) return; const confirmed = await showConfirmation({ title: `Delete '${resellerUsername}'?`, body: `Type '${resellerUsername}' to confirm.`, confirmText: "Delete", confirmColor: "bg-red-600", needsInput: true, inputPlaceholder: `Type '${resellerUsername}'`, matchText: resellerUsername }); if (!confirmed) return; target.disabled = true; target.innerHTML = ``; try { const response = await fetch(`${API_URL}/resellers/${resellerId}`, { method: 'DELETE' }); if (!response.ok) { const data = await response.json(); showToast("Error: " + data.message, true); target.disabled = false; target.innerHTML = ``; } else { showToast("Reseller deleted!"); loadAllResellers(); loadDashboardData(); } } catch (err) { console.error("Reseller Del Error:", err); showToast("Error: " + err.message, true); target.disabled = false; target.innerHTML = ``; } } else if (target.classList.contains('promote-reseller-btn')) { const promotingAdminUsername = sessionStorage.getItem("adminUsername"); if (!resellerUsername || !promotingAdminUsername) { showToast("Error: Admin details missing.", true); return; } const confirmed = await showConfirmation({ title: `Promote '${resellerUsername}'?`, body: `Make user Super Admin?`, confirmText: "Promote", confirmColor: "bg-blue-600" }); if (!confirmed) return; target.disabled = true; target.innerHTML = ``; try { const response = await fetch(`${API_URL}/admins/promote`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ promotingAdminUsername, targetAdminId: resellerId }) }); /* <-- ID BHEJA */ const data = await response.json(); if (!response.ok) { showToast("Error: " + data.message, true); target.disabled = false; target.innerHTML = ``; } else { showToast(data.message); loadAllResellers(); } } catch (err) { console.error("Promote Error:", err); showToast("Error: " + err.message, true); target.disabled = false; target.innerHTML = ``; } } }); // --- SUPER ADMIN CODE MANAGEMENT --- // [JAVASCRIPT - NO CHANGES] generateReferralBtn.addEventListener('click', async () => { const creditsToGrant = parseInt(document.getElementById("creditsToGrant").value); if (!creditsToGrant || creditsToGrant <= 0) { showToast("Enter valid credits.", true); return; } referralBtnText.classList.add("hidden"); referralLoader.classList.remove("hidden"); generateReferralBtn.disabled = true; try { const response = await fetch(`${API_URL}/referral-codes/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ creditsToGrant }) }); const data = await response.json(); if (!response.ok) showToast("Error: " + data.message, true); else { newReferralCode.textContent = data.referralCode.code; referralResultArea.classList.remove("hidden"); showToast("Code generated!"); } } catch (e) { console.error("Code Gen Error: ", e); showToast("Error: " + e.message, true); } finally { referralBtnText.classList.remove("hidden"); referralLoader.classList.add("hidden"); generateReferralBtn.disabled = false; } }); loadCodesBtn.addEventListener('click', loadUnusedReferralCodes); async function loadUnusedReferralCodes() { loadCodesBtnText.classList.add("hidden"); loadCodesLoader.classList.remove("hidden"); loadCodesBtn.disabled = true; try { const response = await fetch(`${API_URL}/referral-codes/unused`); const codesArray = await response.json(); if (codesArray.length === 0) { codesTableContainer.innerHTML = `

No unused codes.

`; return; } let html = ``; codesArray.forEach(data => { html += ``; }); html += `
CodeCreditsAction
${data.code || 'N/A'}${data.creditsToGrant || 0}
`; codesTableContainer.innerHTML = html; } catch (e) { console.error("Code Load Error: ", e); showToast("Error loading codes.", true); } finally { loadCodesBtnText.classList.remove("hidden"); loadCodesLoader.classList.add("hidden"); loadCodesBtn.disabled = false; } } codesTableContainer.addEventListener('click', async (e) => { const target = e.target.closest('button'); if (target && target.classList.contains('delete-code-btn')) { const codeId = target.dataset.id; if (!codeId) return; const confirmed = await showConfirmation({ title: "Delete Code?", body: "Are you sure?", confirmText: "Delete", confirmColor: "bg-red-600" }); if (!confirmed) return; try { await fetch(`${API_URL}/referral-codes/${codeId}`, { method: 'DELETE' }); showToast("Code deleted!"); loadUnusedReferralCodes(); } catch (err) { console.error("Code Del Error:", err); showToast("Error: " + err.message, true); } } }); </script>

@Evendzi
Copy link

Evendzi commented Nov 10, 2025 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment