Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Homepage hero updates + blockchain filter #841

Merged
merged 13 commits into from
Oct 24, 2024
Merged
24 changes: 16 additions & 8 deletions src/lib/components/SegmentedControl.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ button-like control with a segement for each possible value.
export let options: readonly string[];
export let secondary = false;

const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher<{
change: {
name: string | undefined;
value: string;
};
}>();

function dispatchChange(this: HTMLInputElement) {
dispatch('change', {
Expand All @@ -30,7 +35,7 @@ button-like control with a segement for each possible value.
<div class="segmented-control ds-3 {secondary ? 'secondary' : 'primary'}" data-css-props>
{#each options as option}
<label class:selected={option === selected}>
<span>{option}</span>
<div><slot {option}>{option}</slot></div>
<input type="radio" {name} bind:group={selected} value={option} on:change={dispatchChange} />
</label>
{/each}
Expand Down Expand Up @@ -58,13 +63,16 @@ button-like control with a segement for each possible value.
}

.segmented-control {
display: flex;
display: grid;
grid-auto-flow: column;
gap: var(--gap);
overflow: hidden;
}

label {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
background: var(--background-default, inherit);
padding: var(--padding);
border-radius: var(--border-radius, inherit);
Expand All @@ -77,16 +85,16 @@ button-like control with a segement for each possible value.
&:first-child {
border-radius: var(--radius-md) 0 0 var(--radius-md);

span {
padding-left: var(--space-xxs);
div {
padding-left: 0.25rem;
}
}

&:last-child {
border-radius: 0 var(--radius-md) var(--radius-md) 0;

span {
padding-right: var(--space-xxs);
div {
padding-right: 0.25rem;
}
}
}
Expand Down
66 changes: 66 additions & 0 deletions src/lib/trade-executor/components/ChainFilter.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<script context="module" lang="ts">
import type { StrategyRuntimeState } from 'trade-executor/strategy/runtime-state';
import { type Chain, chains, getChain } from '$lib/helpers/chain';

export type ChainOption = Chain['slug'] | 'all';

export function getChainOptions(strategies: StrategyRuntimeState[]): ChainOption[] {
const chainSlugs = chains
.filter((c) => strategies.some((s) => matchesChainOption(s, c.slug))) // comment to break lines
.map((c) => c.slug);

return ['all', ...chainSlugs];
}

// Type predicate to narrow type to ChainOption
function isChainOption(chainOptions: ChainOption[], input: MaybeString): input is ChainOption {
return (chainOptions as MaybeString[]).includes(input);
}

export function parseChainOption(chainOptions: ChainOption[], input: MaybeString): ChainOption {
return isChainOption(chainOptions, input) ? input : 'all';
}

export function matchesChainOption(strategy: StrategyRuntimeState, chainOption?: ChainOption) {
if (chainOption === 'all') return true;
const chain = getChain(chainOption);
return strategy.on_chain_data?.chain_id === chain?.id;
}
</script>

<script lang="ts">
import SegmentedControl from '$lib/components/SegmentedControl.svelte';
import { getLogoUrl } from '$lib/helpers/assets';

export let options: ChainOption[];
export let selected: ChainOption;
</script>

<SegmentedControl name="chainFilter" {options} bind:selected let:option on:change>
<div class="filter-option {option}">
{#if option !== 'all'}
<img class="chain-icon" src={getLogoUrl('blockchain', option)} alt={option} />
{/if}
<span>{option}</span>
</div>
</SegmentedControl>

<style>
.filter-option {
display: flex;
gap: 0.25rem;
align-items: center;
justify-content: center;
padding-inline: 0.25rem;
text-transform: capitalize;

/* tweak Ethereum padding (logo image is narrower) */
&.ethereum {
padding-left: 0;
}

img {
width: 1.25em;
}
}
</style>
5 changes: 0 additions & 5 deletions src/lib/trade-executor/components/index.ts

This file was deleted.

58 changes: 27 additions & 31 deletions src/routes/FeaturedStrategies.svelte
Original file line number Diff line number Diff line change
@@ -1,34 +1,38 @@
<script lang="ts">
import type { StrategyRuntimeState } from 'trade-executor/strategy/runtime-state';
import { slide } from 'svelte/transition';
import { flip } from 'svelte/animate';
import { cubicOut } from 'svelte/easing';
import { Alert, Button, Section } from '$lib/components';
import {
type ChainOption,
default as ChainFilter,
getChainOptions,
matchesChainOption
} from 'trade-executor/components/ChainFilter.svelte';
import StrategyTile from './strategies/StrategyTile.svelte';
import StrategyDifferentiator from './StrategyDifferentiator.svelte';
import { getStrategyChartDateRange } from 'trade-executor/chart/helpers';

export let strategies: StrategyRuntimeState[];

let selected: ChainOption = 'all';

$: filteredStrategies = strategies.filter((s) => matchesChainOption(s, selected));

const chartDateRange = getStrategyChartDateRange(strategies);
</script>

<Section padding="md">
<h2>Open strategies</h2>
<div class="differentiators">
<StrategyDifferentiator
title="100% transparent"
details="All trades, all transactions, 100% transparently visible on the blockchains for you to verify. Building trust through openness."
/>
<StrategyDifferentiator
title="Self-custodial"
details="Withdraw your crypto whenever you want; Trading Strategy does not have access to your money."
/>
<StrategyDifferentiator
title="No fixed fees"
details="No fixed monthly fees; strategies collect performance fees only if they generate profits."
/>
<div class="filters">
<ChainFilter options={getChainOptions(strategies)} bind:selected />
</div>
<div class="strategies">
{#each strategies as strategy (strategy.id)}
<StrategyTile simplified {strategy} {chartDateRange} />
<div class="strategies" style:contain="paint">
{#each filteredStrategies as strategy, idx (strategy.id)}
{@const params = { duration: 200, delay: 50 * idx, easing: cubicOut }}
<div transition:slide={{ axis: 'x', ...params }} animate:flip={params} style:display="grid">
<StrategyTile simplified {strategy} {chartDateRange} />
</div>
{:else}
<div class="fallback">
<Alert size="sm" status="info">Check back soon to see top-performing strategies.</Alert>
Expand All @@ -37,7 +41,7 @@
</div>

<div class="cta">
<Button secondary label="See all strategies" href="/strategies" />
<Button secondary label="See all strategies" href="/strategies?chainFilter={selected}" />
</div>
</Section>

Expand All @@ -46,22 +50,14 @@
text-align: center;
}

.differentiators {
margin-top: 0.75rem;
display: flex;
gap: 2em;
.filters {
display: grid;
gap: 1.25rem;
justify-content: center;
font: var(--f-ui-md-medium);
letter-spacing: var(--f-ui-md-spacing);
margin-top: 1rem;

@media (--viewport-sm-down) {
font: var(--f-ui-sm-medium);
letter-spacing: var(--f-ui-sm-spacing);
}

@media (--viewport-xs) {
display: grid;
gap: 0.875em;
justify-content: stretch;
}
}

Expand Down
Loading
Loading