diff --git a/src/components/AboutCommunity.svelte b/src/components/AboutCommunity.svelte index f7ca35e6..4c5ea02a 100644 --- a/src/components/AboutCommunity.svelte +++ b/src/components/AboutCommunity.svelte @@ -18,7 +18,7 @@ alt="avatar" class="h-24 w-24 rounded-full object-cover" on:error={function () { - this.src = '/images/communities/bitcoin.svg'; + this.src = '/images/bitcoin.svg'; }} /> diff --git a/src/components/AboutIntegration.svelte b/src/components/AboutIntegration.svelte index d97a5bf8..c5fcc623 100644 --- a/src/components/AboutIntegration.svelte +++ b/src/components/AboutIntegration.svelte @@ -16,7 +16,7 @@ alt="logo" class="mx-auto h-24 w-24 rounded-full object-cover" on:error={function () { - this.src = '/images/communities/bitcoin.svg'; + this.src = '/images/bitcoin.svg'; }} /> diff --git a/src/components/AreaLeaderboard.svelte b/src/components/AreaLeaderboard.svelte new file mode 100644 index 00000000..c5898c47 --- /dev/null +++ b/src/components/AreaLeaderboard.svelte @@ -0,0 +1,190 @@ + + +
+ +
+

+ Up-To-Date +

+

+ Grade +

+
+ +
+ {#if leaderboard && leaderboard.length && !loading} + {#each leaderboardPaginated as item, index} + + {/each} + + {#if leaderboardPaginated.length !== leaderboard.length} + + {/if} + {:else} + + {#each Array(50) as skeleton} + + {/each} + {/if} +
+ +

+ *Data is weighted by Up-To-Date locations and then sorted by Total Locations. +
+ *Leaderboard updated once every 24 hours. +

+ +
+ +
+
diff --git a/src/components/CommunityLeaderboardItem.svelte b/src/components/AreaLeaderboardItem.svelte similarity index 89% rename from src/components/CommunityLeaderboardItem.svelte rename to src/components/AreaLeaderboardItem.svelte index 7d157bca..4bba6d51 100644 --- a/src/components/CommunityLeaderboardItem.svelte +++ b/src/components/AreaLeaderboardItem.svelte @@ -1,4 +1,5 @@ + + + {#if type === 'community'} + {#if lightning && lightning.type === 'address'} + + + + {:else} + + + + {/if} + {/if} + + +
+
+
+ {#if avatar} + avatar + {:else} +
+ {/if} +

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

+ {#if org} + + {/if} + {#if sponsor} + + {/if} + {#if continent} +

+ {continent.replace('-', ' ')} + +

+ {:else} +
+ {/if} + {#if data.id && type === 'community'} + View on community map + + + {/if} +
+ + {#if type === 'community'} + {#if dataInitialized} + + {:else} +
+ + {#each Array(3) as skeleton} +
+ {/each} +
+ {/if} + + {#if lightning} + + {/if} + {/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} + + +
+
+ +
+
+

+ {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} +
+
+
+ + + +
+
+
+

+ {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. +

+
diff --git a/src/components/CommunityCard.svelte b/src/components/CommunityCard.svelte index 3768692f..25ce08b9 100644 --- a/src/components/CommunityCard.svelte +++ b/src/components/CommunityCard.svelte @@ -40,11 +40,11 @@ {tags.name} diff --git a/src/components/CountryCard.svelte b/src/components/CountryCard.svelte new file mode 100644 index 00000000..bc71f08d --- /dev/null +++ b/src/components/CountryCard.svelte @@ -0,0 +1,23 @@ + + + diff --git a/src/components/CountrySection.svelte b/src/components/CountrySection.svelte new file mode 100644 index 00000000..af291a74 --- /dev/null +++ b/src/components/CountrySection.svelte @@ -0,0 +1,21 @@ + + +
+
+ {#if countries && countries.length} + {#each countries as country} + + {/each} + {:else} + + {#each Array(4) as skeleton} + + {/each} + {/if} +
+
diff --git a/src/components/CountrySkeleton.svelte b/src/components/CountrySkeleton.svelte new file mode 100644 index 00000000..8bb59f29 --- /dev/null +++ b/src/components/CountrySkeleton.svelte @@ -0,0 +1,11 @@ +
+
+ +
+ + +
+
+
diff --git a/src/components/Header.svelte b/src/components/Header.svelte index cb2c9ebf..d59c6f83 100644 --- a/src/components/Header.svelte +++ b/src/components/Header.svelte @@ -6,7 +6,7 @@ { title: 'Apps', url: '/apps', icon: 'apps' }, { title: 'Contribute', url: '', icon: 'contribute' }, { title: 'Stats', url: '', icon: 'stats' }, - { title: 'Communities', url: '', icon: 'communities' }, + { title: 'Areas', url: '', icon: 'areas' }, { title: 'Wiki', url: '', @@ -28,9 +28,9 @@ { title: 'Leaderboard', url: '/leaderboard', icon: 'leader' } ]; - const communitiesDropdownLinks = [ - { title: 'Directory', url: '/communities', icon: 'directory' }, - { title: 'Leaderboard', url: '/communities/leaderboard', icon: 'leader' } + const areasDropdownLinks = [ + { title: 'Communities', url: '/communities', icon: 'communities' }, + { title: 'Countries', url: '/countries', icon: 'countries' } ]; const wikiDropdownLinks = [ @@ -78,12 +78,12 @@ /> - {:else if link.title === 'Communities'} + {:else if link.title === 'Areas'} @@ -150,8 +150,8 @@ - {:else if link.title === 'Communities'} - + {:else if link.title === 'Areas'} + {:else if link.title === 'Wiki'} diff --git a/src/lib/comp.ts b/src/lib/comp.ts index 045570c3..92c7139f 100644 --- a/src/lib/comp.ts +++ b/src/lib/comp.ts @@ -7,16 +7,21 @@ 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 AreaLeaderboard } from '../components/AreaLeaderboard.svelte'; +export { default as AreaLeaderboardItem } from '../components/AreaLeaderboardItem.svelte'; +export { default as AreaLeaderboardSkeleton } from '../components/AreaLeaderboardSkeleton.svelte'; +export { default as AreaPage } from '../components/AreaPage.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'; export { default as CommunityCard } from '../components/CommunityCard.svelte'; -export { default as CommunityLeaderboardItem } from '../components/CommunityLeaderboardItem.svelte'; -export { default as CommunityLeaderboardSkeleton } from '../components/CommunityLeaderboardSkeleton.svelte'; export { default as CommunitySection } from '../components/CommunitySection.svelte'; export { default as CommunitySkeleton } from '../components/CommunitySkeleton.svelte'; export { default as CopyButton } from '../components/CopyButton.svelte'; export { default as Countdown } from '../components/Countdown.svelte'; +export { default as CountryCard } from '../components/CountryCard.svelte'; +export { default as CountrySection } from '../components/CountrySection.svelte'; +export { default as CountrySkeleton } from '../components/CountrySkeleton.svelte'; export { default as DashboardStat } from '../components/DashboardStat.svelte'; export { default as DonationOption } from '../components/DonationOption.svelte'; export { default as Footer } from '../components/Footer.svelte'; @@ -57,3 +62,34 @@ export { default as ThemeToggle } from '../components/ThemeToggle.svelte'; export { default as TicketLabel } from '../components/TicketLabel.svelte'; export { default as Tip } from '../components/Tip.svelte'; export { default as TopButton } from '../components/TopButton.svelte'; + +export const GradeTable = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Up-To-DateGrade
95-100%5 Star
75-95%4 Star
50-75%3 Star
25-50%2 Star
0-25%1 Star
`; diff --git a/src/lib/types.ts b/src/lib/types.ts index e75a0722..943d7384 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -13,12 +13,12 @@ export type Area = { }; export type AreaTags = { - type: 'community' | 'country'; + type: AreaType; name: string; continent: Continents; url_alias: string; geo_json: GeoJSON; - ['icon:square']: string; + ['icon:square']: string; // countries don't have this tag yet organization?: string; language?: string; population?: string; @@ -50,13 +50,21 @@ export type AreaTags = { ['box:west']?: string; }; +export type AreaType = 'community' | 'country'; + export type Continents = | 'africa' | 'asia' | 'europe' | 'north-america' | 'oceania' - | 'south-america'; + | 'south-america' + | 'Africa' + | 'Asia' + | 'Europe' + | 'North America' + | 'Oceania' + | 'South America'; export type Element = { id: string; @@ -266,3 +274,5 @@ export type DonationType = 'On-chain' | 'Lightning'; export type DropdownLink = { url: string; external?: boolean; icon: string; title: string }; export type ChartHistory = '7D' | '1M' | '3M' | '6M' | 'YTD' | '1Y' | 'ALL'; + +export type AreaPageProps = { id: string; name: string; tickets: [] | 'error' }; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 9e4fda14..8af56f5b 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,5 +1,5 @@ import { theme } from '$lib/store'; -import type { Element, Grade, IssueIcon, IssueType, Issues } from '$lib/types'; +import type { Continents, Element, Grade, IssueIcon, IssueType, Issues } from '$lib/types'; import { toast } from '@zerodevx/svelte-toast'; import type { Chart } from 'chart.js'; import { get } from 'svelte/store'; @@ -146,3 +146,19 @@ export function debounce(func: (e?: any) => void, timeout = 500) { }, timeout); }; } + +export const validateContinents = (continent: Continents) => + [ + 'africa', + 'asia', + 'europe', + 'north-america', + 'oceania', + 'south-america', + 'Africa', + 'Asia', + 'Europe', + 'North America', + 'Oceania', + 'South America' + ].includes(continent); diff --git a/src/routes/communities/+page.svelte b/src/routes/communities/+page.svelte index 64b76e4e..ef46ae4e 100644 --- a/src/routes/communities/+page.svelte +++ b/src/routes/communities/+page.svelte @@ -263,7 +263,7 @@ continentChartCanvas.getContext('2d'); if (location.hash) { - section = decodeURI(location.hash).slice(1); + section = decodeURIComponent(location.hash).slice(1); } else { section = 'Africa'; } @@ -302,17 +302,25 @@ and have fun! -
- +
+ +
+ + +
@@ -338,17 +346,17 @@

- {section} + {section}

{ + // @ts-expect-error + section = e.target?.value; + // @ts-expect-error + location.hash = encodeURIComponent(e.target?.value); + }} + > + {#each sections as option} + + {/each} + + {/if} +
+ + {#each countrySections as item} + {#if section === item.section} + + {/if} + {/each} +
+ + +
+
+
+ +{#if typeof window !== 'undefined'} + {#if detectTheme() === 'dark' || $theme === 'dark'} + + {/if} +{/if} diff --git a/src/routes/countries/leaderboard/+page.svelte b/src/routes/countries/leaderboard/+page.svelte new file mode 100644 index 00000000..d661e79b --- /dev/null +++ b/src/routes/countries/leaderboard/+page.svelte @@ -0,0 +1,48 @@ + + + + BTC Map - Countries Leaderboard + + + + + +
+
+ +
+
+ {#if typeof window !== 'undefined'} +

+ Top Countries +

+ {:else} + + {/if} + +

+ Insights into bitcoin adoption worldwide! +

+ + + + + +
+
+
+
diff --git a/src/routes/country/[area]/+page.server.ts b/src/routes/country/[area]/+page.server.ts new file mode 100644 index 00000000..559dd5ed --- /dev/null +++ b/src/routes/country/[area]/+page.server.ts @@ -0,0 +1,42 @@ +import { GITHUB_API_KEY } from '$env/static/private'; +import { error } from '@sveltejs/kit'; +import axios from 'axios'; +import axiosRetry from 'axios-retry'; +import type { PageServerLoad } from './$types'; + +axiosRetry(axios, { retries: 3, retryDelay: axiosRetry.exponentialDelay }); + +export const load: PageServerLoad = async ({ params }) => { + const { area } = params; + try { + const response = await axios.get(`https://api.btcmap.org/v2/areas/${area}`); + + const data = response.data; + + if (data && data.id && data.tags && data.tags.name) { + const headers = { + Authorization: `Bearer ${GITHUB_API_KEY}`, + Accept: 'application/vnd.github+json' + }; + + const issues = await axios + .get( + `https://api.github.com/repos/teambtcmap/btcmap-data/issues?per_page=100&labels=${data.tags.name}`, + { headers } + ) + .then(function (response) { + // handle success + return response.data; + }) + .catch(function (error) { + // handle error + console.log(error); + return 'error'; + }); + + return { id: data.id, name: data.tags.name, tickets: issues }; + } + } catch (err) { + error(404, 'Country Not Found'); + } +}; diff --git a/src/routes/country/[area]/+page.svelte b/src/routes/country/[area]/+page.svelte new file mode 100644 index 00000000..3b8b0c27 --- /dev/null +++ b/src/routes/country/[area]/+page.svelte @@ -0,0 +1,24 @@ + + + + {name ? name + ' - ' : ''}BTC Map Country + + + + + +
+
+
+ + +
+
+
diff --git a/src/routes/media/+page.svelte b/src/routes/media/+page.svelte index 01e03062..43c0d67e 100644 --- a/src/routes/media/+page.svelte +++ b/src/routes/media/+page.svelte @@ -15,6 +15,7 @@ { link: '/images/og/add-community.png', name: 'Add Community' }, { link: '/images/og/add.png', name: 'Add Location' }, { link: '/images/og/communities.png', name: 'Communities' }, + { link: '/images/og/countries.png', name: 'Countries' }, { link: '/images/og/apps.png', name: 'Download Apps' }, { link: '/images/og/badges.png', name: 'Earn Badges' }, { link: '/images/og/home.png', name: 'Main' }, @@ -25,6 +26,7 @@ { link: '/images/og/supertagger.png', name: 'Supertagger Profile' }, { link: '/images/og/support.png', name: 'Support Us' }, { link: '/images/og/top-communities.png', name: 'Top Communities' }, + { link: '/images/og/top-countries.png', name: 'Top Countries' }, { link: '/images/og/verify.png', name: 'Verify Locations' } ]; diff --git a/src/routes/merchant/[id]/+page.svelte b/src/routes/merchant/[id]/+page.svelte index e85fbb17..15dac309 100644 --- a/src/routes/merchant/[id]/+page.svelte +++ b/src/routes/merchant/[id]/+page.svelte @@ -853,7 +853,7 @@ alt="logo" class="mx-auto h-20 w-20 rounded-full object-cover" on:error={function () { - this.src = '/images/communities/bitcoin.svg'; + this.src = '/images/bitcoin.svg'; }} />

diff --git a/static/btcmap.webmanifest b/static/btcmap.webmanifest index cc9a3e18..4774a3db 100644 --- a/static/btcmap.webmanifest +++ b/static/btcmap.webmanifest @@ -634,6 +634,20 @@ } ] }, + { + "name": "Countries", + "short_name": "Countries", + "description": "Bitcoin adoption by countries", + "url": "/countries", + "icons": [ + { + "src": "/icons/pwa/maskable/maskable_icon_x96.png", + "sizes": "96x96", + "type": "image/png", + "purpose": "maskable" + } + ] + }, { "name": "Support Us", "short_name": "Support", diff --git a/static/icons/mobile-nav/spritesheet.svg b/static/icons/mobile-nav/spritesheet.svg index c70212c5..1b996453 100644 --- a/static/icons/mobile-nav/spritesheet.svg +++ b/static/icons/mobile-nav/spritesheet.svg @@ -13,6 +13,12 @@ + + + + + + @@ -24,14 +30,14 @@ - - - + + + diff --git a/static/images/communities/bitcoin.svg b/static/images/bitcoin.svg similarity index 100% rename from static/images/communities/bitcoin.svg rename to static/images/bitcoin.svg diff --git a/static/images/og/countries.png b/static/images/og/countries.png new file mode 100644 index 00000000..28c4e7c2 Binary files /dev/null and b/static/images/og/countries.png differ diff --git a/static/images/og/top-countries.png b/static/images/og/top-countries.png new file mode 100644 index 00000000..136547b9 Binary files /dev/null and b/static/images/og/top-countries.png differ