diff --git a/src/components/AreaActivity.svelte b/src/components/AreaActivity.svelte new file mode 100644 index 0000000..68282b7 --- /dev/null +++ b/src/components/AreaActivity.svelte @@ -0,0 +1,136 @@ + + +
+
+

+ {name || 'BTC Map Area'} Supertaggers +

+
+ {#if taggers && taggers.length} + + + {#if taggersPaginated.length !== taggers.length} + + {/if} + {:else if !dataInitialized} +
+ + {#each Array(5) as tagger} +
+

+

+

+ {/each} +
+ {:else} +

No supertaggers to display.

+ {/if} +
+
+
+ +
+
+

+ {name || 'BTC Map Area'} Activity +

+ +
{ + if (dataInitialized && !hideArrow) { + hideArrow = true; + } + }} + > + {#if eventElements && eventElements.length} + {#each eventElementsPaginated as event} + + {/each} + + {#if eventElementsPaginated.length !== eventElements.length} + + {:else if eventElements.length > 10} + + {/if} + + {#if !hideArrow && eventElements.length > 5} + + {/if} + {:else if !dataInitialized} + + {#each Array(5) as skeleton} + + {/each} + {:else} +

No activity to display.

+ {/if} +
+
+
diff --git a/src/components/AreaMap.svelte b/src/components/AreaMap.svelte new file mode 100644 index 0000000..7a508d8 --- /dev/null +++ b/src/components/AreaMap.svelte @@ -0,0 +1,277 @@ + + +
+

+ {name || 'BTC Map Area'} Map + +

+ +
+
+ {#if !mapLoaded} + + {/if} +
+
diff --git a/src/components/AreaPage.svelte b/src/components/AreaPage.svelte index e5f6356..0021504 100644 --- a/src/components/AreaPage.svelte +++ b/src/components/AreaPage.svelte @@ -2,36 +2,19 @@ export let type: AreaType; export let data: AreaPageProps; - import { browser } from '$app/environment'; import { goto } from '$app/navigation'; import { - GradeTable, - InfoTooltip, + AreaActivity, + AreaMap, + AreaStats, + AreaTickets, IssuesTable, - LatestTagger, - MapLoadingEmbed, - OpenTicket, OrgBadge, - ProfileStat, Socials, SponsorBadge, - TaggerSkeleton, - Tip, - TopButton + Tip } from '$lib/comp'; - import { - attribution, - calcVerifiedDate, - changeDefaultIcons, - generateIcon, - generateMarker, - geolocate, - latCalc, - layers, - longCalc, - toggleMapButtons, - verifiedArr - } from '$lib/map/setup'; + import { latCalc, longCalc } from '$lib/map/setup'; import { areaError, areas, @@ -41,7 +24,6 @@ events, reportError, reports, - theme, userError, users } from '$lib/store'; @@ -49,33 +31,19 @@ TipType, type ActivityEvent, type AreaPageProps, + type AreaTags, type AreaType, - type BaseMaps, type Continents, - type DomEventType, type Element, type Event, - type Grade, type Issues, - type Leaflet, + type Report, type User } from '$lib/types.js'; - import { - detectTheme, - errToast, - formatElementID, - getGrade, - getIssues, - updateChartThemes, - validateContinents - } from '$lib/utils'; + import { errToast, formatElementID, getIssues, validateContinents } from '$lib/utils'; // @ts-expect-error import rewind from '@mapbox/geojson-rewind'; - import Chart from 'chart.js/auto'; import { geoContains } from 'd3-geo'; - import type { Map } from 'leaflet'; - import { onDestroy, onMount } from 'svelte'; - import tippy from 'tippy.js'; // alert for user errors $: $userError && errToast($userError); @@ -88,7 +56,16 @@ // alert for report errors $: $reportError && errToast($reportError); - let initialRenderComplete = false; + enum Sections { + merchants = 'Merchants', + stats = 'Stats', + activity = 'Activity', + contibute = 'Contribute' + } + + const sections = Object.values(Sections); + let activeSection = Sections.merchants; + let dataInitialized = false; const initializeData = () => { @@ -124,7 +101,7 @@ return; } - const areaReports = $reports + areaReports = $reports .filter((report) => report.area_id === data.id) .sort((a, b) => Date.parse(b['created_at']) - Date.parse(a['created_at'])); @@ -136,7 +113,7 @@ return; } - const area = areaFound.tags; + area = areaFound.tags; avatar = type === 'community' @@ -179,7 +156,7 @@ const rewoundPoly = rewind(area.geo_json, true); // filter elements within area - const filteredElements: Element[] = $elements.filter((element) => { + filteredElements = $elements.filter((element) => { let lat = latCalc(element['osm_json']); let long = longCalc(element['osm_json']); @@ -228,482 +205,8 @@ eventElements = eventElements; taggers = taggers; - const populateMap = () => { - // add map - map = leaflet.map(mapElement, { attributionControl: false }); - - // add tiles and basemaps - baseMaps = layers(leaflet, map); - - // change broken marker image path in prod - leaflet.Icon.Default.prototype.options.imagePath = '/icons/'; - - // add OSM attribution - attribution(leaflet, map); - - // create marker cluster groups - /* eslint-disable no-undef */ - // @ts-expect-error - let markers = L.markerClusterGroup(); - /* eslint-enable no-undef */ - let upToDateLayer = leaflet.featureGroup.subGroup(markers); - let outdatedLayer = leaflet.featureGroup.subGroup(markers); - let legacyLayer = leaflet.featureGroup.subGroup(markers); - - let overlayMaps = { - 'Up-To-Date': upToDateLayer, - Outdated: outdatedLayer, - Legacy: legacyLayer - }; - - leaflet.control.layers(baseMaps, overlayMaps).addTo(map); - - // add locate button to map - geolocate(leaflet, map); - - // change default icons - changeDefaultIcons(true, leaflet, mapElement, DomEvent); - - // get date from 1 year ago to add verified check if survey is current - let verifiedDate = calcVerifiedDate(); - - // add area poly to map - if (area.geo_json) { - leaflet.geoJSON(area.geo_json, { style: { fill: false } }).addTo(map); - } - - // add elements to map - filteredElements.forEach((element) => { - let icon = element.tags['icon:android']; - let payment = element.tags['payment:uri'] - ? { type: 'uri', url: element.tags['payment:uri'] } - : element.tags['payment:pouch'] - ? { type: 'pouch', username: element.tags['payment:pouch'] } - : element.tags['payment:coinos'] - ? { type: 'coinos', username: element.tags['payment:coinos'] } - : undefined; - let boosted = - element.tags['boost:expires'] && Date.parse(element.tags['boost:expires']) > Date.now() - ? element.tags['boost:expires'] - : undefined; - - const elementOSM = element['osm_json']; - - const lat = latCalc(elementOSM); - const long = longCalc(elementOSM); - - let divIcon = generateIcon(leaflet, icon, boosted ? true : false); - - let marker = generateMarker( - lat, - long, - divIcon, - elementOSM, - payment, - leaflet, - verifiedDate, - true, - boosted - ); - - let verified = verifiedArr(elementOSM); - - if (verified.length && Date.parse(verified[0]) > verifiedDate) { - upToDateLayer.addLayer(marker); - - if (upToDate === undefined) { - upToDate = 1; - } else { - upToDate++; - } - } else { - outdatedLayer.addLayer(marker); - - if (outdated === undefined) { - outdated = 1; - } else { - outdated++; - } - } - - if (elementOSM.tags && elementOSM.tags['payment:bitcoin']) { - legacyLayer.addLayer(marker); - - if (legacy === undefined) { - legacy = 1; - } else { - legacy++; - } - } - - if (total === undefined) { - total = 1; - } else { - total++; - } - }); - - map.addLayer(markers); - map.addLayer(upToDateLayer); - map.addLayer(outdatedLayer); - map.addLayer(legacyLayer); - - map.fitBounds(leaflet.geoJSON(area.geo_json).getBounds()); - - mapLoaded = true; - }; - - populateMap(); - - if (!upToDate) { - upToDate = 0; - } - - if (!outdated) { - outdated = 0; - } - - if (!legacy) { - legacy = 0; - } - - if (!total) { - total = 0; - } - - upToDatePercent = upToDate ? (upToDate / (total / 100)).toFixed(0) : '0'; - - outdatedPercent = outdated ? (outdated / (total / 100)).toFixed(0) : '0'; - - legacyPercent = legacy ? (legacy / (total / 100)).toFixed(0) : '0'; - - grade = getGrade(Number(upToDatePercent)); - issues = getIssues(filteredElements); - const populateCharts = () => { - const chartsReports = [...areaReports].sort( - (a, b) => Date.parse(a['created_at']) - Date.parse(b['created_at']) - ); - - const today = new Date(); - const latestReport = chartsReports[chartsReports.length - 1]; - const latestReportDate = new Date(latestReport.created_at); - const reportIsCurrent = - today.getDate() === latestReportDate.getDate() && - today.getMonth() === latestReportDate.getMonth() && - today.getFullYear() === latestReportDate.getFullYear(); - - if (!reportIsCurrent) { - chartsReports.push({ - ...latestReport, - id: latestReport.id + 1, - date: `${today.getFullYear()}-${today.getMonth() + 1}-${today.getDate()}`, - created_at: today.toISOString(), - updated_at: today.toISOString() - }); - } - - const theme = detectTheme(); - - updatedChart = new Chart(updatedChartCanvas, { - type: 'pie', - data: { - labels: ['Up-To-Date', 'Outdated'], - datasets: [ - { - label: 'Locations', - data: [upToDate, outdated], - backgroundColor: ['rgb(16, 183, 145)', 'rgb(235, 87, 87)'], - hoverOffset: 4 - } - ] - }, - options: { - maintainAspectRatio: false, - plugins: { - legend: { - labels: { - font: { - weight: 600 - } - } - } - } - } - }); - - let percents = chartsReports.filter((report) => report.tags.up_to_date_percent); - - upToDateChart = new Chart(upToDateChartCanvas, { - type: 'line', - data: { - labels: percents.map(({ date }) => date), - datasets: [ - { - label: 'Up-To-Date Percent', - data: percents.map(({ tags: { up_to_date_percent } }) => up_to_date_percent), - fill: { - target: 'origin', - above: 'rgba(11, 144, 114, 0.2)' - }, - borderColor: 'rgb(11, 144, 114)', - tension: 0.1, - pointStyle: false - } - ] - }, - options: { - maintainAspectRatio: false, - plugins: { - legend: { - labels: { - font: { - weight: 600 - } - } - } - }, - scales: { - x: { - ticks: { - maxTicksLimit: 5, - font: { - weight: 600 - } - }, - grid: { - color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' - } - }, - y: { - min: 0, - max: 100, - ticks: { - precision: 0, - font: { - weight: 600 - } - }, - grid: { - color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' - } - } - }, - interaction: { - intersect: false - } - } - }); - - totalChart = new Chart(totalChartCanvas, { - type: 'line', - data: { - labels: chartsReports.map(({ date }) => date), - datasets: [ - { - label: 'Total Locations', - data: chartsReports.map(({ tags: { total_elements } }) => total_elements), - fill: { - target: 'origin', - above: 'rgba(0, 153, 175, 0.2)' - }, - borderColor: 'rgb(0, 153, 175)', - tension: 0.1, - pointStyle: false - } - ] - }, - options: { - maintainAspectRatio: false, - plugins: { - legend: { - labels: { - font: { - weight: 600 - } - } - } - }, - scales: { - x: { - ticks: { - maxTicksLimit: 5, - font: { - weight: 600 - } - }, - grid: { - color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' - } - }, - y: { - min: 0, - grace: '5%', - ticks: { - precision: 0, - font: { - weight: 600 - } - }, - grid: { - color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' - } - } - }, - interaction: { - intersect: false - } - } - }); - - legacyChart = new Chart(legacyChartCanvas, { - type: 'line', - data: { - labels: chartsReports.map(({ date }) => date), - datasets: [ - { - label: 'Legacy Locations', - data: chartsReports.map(({ tags: { legacy_elements } }) => legacy_elements), - fill: { - target: 'origin', - above: 'rgba(235, 87, 87, 0.2)' - }, - borderColor: 'rgb(235, 87, 87)', - tension: 0.1, - pointStyle: false - } - ] - }, - options: { - maintainAspectRatio: false, - plugins: { - legend: { - labels: { - font: { - weight: 600 - } - } - } - }, - scales: { - x: { - ticks: { - maxTicksLimit: 5, - font: { - weight: 600 - } - }, - grid: { - color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' - } - }, - y: { - min: 0, - grace: '5%', - ticks: { - precision: 0, - font: { - weight: 600 - } - }, - grid: { - color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' - } - } - }, - interaction: { - intersect: false - } - } - }); - - paymentMethodChart = new Chart(paymentMethodChartCanvas, { - type: 'line', - data: { - labels: chartsReports.map(({ date }) => date), - datasets: [ - { - label: 'On-chain', - data: chartsReports.map( - ({ tags: { total_elements_onchain } }) => total_elements_onchain - ), - fill: false, - borderColor: 'rgb(247, 147, 26)', - tension: 0.1, - pointStyle: false - }, - { - label: 'Lightning', - data: chartsReports.map( - ({ tags: { total_elements_lightning } }) => total_elements_lightning - ), - fill: false, - borderColor: 'rgb(249, 193, 50)', - tension: 0.1, - pointStyle: false - }, - { - label: 'Contactless', - data: chartsReports.map( - ({ tags: { total_elements_lightning_contactless } }) => - total_elements_lightning_contactless - ), - fill: false, - borderColor: 'rgb(102, 16, 242)', - tension: 0.1, - pointStyle: false - } - ] - }, - options: { - maintainAspectRatio: false, - plugins: { - legend: { - labels: { - font: { - weight: 600 - } - } - } - }, - scales: { - x: { - ticks: { - maxTicksLimit: 5, - font: { - weight: 600 - } - }, - grid: { - color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' - } - }, - y: { - min: 0, - grace: '5%', - ticks: { - precision: 0, - font: { - weight: 600 - } - }, - grid: { - color: theme === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)' - } - } - }, - interaction: { - intersect: false - } - } - }); - - chartsLoading = false; - }; - - populateCharts(); - dataInitialized = true; }; @@ -717,35 +220,9 @@ $areas.length && $reports && $reports.length && - initialRenderComplete && !dataInitialized && initializeData(); - const ticketTypes = ['Add', 'Verify']; - let showType = 'Add'; - - const tickets = data.tickets; - const ticketError = tickets === 'error' ? true : false; - - $: ticketError && errToast('Could not load open tickets, please try again or contact BTC Map.'); - - const add = - tickets && tickets.length && !ticketError - ? // @ts-expect-error - tickets.filter((issue: any) => - issue.labels.find((label: any) => label.name === 'location-submission') - ) - : []; - const verify = - tickets && tickets.length && !ticketError - ? // @ts-expect-error - tickets.filter((issue: any) => - issue.labels.find((label: any) => label.name === 'verify-submission') - ) - : []; - - const totalTickets = add.length + verify.length; - const getContinentIcon = (continent: Continents) => { switch (continent) { case 'africa': @@ -774,6 +251,10 @@ } }; + let area: AreaTags; + let filteredElements: Element[]; + let areaReports: Report[]; + let avatar: string; const name = data.name; let continent: Continents; @@ -799,114 +280,10 @@ let signal: string | undefined; let lightning: { destination: string; type: TipType } | undefined; - let total: number | undefined; - let upToDate: number | undefined; - let outdated: number | undefined; - let legacy: number | undefined; - let grade: Grade; - - let gradeTooltip: HTMLButtonElement; - - $: gradeTooltip && - tippy([gradeTooltip], { - content: GradeTable, - allowHTML: true - }); - - let upToDatePercent: string | undefined; - let outdatedPercent: string | undefined; - let legacyPercent: string | undefined; - - let updatedChartCanvas: HTMLCanvasElement; - // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars - let updatedChart; - - let hideArrow = false; - let activityDiv: HTMLDivElement; let eventElements: ActivityEvent[] = []; - - let eventCount = 50; - $: eventElementsPaginated = eventElements.slice(0, eventCount); - let taggers: User[] = []; - let taggerCount = 50; - $: taggersPaginated = taggers.slice(0, taggerCount); - let taggerDiv: HTMLDivElement; - - let mapElement: HTMLDivElement; - let map: Map; - let mapLoaded = false; - - let baseMaps: BaseMaps; let issues: Issues = []; - - let chartsLoading = true; - let upToDateChartCanvas: HTMLCanvasElement; - let upToDateChart: Chart<'line', number[], string>; - let totalChartCanvas: HTMLCanvasElement; - let totalChart: Chart<'line', number[], string>; - let legacyChartCanvas: HTMLCanvasElement; - let legacyChart: Chart<'line', number[], string>; - let paymentMethodChartCanvas: HTMLCanvasElement; - let paymentMethodChart: Chart<'line', number[], string>; - - $: $theme !== undefined && - !chartsLoading && - updateChartThemes([upToDateChart, totalChart, legacyChart, paymentMethodChart]); - - let leaflet: Leaflet; - let DomEvent: DomEventType; - - onMount(async () => { - if (browser) { - // setup charts - updatedChartCanvas.getContext('2d'); - upToDateChartCanvas.getContext('2d'); - totalChartCanvas.getContext('2d'); - legacyChartCanvas.getContext('2d'); - paymentMethodChartCanvas.getContext('2d'); - - //import packages - leaflet = await import('leaflet'); - // @ts-expect-error - DomEvent = await import('leaflet/src/dom/DomEvent'); - /* eslint-disable no-unused-vars, @typescript-eslint/no-unused-vars */ - const leafletMarkerCluster = await import('leaflet.markercluster'); - const leafletFeaturegroupSubgroup = await import('leaflet.featuregroup.subgroup'); - const leafletLocateControl = await import('leaflet.locatecontrol'); - /* eslint-enable no-unused-vars, @typescript-eslint/no-unused-vars */ - - initialRenderComplete = true; - } - }); - - $: $theme !== undefined && mapLoaded && toggleMapButtons(); - - const closePopup = () => { - map.closePopup(); - }; - - $: $theme !== undefined && mapLoaded && closePopup(); - - const toggleTheme = () => { - if ($theme === 'dark') { - baseMaps['OSM Bright'].remove(); - baseMaps['Alidade Smooth Dark'].addTo(map); - } else { - baseMaps['Alidade Smooth Dark'].remove(); - baseMaps['OSM Bright'].addTo(map); - } - }; - - $: $theme !== undefined && mapLoaded && toggleTheme(); - - onDestroy(async () => { - if (map) { - console.log('Unloading Leaflet map.'); - map.remove(); - } - }); @@ -1019,401 +396,32 @@ {/if} -
-

- {name || 'BTC Map Area'} Map - -

- -
-
- {#if !mapLoaded} - - {/if} -
-
- -
-
- - 0 ? upToDatePercent : undefined} - border="border-b xl:border-b-0 xl:border-r border-statBorder" - tooltip="Locations that have been verified within one year." - /> - 0 ? outdatedPercent : undefined} - border="border-b md:border-b-0 md:border-r border-statBorder" - /> - 0 ? legacyPercent : undefined} - tooltip="Locations with a payment:bitcoin tag instead of the - currency:XBT tag." - /> -
- -
- {#if chartsLoading} -
- -
- {/if} - - -
-
- -
-
-

+ {#each sections as section} +

-
- {#if taggers && taggers.length} - - - {#if taggersPaginated.length !== taggers.length} - - {/if} - {:else if !dataInitialized} -
- - {#each Array(5) as tagger} -
-

-

-

- {/each} -
- {:else} -

No supertaggers to display.

- {/if} -
-
-
- -
-
-

- {name || 'BTC Map Area'} Activity -

- -
{ - if (dataInitialized && !hideArrow) { - hideArrow = true; - } - }} - > - {#if eventElements && eventElements.length} - {#each eventElementsPaginated as event} - - {/each} - - {#if eventElementsPaginated.length !== eventElements.length} - - {:else if eventElements.length > 10} - - {/if} - - {#if !hideArrow && eventElements.length > 5} - - {/if} - {:else if !dataInitialized} - - {#each Array(5) as skeleton} - - {/each} - {:else} -

No activity to display.

- {/if} -
-
-
- - - -
-
-
-

- {name || 'BTC Map Area'} Tickets - {#if tickets && !ticketError} - ({totalTickets}) - {/if} - -

- - {#each ticketTypes as type} - - {/each} -
- - {#if tickets && !ticketError} - {#if showType === 'Add'} - {#if add.length} - {#each add as ticket} - - {/each} - {:else} -

- No open add tickets. -

- {/if} - {:else if showType === 'Verify'} - {#if verify.length} - {#each verify as ticket} - - {/each} - {:else} -

- No open verify tickets. -

- {/if} - {/if} - - {#if tickets?.length === 100} -

- View all open tickets directly on GitHub. -

- {/if} - {:else} -

- Error fetching tickets. -

- {/if} -
-
- -
-
-

- {name || 'BTC Map Area'} Charts -

-
-
- {#if chartsLoading} -
- -
- {/if} - -
-

- *Locations with a survey:date, check_date, or - check_date:currency:XBT tag less than one year old. -

-
- -
-
- {#if chartsLoading} -
- -
- {/if} - -
-

- *Locations accepting any bitcoin payment method. -

-
- -
-
- {#if chartsLoading} -
- -
- {/if} - -
-

- *Locations with a payment:bitcoin tag instead of the - currency:XBT tag. -

-
- -
-
- {#if chartsLoading} -
- -
- {/if} - -
-

- *Locations with payment:onchain, payment:lightning and - payment:lightning_contactless tags. -

-
-
-
- -

- *More information on bitcoin mapping tags can be found here. -
- *Chart data updated once every 24 hours. -

+ {section} + + {/each} + + + {#if activeSection === Sections.merchants} + + {:else if activeSection === Sections.stats} + + {:else if activeSection === Sections.activity} + + {:else if activeSection === Sections.contibute} + + + {/if} diff --git a/src/components/AreaStats.svelte b/src/components/AreaStats.svelte new file mode 100644 index 0000000..d303ef0 --- /dev/null +++ b/src/components/AreaStats.svelte @@ -0,0 +1,585 @@ + + +
+
+ + 0 ? upToDatePercent : undefined} + border="border-b xl:border-b-0 xl:border-r border-statBorder" + tooltip="Locations that have been verified within one year." + /> + 0 ? outdatedPercent : undefined} + border="border-b md:border-b-0 md:border-r border-statBorder" + /> + 0 ? legacyPercent : undefined} + tooltip="Locations with a payment:bitcoin tag instead of the + currency:XBT tag." + /> +
+ +
+ {#if chartsLoading} +
+ +
+ {/if} + + +
+
+ +
+
+

+ {name || 'BTC Map Area'} Charts +

+
+
+ {#if chartsLoading} +
+ +
+ {/if} + +
+

+ *Locations with a survey:date, check_date, or + check_date:currency:XBT tag less than one year old. +

+
+ +
+
+ {#if chartsLoading} +
+ +
+ {/if} + +
+

+ *Locations accepting any bitcoin payment method. +

+
+ +
+
+ {#if chartsLoading} +
+ +
+ {/if} + +
+

+ *Locations with a payment:bitcoin tag instead of the + currency:XBT tag. +

+
+ +
+
+ {#if chartsLoading} +
+ +
+ {/if} + +
+

+ *Locations with payment:onchain, payment:lightning and + payment:lightning_contactless tags. +

+
+
+
+ +

+ *More information on bitcoin mapping tags can be found here. +
+ *Chart data updated once every 24 hours. +

diff --git a/src/components/AreaTickets.svelte b/src/components/AreaTickets.svelte new file mode 100644 index 0000000..b4068eb --- /dev/null +++ b/src/components/AreaTickets.svelte @@ -0,0 +1,124 @@ + + +
+
+
+

+ {name || 'BTC Map Area'} Tickets + {#if tickets && !ticketError} + ({totalTickets}) + {/if} + +

+ + {#each ticketTypes as type} + + {/each} +
+ + {#if tickets && !ticketError} + {#if showType === 'Add'} + {#if add.length} + {#each add as ticket} + + {/each} + {:else} +

+ No open add tickets. +

+ {/if} + {:else if showType === 'Verify'} + {#if verify.length} + {#each verify as ticket} + + {/each} + {:else} +

+ No open verify tickets. +

+ {/if} + {/if} + + {#if tickets?.length === 100} +

+ View all open tickets directly on GitHub. +

+ {/if} + {:else} +

+ Error fetching tickets. +

+ {/if} +
+
diff --git a/src/lib/comp.ts b/src/lib/comp.ts index 92c7139..554b8eb 100644 --- a/src/lib/comp.ts +++ b/src/lib/comp.ts @@ -7,10 +7,14 @@ export { default as AboutMerchant } from '../components/AboutMerchant.svelte'; export { default as AboutPlus } from '../components/AboutPlus.svelte'; export { default as AboutTagger } from '../components/AboutTagger.svelte'; export { default as AppCard } from '../components/AppCard.svelte'; +export { default as AreaActivity } from '../components/AreaActivity.svelte'; export { default as AreaLeaderboard } from '../components/AreaLeaderboard.svelte'; export { default as AreaLeaderboardItem } from '../components/AreaLeaderboardItem.svelte'; export { default as AreaLeaderboardSkeleton } from '../components/AreaLeaderboardSkeleton.svelte'; +export { default as AreaMap } from '../components/AreaMap.svelte'; export { default as AreaPage } from '../components/AreaPage.svelte'; +export { default as AreaStats } from '../components/AreaStats.svelte'; +export { default as AreaTickets } from '../components/AreaTickets.svelte'; export { default as BadgeCard } from '../components/BadgeCard.svelte'; export { default as Boost } from '../components/Boost.svelte'; export { default as CloseButton } from '../components/CloseButton.svelte'; diff --git a/src/lib/types.ts b/src/lib/types.ts index 943d738..1cc8220 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -275,4 +275,6 @@ export type DropdownLink = { url: string; external?: boolean; icon: string; titl export type ChartHistory = '7D' | '1M' | '3M' | '6M' | 'YTD' | '1Y' | 'ALL'; -export type AreaPageProps = { id: string; name: string; tickets: [] | 'error' }; +export type Tickets = [] | 'error'; + +export type AreaPageProps = { id: string; name: string; tickets: Tickets };