Skip to content

feat(frontend): add Tefnut static page for issue #154#158

Open
sameezy667 wants to merge 1 commit into
DjedAlliance:mainfrom
sameezy667:sameezy667/tefnut-frontend
Open

feat(frontend): add Tefnut static page for issue #154#158
sameezy667 wants to merge 1 commit into
DjedAlliance:mainfrom
sameezy667:sameezy667/tefnut-frontend

Conversation

@sameezy667

@sameezy667 sameezy667 commented Dec 13, 2025

Copy link
Copy Markdown

Summary: Add an initial static frontend page for the Djed Tefnut frontend (Sepolia testnet preview / visual reference) to help implement the Tefnut specification and speed up frontend integration for issue #154.

What I changed

Added tefnut.html — main preserved/generated page for review.
Added generated_page.html — backup / original copy.
Branch: sameezy667/tefnut-frontend
Why

Provide a ready HTML/CSS/JS prototype of the Tefnut UI so maintainers can review the visual/layout, and developers can convert it into the repository's React/Next pages.
Preserves the page you supplied so we don't lose the original iteration.
How to preview

Install dependencies and run the dev server:
Or preview the static file directly by opening tefnut.html in a browser.
Notes / Next steps

These are static HTML files and are not yet wired into the Next.js app. Next actions I recommend:
Convert the page into a Next.js route (e.g., src/pages/tefnut.jsx or app/tefnut/page.tsx) and reuse existing layout/components.
Integrate wallet connection + contract interaction to match the Tefnut spec.
Add tests/visual regression and accessibility checks.
This PR is intended as a first step toward implementing Djed Tefnut frontend per issue

Summary by CodeRabbit

  • New Features
    • Launched a financial protocol dashboard with market statistics for ETH, DJED, and SHEN tokens.
    • Added an animated protocol health gauge displaying real-time reserve ratio status.
    • Introduced a tabbed Mint/Redeem interface for seamless token interactions.
    • Enabled live exchange rate calculations with transparent fee breakdowns.
    • Implemented asset selection and input/output fields for trading operations.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel

vercel Bot commented Dec 13, 2025

Copy link
Copy Markdown

@sameezy667 is attempting to deploy a commit to the djed-solidity Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Dec 13, 2025

Copy link
Copy Markdown

Walkthrough

Two new self-contained HTML pages implement a financial protocol dashboard UI. Each page includes market statistics, a protocol health gauge with dynamic updates, and a tabbed mint/redeem interaction interface with real-time calculation logic for exchanging between assets (ETH, DJED, SHEN).

Changes

Cohort / File(s) Summary
New Financial Protocol Dashboard Pages
generated_page.html, public/tefnut.html
Two new HTML pages implementing a financial protocol dashboard with Tailwind-based responsive layout, sidebar navigation, market stats cards (ETH/DJED/SHEN prices), SVG protocol health gauge with animated dashoffset, and a tabbed Interaction Bank supporting mint and redeem flows. Includes embedded CSS for theming and animations, and JavaScript logic for tab switching, input validation, dynamic rate calculations (1.5% fee), and autonomous gauge updates with random walk simulation between 400%–800% reserve ratio bounds.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Calculation logic validation: Verify price constants (PRICES object), fee application (1.5%), and mint vs. redeem directional flows (ETH → token vs. token → ETH)
  • Input validation and error handling: Check guards for non-numeric and non-positive input amounts, including edge cases
  • Gauge animation and state: Review SVG dashoffset interpolation, color transitions based on reserve ratio thresholds, and periodic update intervals (~3 seconds)
  • Tab state management: Confirm switchTab() correctly toggles UI states, updates label text, and triggers recalculations
  • Responsive design: Verify layout behavior across breakpoints and accessibility of interactive elements (radio buttons, buttons, inputs)

Poem

🐰 Two dashboards bloom with stats and grace,
A gauge that dances through reserves' space,
Mint and redeem in harmony they play,
With ETH and tokens in dynamic ballet.
The rabbit hops between the tabs with glee,
A financial protocol for all to see! 💰✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(frontend): add Tefnut static page for issue #154' directly and concisely summarizes the main change: adding a new static HTML page for the Tefnut frontend prototype.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
generated_page.html (2)

312-312: Balance display is hardcoded and will not update dynamically.

Line 312 shows a static balance hint: Balance: 4.2 ETH. This is hardcoded and won't reflect the actual connected wallet's balance. When wallet integration is added, this must be dynamically updated via Web3 calls.

For now, mark this as a placeholder:

<span class="text-xs text-orange-600 cursor-pointer hover:underline font-medium">
  Balance: <span id="wallet-balance">4.2</span> ETH
</span>

Then update via contract query:

// TODO: Fetch from wallet provider (ethers.js / Web3.js)
document.getElementById('wallet-balance').innerText = userBalance;

499-520: Document magic numbers in gauge animation logic.

Lines 505–510 compute the SVG stroke-dashoffset using undocumented approximations:

const percentage = (currentRatio - 400) / 400; // 0 to 1
const offset = 251 - (200 * percentage);       // ???

The constant 251.2 (from line 225, stroke-dasharray) and the offset formula 251 - (200 * percentage) lack explanation. It's unclear if this correctly maps the reserve ratio (400%–800%) to the visual gauge fill.

Add comments explaining the mapping:

// SVG stroke-dasharray is ~251 (full arc); we map reserve ratio to visible arc
// Range: 400% (empty) → 800% (full), normalized to 0–1
const percentage = (currentRatio - 400) / 400; // 0 = min ratio (empty), 1 = max ratio (full)
// Invert offset: at 0%, offset=251 (no fill); at 100%, offset ≈51 (full fill)
const maxOffset = 251.2; // dasharray length (approximate)
const offset = maxOffset - (maxOffset * percentage);

Verify the calculation matches the intended visual behavior by testing edge cases (currentRatio = 400, 600, 800).

public/tefnut.html (1)

1-1: Canonical page location: Clarify if public/tefnut.html is the primary version for Next.js conversion.

This file is located in the public/ folder and appears to be the primary page. Per the PR description, tefnut.html is labeled as the "main preserved/generated page for review." When converting to Next.js/React, this should become the single canonical source (e.g., src/pages/tefnut.jsx or app/tefnut/page.tsx), and generated_page.html should be deleted to eliminate duplication.

In the meantime, ensure this file is treated as the reference version for all updates and refinements.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e46b850 and 0fa2275.

📒 Files selected for processing (2)
  • generated_page.html (1 hunks)
  • public/tefnut.html (1 hunks)
🔇 Additional comments (1)
generated_page.html (1)

391-396: Hardcoded prices will not reflect contract state; plan dynamic fetching.

The PRICES object (lines 391–396) and FEES constant are hardcoded to static values. In a production system, these must be fetched from the blockchain contract in real time. Even for a prototype, this should be called out as a placeholder.

For the static prototype, this is acceptable, but document it clearly and plan the integration path:

// TODO: Replace with dynamic contract queries (Web3.js / ethers.js)
const PRICES = {
  ETH: 2042.50,   // Fetch from price oracle or DEX
  DJED: 1.00,     // Query contract reserve ratio
  SHEN: 2.45      // Fetch from price oracle
};
const FEES = 0.015; // Query from contract

Add a comment block above these constants noting they are placeholder values for the static prototype and must be replaced with contract calls before production conversion.

When converting to Next.js/React, ensure you integrate with:

  • A price oracle (e.g., Chainlink, Uniswap TWAP) for ETH and SHEN prices.
  • Direct contract queries for DJED price and reserve ratio.
  • Real wallet balance and transaction fee estimation.

Comment thread generated_page.html
Comment on lines +1 to +10
<html lang="en"><head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Djed Tefnut | Financial Protocol</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&amp;family=Lora:ital,wght@0,400;0,500;0,600;1,400&amp;family=Space+Mono:wght@400;700&amp;display=swap" rel="stylesheet">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Eliminate duplicate static HTML files; consolidate to single source.

Both generated_page.html and public/tefnut.html are 99% identical (~524 lines each). This violates DRY and creates a significant maintenance burden: any UI or logic change must be applied twice, and inconsistencies will emerge.

Per the PR description, this PR includes both a "main preserved/generated page for review" and "backup / original copy." Clarify which is the canonical version and delete the other. As you convert this to Next.js/React, this duplication should be eliminated entirely in favor of a single component/page definition.

🤖 Prompt for AI Agents
In generated_page.html around lines 1 to 10: there is a near-identical duplicate
of public/tefnut.html; choose one canonical source (either keep
generated_page.html or public/tefnut.html), delete the other copy, and update
any references/build scripts to point to the retained file. If this is being
converted to Next.js/React, replace the retained static file with a single React
component/page and remove the duplicate static asset, ensuring the router/build
uses only the new component; also run a quick search to remove any remaining
references to the deleted file.

Comment thread generated_page.html
<span class="text-xs text-orange-600 cursor-pointer hover:underline font-medium">Balance: 4.2 ETH</span>
</div>
<div class="relative group">
<input type="number" id="amount-in" placeholder="0.00" class="w-full bg-white border border-gray-200 rounded-lg pl-4 pr-24 py-4 text-xl font-mono text-slate-900 placeholder-slate-300 focus:outline-none focus:border-orange-500 focus:ring-1 focus:ring-orange-500 transition-all shadow-sm" oninput="updateCalculation()">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add input validation and error handling for amount inputs.

The amount-in input (line 315) accepts any numeric value without bounds checking or validation feedback. The calculation logic (lines 457–492) handles NaN gracefully (lines 461–465), but there are missing safeguards:

  • No upper bound: User can enter arbitrarily large numbers; no warning if it exceeds wallet balance (which is hardcoded at 4.2 ETH anyway).
  • No decimal precision limit: Depending on token decimals, users could enter amounts that cause rounding errors or precision loss.
  • Silent failures: If updateCalculation() encounters an error, there's no user-facing error message or validation feedback.

Add client-side validation and provide clear feedback:

function validateAmount(val, maxBalance = 4.2) {
  if (isNaN(val) || val < 0) return { valid: false, error: "Invalid amount" };
  if (val > maxBalance) return { valid: false, error: "Insufficient balance" };
  if (!/^\d+\.?\d{0,8}$/.test(val.toString())) return { valid: false, error: "Too many decimals" };
  return { valid: true };
}

// Use in updateCalculation():
const val = parseFloat(elAmountIn.value);
const validation = validateAmount(val);
if (!validation.valid) {
  elAmountOut.value = "";
  elRateDisplay.innerText = validation.error;
  return;
}

Display validation errors clearly in the UI (e.g., below the input or in a toast).

Also applies to: 335-335

🤖 Prompt for AI Agents
In generated_page.html around lines 315 and 335, the amount-in input needs
client-side validation and user-facing error handling: add a validateAmount
function that checks NaN, negative values, an upper bound (use the 4.2 ETH
maxBalance or a configurable constant), and a decimal-precision limit (e.g., max
8 decimals); call this validator at the start of updateCalculation(), and if
invalid clear the output, display the validation error text in a visible UI
element (or inline message below the input) and apply an error style to the
input, and wrap updateCalculation logic in a try/catch that shows a friendly
error message on exception so calculations never fail silently.

Comment thread generated_page.html
Comment on lines +387 to +522
<script>
// --- Bank Logic ---

// Constants
const PRICES = {
ETH: 2042.50,
DJED: 1.00,
SHEN: 2.45
};
const FEES = 0.015; // 1.5%

// State
let currentTab = 'mint';
let currentToken = 'DJED';

// Elements
const elAmountIn = document.getElementById('amount-in');
const elAmountOut = document.getElementById('amount-out');
const elTickerIn = document.getElementById('ticker-in');
const elTickerOut = document.getElementById('ticker-out');
const elRateDisplay = document.getElementById('rate-display');
const elActionBtn = document.getElementById('action-btn');
const elInputLabel = document.getElementById('input-label');
const elOutputLabel = document.getElementById('output-label');
const btnMint = document.getElementById('tab-mint');
const btnRedeem = document.getElementById('tab-redeem');

function switchTab(tab) {
currentTab = tab;

// Style Updates
if(tab === 'mint') {
btnMint.classList.remove('text-slate-500', 'border-transparent', 'hover:bg-gray-50');
btnMint.classList.add('text-orange-600', 'border-orange-500', 'bg-orange-50/50');

btnRedeem.classList.add('text-slate-500', 'border-transparent', 'hover:bg-gray-50');
btnRedeem.classList.remove('text-orange-600', 'border-orange-500', 'bg-orange-50/50');

elActionBtn.innerHTML = `Connect Wallet to Mint <iconify-icon icon="lucide:arrow-right" width="16"></iconify-icon>`;
elInputLabel.innerText = "You Pay";
} else {
btnRedeem.classList.remove('text-slate-500', 'border-transparent', 'hover:bg-gray-50');
btnRedeem.classList.add('text-orange-600', 'border-orange-500', 'bg-orange-50/50');

btnMint.classList.add('text-slate-500', 'border-transparent', 'hover:bg-gray-50');
btnMint.classList.remove('text-orange-600', 'border-orange-500', 'bg-orange-50/50');

elActionBtn.innerHTML = `Connect Wallet to Redeem <iconify-icon icon="lucide:arrow-right" width="16"></iconify-icon>`;
elInputLabel.innerText = "You Burn";
}

updateLabels();
updateCalculation();
}

function updateLabels() {
const radios = document.getElementsByName('token');
for (let radio of radios) {
if (radio.checked) currentToken = radio.value;
}

if (currentTab === 'mint') {
elTickerIn.innerText = "ETH";
elTickerOut.innerText = currentToken;
} else {
elTickerIn.innerText = currentToken;
elTickerOut.innerText = "ETH";
}
}

function updateCalculation() {
updateLabels();
const val = parseFloat(elAmountIn.value);

if (isNaN(val) || val <= 0) {
elAmountOut.value = "";
elRateDisplay.innerText = "-";
return;
}

let result = 0;
let rateText = "";

if (currentTab === 'mint') {
// ETH -> Token
const ethValue = val * PRICES.ETH;
const tokenPrice = PRICES[currentToken];
const rawAmount = ethValue / tokenPrice;
const finalAmount = rawAmount * (1 - FEES);

result = finalAmount;
rateText = `1 ETH = ${(PRICES.ETH / tokenPrice).toFixed(2)} ${currentToken}`;

} else {
// Token -> ETH
const tokenValue = val * PRICES[currentToken];
const rawEth = tokenValue / PRICES.ETH;
const finalEth = rawEth * (1 - FEES);

result = finalEth;
rateText = `1 ${currentToken} = ${(PRICES[currentToken] / PRICES.ETH).toFixed(6)} ETH`;
}

elAmountOut.value = result.toFixed(4);
elRateDisplay.innerText = rateText;
}

// --- Gauge Animation ---
const gaugePath = document.getElementById('health-gauge');
const ratioDisplay = document.getElementById('ratio-display');
let currentRatio = 546;

setInterval(() => {
const change = (Math.random() - 0.5) * 8;
currentRatio = Math.max(400, Math.min(800, currentRatio + change));

ratioDisplay.innerText = Math.floor(currentRatio) + "%";

// Max dasharray is approx 251 (2 * PI * 40 - partial circle logic)
// Let's approximate the stroke-dashoffset logic for the SVG path
// Range 400% to 800%
const percentage = (currentRatio - 400) / 400; // 0 to 1
// Invert logic for dashoffset: 251 is empty, 0 is full (roughly)
const offset = 251 - (200 * percentage);

gaugePath.style.strokeDashoffset = offset;

// Visual warning color
if(currentRatio < 420) {
gaugePath.setAttribute('stroke', '#ef4444'); // Red warning
} else {
gaugePath.setAttribute('stroke', '#f97316'); // Orange normal
}
}, 3000);

</script>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix memory leak: setInterval has no cleanup handler.

Line 499 starts a setInterval that runs every 3 seconds indefinitely. If the page is navigated away or the component is unmounted (especially important when converting to React/Next.js), the interval persists in memory and continues polling, consuming resources and potentially causing stale state mutations.

Store the interval ID and clear it on page unload or, preferably, wrap this in a page lifecycle handler.

// Current (lines 499-520):
setInterval(() => {
  // gauge update logic
}, 3000);

// Proposed fix (for now, for static HTML):
const gaugeIntervalId = setInterval(() => {
  // gauge update logic
}, 3000);

// Add cleanup on page unload:
window.addEventListener('beforeunload', () => {
  clearInterval(gaugeIntervalId);
});

When converting to React/Next.js, use useEffect with cleanup:

useEffect(() => {
  const id = setInterval(() => { /* ... */ }, 3000);
  return () => clearInterval(id);
}, []);
🤖 Prompt for AI Agents
In generated_page.html around lines 387 to 522, the setInterval started at line
~499 is never cleared causing a memory leak; capture the interval ID when
creating it and call clearInterval on page unload (e.g., window.beforeunload or
unload) to stop it, or if migrating to React/Next.js move the logic into a
useEffect and return a cleanup function that clears the interval; ensure the
interval ID is stored in a variable with appropriate scope so the cleanup can
reference it.

Comment thread generated_page.html
btnRedeem.classList.add('text-slate-500', 'border-transparent', 'hover:bg-gray-50');
btnRedeem.classList.remove('text-orange-600', 'border-orange-500', 'bg-orange-50/50');

elActionBtn.innerHTML = `Connect Wallet to Mint <iconify-icon icon="lucide:arrow-right" width="16"></iconify-icon>`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid dynamic innerHTML for UI elements; use safer patterns.

Lines 425 and 434 use innerHTML to dynamically set button text with an embedded <iconify-icon> tag:

elActionBtn.innerHTML = `Connect Wallet to Mint <iconify-icon icon="lucide:arrow-right" width="16"></iconify-icon>`;

While the Iconify library is from a trusted CDN, using innerHTML with template strings is generally unsafe and can introduce XSS if user input or untrusted data ever enters the template. Additionally, replacing the entire HTML content destroys any event listeners attached to child nodes.

Safer approach: Update only the text content, or append pre-built elements:

// Option 1: Update text only, preserve iconify element
const textNode = elActionBtn.querySelector('span:first-child');
textNode.innerText = 'Connect Wallet to Mint';

// Option 2: Use textContent + createElement for dynamic elements
elActionBtn.textContent = 'Connect Wallet to Mint ';
const icon = document.createElement('iconify-icon');
icon.setAttribute('icon', 'lucide:arrow-right');
icon.setAttribute('width', '16');
elActionBtn.appendChild(icon);

Also applies to: 434-434

🤖 Prompt for AI Agents
In generated_page.html around lines 425 and 434, avoid using innerHTML to set
the button content; instead preserve existing child nodes and event listeners by
updating only the button text (e.g., update a child span's textContent) or by
setting the button's textContent and creating a new icon element via
document.createElement('iconify-icon'), set its attributes (icon and width) and
append it to the button; ensure you do not use string templates for HTML
injection and that any existing listeners on child nodes are not removed.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant