Skip to content

Commit

Permalink
Merge pull request #160 from alexmf91/release/1.5.1
Browse files Browse the repository at this point in the history
feat: Add archiver v2 implementation and update related transactions fetching
  • Loading branch information
AndyQus authored Sep 22, 2024
2 parents 9e490af + 6436488 commit e57ca02
Show file tree
Hide file tree
Showing 46 changed files with 664 additions and 526 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
VITE_ENABLE_PROXY=
VITE_QLI_API_URL=
VITE_ARCHIVER_API_URL=
19 changes: 2 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ cd <project-directory-name>

### Step 2: Configure Environment Variables

Before running the project, you must configure environment-specific variables for development and
production environments.
Before running the project, you must configure environment-specific variables for development environment.

- **Development Environment**:

Expand All @@ -30,25 +29,11 @@ production environments.
Open this file and add the development API URL:

```
VITE_ENABLE_PROXY=true
VITE_QLI_API_URL=/dev-proxy-qli-api
VITE_ARCHIVER_API_URL=/dev-proxy-archiver-api
```

- **Production Environment**:

Copy the `.env.example` file, renaming it to `.env.production.local`:

```
cp .env.production.local.example .env.production.local
```

Then, set the production API URL:

```
VITE_QLI_API_URL=https://api.qubic.li
VITE_ARCHIVER_API_URL=https://rpc.qubic.org/v1
```

Ensure these files are not committed to the repository to protect sensitive information.

### Step 3: Install Dependencies
Expand Down
110 changes: 46 additions & 64 deletions dev-proxy.config.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,51 @@
import type { HttpProxy, ProxyOptions } from 'vite'

export const qliApiProxy: ProxyOptions = {
target: 'https://api.qubic.li',
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/dev-proxy-qli-api/, ''),
configure: (proxy: HttpProxy.Server, options: ProxyOptions) => {
proxy.on('proxyReq', (proxyReq, req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE')
res.setHeader(
'Access-Control-Allow-Headers',
req.headers['access-control-request-headers'] || ''
)

if (req.method === 'OPTIONS') {
res.writeHead(200)
res.end()
return
}

// eslint-disable-next-line no-console
console.log(`[QLI-API-DEV-PROXY] - API CALL - [${req.method}] ${options.target}${req.url}`)
proxyReq.setHeader('Authorization', req.headers.authorization || '')
})

proxy.on('error', (err, _req, res) => {
// eslint-disable-next-line no-console
console.error(`Proxy error: ${err.message}`)
res.writeHead(500, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ error: 'Proxy error', details: err.message }))
})
export const createProxyConfig = (
target: string,
rewritePath: string,
label = 'PROXY'
): ProxyOptions => {
return {
target,
changeOrigin: true,
rewrite: (path: string) => path.replace(rewritePath, ''),
configure: (proxy: HttpProxy.Server, options: ProxyOptions) => {
proxy.on('proxyReq', (proxyReq, req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE')
res.setHeader(
'Access-Control-Allow-Headers',
req.headers['access-control-request-headers'] || ''
)

if (req.method === 'OPTIONS') {
res.writeHead(200)
res.end()
return
}

// eslint-disable-next-line no-console
console.log(`[${label}] - API CALL - [${req.method}] ${options.target}${req.url}`)
proxyReq.setHeader('Authorization', req.headers.authorization || '')
})

proxy.on('error', (err, _req, res) => {
// eslint-disable-next-line no-console
console.error(`Proxy error: ${err.message}`)
res.writeHead(500, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ error: 'Proxy error', details: err.message }))
})
}
}
}

export const archiverApiProxy: ProxyOptions = {
target: 'https://rpc.qubic.org/v1',
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/dev-proxy-archiver-api/, ''),
configure: (proxy: HttpProxy.Server, options: ProxyOptions) => {
proxy.on('proxyReq', (proxyReq, req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE')
res.setHeader(
'Access-Control-Allow-Headers',
req.headers['access-control-request-headers'] || ''
)

if (req.method === 'OPTIONS') {
res.writeHead(200)
res.end()
return
}

// eslint-disable-next-line no-console
console.log(
`[ARCHIVER-API-DEV-PROXY] - API CALL - [${req.method}] ${options.target}${req.url}`
)
proxyReq.setHeader('Authorization', req.headers.authorization || '')
})

proxy.on('error', (err, _req, res) => {
// eslint-disable-next-line no-console
console.error(`Proxy error: ${err.message}`)
res.writeHead(500, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ error: 'Proxy error', details: err.message }))
})
}
}
export const qliApiProxy = createProxyConfig(
'https://api.qubic.li',
'/dev-proxy-qli-api',
'QLI-API-DEV-PROXY'
)
export const archiverApiProxy = createProxyConfig(
'https://rpc.qubic.org',
'/dev-proxy-archiver-api',
'ARCHIVER-API-DEV-PROXY'
)
3 changes: 2 additions & 1 deletion public/locales/ar/network-page.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@
"rank": "الترتيب",
"addressID": "معرّف العنوان",
"richListLoadFailed": "خطأ: فشل تحميل قائمة الأثرياء. يرجى تحديث الصفحة أو المحاولة مرة أخرى لاحقًا.",
"richListWarning": "يتم تحديث بيانات قائمة الأثرياء في بداية كل فترة"
"richListWarning": "يتم تحديث بيانات قائمة الأثرياء في بداية كل فترة",
"timestamp": "الطابع الزمني"
}
3 changes: 2 additions & 1 deletion public/locales/de/network-page.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@
"rank": "Rang",
"addressID": "Adress-ID",
"richListLoadFailed": "Fehler: Reichenliste konnte nicht geladen werden. Bitte aktualisieren Sie die Seite oder versuchen Sie es später erneut.",
"richListWarning": "Die Daten der Reichenliste werden zu Beginn jeder Epoche aktualisiert"
"richListWarning": "Die Daten der Reichenliste werden zu Beginn jeder Epoche aktualisiert",
"timestamp": "Zeitstempel"
}
3 changes: 2 additions & 1 deletion public/locales/en/network-page.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@
"rank": "Rank",
"addressID": "Address ID",
"richListLoadFailed": "Error: Failed to load rich list. Please refresh the page or try again later.",
"richListWarning": "Rich list data is updated at the beginning of each epoch"
"richListWarning": "Rich list data is updated at the beginning of each epoch",
"timestamp": "Timestamp"
}
3 changes: 2 additions & 1 deletion public/locales/es/network-page.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@
"rank": "Rango",
"addressID": "ID de Dirección",
"richListLoadFailed": "Error: No se pudo cargar la lista de ricos. Por favor, actualice la página o intente nuevamente más tarde.",
"richListWarning": "Los datos de la lista de ricos se actualizan al comienzo de cada época"
"richListWarning": "Los datos de la lista de ricos se actualizan al comienzo de cada época",
"timestamp": "Fecha"
}
3 changes: 2 additions & 1 deletion public/locales/fr/network-page.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@
"rank": "Rang",
"addressID": "ID d'adresse",
"richListLoadFailed": "Erreur : Impossible de charger la liste des riches. Veuillez actualiser la page ou réessayer plus tard.",
"richListWarning": "Les données de la liste des riches sont mises à jour au début de chaque époque"
"richListWarning": "Les données de la liste des riches sont mises à jour au début de chaque époque",
"timestamp": "Horodatage"
}
3 changes: 2 additions & 1 deletion public/locales/ja/network-page.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@
"rank": "ランク",
"addressID": "アドレスID",
"richListLoadFailed": "エラー:リッチリストの読み込みに失敗しました。ページを更新するか、後でもう一度お試しください。",
"richListWarning": "リッチリストのデータは各エポックの開始時に更新されます"
"richListWarning": "リッチリストのデータは各エポックの開始時に更新されます",
"timestamp": "タイムスタンプ"
}
3 changes: 2 additions & 1 deletion public/locales/nl/network-page.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@
"rank": "Rang",
"addressID": "Adres-ID",
"richListLoadFailed": "Fout: Het laden van de rijkelijst is mislukt. Ververs de pagina of probeer het later opnieuw.",
"richListWarning": "Rijkelijstgegevens worden aan het begin van elke epoche bijgewerkt"
"richListWarning": "Rijkelijstgegevens worden aan het begin van elke epoche bijgewerkt",
"timestamp": "Tijdstempel"
}
3 changes: 2 additions & 1 deletion public/locales/pt/network-page.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@
"rank": "Classificação",
"addressID": "ID do Endereço",
"richListLoadFailed": "Erro: Falha ao carregar a lista de ricos. Por favor, atualize a página ou tente novamente mais tarde.",
"richListWarning": "Os dados da lista de ricos são atualizados no início de cada época"
"richListWarning": "Os dados da lista de ricos são atualizados no início de cada época",
"timestamp": "Carimbo de tempo"
}
3 changes: 2 additions & 1 deletion public/locales/ru/network-page.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@
"rank": "Ранг",
"addressID": "ID адреса",
"richListLoadFailed": "Ошибка: Не удалось загрузить список богатых. Пожалуйста, обновите страницу или попробуйте позже.",
"richListWarning": "Данные списка богатых обновляются в начале каждой эпохи"
"richListWarning": "Данные списка богатых обновляются в начале каждой эпохи",
"timestamp": "Метка времени"
}
3 changes: 2 additions & 1 deletion public/locales/tr/network-page.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@
"rank": "Rütbe",
"addressID": "Adres Kimliği",
"richListLoadFailed": "Hata: Zenginler listesi yüklenemedi. Lütfen sayfayı yenileyin veya daha sonra tekrar deneyin.",
"richListWarning": "Zenginler listesi verileri her dönemin başında güncellenir"
"richListWarning": "Zenginler listesi verileri her dönemin başında güncellenir",
"timestamp": "Zaman damgası"
}
3 changes: 2 additions & 1 deletion public/locales/zh/network-page.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@
"rank": "排名",
"addressID": "地址标识",
"richListLoadFailed": "错误:无法加载富豪榜。请刷新页面或稍后再试。",
"richListWarning": "富豪榜数据会在每个纪元开始时更新"
"richListWarning": "富豪榜数据会在每个纪元开始时更新",
"timestamp": "时间戳"
}
8 changes: 6 additions & 2 deletions src/components/ui/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Children, Fragment } from 'react'
import { Children, Fragment, memo } from 'react'

type Props = {
children?: React.ReactNode | React.ReactNode[]
}

export default function Breadcrumbs({ children }: Props) {
function Breadcrumbs({ children }: Props) {
return (
<nav aria-label="Breadcrumbs" className="flex">
<ol className="flex items-center font-sans">
Expand All @@ -22,3 +22,7 @@ export default function Breadcrumbs({ children }: Props) {
</nav>
)
}

const MemoizedBreadcrumbs = memo(Breadcrumbs)

export default MemoizedBreadcrumbs
44 changes: 23 additions & 21 deletions src/components/ui/InfiniteScroll.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { useCallback, useEffect, useRef, useState } from 'react'

import { ArrowUpIcon } from '@app/assets/icons'
import { Alert } from '@app/components/ui'
import { DotsLoader } from '@app/components/ui/loaders'
import { clsxTwMerge } from '@app/utils'
import { DotsLoader } from './loaders'

interface InfiniteScrollProps<T> {
items: T[] // Array of items to display
Expand Down Expand Up @@ -36,8 +36,8 @@ export default function InfiniteScroll<T>({
const [internalError, setInternalError] = useState<string | null>(null)
const [showScrollToTop, setShowScrollToTop] = useState<boolean>(false)

const isLoading = externalIsLoading !== undefined ? externalIsLoading : internalIsLoading
const error = externalError !== undefined ? externalError : internalError
const isLoading = externalIsLoading ?? internalIsLoading
const error = externalError ?? internalError

const handleLoadMore = useCallback(async () => {
try {
Expand Down Expand Up @@ -69,22 +69,29 @@ export default function InfiniteScroll<T>({
[handleLoadMore, hasMore, isLoading, threshold]
)

const handleScrollToTop = () => {
const handleScrollToTop = useCallback(() => {
window.scrollTo({ top: 0, behavior: 'smooth' })
}
}, [])

useEffect(() => {
const handleScroll = () => {
if (window.scrollY > window.innerHeight) {
setShowScrollToTop(true)
} else {
setShowScrollToTop(false)
}
const handleScroll = useCallback(() => {
if (window.scrollY > window.innerHeight) {
setShowScrollToTop(true)
} else {
setShowScrollToTop(false)
}
}, [])

useEffect(() => {
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
}, [])
}, [handleScroll])

const renderStatus = useCallback(() => {
if (error) return <Alert variant="error">{error}</Alert>
if (isLoading) return loader
if (!hasMore && endMessage) return endMessage
return null
}, [error, isLoading, loader, hasMore, endMessage])

return (
<div className="relative">
Expand All @@ -98,20 +105,15 @@ export default function InfiniteScroll<T>({
</li>
))}
</ul>
{isLoading && loader}
{!hasMore && !isLoading && endMessage}
{error && (
<Alert variant="error" className="my-16">
{error}
</Alert>
)}

{renderStatus()}

{showScrollToTop && (
<button
type="button"
aria-label="Scroll to top"
onClick={handleScrollToTop}
className="fixed bottom-16 right-16 rounded-full border border-gray-60 bg-primary-80 p-12 text-gray-60 shadow-lg hover:border-gray-50 hover:text-gray-50"
className="fixed bottom-16 right-16 rounded-full border border-gray-60 bg-primary-80 p-12 text-gray-60 shadow-lg transition duration-300 hover:border-gray-50 hover:text-gray-50"
>
<ArrowUpIcon className="size-24" />
</button>
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/SearchBar/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export default function SearchBar() {
closeOnOutsideClick
onClose={handleCloseCallback}
>
<div className="bg-primary-70 h-fit w-full">
<div className="h-fit w-full bg-primary-70">
{isLoading && (
<div className="absolute w-full">
<LinearProgress />
Expand Down
8 changes: 5 additions & 3 deletions src/pages/network/OverviewPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
StarsIcon,
WalletIcon
} from '@app/assets/icons'
import { PaginationBar } from '@app/components/ui'
import { PaginationBar, Tooltip } from '@app/components/ui'
import { LinearProgress } from '@app/components/ui/loaders'
import { useAppDispatch, useAppSelector } from '@app/hooks/redux'
import { getOverview, selectOverview } from '@app/store/network/overviewSlice'
Expand Down Expand Up @@ -105,9 +105,11 @@ export default function OverviewPage() {
id: 'empty-ticks',
icon: EmptyTicksIcon,
label: (
<span className="flex items-center gap-10 text-inherit">
<span className="flex items-center gap-4 text-inherit">
{t('empty')}
<Infocon />
<Tooltip content={t('emptyTooltip')}>
<Infocon className="size-16 shrink-0" />
</Tooltip>
</span>
),
value: formatString(overview?.numberOfEmptyTicks)
Expand Down
Loading

0 comments on commit e57ca02

Please sign in to comment.