diff --git a/.gitignore b/.gitignore index 844c4d1..b5f76c4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ vendor/ dist/ build/ .next/ +*.tsbuildinfo *.exe *.exe~ *.dll diff --git a/frontend/FRONTEND_REVIEW.md b/frontend/FRONTEND_REVIEW.md new file mode 100644 index 0000000..d031498 --- /dev/null +++ b/frontend/FRONTEND_REVIEW.md @@ -0,0 +1,139 @@ +# Frontend Code Review – Explorer Monorepo + +**Scope:** `explorer-monorepo/frontend/` +**Reviewed:** Vanilla JS SPA (`public/index.html`), React/Next.js app (`src/`), services, config. +**Date:** 2025-02-09 + +--- + +## 1. Overview + +The frontend has two delivery paths: + +| Asset | Purpose | Deployed | +|-------|--------|----------| +| **`public/index.html`** | Single-page explorer (Blocks, Transactions, Bridge, WETH, search, wallet connect). Vanilla JS, ~4.2k lines. | Yes (VMID 5000, https://explorer.d-bis.org) | +| **`src/`** (Next.js) | React app (blocks, transactions, addresses, search, wallet). Uses Blockscout-style API. | No (dev/build only) | + +--- + +## 2. Vanilla JS SPA (`public/index.html`) + +### 2.1 Security + +**Good:** +- **`escapeHtml()`** is used for error messages, revert reasons, method names, ABI/bytecode, token names/symbols, NFT metadata, and other user/API-derived strings before `innerHTML`. Reduces XSS from API or user input. +- **CSP** in `` restricts script/style/font/connect sources; comment documents `unsafe-eval` for ethers v5 UMD. +- **Credentials:** `fetchAPI` uses `credentials: 'omit'` for API calls. +- **Wallet:** No private keys or secrets in code; MetaMask/ethers used for signing. + +**Improve:** +- **Defense in depth for hashes/addresses:** Any API-derived string (e.g. `hash`, `from`, `to`, `address`) that is interpolated into `innerHTML` should be escaped. Currently: + - Block cards: `shortenHash(hash)` and block number are injected without `escapeHtml`. Block numbers are numeric; hashes are usually hex from a trusted API, but escaping would harden against a compromised or malicious API. + - Breadcrumbs: `shortenHash(identifier)` and `identifier` in `href="#/address/' + identifier + '"` – if `identifier` can contain `'` or `"`, it could break attributes or enable injection. Recommend `escapeHtml(shortenHash(hash))` for display and sanitize/validate for attributes. +- **`shortenHash`:** Only truncates; does not strip HTML. Use `escapeHtml(shortenHash(hash))` wherever the result is used in HTML. + +### 2.2 Correctness & Robustness + +**Good:** +- **Navigation:** Re-entrancy guard (`_inNavHandler`) and “showView first, then set hash” prevent the previous infinite recursion from hashchange. +- **`applyHashRoute`:** Uses `currentView` and `currentDetailKey` so the same view/detail is not re-applied unnecessarily. +- **`fetchAPI`:** Uses `AbortController` and 15s timeout; `AbortError` is handled in retry logic. +- **`fetchAPIWithRetry`:** Exponential backoff; retries on timeout/5xx/network; does not retry on non-retryable errors. +- **RPC fallback:** When Blockscout API fails, blocks/transactions can be loaded via `rpcCall` (e.g. `eth_blockNumber`, `eth_getBlockByNumber`). +- **Validation:** `safeBlockNumber`, `safeTxHash`, `safeAddress` used before detail views and in retry buttons. +- **Wrap/Unwrap:** Balance checks and `callStatic` simulation before deposit/withdraw; user rejection vs contract error distinguished in messages. + +**Improve:** +- **`rpcCall`:** Uses a single RPC URL; does not use `getRpcUrl()` for failover. Consider using `getRpcUrl()` for critical RPC calls so the app benefits from the same failover as elsewhere. +- **Memory/cleanup:** `requestAnimationFrame(animateScroll)` in the blocks scroll runs indefinitely. On view change, the loop is not cancelled; consider storing the frame id and cancelling in a cleanup when leaving the blocks view. +- **Breadcrumb `identifier`:** In `updateBreadcrumb`, `identifier` is used in `href="#/address/' + identifier + '"`. If `identifier` contains `'`, the attribute can break. Prefer escaping or using `encodeURIComponent` for path segments. + +### 2.3 Structure & Maintainability + +- **Single file:** ~4.2k lines in one HTML file makes navigation and testing harder. Consider splitting script into logical modules (e.g. API, nav, views, wallet) and bundling, or at least grouping related functions and marking sections. +- **Duplicate logic:** Block card HTML is built in multiple places (home stats area, blocks list, block detail). A single `createBlockCard`-style helper (or shared template) would reduce drift and bugs. +- **Magic numbers:** Timeouts (15s, 5s), retry counts (3), and delays are literal; consider named constants at the top of the script. +- **Console:** Several `console.log`/`console.warn`/`console.error` calls are useful for debugging but could be gated by a `DEBUG` flag or removed for production if desired. + +### 2.4 Accessibility & UX + +- Nav links use `onclick` and `aria-label` where checked; focus and keyboard flow should be verified (e.g. tab order, Enter to activate). +- Error messages and retry buttons are visible; consider ensuring they are announced (e.g. live region) for screen readers. +- Dark theme is supported and persisted in `localStorage`. + +--- + +## 3. React/Next.js App (`src/`) + +### 3.1 Security + +- **React escaping:** Components render props as text by default, so no raw `dangerouslySetInnerHTML` was found; Address, Card, Table, etc. are safe from XSS in normal use. +- **API client:** Uses `localStorage.getItem('api_key')` for `X-API-Key`; ensure key is not exposed in logs or error messages. +- **Env:** `NEXT_PUBLIC_*` is appropriate for client-side config; no secrets in frontend code. + +### 3.2 Data Fetching & API Shape + +**Issues:** +- **`addresses/[address].tsx`:** Uses `fetch()` then `response.json()` without checking `response.ok`. On 4xx/5xx, `data` may be an error body and `setAddressInfo(data.data)` can set invalid state. Recommend: check `response.ok`, handle errors, and only set state on success. +- **`blocksApi.list`:** Returns `ApiResponse` (expects `{ data: Block[] }`). If the backend returns a different shape (e.g. `{ items: [] }`), `response.data` may be `undefined` and `setBlocks(response.data)` can lead to runtime errors in `blocks.map()`. Align client with actual API response or normalize in the client. +- **Home page:** `loadRecentBlocks()` uses `blocksApi.list`; same shape assumption as above. `loadStats()` is a placeholder (no real API call). + +### 3.3 Correctness + +- **useEffect deps:** In `page.tsx`, `useEffect(..., [])` calls `loadStats` and `loadRecentBlocks` which are recreated each render. This is fine with empty deps (run once). In `blocks/index.tsx` and `transactions/index.tsx`, deps are `[page]`; `loadBlocks`/`loadTransactions` are not in the dependency array, which is intentional to avoid unnecessary runs; no bug found. +- **Block detail:** `blocks/[number].tsx` uses `parseInt((params?.number as string) ?? '0')` – invalid or missing number becomes `NaN`/`0`; the API may return 404. Consider validating and showing a clear “Invalid block number” message. +- **Table key:** `Table.tsx` uses `key={rowIndex}`; if the list is reordered or filtered, prefer a stable key (e.g. `block.number`, `tx.hash`). + +### 3.4 Consistency & Gaps + +- **Routing:** Next.js app uses file-based routes (`/blocks`, `/transactions`, `/addresses/[address]`). The deployed SPA uses hash routing (`#/blocks`, `#/address/0x...`). They are separate; no conflict, but be aware that deep links differ between the two frontends. +- **API base:** React app uses `NEXT_PUBLIC_API_URL` (default `http://localhost:8080`). The vanilla SPA uses same-origin `/api` or Blockscout API. Ensure backend and env are aligned when running the Next app against a real API. +- **blocks API:** Only blocks are implemented in `services/api/`; transactions and addresses use raw `fetch` in pages. Consider moving to shared API modules and the same client for consistency and error handling. + +--- + +## 4. Services & Config + +### 4.1 API Client (`src/services/api/client.ts`) + +- Axios instance with timeout (30s), JSON headers, and optional `X-API-Key` from `localStorage`. +- Response interceptor rejects with `error.response.data`; type is `ApiError`. Callers should handle both API error shape and network errors. +- **Note:** Used by blocks API only; other pages use `fetch` directly. + +### 4.2 Blocks API (`src/services/api/blocks.ts`) + +- Builds query params correctly; uses `apiClient.get`. +- Return type `ApiResponse` assumes backend returns `{ data: T }`. If backend is Blockscout-style (`items`, etc.), either add an adapter or document the expected backend contract. + +### 4.3 Next.js Config + +- `reactStrictMode: true`, `output: 'standalone'`. +- `NEXT_PUBLIC_API_URL` and `NEXT_PUBLIC_CHAIN_ID` defaulted in `next.config.js`; can be overridden by env. + +--- + +## 5. Recommendations Summary + +| Priority | Item | Action | +|----------|------|--------| +| **P1** | Address page fetch | In `addresses/[address].tsx`, check `response.ok` and handle non-2xx before parsing JSON and setting state. | +| **P1** | API response shape | Confirm backend response shape for blocks (and any shared APIs). Normalize to `{ data }` in client or document and handle `items`/other shapes. | +| **P2** | XSS hardening (SPA) | Use `escapeHtml(shortenHash(hash))` (and escape other API-derived strings) wherever content is written with `innerHTML`. Escape or encode `identifier` in breadcrumb `href`. | +| **P2** | RPC failover | Use `getRpcUrl()` inside `rpcCall()` (or for critical paths) so RPC failover is consistent. | +| **P2** | Blocks scroll animation | Cancel `requestAnimationFrame` when leaving the blocks view (e.g. in a cleanup or when switching view). | +| **P3** | SPA structure | Split script into modules or clearly grouped sections; extract shared constants and block-card markup. | +| **P3** | Table keys | Use stable keys (e.g. `block.number`, `tx.hash`) in list components instead of index. | +| **P3** | Block number validation | In `blocks/[number].tsx`, validate `params.number` and show a clear message for invalid or missing block number. | + +--- + +## 6. Files Reviewed + +- `public/index.html` – full read and grep for escapeHtml, innerHTML, fetch, navigation, wallet. +- `src/app/layout.tsx`, `src/app/page.tsx`, `src/app/wallet/page.tsx` +- `src/pages/_app.tsx`, `src/pages/blocks/index.tsx`, `src/pages/blocks/[number].tsx`, `src/pages/transactions/index.tsx`, `src/pages/transactions/[hash].tsx`, `src/pages/addresses/[address].tsx`, `src/pages/search/index.tsx` +- `src/components/common/Card.tsx`, `Button.tsx`, `Table.tsx` +- `src/components/blockchain/Address.tsx`, `src/components/wallet/AddToMetaMask.tsx` +- `src/services/api/client.ts`, `src/services/api/blocks.ts` +- `package.json`, `next.config.js` diff --git a/frontend/FRONTEND_TASKS_AND_REVIEW.md b/frontend/FRONTEND_TASKS_AND_REVIEW.md new file mode 100644 index 0000000..33e664f --- /dev/null +++ b/frontend/FRONTEND_TASKS_AND_REVIEW.md @@ -0,0 +1,140 @@ +# Frontend: Full Task List (Critical → Optional) + Detail Review + +**Full parallel mode:** Tasks in the same tier can be executed in parallel by different owners. Dependencies are called out where a later task requires an earlier one. + +--- + +## Quick reference (all tasks) + +| Priority | ID | One-line description | +|----------|----|----------------------| +| Critical | C1 | Address page: check `response.ok` before setting state | +| Critical | C2 | Transaction detail page: check `response.ok` before setting state | +| Critical | C3 | Search page: check `response.ok` before setting results | +| Critical | C4 | Blocks API: confirm/normalize backend response shape `{ data }` | +| High | H1 | SPA: `escapeHtml(shortenHash(...))` and escape API text in all innerHTML | +| High | H2 | SPA: safe breadcrumb/href (encode identifier, contract, addr in URLs) | +| High | H3 | SPA: escape dynamic values in all onclick attributes | +| High | H4 | SPA: use `getRpcUrl()` in `rpcCall()` for failover | +| High | H5 | SPA: cancel blocks scroll `requestAnimationFrame` on view change | +| Medium | M1 | React: validate block number in `blocks/[number].tsx` | +| Medium | M2 | React: stable list keys (Table + search results) | +| Medium | M3 | SPA: named constants for timeouts/retries | +| Medium | M4 | Blocks API: normalizer for backend shape (if needed after C4) | +| Low | L1 | SPA: extract shared block card markup helper | +| Low | L2 | SPA: DEBUG flag for console logs | +| Low | L3 | A11y: live region for error messages | +| Low | L4 | React: shared API modules for transactions/addresses | + +--- + +## Part 1: Task List (Critical → Optional) + +### CRITICAL (P1) – Fix first; blocks correct behavior or security + +| ID | Task | Owner / parallel | Notes | +|----|------|------------------|--------| +| **C1** | **Address page: check `response.ok`** – In `src/pages/addresses/[address].tsx`, before `response.json()` and `setAddressInfo(data.data)`, check `response.ok`. On non-2xx: do not set state; set error/empty and optionally `setLoading(false)`. | 1 | Prevents treating error body as address data. | +| **C2** | **Transaction detail page: check `response.ok`** – In `src/pages/transactions/[hash].tsx`, same pattern: check `response.ok` before parsing and `setTransaction(data.data)`. Handle 404/5xx without setting transaction state. | 1 | Same bug as C1. | +| **C3** | **Search page: check `response.ok`** – In `src/pages/search/index.tsx`, check `response.ok` before `setResults(data.results \|\| [])`. On failure, set empty results and optionally show a message. | 1 | Avoids showing error payload as results. | +| **C4** | **Blocks API response shape** – Confirm backend for `/api/v1/blocks` returns `{ data: Block[] }` (or document contract). If it returns `{ items: [] }` or similar, add a normalizer in `src/services/api/blocks.ts` (e.g. `response.data = response.items ?? response.data`) so `response.data` is always an array for list. | 1 | Prevents `blocks.map` / `recentBlocks.map` runtime errors. | + +**Parallel:** C1, C2, C3, C4 can all be done in parallel. + +--- + +### HIGH (P2) – Security and correctness; no known exploit but reduces risk + +| ID | Task | Owner / parallel | Notes | +|----|------|------------------|--------| +| **H1** | **SPA: escape all `shortenHash(...)` in `innerHTML`** – Every place that assigns to `innerHTML` and includes `shortenHash(hash|address|from|to|identifier|...)` must use `escapeHtml(shortenHash(...))`. Locations (grep): breadcrumbs (2243, 2247, 2254, 2259, 2280, 2284, 2288, 2292), block cards (2628), transaction rows (2794–2796, 2909, 2994–2996), bridge table (3108, 3144), internal tx/logs (3526, 3543), token balances (3746), address internal txs (3774), address tx table (3832), token detail (3887, 3905), NFT detail (3935, 3943), statusEl (1639, 1722). Also escape any other API-derived text in those same innerHTML strings (e.g. `displayBalance`, `val` if from API). | 1 | XSS defense in depth. | +| **H2** | **SPA: safe breadcrumb and `href` attributes** – In `updateBreadcrumb`, ensure `identifier` in `href="#/address/' + identifier + '"` cannot break the attribute. Use a safe encoding (e.g. `encodeURIComponent(identifier)` for the path segment, or ensure identifier is validated hex address). Same for token/nft breadcrumb links. | 1 | Prevents attribute breakout. | +| **H3** | **SPA: safe `onclick` attribute values** – Every `onclick="showAddressDetail('...')"` / `showBlockDetail` / `showTransactionDetail` that injects a dynamic value (address, hash, block number) must use an escaped value so a quote in the value cannot break the attribute. Use `escapeHtml(address)` (or equivalent) for the value inside the quoted string. Applies to all dynamic onclick handlers in index.html (see grep list: 3108, 3144, 3526, 3543, 3638, 3746, 3774, 3832, 3887, 3905, 3935, 3943, and template literals with `${address}`, `${hash}`, `${from}`, `${to}`, etc.). | 1 | Prevents attribute injection / XSS. | +| **H4** | **RPC failover in `rpcCall`** – In `public/index.html`, change `rpcCall` to use `getRpcUrl()` (await) instead of a single RPC_IP/RPC_FQDN, so RPC calls benefit from the same health-check and failover as elsewhere. | 1 | Consistency and resilience. | +| **H5** | **Blocks scroll: cancel `requestAnimationFrame` on view change** – When leaving the blocks view (or when `loadLatestBlocks` runs again), cancel the animation loop. Store the `requestAnimationFrame` id (e.g. in a variable or on `scrollContainer.dataset`) and call `cancelAnimationFrame(id)` when switching view or before re-running the block list render. | 1 | Avoids leaking animation loop and unnecessary work. | + +**Parallel:** H1, H2, H3 can be done together; H4 and H5 are independent and can run in parallel with each other and with H1–H3. + +--- + +### MEDIUM (P3) – Quality and maintainability + +| ID | Task | Owner / parallel | Notes | +|----|------|------------------|--------| +| **M1** | **Block number validation (React)** – In `src/pages/blocks/[number].tsx`, validate `params.number`: reject non-numeric or NaN; show a clear “Invalid block number” (or “Block number required”) message instead of calling API with 0. | 1 | Better UX. | +| **M2** | **Stable list keys (React)** – In `src/components/common/Table.tsx`, accept an optional `keyExtractor` prop; in pages that use Table (e.g. addresses, blocks), pass a stable key (e.g. `tx.hash`, `block.number`). In search results, use `key={result.data?.hash ?? result.data?.address ?? index}` instead of `key={index}`. | 1 | Avoids React reconciliation issues. | +| **M3** | **SPA: named constants** – In `public/index.html`, replace magic numbers with named constants at the top of the script: e.g. `FETCH_TIMEOUT_MS = 15000`, `RPC_HEALTH_TIMEOUT_MS = 5000`, `FETCH_MAX_RETRIES = 3`, `RETRY_DELAY_MS = 1000`. | 1 | Maintainability. | +| **M4** | **API response normalization (blocks)** – If backend is Blockscout-style, add in `blocks.ts` (or client): normalize `{ items: [] }` → `{ data: [] }` so all consumers get a consistent shape without each page handling both. | 1 | Depends on C4 decision; can follow C4. | + +**Parallel:** M1, M2, M3, M4 can be done in parallel (M4 after C4 if adding normalizer). + +--- + +### LOW / OPTIONAL (P4) + +| ID | Task | Owner / parallel | Notes | +|----|------|------------------|--------| +| **L1** | **SPA: extract shared block card markup** – Introduce a single helper (e.g. `createBlockCardHtml(block, options)`) used by home stats, blocks list, and any other block card HTML to reduce duplication. | 1 | Maintainability. | +| **L2** | **SPA: DEBUG flag for console** – Gate `console.log`/`console.warn` (and optionally `console.error`) behind a flag (e.g. `window.DEBUG_EXPLORER`) so production builds can suppress verbose logs. | 1 | Optional. | +| **L3** | **A11y: live region for errors** – Add an `aria-live="polite"` (or `assertive`) region for error messages and retry buttons so screen readers announce them. | 1 | Accessibility. | +| **L4** | **Shared API modules (React)** – Add `src/services/api/transactions.ts` and `addresses.ts` (or similar) using the same `apiClient`, and refactor `addresses/[address].tsx` and `transactions/[hash].tsx` to use them with consistent error handling. | 1 | Consistency. | + +**Parallel:** L1–L4 independent; all optional. + +--- + +## Part 2: Detail Review – Misses, Gaps, Additional Fixes + +### 2.1 Additional React fetch bugs (misses from original review) + +- **`transactions/[hash].tsx`** – Same pattern as addresses: no `response.ok` check; `setTransaction(data.data)` can run on 4xx/5xx. **Action:** Add to critical list as **C2** (done above). +- **`search/index.tsx`** – No `response.ok` check; `setResults(data.results || [])` can set error payload. **Action:** Add as **C3** (done above). + +### 2.2 SPA: Gaps in escapeHtml / innerHTML + +- **Wallet status (1639, 1722)** – `statusEl.innerHTML` uses `shortenHash(userAddress)`. If `userAddress` were ever from an untrusted source, it should be escaped. **Action:** Use `escapeHtml(shortenHash(userAddress))` for consistency (in **H1**). +- **loadGasAndNetworkStats (2509)** – `el.innerHTML` uses `gasGwei`, `blockTimeSec`, `tps`. These are from API; escaping is low risk but recommended for defense in depth. **Action:** Escape these values (in **H1** or small follow-up). +- **Token list: `#/token/' + contract`** – The `contract` in `href="#/token/' + contract + '"` can break the attribute if it contains a quote. **Action:** Encode or validate; include in **H2** (safe href/attributes). +- **External link (3800)** – `'https://explorer.d-bis.org/address/' + addr + '/contract'` – `addr` should be validated or encoded so the URL cannot be malformed. **Action:** Use `encodeURIComponent(addr)` for the path segment (in **H2**). + +### 2.3 SPA: onclick and attribute injection + +- **All dynamic onclick handlers** – Values like `address`, `hash`, `from`, `to`, `block`, `txHash`, `contract`, `contractAddress` are interpolated into onclick strings. A single quote in any of them breaks the attribute and can lead to script execution. **Action:** Consistently use `escapeHtml(value)` for every dynamic part inside the quoted argument (in **H3**). Example: `onclick="showAddressDetail('" + escapeHtml(address) + "')"` (and equivalent for template literals). + +### 2.4 SPA: requestAnimationFrame leak (clarification) + +- The blocks scroll animation starts inside `loadLatestBlocks()` and is never cancelled. When the user navigates to Transactions or Home, the blocks view is hidden but the animation loop keeps running. **Action:** Store the rAF id (e.g. `let scrollAnimationId` in a scope that can be cleared when switching view). When `switchToView` or the blocks view is left, or before the next `loadLatestBlocks` run, call `cancelAnimationFrame(scrollAnimationId)`. Optionally clear the interval/timeout if any. **In task H5.** + +### 2.5 React: API shape and Table key (additional) + +- **Home page `recentBlocks`** – Uses `response.data` from `blocksApi.list`; if `response.data` is undefined (e.g. backend returns `items`), `recentBlocks.map` will throw. **Action:** Covered by **C4** and **M4** (normalize in client). +- **Table key** – Using `rowIndex` is acceptable for static lists but can cause unnecessary re-renders or focus issues when list order changes. **Action:** **M2** (stable keys). +- **Search results key** – `key={index}` is weak when results can change; prefer `key={result.data?.hash ?? result.data?.address ?? result.data?.number ?? index}`. **Action:** Include in **M2**. + +### 2.6 Additional checks performed + +- **rpcCall** – Confirmed it does not use `getRpcUrl()`; single URL used. **Action:** **H4**. +- **Breadcrumb identifier** – Multiple places use `identifier` in innerHTML and in `href`; both display and href need to be safe. **Action:** **H1**, **H2**. +- **No other raw `dangerouslySetInnerHTML`** in React components; no additional React XSS findings. +- **transactions/[hash].tsx** – No validation of `hash` format (e.g. 0x + 64 hex); invalid hash could trigger odd API/UI behavior. Optional: add validation and show “Invalid transaction hash” (can be P4). + +### 2.7 Summary of additional fixes required + +| Category | Fix | +|----------|-----| +| React fetch | C2 (transaction detail), C3 (search) – check `response.ok` and handle errors. | +| SPA innerHTML | H1 – include statusEl (1639, 1722), loadGasAndNetworkStats (2509); escape all shortenHash and API-derived text. | +| SPA href/attributes | H2 – encode/validate `identifier` and `contract` in hrefs; encode `addr` in external URL. | +| SPA onclick | H3 – escape every dynamic value in onclick (address, hash, from, to, block, txHash, contract, etc.). | +| SPA rAF | H5 – store and cancel requestAnimationFrame when leaving blocks view or re-running loadLatestBlocks. | +| Optional | Validate tx hash in transactions/[hash].tsx (P4); add transactions/addresses API modules (L4). | + +--- + +## Part 3: Execution Order (Full Parallel Mode) + +- **Wave 1 (all parallel):** C1, C2, C3, C4, H1, H2, H3, H4, H5. +- **Wave 2 (after C4/M4 if normalizer added):** M1, M2, M3, M4. +- **Wave 3 (optional):** L1, L2, L3, L4. + +No task in Wave 2 or 3 blocks another within the same wave; only M4 may depend on C4’s decision (normalize in client vs. backend contract). diff --git a/frontend/public/apple-touch-icon.png b/frontend/public/apple-touch-icon.png new file mode 100644 index 0000000..5804ed0 Binary files /dev/null and b/frontend/public/apple-touch-icon.png differ diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico new file mode 100644 index 0000000..f682711 Binary files /dev/null and b/frontend/public/favicon.ico differ diff --git a/frontend/public/icon.svg b/frontend/public/icon.svg new file mode 100644 index 0000000..455d299 --- /dev/null +++ b/frontend/public/icon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/frontend/public/index.html b/frontend/public/index.html index 653b36a..5cc4c95 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -756,14 +756,14 @@
@@ -777,6 +777,7 @@
+
@@ -1056,19 +1057,57 @@