Skip to content

Commit

Permalink
feat: campaigns campanion tab (#1834)
Browse files Browse the repository at this point in the history
* feat: initial campaign routes (#1769)

* feat: initial campaign routes

Co-authored-by: MarkNerdi996 <[email protected]>

* fix: campaigns analytic string

---------

Co-authored-by: MarkNerdi996 <[email protected]>

* feat: initialise empty campaigns gallery (#1771)

* ui: create leaderboard component (#1773)

* create leaderboard component

* remove rebase error

* feat: initial campaigns grid and card (#1774)

* enhancement: add breadcrumbs for campaigns tab (#1776)

* feat: initial tide api class (#1780)

* feat: initial tide api class

* update type

* feat: fetch and persist campaign leaderboard data (#1784)

* feat: initial tide api class

* update type

* populate leaderboard

Co-authored-by: Tuditi <[email protected]>

* update api types

* refactor: add project and campaign id to campaign interface

* rename store

* rename response prop

* fix: use total xp

* fix: campaign id type in leaderboard store

---------

Co-authored-by: Nicole O'Brien <[email protected]>
Co-authored-by: Tuditi <[email protected]>

* add initial campaign details layout (#1800)

Co-authored-by: Mark Nardi <[email protected]>

* feat: show user position in campaign details (#1791)

* get user position from leaderboard

* add empty user position

* filter for correct network address

* update userposition card

Co-authored-by: Tuditi <[email protected]>

* fetch user position

* fix user position on account change

* fix: formatting

---------

Co-authored-by: Tuditi <[email protected]>
Co-authored-by: Tuditi <[email protected]>

* ui: campaign icons (#1804)

* ui: load campaign header and card image (#1806)

* update campaign image urls + show them in gallery

Co-authored-by: Tuditi <[email protected]>

* show image in campaign details view

* handle error on loading

---------

Co-authored-by: Tuditi <[email protected]>

* feat: add additional campaign tide apis

* enhancement: reset campaign router on tab click (#1808)

Co-authored-by: Tuditi <[email protected]>

* feat: fetch campaigns on load (#1815)

* feat: fetch campaigns on load

* enhancement: fetch and store campaigns per chain

* enhancement: campaign cards (#1816)

* feat: fetch campaigns on load

* initial campaign card enhancements

* enhancement: fetch and store campaigns per chain

* enhancement: display correct time difference

* ui: campaign status colors and strings

* ui: update campaigns icon to trophy (#1822)

* enhancement: add different states for the leaderboard (#1817)

* add empty state + mock loading/error

* add loading / error state

---------

Co-authored-by: Tuditi <[email protected]>

* enhancement: add rewards pill to campaign card (#1823)

Co-authored-by: Mark Nardi <[email protected]>

* feat: add external link buttons (#1818)

* add external link buttons

* fix size for header image

* extract tide urls into constants

* fix: pr review

---------

Co-authored-by: Nicole O'Brien <[email protected]>

* feat: featured campaigns (#1824)

* feat: sort campaigns by featured

* feat: featured campaign pill

* Update packages/desktop/views/dashboard/campaigns/views/CampaignsGalleryView.svelte

Co-authored-by: Mark Nardi <[email protected]>

* Update packages/desktop/views/dashboard/campaigns/components/CampaignsGalleryItem.svelte

Co-authored-by: Mark Nardi <[email protected]>

---------

Co-authored-by: Mark Nardi <[email protected]>

* ehancement: improve time diff (#1828)

* ui: highlight your own address if its in the leaderboard (#1810)

* hightlight own address in leaderboard

* add user to leaderboard

* update to new designs

* resolve merge conflict

* pass props

---------

Co-authored-by: Tuditi <[email protected]>

* add loading spinner to campaign fetch (#1832)

Co-authored-by: Tuditi <[email protected]>

* Feat: load campaign nft for user + show in campaign details (#1809)

* add contact address to campaign

* fix: return if no tokenId

* show placeholder if nft is not owned

* fix: correctly persist nfts

* chore: simplify data fetching

* fix: update prop name

* fix: use ownedNfts

* fix: handle swift switching

---------

Co-authored-by: Mark Nardi <[email protected]>

* fix: prevent crash in fetch L2 balance for account (#1833)

* ui: update design campaign details (#1820)

* update campaign details

* add locale

---------

Co-authored-by: Tuditi <[email protected]>
Co-authored-by: Tuditi <[email protected]>

* ui: create campaign details header (#1829)

* add pills to campaign details header

* fix types

* add networkPill

* chore: merge in campaings

* refactor: campaign details use of network and campaign

Co-authored-by: Tuditi <[email protected]>

* ui: campaign details gaps

---------

Co-authored-by: Tuditi <[email protected]>
Co-authored-by: Nicole O'Brien <[email protected]>
Co-authored-by: Tuditi <[email protected]>

* Feat: update the design of the user position in campaign details view (#1835)

* feat: add campaign NFT

* feat: add scrollbar to leaderboard

* feat: add NFT download

* fix: locales

* fix: glitch when position vs no position

* fix: use pill instead of avatar

* fix: campaign nft badge pill icon and strings

* ui: decrease gap on campaign details tab

---------

Co-authored-by: Nicole O'Brien <[email protected]>

* chore: set featured campaigns

* enhancement: show total tasks (#1839)

* enhancement: show total tasks

* chore: format

---------

Co-authored-by: Tuditi <[email protected]>
Co-authored-by: Tuditi <[email protected]>

* fix: fetch leaderboard on load of campaign

* enhancement: polish campaign description (#1836)

* add html sanitization

* extract header into own component

* sanitize html

* sanitize html

* use newspaces in description

* truncate long campaign names + localize lnk buttons

* revert

* update gap

* update description height

* fix ui

* fix: header dimensions and line clamping

---------

Co-authored-by: Nicole O'Brien <[email protected]>

* feat: adds Tide logo in CampaignsGalleryView (#1846)

* enhancement: polish campaign leaderboard (#1845)

* fix: align leaderboard items

* fix: leaderboard bottom row unexpected expanding

* fix: switch ordering of badges

* refactor: polish leaderboard and make it a table

* fix highlight

---------

Co-authored-by: Mark Nardi <[email protected]>

* enhancement: sort campaigns by end time (#1841)

* enhancement: sort campaigns by end time

* fix: improve sorting algorithm for campaigns

* chore: use enum

* fix: pr comments

---------

Co-authored-by: Mark Nardi <[email protected]>
Co-authored-by: Tuditi <[email protected]>

* feat: poll user status of campaign (#1847)

* feat: add polling of tide data

* restart poll after account change

---------

Co-authored-by: Mark Nardi <[email protected]>

* fix: improve leaderboard layout (#1848)

* temp: leaderboard layout

Co-authored-by: MarkNerdi996 <[email protected]>

* update leaderboard layout

* fix: url length in campaign header

---------

Co-authored-by: MarkNerdi996 <[email protected]>
Co-authored-by: Mark Nardi <[email protected]>
Co-authored-by: Tuditi <[email protected]>

* enhancement: track campaign id in analytics

---------

Co-authored-by: MarkNerdi996 <[email protected]>
Co-authored-by: Mark Nardi <[email protected]>
Co-authored-by: Tuditi <[email protected]>
Co-authored-by: Tuditi <[email protected]>
Co-authored-by: Tuditi <[email protected]>
Co-authored-by: Jean Ribeiro <[email protected]>
  • Loading branch information
7 people authored Jan 31, 2024
1 parent e0e5963 commit 644b57d
Show file tree
Hide file tree
Showing 77 changed files with 1,503 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
},
})
} catch (err) {
callback({ error: err })
modifyPopupState({ preventClose: false }, true)
handleError(err)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { closePopup } from '@desktop/auxiliary/popup'
import { showNotification } from '@auxiliary/notification'
import {
addNftsToDownloadQueue,
addNewTrackedNftToActiveProfile,
persistErc721Nft,
updateAllAccountNftsForAccount,
Expand Down Expand Up @@ -45,6 +46,7 @@
const l2Address = getAddressFromAccountForNetwork(account, networkId)
const nft = buildNftFromPersistedErc721Nft(persistedNft, l2Address)
updateAllAccountNftsForAccount(account.index, nft)
void addNftsToDownloadQueue([nft])
}
showNotification({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@
},
})
} catch (err) {
callback({ error: err })
handleError(err)
} finally {
isBusy = false
Expand Down
3 changes: 3 additions & 0 deletions packages/desktop/features/analytics.features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ const analyticsFeatures: IAnalyticsFeatures = {
buySell: {
enabled: true,
},
campaigns: {
enabled: true,
},
},
loginRoute: {
enabled: true,
Expand Down
10 changes: 10 additions & 0 deletions packages/desktop/features/campaigns.features.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ICampaignsFeatures } from '@lib/features/interfaces'

const campaignsFeatures: ICampaignsFeatures = {
enabled: true,
importCampaign: {
enabled: false,
},
}

export default campaignsFeatures
10 changes: 6 additions & 4 deletions packages/desktop/features/features.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import analyticsFeatures from './analytics.features'
import appFeatures from './app.features'
import buySellFeatures from './buy-sell.features'
import campaignsFeatures from './campaigns.features'
import collectiblesFeatures from './collectibles.features'
import contactsFeatures from './contacts.features'
import developerToolsFeatures from './developer-tools.features'
import electronFeatures from './electron.features'
import governanceFeatures from './governance.features'
import { IDesktopFeatures } from './interfaces'
import loginFeatures from './login.features'
import networkFeatures from './network.features'
import onboardingFeatures from './onboarding.features'
import settingsFeatures from './settings.features'
import walletFeatures from './wallet.features'
import walletConnectFeatures from './wallet-connect.features'
import analyticsFeatures from './analytics.features'
import buySellFeatures from './buy-sell.features'
import { IDesktopFeatures } from './interfaces'
import walletFeatures from './wallet.features'

const features: IDesktopFeatures = {
app: appFeatures,
Expand All @@ -29,6 +30,7 @@ const features: IDesktopFeatures = {
wallet: walletFeatures,
walletConnect: walletConnectFeatures,
buySell: buySellFeatures,
campaigns: campaignsFeatures,
}

export default features
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface IAnalyticsFeatures extends IFeatureFlag {
sendFlow: IFeatureFlag
}
buySell: IFeatureFlag
campaigns: IFeatureFlag
}
loginRoute: IFeatureFlag
onboardingRoute: IFeatureFlag
Expand Down
2 changes: 2 additions & 0 deletions packages/desktop/lib/routers/actions/initialiseRouters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
settingsRouter,
} from '@core/router/routers'
import { loginRouter, LoginRouter } from '@core/router/subrouters'
import { CampaignsRouter, campaignsRouter } from '@views/dashboard/campaigns'
import { OnboardingRouter, onboardingRouter } from '@views/onboarding'

export function initialiseRouters(): void {
Expand All @@ -33,6 +34,7 @@ function initialiseBaseRouters(): void {
settingsRouter.set(new SettingsRouter())
collectiblesRouter.set(new CollectiblesRouter())
governanceRouter.set(new GovernanceRouter())
campaignsRouter.set(new CampaignsRouter())
initialiseBaseOnboardingRouters()
}

Expand Down
2 changes: 2 additions & 0 deletions packages/desktop/views/dashboard/Dashboard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import { Settings } from '../settings'
import { Background } from '@views/components'
import { BuySell } from './buy-sell'
import { CampaignsRouterView } from './campaigns'
const tabs = {
wallet: Wallet,
Expand All @@ -33,6 +34,7 @@
governance: Governance,
developer: Developer,
buySell: BuySell,
campaigns: CampaignsRouterView,
}
let previousAccountIndex = get(selectedAccountIndex)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script lang="ts">
import { selectedCampaign } from '@contexts/campaigns/stores'
import { Platform } from '@core/app'
import features from '@features/features'
import { CampaignsRoute, campaignsRoute } from './'
import { CampaignDetailsView, CampaignsGalleryView } from './views'
$: if (features.analytics.dashboardRoute.campaigns.enabled && $campaignsRoute) {
const eventProperties = {
route: $campaignsRoute,
...($campaignsRoute === CampaignsRoute.CampaignDetails && { campaignId: $selectedCampaign?.id }),
}
Platform.trackEvent('campaigns-route', eventProperties)
}
</script>

<div class="w-full h-full flex flex-col flex-nowrap p-8 relative flex-1 overflow-hidden">
{#if $campaignsRoute === CampaignsRoute.Gallery}
<CampaignsGalleryView />
{/if}
{#if $campaignsRoute === CampaignsRoute.CampaignDetails}
<CampaignDetailsView />
{/if}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum CampaignsRoute {
Gallery = 'gallery',
CampaignDetails = 'campaignDetails',
}
30 changes: 30 additions & 0 deletions packages/desktop/views/dashboard/campaigns/campaigns-router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Router } from '@core/router'
import { appRouter } from '@core/router/routers/app-router'
import { get, writable } from 'svelte/store'
import { CampaignsRoute } from './campaigns-route.enum'

export const campaignsRoute = writable<CampaignsRoute>(undefined)
export const campaignsRouter = writable<CampaignsRouter>(undefined)

export class CampaignsRouter extends Router<CampaignsRoute> {
protected breadcrumb: string | undefined
constructor() {
super(CampaignsRoute.Gallery, campaignsRoute)
}

previous(): void {
if (this.history.length > 0) {
super.previous()
} else {
get(appRouter).previous()
}
}

setBreadcrumb(breadcrumb: string | undefined): void {
this.breadcrumb = breadcrumb
}

getBreadcrumb(): string | undefined {
return this.breadcrumb
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<script lang="ts">
import { truncateString } from '@core/utils'
import { Button, IconName, Link, Text } from '@bloomwalletio/ui'
import { ICampaign } from '@contexts/campaigns'
import { openUrlInBrowser } from '@core/app'
import { TIDE_BASE_URL, TideWebsiteEndpoint } from '@core/tide'
import Pane from '@ui/atoms/Pane.svelte'
import { MediaPlaceholder } from '@ui/molecules'
import CampaignParticipantsPill from './CampaignParticipantsPill.svelte'
import CampaignRewardsPill from './CampaignRewardsPill.svelte'
import CampaignStatusPill from './CampaignStatusPill.svelte'
import CampaignTimestampPill from './CampaignTimestampPill.svelte'
import sanitizeHtml from 'sanitize-html'
import { localize } from '@core/i18n'
export let campaign: ICampaign
let imageLoadError = false
$: description = getDescription(campaign.description)
function getDescription(rawDescription: string): string {
const sanitizedDescription = sanitizeHtml(rawDescription, {
allowedTags: ['b', 'i', 'em', 'strong', 'a', 'p', 'h1', 'h2', 'h3', 'h4'],
transformTags: {
a: 'x',
b: 'x',
i: 'x',
em: 'x',
strong: 'x',
h1: 'p',
h2: 'p',
h3: 'p',
h4: 'p',
},
})
const concatenatedDescription = sanitizedDescription
.replace(/<p>/g, '')
.replace(/<\/p>/g, ' ')
.replace(/<x>/g, '')
.replace(/<\/x>/g, '')
return concatenatedDescription
}
function onProjectClick(): void {
openUrlInBrowser(`${TIDE_BASE_URL}/${TideWebsiteEndpoint.Project}/${campaign.projectId}`)
}
function onCampaignClick(): void {
openUrlInBrowser(`${TIDE_BASE_URL}/${TideWebsiteEndpoint.Campaign}/${campaign.id}`)
}
</script>

<Pane
classes="
w-full max-h-64 shrink-0 flex flex-row
bg-surface dark:bg-surface-dark
border border-solid border-stroke dark:border-stroke-dark
divide-x divide-solid divide-stroke dark:divide-stroke-dark
shadow-lg
"
>
{#if campaign.imageUrl && !imageLoadError}
<div class="w-2/5 h-full p-4">
<img
src={campaign.imageUrl}
alt={campaign?.title}
class="w-full h-full object-cover rounded-lg"
on:error={() => (imageLoadError = true)}
/>
</div>
{:else}
<div class="w-2/5 h-full object-cover">
<MediaPlaceholder size="md" />
</div>
{/if}
<div
class="w-full flex flex-col items-start justify-between divide-y divide-solid divide-stroke dark:divide-stroke-dark"
>
<div class="w-full flex flex-row justify-between items-center gap-4 py-4 px-5">
<Text type="body1" class="whitespace-nowrap" truncate>{campaign.title}</Text>
<div class="flex flex-row gap-3">
<Button
size="xs"
icon={IconName.Send}
variant="outlined"
on:click={onProjectClick}
text={localize('views.campaigns.details.project')}
/>
<Button
size="xs"
icon={IconName.Send}
on:click={onCampaignClick}
text={localize('views.campaigns.details.campaign')}
/>
</div>
</div>
<div class="flex-grow w-full flex flex-col justify-between p-5 gap-2 overflow-hidden">
<Text
type="base"
textColor="secondary"
class="whitespace-pre-line overflow-hidden text-ellipsis line-clamp-3">{description}</Text
>
<div class="w-full flex flex-row justify-between gap-4">
<div class="flex flex-row gap-2">
<CampaignStatusPill {campaign} />
<CampaignTimestampPill {campaign} />
<CampaignParticipantsPill {campaign} />
<CampaignRewardsPill {campaign} />
</div>
<Link
text={truncateString(campaign.url, 30, 10)}
external
on:click={() => openUrlInBrowser(campaign.url)}
/>
</div>
</div>
</div>
</Pane>
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<script lang="ts">
import { Icon, IconName, Pill } from '@bloomwalletio/ui'
import { localize } from '@core/i18n'
import { MediaPlaceholder, NftMedia } from '@ui'
import { downloadingNftId } from '@core/nfts/stores'
import { MimeType } from '@core/nfts/enums'
import { Nft } from '@core/nfts'
export let nft: Nft | undefined
let placeholderWidth: number
</script>

<div class="absolute flex flex-row w-full p-4">
<Pill color={nft ? 'primary' : 'neutral'} opaque>
<div class="flex flex-row space-x-1 items-center text-invert">
<Icon size="xxs" customColor="text-invert" name={IconName.Gift} />
<div>
{localize(`views.campaigns.details.${nft ? 'nftBadge' : 'noNftBadge'}`)}
</div>
</div>
</Pill>
</div>
<div
bind:clientWidth={placeholderWidth}
style={`max-height: ${placeholderWidth}px;`}
class="flex w-full h-full overflow-hidden"
>
{#if nft}
<NftMedia {nft} classes="w-full h-full object-cover" loop muted>
<MediaPlaceholder
type={nft?.type}
textColor="primary"
downloading={$downloadingNftId === nft?.id}
size="md"
slot="placeholder"
/>
</NftMedia>
{:else}
<MediaPlaceholder type={MimeType.ImagePng} textColor="primary" size="lg" />
{/if}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script lang="ts">
import { Pill, Icon, IconName } from '@bloomwalletio/ui'
import { ICampaign } from '@contexts/campaigns'
export let campaign: ICampaign
</script>

<Pill color="neutral" compact>
<div class="flex flex-row space-x-1 items-center">
<Icon size="xxs" customColor="neutral" name={IconName.Users} />
<div>
{campaign.participants ?? 0}
</div>
</div>
</Pill>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script lang="ts">
import { localize } from '@core/i18n'
import { Pill, Icon, IconName } from '@bloomwalletio/ui'
import { ICampaign } from '@contexts/campaigns'
export let campaign: ICampaign
</script>

<Pill color="neutral" compact>
<div class="flex flex-row space-x-1 items-center">
<Icon size="xxs" customColor="neutral" name={IconName.Gift} />
<div>
{localize(`general.${campaign.ERC20Reward ? 'raffle' : 'nft'}`)}
</div>
</div>
</Pill>
Loading

0 comments on commit 644b57d

Please sign in to comment.