webapp - ESLint + Prettier with pre-commit hooks (#54)

resolves #47

Co-authored-by: johba <johba@harb.eth>
Reviewed-on: https://codeberg.org/johba/harb/pulls/54
This commit is contained in:
johba 2025-10-03 16:51:44 +02:00
parent 2acb619a11
commit f8927b426e
83 changed files with 7137 additions and 5113 deletions

View file

@ -1,7 +1,7 @@
<script setup lang="ts">
defineProps<{
msg: string
}>()
msg: string;
}>();
</script>
<template>

View file

@ -0,0 +1,55 @@
<template>
<a :href="props.href" target="_blank">
<div class="social-badge" :style="{ color: color, 'border-color': color }" :class="{ 'social-badge--dark': props.dark }">
<div class="social-badge-icon">
<component :color="color" :is="img" />
</div>
</div>
</a>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import IconDiscord from '@/components/icons/IconDiscord.vue';
import IconTwitter from '@/components/icons/IconTwitter.vue';
import IconTelegram from '@/components/icons/IconTelegram.vue';
interface Props {
type?: string;
dark?: boolean;
href?: string;
}
const props = withDefaults(defineProps<Props>(), {
type: 'discord',
dark: false,
href: '',
});
const color = computed(() => (props.dark ? 'white' : 'black'));
const icons = {
discord: IconDiscord,
twitter: IconTwitter,
telegram: IconTelegram,
} as const;
const img = computed(() => icons[props.type as keyof typeof icons] ?? null);
</script>
<style lang="sass">
.social-badge
border-radius: 14px
display: flex
border: 1px solid var(--color-social-border)
padding: 6px 20px
align-items: center
flex: 0 1 0
color: black
&:hover,&:active,&:focus
background-color: var(--color-white-hovered)
cursor: pointer
&.social-badge--dark
&:hover, &:active, &:focus
background-color: var(--color-black-hovered)
// font-size: 0
</style>

View file

@ -1,241 +1,213 @@
<template>
<div class="hold-inner">
<div class="stake-inner">
<template v-if="!statCollection.initialized">
<div>
<f-loader></f-loader>
</div>
</template>
<div class="hold-inner">
<div class="stake-inner">
<template v-if="!statCollection.initialized">
<div>
<FLoader></FLoader>
</div>
</template>
<template v-else>
<div class="subheader2">Token Amount</div>
<FSlider :min="minStakeAmount" :max="maxStakeAmount" v-model="stake.stakingAmountNumber"></FSlider>
<div class="formular">
<div class="row row-1">
<f-input label="Staking Amount" class="staking-amount" v-model="stake.stakingAmountNumber">
<template v-slot:details>
<div class="balance">Balance: {{ maxStakeAmount.toFixed(2) }} $KRK</div>
<div @click="setMaxAmount" class="staking-amount-max">
<b>Max</b>
</div>
</template>
</f-input>
<Icon class="stake-arrow" icon="mdi:chevron-triple-right"></Icon>
<f-input
label="Owner Slots"
class="staking-amount"
disabled
:modelValue="`${stakeSlots}(${supplyFreeze?.toFixed(4)})`"
>
<template #info>
Slots correspond to a percentage of ownership in the protocol.<br /><br />1,000 Slots =
1% Ownership<br /><br />When you unstake you get the exact percentage of the current
$KRK total supply. When the total supply increased since you staked you get more tokens
back than before.
</template>
</f-input>
</div>
<div class="row row-2">
<f-select :items="adjustTaxRate.taxRates" label="Tax" v-model="taxRate">
<template v-slot:info>
The yearly tax you have to pay to keep your slots open. The tax is paid when unstaking
or manually in the dashboard. If someone pays a higher tax they can buy you out.
</template>
</f-select>
<f-input label="Floor Tax" disabled :modelValue="snatchSelection.floorTax">
<template v-slot:info>
This is the current minimum tax you have to pay to claim owner slots from other owners.
</template>
</f-input>
<f-input label="Positions Buyout" disabled :modelValue="snatchSelection.snatchablePositions.length">
<template v-slot:info>
This shows you the numbers of staking positions you buy out from current owners by
paying a higher tax. If you get bought out yourself by new owners you get paid out the
current market value of your position incl. your profits.
</template>
</f-input>
</div>
</div>
<f-button size="large" disabled block v-if="stake.state === 'NoBalance'">Insufficient Balance</f-button>
<f-button size="large" disabled block v-else-if="stake.stakingAmountNumber < minStakeAmount"
>Stake amount too low</f-button
>
<f-button
size="large"
disabled
block
v-else-if="
!snatchSelection.openPositionsAvailable && stake.state === 'StakeAble' && snatchSelection.snatchablePositions.length === 0
"
>taxRate too low to snatch</f-button
>
<f-button
size="large"
block
v-else-if="stake.state === 'StakeAble' && snatchSelection.snatchablePositions.length === 0"
@click="stakeSnatch"
>Stake</f-button
>
<f-button
size="large"
block
v-else-if="stake.state === 'StakeAble' && snatchSelection.snatchablePositions.length > 0"
@click="stakeSnatch"
>Snatch and Stake</f-button
>
<f-button size="large" outlined block v-else-if="stake.state === 'SignTransaction'"
>Sign Transaction ...</f-button
>
<f-button size="large" outlined block v-else-if="stake.state === 'Waiting'">Waiting ...</f-button>
</template>
</div>
</div>
<template v-else>
<div class="subheader2">Token Amount</div>
<FSlider :min="minStakeAmount" :max="maxStakeAmount" v-model="stake.stakingAmountNumber"></FSlider>
<div class="formular">
<div class="row row-1">
<FInput label="Staking Amount" class="staking-amount" v-model="stake.stakingAmountNumber">
<template v-slot:details>
<div class="balance">Balance: {{ maxStakeAmount.toFixed(2) }} $KRK</div>
<div @click="setMaxAmount" class="staking-amount-max">
<b>Max</b>
</div>
</template>
</FInput>
<Icon class="stake-arrow" icon="mdi:chevron-triple-right"></Icon>
<FInput label="Owner Slots" class="staking-amount" disabled :modelValue="`${stakeSlots}(${supplyFreeze?.toFixed(4)})`">
<template #info>
Slots correspond to a percentage of ownership in the protocol.<br /><br />1,000 Slots = 1% Ownership<br /><br />When you
unstake you get the exact percentage of the current $KRK total supply. When the total supply increased since you staked you
get more tokens back than before.
</template>
</FInput>
</div>
<div class="row row-2">
<FSelect :items="adjustTaxRate.taxRates" label="Tax" v-model="taxRate">
<template v-slot:info>
The yearly tax you have to pay to keep your slots open. The tax is paid when unstaking or manually in the dashboard. If
someone pays a higher tax they can buy you out.
</template>
</FSelect>
<FInput label="Floor Tax" disabled :modelValue="String(snatchSelection.floorTax)">
<template v-slot:info> This is the current minimum tax you have to pay to claim owner slots from other owners. </template>
</FInput>
<FInput label="Positions Buyout" disabled :modelValue="String(snatchSelection.snatchablePositions.value.length)">
<template v-slot:info>
This shows you the numbers of staking positions you buy out from current owners by paying a higher tax. If you get bought
out yourself by new owners you get paid out the current market value of your position incl. your profits.
</template>
</FInput>
</div>
</div>
<FButton size="large" disabled block v-if="stake.state === 'NoBalance'">Insufficient Balance</FButton>
<FButton size="large" disabled block v-else-if="stake.stakingAmountNumber < minStakeAmount">Stake amount too low</FButton>
<FButton
size="large"
disabled
block
v-else-if="
!snatchSelection.openPositionsAvailable && stake.state === 'StakeAble' && snatchSelection.snatchablePositions.value.length === 0
"
>taxRate too low to snatch</FButton
>
<FButton
size="large"
block
v-else-if="stake.state === 'StakeAble' && snatchSelection.snatchablePositions.value.length === 0"
@click="stakeSnatch"
>Stake</FButton
>
<FButton
size="large"
block
v-else-if="stake.state === 'StakeAble' && snatchSelection.snatchablePositions.value.length > 0"
@click="stakeSnatch"
>Snatch and Stake</FButton
>
<FButton size="large" outlined block v-else-if="stake.state === 'SignTransaction'">Sign Transaction ...</FButton>
<FButton size="large" outlined block v-else-if="stake.state === 'Waiting'">Waiting ...</FButton>
</template>
</div>
</div>
</template>
<script setup lang="ts">
import FButton from "@/components/fcomponents/FButton.vue";
import FInput from "@/components/fcomponents/FInput.vue";
import FSelect from "@/components/fcomponents/FSelect.vue";
import FLoader from "@/components/fcomponents/FLoader.vue";
import FSlider from "@/components/fcomponents/FSlider.vue";
import FOutput from "@/components/fcomponents/FOutput.vue";
import { Icon } from "@iconify/vue";
import { formatBigIntDivision, InsertCommaNumber, formatBigNumber, bigInt2Number } from "@/utils/helper";
import { formatUnits } from "viem";
import { loadPositions, usePositions } from "@/composables/usePositions";
import { useStake } from "@/composables/useStake";
import { useClaim } from "@/composables/useClaim";
import { useAdjustTaxRate } from "@/composables/useAdjustTaxRates";
import { useSnatchSelection } from "@/composables/useSnatchSelection";
import { getMinStake } from "@/contracts/harb";
import { useWallet } from "@/composables/useWallet";
import { ref, onMounted, watch, computed, inject, watchEffect } from "vue";
import { useStatCollection, loadStats } from "@/composables/useStatCollection";
import { useRoute } from "vue-router";
import FButton from '@/components/fcomponents/FButton.vue';
import FInput from '@/components/fcomponents/FInput.vue';
import FSelect from '@/components/fcomponents/FSelect.vue';
import FLoader from '@/components/fcomponents/FLoader.vue';
import FSlider from '@/components/fcomponents/FSlider.vue';
import { Icon } from '@iconify/vue';
import { bigInt2Number } from '@/utils/helper';
import { loadPositions, usePositions, type Position } from '@/composables/usePositions';
import { useStake } from '@/composables/useStake';
import { useClaim } from '@/composables/useClaim';
import { useAdjustTaxRate } from '@/composables/useAdjustTaxRates';
import { useSnatchSelection } from '@/composables/useSnatchSelection';
import { assetsToShares } from '@/contracts/stake';
import { getMinStake } from '@/contracts/harb';
import { useWallet } from '@/composables/useWallet';
import { ref, onMounted, watch, computed, watchEffect } from 'vue';
import { useStatCollection, loadStats } from '@/composables/useStatCollection';
import { useRoute } from 'vue-router';
const demo = sessionStorage.getItem("demo") === "true";
const demo = sessionStorage.getItem('demo') === 'true';
const route = useRoute();
const adjustTaxRate = useAdjustTaxRate();
const activeTab = ref("stake");
const StakeMenuOpen = ref(false);
const taxRate = ref<number>(1.0);
const loading = ref<boolean>(true);
const stakeSnatchLoading = ref<boolean>(false);
const stake = useStake();
const claim = useClaim();
const _claim = useClaim();
const wallet = useWallet();
const statCollection = useStatCollection();
const { activePositions } = usePositions();
const { activePositions: _activePositions } = usePositions();
const minStake = ref(0n);
const stakeSlots = ref();
const supplyFreeze = ref<number>(0);
let debounceTimer: ReturnType<typeof setTimeout>;
watchEffect(() => {
console.log("supplyFreeze");
if (!stake.stakingAmount) {
supplyFreeze.value = 0;
return;
}
if (!stake.stakingAmount) {
supplyFreeze.value = 0;
return;
}
clearTimeout(debounceTimer);
debounceTimer = setTimeout(async () => {
stake.stakingAmountShares = await assetsToShares(stake.stakingAmount);
const stakingAmountSharesNumber = bigInt2Number(stake.stakingAmountShares, 18);
const stakeableSupplyNumber = bigInt2Number(statCollection.stakeableSupply, 18);
minStake.value = await getMinStake();
clearTimeout(debounceTimer);
debounceTimer = setTimeout(async () => {
stake.stakingAmountShares = await assetsToShares(stake.stakingAmount);
const stakingAmountSharesNumber = bigInt2Number(stake.stakingAmountShares, 18);
const stakeableSupplyNumber = bigInt2Number(statCollection.stakeableSupply, 18);
minStake.value = await getMinStake();
console.log(stakingAmountSharesNumber / stakeableSupplyNumber);
supplyFreeze.value = stakingAmountSharesNumber / stakeableSupplyNumber;
}, 500); // Verzögerung von 500ms
supplyFreeze.value = stakingAmountSharesNumber / stakeableSupplyNumber;
}, 500);
});
watchEffect(() => {
console.log("stakeSlots");
stakeSlots.value = (supplyFreeze.value * 1000)?.toFixed(2);
stakeSlots.value = (supplyFreeze.value * 1000)?.toFixed(2);
});
const tokenIssuance = computed(() => {
if (statCollection.kraikenTotalSupply === 0n) {
return 0n;
}
const _tokenIssuance = computed(() => {
if (statCollection.kraikenTotalSupply === 0n) {
return 0n;
}
return (statCollection.nettoToken7d / statCollection.kraikenTotalSupply) * 100n;
return (statCollection.nettoToken7d / statCollection.kraikenTotalSupply) * 100n;
});
async function stakeSnatch() {
if (snatchSelection.snatchablePositions.value.length === 0) {
await stake.snatch(stake.stakingAmount, taxRate.value);
} else {
const snatchAblePositionsIds = snatchSelection.snatchablePositions.value.map((p: Position) => p.positionId);
await stake.snatch(stake.stakingAmount, taxRate.value, snatchAblePositionsIds);
}
stakeSnatchLoading.value = true;
await new Promise((resolve) => setTimeout(resolve, 10000));
await loadPositions();
await loadStats();
stakeSnatchLoading.value = false;
if (snatchSelection.snatchablePositions.value.length === 0) {
await stake.snatch(stake.stakingAmount, taxRate.value);
} else {
const snatchAblePositionsIds = snatchSelection.snatchablePositions.value.map((p: Position) => p.positionId);
await stake.snatch(stake.stakingAmount, taxRate.value, snatchAblePositionsIds);
}
stakeSnatchLoading.value = true;
await new Promise(resolve => setTimeout(resolve, 10000));
await loadPositions();
await loadStats();
stakeSnatchLoading.value = false;
}
watch(
route,
async (to) => {
console.log("to", to.hash);
if (to.hash === "#stake") {
console.log("StakeMenuOpen", StakeMenuOpen.value);
StakeMenuOpen.value = true;
}
},
{ flush: "pre", immediate: true, deep: true }
route,
async to => {
if (to.hash === '#stake') {
StakeMenuOpen.value = true;
}
},
{ flush: 'pre', immediate: true, deep: true }
);
onMounted(async () => {
try {
minStake.value = await getMinStake();
stake.stakingAmountNumber = minStakeAmount.value;
} catch (error) {
console.error("error", error);
} finally {
loading.value = false;
}
try {
minStake.value = await getMinStake();
stake.stakingAmountNumber = minStakeAmount.value;
} finally {
loading.value = false;
}
});
const minStakeAmount = computed(() => {
console.log("minStake", minStake.value);
return bigInt2Number(minStake.value, 18);
return bigInt2Number(minStake.value, 18);
});
const maxStakeAmount = computed(() => {
if (wallet.balance?.value) {
console.log("wallet.balance.value", wallet.balance);
console.log("formatBigIntDivision(wallet.balance.value, 10n ** 18n)", bigInt2Number(wallet.balance.value, 18));
return bigInt2Number(wallet.balance.value, 18);
} else {
return 0;
}
if (wallet.balance?.value) {
return bigInt2Number(wallet.balance.value, 18);
} else {
return 0;
}
});
watch(
minStakeAmount,
async (newValue) => {
console.log("newValue", newValue);
if (newValue > stake.stakingAmountNumber && stake.stakingAmountNumber === 0) {
stake.stakingAmountNumber = minStakeAmount.value;
}
},
{ immediate: true }
minStakeAmount,
async newValue => {
if (newValue > stake.stakingAmountNumber && stake.stakingAmountNumber === 0) {
stake.stakingAmountNumber = minStakeAmount.value;
}
},
{ immediate: true }
);
function setMaxAmount() {
console.log("maxStakeAmount.value", maxStakeAmount.value);
stake.stakingAmountNumber = maxStakeAmount.value;
stake.stakingAmountNumber = maxStakeAmount.value;
}
const snatchSelection = useSnatchSelection(demo);
const snatchSelection = useSnatchSelection(demo, taxRate);
</script>
<style lang="sass">
@ -274,4 +246,4 @@ const snatchSelection = useSnatchSelection(demo);
.stake-arrow
align-self: center
font-size: 30px
</style>
</style>

View file

@ -1,39 +1,42 @@
<template>
<div class="stats-output" :styles="styles">
<f-card>
<h6>{{ props.headline }}</h6>
<f-output :name="props.name" :price="props.price">
<template #price>
<slot name="price"></slot>
</template>
</f-output>
</f-card>
</div>
<div class="stats-output" :styles="styles">
<FCard>
<h6>{{ props.headline }}</h6>
<FOutput :name="props.name" :price="props.price">
<template #price>
<slot name="price"></slot>
</template>
</FOutput>
</FCard>
</div>
</template>
<script setup lang="ts">
import { computed } from "vue";
import FCard from "@/components/fcomponents/FCard.vue"
import FOutput from "@/components/fcomponents/FOutput.vue"
import { computed } from 'vue';
import FCard from '@/components/fcomponents/FCard.vue';
import FOutput from '@/components/fcomponents/FOutput.vue';
interface Props {
name?: string;
headline: string;
price: string | number;
width?: number;
name?: string;
headline: string;
price: string | number;
width?: number;
}
interface Styles {
width?: string;
width?: string;
}
const props = withDefaults(defineProps<Props>(), {});
const props = withDefaults(defineProps<Props>(), {
name: '',
width: undefined,
});
const styles = computed(() => {
const returnObject: Styles = {};
if (props.width) {
returnObject.width = `${props.width}px`;
}
return returnObject;
const returnObject: Styles = {};
if (props.width) {
returnObject.width = `${props.width}px`;
}
return returnObject;
});
</script>

View file

@ -1,121 +0,0 @@
<template>
<f-card :bg-color="bgcolor" :border-width="0" box-shadow="unset">
<div class="info-popup">
<div class="info-popup__header">
<h6>{{ props.header }}</h6>
</div>
<div class="info-popup__body">
<div>
{{ props.subheader }}
</div>
<div v-if="props.value">
<span class="number-big">{{ props.value }}</span
>&nbsp;<span>{{ props.token }}</span>
</div>
</div>
<div v-if="!props.value">
<hr />
</div>
<div class="info-popup__body2" v-if="props.info" v-html="props.info"></div>
<div class="info-popup__footer">
<f-button light block small @click="closeToast">Okay</f-button>
</div>
</div>
<!-- <div class="body">
<h6 class="header">test{{ props.header }}</h6>
<div class="subheader toast-header">test{{ props.subheader }}</div>
<div class="amount-number">
<div class="token-amount number-mobile">test{{ props.value }}</div>
<div class="token-name">test{{ props.token }}</div>
</div>
<div class="info toast-body">test{{ props.info }}</div>
</div> -->
</f-card>
</template>
<script setup lang="ts">
import { getCurrentInstance, computed } from "vue";
import FButton from "@/components/fcomponents/FButton.vue";
import FCard from "@/components/fcomponents/FCard.vue";
import { useToast } from "vue-toastification";
import type { ToastID } from "vue-toastification/dist/types/types";
const props = defineProps({
value: String,
header: String,
subheader: String,
info: String,
token: String,
type: String,
});
const bgcolor = computed(() => {
let color = "white";
console.log("props.type");
console.log(props.type);
switch (props.type) {
case "info":
color = "#5f4884";
break;
case "error":
color = "#8B0000";
break;
default:
break;
}
return color;
});
// element.classList.add("toast-open");
const toast = useToast();
const instance = getCurrentInstance();
instance!.parent!.parent!.vnode!.el!.classList.add("toast-open");
const id = instance!.attrs["toast-id"] as ToastID;
console.log("instance", instance!.attrs["toast-id"]);
function closeToast() {
instance!.parent!.parent!.vnode!.el!.classList.remove("toast-open");
toast.dismiss(id);
}
</script>
<style lang="sass">
.info-popup
width: 342px
display: flex
color: var(--color-white)
flex-direction: column
gap: 16px
font-size: var(--font-body1)
text-align: center
hr
border: 1px solid var(--color-grey)
.info-popup__header
h6
margin: 0
color: var(--color-white)
.info-popup__body
display: flex
flex-direction: column
gap: 8px
.Vue-Toastification__container
&.toast-open
background-color: rgb(15, 15, 15, 0.7)
height: 100vh
width: 100vw
position: fixed
left: 0
bottom: 0
z-index: 10
@media (min-width: 992px)
background-color: unset
&.top-center
@media (min-width: 600px)
left: unset
margin-left: unset
.Vue-Toastification__toast
box-shadow: unset
&.modal-overlay
background-color: unset
</style>

View file

@ -0,0 +1,118 @@
<template>
<FCard :bg-color="bgcolor" :border-width="0" box-shadow="unset">
<div class="info-popup">
<div class="info-popup__header">
<h6>{{ props.header }}</h6>
</div>
<div class="info-popup__body">
<div>
{{ props.subheader }}
</div>
<div v-if="props.value">
<span class="number-big">{{ props.value }}</span
>&nbsp;<span>{{ props.token }}</span>
</div>
</div>
<div v-if="!props.value">
<hr />
</div>
<div class="info-popup__body2" v-if="props.info" v-html="props.info"></div>
<div class="info-popup__footer">
<FButton light block small @click="closeToast">Okay</FButton>
</div>
</div>
<!-- <div class="body">
<h6 class="header">test{{ props.header }}</h6>
<div class="subheader toast-header">test{{ props.subheader }}</div>
<div class="amount-number">
<div class="token-amount number-mobile">test{{ props.value }}</div>
<div class="token-name">test{{ props.token }}</div>
</div>
<div class="info toast-body">test{{ props.info }}</div>
</div> -->
</FCard>
</template>
<script setup lang="ts">
import { getCurrentInstance, computed } from 'vue';
import FButton from '@/components/fcomponents/FButton.vue';
import FCard from '@/components/fcomponents/FCard.vue';
import { useToast } from 'vue-toastification';
import type { ToastID } from 'vue-toastification/dist/types/types';
interface Props {
value?: string;
header?: string;
subheader?: string;
info?: string;
token?: string;
type?: string;
}
const props = defineProps<Props>();
const bgcolor = computed(() => {
let color = 'white';
switch (props.type) {
case 'info':
color = '#5f4884';
break;
case 'error':
color = '#8B0000';
break;
default:
break;
}
return color;
});
// element.classList.add("toast-open");
const toast = useToast();
const instance = getCurrentInstance();
instance!.parent!.parent!.vnode!.el!.classList.add('toast-open');
const id = instance!.attrs['toast-id'] as ToastID;
function closeToast() {
instance!.parent!.parent!.vnode!.el!.classList.remove('toast-open');
toast.dismiss(id);
}
</script>
<style lang="sass">
.info-popup
width: 342px
display: flex
color: var(--color-white)
flex-direction: column
gap: 16px
font-size: var(--font-body1)
text-align: center
hr
border: 1px solid var(--color-grey)
.info-popup__header
h6
margin: 0
color: var(--color-white)
.info-popup__body
display: flex
flex-direction: column
gap: 8px
.Vue-Toastification__container
&.toast-open
background-color: rgb(15, 15, 15, 0.7)
height: 100vh
width: 100vw
position: fixed
left: 0
bottom: 0
z-index: 10
@media (min-width: 992px)
background-color: unset
&.top-center
@media (min-width: 600px)
left: unset
margin-left: unset
.Vue-Toastification__toast
box-shadow: unset
&.modal-overlay
background-color: unset
</style>

View file

@ -1,30 +1,24 @@
<template>
<chart-js
:snatchedPositions="snatchPositions.map((obj) => obj.id)"
:positions="activePositions"
:dark="darkTheme"
></chart-js>
<ChartJs :snatchedPositions="snatchPositions.map(obj => obj.id)" :positions="activePositions" :dark="darkTheme"></ChartJs>
</template>
<script setup lang="ts">
import ChartJs from "@/components/chart/ChartJs.vue";
import {bigInt2Number, formatBigIntDivision} from "@/utils/helper";
import { computed, ref } from "vue";
import { useStatCollection } from "@/composables/useStatCollection";
import { useStake } from "@/composables/useStake";
import { usePositions, type Position } from "@/composables/usePositions";
import { useDark } from "@/composables/useDark";
import ChartJs from '@/components/chart/ChartJs.vue';
import { bigInt2Number, formatBigIntDivision } from '@/utils/helper';
import { computed, ref } from 'vue';
import { useStatCollection } from '@/composables/useStatCollection';
import { useStake } from '@/composables/useStake';
import { usePositions, type Position } from '@/composables/usePositions';
import { useDark } from '@/composables/useDark';
const { darkTheme } = useDark();
const { activePositions, myActivePositions, tresholdValue, myClosedPositions, createRandomPosition } = usePositions();
const { activePositions } = usePositions();
const ignoreOwner = ref(false);
const taxRate = ref<number>(1.0);
const minStakeAmount = computed(() => {
console.log("minStake", minStake.value);
return formatBigIntDivision(minStake.value, 10n ** 18n);
return formatBigIntDivision(minStake.value, 10n ** 18n);
});
const stakeAbleHarbAmount = computed(() => statCollection.kraikenTotalSupply / 5n);
@ -34,35 +28,33 @@ const minStake = computed(() => stakeAbleHarbAmount.value / 600n);
const stake = useStake();
const statCollection = useStatCollection();
const snatchPositions = computed(() => {
if (
if (
bigInt2Number(statCollection.outstandingStake, 18) + stake.stakingAmountNumber <=
bigInt2Number(statCollection.kraikenTotalSupply, 18) * 0.2
) {
) {
return [];
}
//Differenz aus outstandingSupply und totalSupply bestimmen, wie viel HARB kann zum Snatch verwendet werden
const difference =
}
//Differenz aus outstandingSupply und totalSupply bestimmen, wie viel HARB kann zum Snatch verwendet werden
const difference =
bigInt2Number(statCollection.outstandingStake, 18) +
stake.stakingAmountNumber -
bigInt2Number(statCollection.kraikenTotalSupply, 18) * 0.2;
console.log("difference", difference);
//Division ohne Rest, um zu schauen wie viele Positionen gesnatched werden könnten
const snatchAblePositionsCount = Math.floor(difference / minStakeAmount.value);
//Division ohne Rest, um zu schauen wie viele Positionen gesnatched werden könnten
const snatchAblePositionsCount = Math.floor(difference / minStakeAmount.value);
//wenn mehr als 0 Positionen gesnatched werden könnten, wird geschaut wie viele Positionen in Frage kommen
if (snatchAblePositionsCount > 0) {
//wenn mehr als 0 Positionen gesnatched werden könnten, wird geschaut wie viele Positionen in Frage kommen
if (snatchAblePositionsCount > 0) {
const snatchAblePositions = activePositions.value.filter((obj: Position) => {
if (ignoreOwner.value) {
return obj.taxRatePercentage < taxRate.value;
}
return obj.taxRatePercentage < taxRate.value && !obj.iAmOwner;
if (ignoreOwner.value) {
return obj.taxRatePercentage < taxRate.value;
}
return obj.taxRatePercentage < taxRate.value && !obj.iAmOwner;
});
const slicedArray = snatchAblePositions.slice(0, snatchAblePositionsCount);
return slicedArray;
}
}
return [];
return [];
});
</script>

View file

@ -1,79 +1,68 @@
<template>
<div class="positions-graph">
<div class="chart-modal" :class="{ 'chart--fullscreen': fullscreenOpen }" @click="toggleFullscreen">
<div class="chart-inner" @click.stop>
<div class="chart--header">
<div class="chart--actions" v-if="props.positions?.length > 0">
<reset-zoom-button @click="resetZoom"></reset-zoom-button>
<fullscreen-button v-if="isMobile" @click="toggleFullscreen"></fullscreen-button>
</div>
</div>
<div class="positions-graph">
<div class="chart-modal" :class="{ 'chart--fullscreen': fullscreenOpen }" @click="toggleFullscreen">
<div class="chart-inner" @click.stop>
<div class="chart--header">
<div class="chart--actions" v-if="props.positions?.length > 0">
<ResetZoomButton @click="resetZoom"></ResetZoomButton>
<FullscreenButton v-if="isMobile" @click="toggleFullscreen"></FullscreenButton>
</div>
</div>
<div
class="chart--body"
:class="{ 'disable-actions': props.positions?.length === 0, dark: props.dark }"
>
<canvas ref="Chart1"></canvas>
<template v-if="props.positions?.length === 0">
<p class="chart--no-positions">No positions</p>
</template>
</div>
</div>
</div>
<div class="tooltipRef" ref="tooltipRef" :class="{ iAmOwner: activePosition?.iAmOwner }">
<template v-if="activePosition?.iAmOwner">
<p>Your staking position</p>
</template>
<template v-else>
<p>Staking position</p>
</template>
<b>ID {{ activePosition?.id }}</b>
<b>{{ activePosition?.amount }} $KRK</b>
<b>Tax {{ activePosition?.taxRatePercentage }} %</b>
</div>
</div>
<div class="chart--body" :class="{ 'disable-actions': props.positions?.length === 0, dark: props.dark }">
<canvas ref="Chart1"></canvas>
<template v-if="props.positions?.length === 0">
<p class="chart--no-positions">No positions</p>
</template>
</div>
</div>
</div>
<div class="tooltipRef" ref="tooltipRef" :class="{ iAmOwner: activePosition?.iAmOwner }">
<template v-if="activePosition?.iAmOwner">
<p>Your staking position</p>
</template>
<template v-else>
<p>Staking position</p>
</template>
<b>ID {{ activePosition?.id }}</b>
<b>{{ activePosition?.amount }} $KRK</b>
<b>Tax {{ activePosition?.taxRatePercentage }} %</b>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, watch, shallowRef, computed } from "vue";
import { onMounted, ref, watch, shallowRef } from 'vue';
import {
BarController,
BarElement,
CategoryScale,
Chart,
LineController,
LineElement,
LinearScale,
PointElement,
Tooltip,
} from "chart.js";
BarController,
BarElement,
CategoryScale,
Chart,
LineController,
LineElement,
LinearScale,
PointElement,
Tooltip,
type TooltipModel,
type ChartConfiguration,
} from 'chart.js';
// import { Chart } from "chart.js";
import zoomPlugin from "chartjs-plugin-zoom";
import { useAccount } from "@wagmi/vue";
import { useMobile } from "@/composables/useMobile";
import type { Position } from "@/composables/usePositions";
import FullscreenButton from "@/components/chart/FullscreenButton.vue";
import ResetZoomButton from "@/components/chart/ResetZoomButton.vue";
Chart.register(
zoomPlugin,
LinearScale,
CategoryScale,
BarController,
BarElement,
LineController,
LineElement,
PointElement,
Tooltip
);
import zoomPlugin from 'chartjs-plugin-zoom';
import { useAccount } from '@wagmi/vue';
import { useMobile } from '@/composables/useMobile';
import type { Position } from '@/composables/usePositions';
import FullscreenButton from '@/components/chart/FullscreenButton.vue';
import ResetZoomButton from '@/components/chart/ResetZoomButton.vue';
Chart.register(zoomPlugin, LinearScale, CategoryScale, BarController, BarElement, LineController, LineElement, PointElement, Tooltip);
interface Props {
positions: Array<Position>;
snatchedPositions: Array<string>;
dark?: boolean;
positions: Array<Position>;
snatchedPositions: Array<string>;
dark?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
dark: false,
dark: false,
});
const Chart1 = ref();
@ -81,298 +70,290 @@ const fullscreenOpen = ref(false);
const myChart = ref();
const activePosition = ref();
const tooltipRef = ref();
const positionSnatched = ref();
const _positionSnatched = ref();
const account = useAccount();
const isMobile = useMobile();
function resetZoom() {
console.log("resetZoom", { Chart1, myChart });
myChart.value.value.resetZoom();
myChart.value.value.resetZoom();
}
function toggleFullscreen() {
if (fullscreenOpen.value) {
document.body.style.position = "unset";
document.body.style.overflow = "unset";
} else {
document.body.style.overflow = "hidden";
document.body.style.position = "relative";
}
fullscreenOpen.value = !fullscreenOpen.value;
window.scrollTo(0, 0);
if (fullscreenOpen.value) {
document.body.style.position = 'unset';
document.body.style.overflow = 'unset';
} else {
document.body.style.overflow = 'hidden';
document.body.style.position = 'relative';
}
fullscreenOpen.value = !fullscreenOpen.value;
window.scrollTo(0, 0);
}
watch(
() => props.positions,
(newData) => {
console.log("props.positions", props.positions);
() => props.positions,
() => {
myChart.value.value.destroy();
renderChart(props.positions);
myChart.value.value.destroy();
renderChart(props.positions);
// myChart.value.value.update();
// myChart.value.datasets[0].bars[0].fillColor = "green"; //bar 1
},
{
deep: true,
}
// myChart.value.value.update();
// myChart.value.datasets[0].bars[0].fillColor = "green"; //bar 1
},
{
deep: true,
}
);
watch(
() => props.dark,
(newData) => {
myChart.value.value.destroy();
renderChart(props.positions);
() => props.dark,
() => {
myChart.value.value.destroy();
renderChart(props.positions);
// myChart.value.value.update();
// myChart.value.datasets[0].bars[0].fillColor = "green"; //bar 1
}
// myChart.value.value.update();
// myChart.value.datasets[0].bars[0].fillColor = "green"; //bar 1
}
);
watch(
() => props.snatchedPositions,
(newData) => {
// myChart.value.value.destroy();
// renderChart(props.positions);
let positionIndex = 0;
if (myChart.value.value.data?.datasets[0]) {
let backgroundColorArray = myChart.value.value.data.datasets[0].backgroundColor;
() => props.snatchedPositions,
() => {
// myChart.value.value.destroy();
// renderChart(props.positions);
let positionIndex = 0;
if (myChart.value.value.data?.datasets[0]) {
const backgroundColorArray = myChart.value.value.data.datasets[0].backgroundColor;
for (let index = 0; index < backgroundColorArray.length; index++) {
const position: Position = myChart.value.value.data.datasets[0].data[index];
if (!position) {
continue;
}
for (let index = 0; index < backgroundColorArray.length; index++) {
const position: Position = myChart.value.value.data.datasets[0].data[index];
if (!position) {
continue;
}
if (!position.iAmOwner) {
positionIndex++;
}
if (positionIndex <= props.snatchedPositions.length && props.snatchedPositions.includes(position.id)) {
backgroundColorArray[index] = "##7550AE";
} else {
backgroundColorArray[index] = "##7550AE";
// const position = myChart.value.value.data.datasets[0].data[index];
if (position.iAmOwner) {
backgroundColorArray[index] = "#7550AE";
} else if (props.dark) {
backgroundColorArray[index] = "white";
} else {
backgroundColorArray[index] = "black";
}
}
}
myChart.value.value.data.datasets[0].backgroundColor = backgroundColorArray;
if (!position.iAmOwner) {
positionIndex++;
}
if (positionIndex <= props.snatchedPositions.length && props.snatchedPositions.includes(position.id)) {
backgroundColorArray[index] = '##7550AE';
} else {
backgroundColorArray[index] = '##7550AE';
// const position = myChart.value.value.data.datasets[0].data[index];
if (position.iAmOwner) {
backgroundColorArray[index] = '#7550AE';
} else if (props.dark) {
backgroundColorArray[index] = 'white';
} else {
backgroundColorArray[index] = 'black';
}
}
}
myChart.value.value.data.datasets[0].backgroundColor = backgroundColorArray;
myChart.value.value.ctx.save();
myChart.value.value?.update();
}
},
{
deep: true,
}
myChart.value.value.ctx.save();
myChart.value.value?.update();
}
},
{
deep: true,
}
);
const externalTooltipHandler = (context: any) => {
const { chart, tooltip } = context;
const tooltipEl = tooltipRef.value;
const externalTooltipHandler = (context: { chart: Chart; tooltip: TooltipModel<'bar'> }) => {
const { chart, tooltip } = context;
const tooltipEl = tooltipRef.value;
// Tooltip ausblenden, wenn keine Daten angezeigt werden sollen
if (!tooltip.opacity) {
tooltipEl.style.opacity = "0";
tooltipEl.style.display = "none";
return;
}
// Tooltip ausblenden, wenn keine Daten angezeigt werden sollen
if (!tooltip.opacity) {
tooltipEl.style.opacity = '0';
tooltipEl.style.display = 'none';
return;
}
// Aktive Position setzen (Daten des angezeigten Punktes)
activePosition.value = tooltip.dataPoints[0].element.$context.raw;
// Aktive Position setzen (Daten des angezeigten Punktes)
activePosition.value = (tooltip.dataPoints[0] as { element?: { $context?: { raw?: Position } } }).element?.$context?.raw;
// Positionierung des Tooltips
const { offsetLeft: chartX, offsetTop: chartY } = chart.canvas;
// Positionierung des Tooltips
const { offsetLeft: chartX, offsetTop: chartY } = chart.canvas;
// Tooltip anpassen
tooltipEl.style.opacity = "1";
tooltipEl.style.display = "flex";
tooltipEl.style.position = "absolute";
// Tooltip anpassen
tooltipEl.style.opacity = '1';
tooltipEl.style.display = 'flex';
tooltipEl.style.position = 'absolute';
// Tooltip mittig über dem Punkt platzieren
tooltipEl.style.left = `${chartX + tooltip.caretX}px`;
tooltipEl.style.top = `${chartY + tooltip.y}px`;
// Tooltip mittig über dem Punkt platzieren
tooltipEl.style.left = `${chartX + tooltip.caretX}px`;
tooltipEl.style.top = `${chartY + tooltip.y}px`;
// Tooltip für saubere Mitte ausrichten
tooltipEl.style.transform = "translateX(-50%)";
tooltipEl.style.pointerEvents = "none";
// Tooltip für saubere Mitte ausrichten
tooltipEl.style.transform = 'translateX(-50%)';
tooltipEl.style.pointerEvents = 'none';
};
function renderChart(data: any) {
console.log("renderChart");
function renderChart(data: Position[]) {
const backgroundColors = [];
const data1 = data.map(obj => {
return {
...obj,
// taxRatePercentage: obj.taxRate * 100,
iAmOwner: obj.owner?.toLowerCase() === account.address.value?.toLowerCase(),
};
});
const backgroundColors = [];
const data1 = data.map((obj: any) => {
return {
...obj,
// taxRatePercentage: obj.taxRate * 100,
iAmOwner: obj.owner?.toLowerCase() === account.address.value?.toLowerCase(),
};
});
console.log("data1", data1);
for (let index = 0; index < data1.length; index++) {
const position = data1[index];
for (let index = 0; index < data1.length; index++) {
const position = data1[index];
// if(index < props.snatchedPositions){
// backgroundColors.push("rgba(26,84,244, 0.5)");
// positionSnatched.value = index;
// if(index < props.snatchedPositions){
// backgroundColors.push("rgba(26,84,244, 0.5)");
// positionSnatched.value = index;
// }
if (position.iAmOwner) {
backgroundColors.push('rgba(117,80,174, 0.8)');
} else if (props.dark) {
backgroundColors[index] = 'white';
} else {
backgroundColors[index] = 'black';
}
}
// }
if (position.iAmOwner) {
backgroundColors.push("rgba(117,80,174, 0.8)");
} else if (props.dark) {
backgroundColors[index] = "white";
} else {
backgroundColors[index] = "black";
}
}
console.log("backgroundColors", backgroundColors);
myChart.value = shallowRef(
new Chart(Chart1.value, {
type: "bar",
data: {
labels: data1.map((row: any) => row.id),
datasets: [
{
type: "line",
label: "TaxRate",
data: data1,
backgroundColor: ["#7550AE"],
borderColor: ["#7550AE"],
yAxisID: "y",
parsing: {
yAxisKey: "taxRatePercentage",
xAxisKey: "id",
},
},
{
type: "bar",
label: "Amount",
data: data1,
backgroundColor: backgroundColors,
yAxisID: "y1",
parsing: {
yAxisKey: "amount",
xAxisKey: "id",
},
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: {
duration: 1,
},
interaction: {
intersect: false,
mode: "index",
},
plugins: {
legend: {
display: false,
},
tooltip: {
enabled: false,
position: "nearest",
external: externalTooltipHandler,
},
zoom: {
pan: {
enabled: true,
mode: "xy",
},
limits: {
y: { min: 0 },
},
zoom: {
wheel: {
enabled: true,
},
pinch: {
enabled: true,
},
mode: "xy",
onZoomComplete(object: any) {
// This update is needed to display up to date zoom level in the title.
// Without this, previous zoom level is displayed.
// The reason is: title uses the same beforeUpdate hook, and is evaluated before zoom.
object.chart?.update("none");
},
},
},
},
scales: {
y: {
// type: "linear",
display: true,
position: "left",
max: Math.max(...data.map((o: any) => o.taxRate)) * 100 * 1.5,
// min: 0,
title: {
display: true,
text: "Tax",
color: "#7550AE",
font: {
size: 16, // Hier die Schriftgröße ändern (z. B. 16px)
weight: "bold", // Falls du den Text fett machen möchtest
},
},
},
y1: {
// type: "linear",
display: true,
position: "right",
max: Math.max(...data.map((o: any) => o.amount)) * 1.5,
title: {
display: true,
text: "Slots",
color: "white",
font: {
size: 16, // Hier die Schriftgröße ändern (z. B. 16px)
weight: "bold", // Falls du den Text fett machen möchtest
},
},
grid: {
display: false,
},
// min: 0,
},
x: {
display: true,
ticks: {
display: false,
},
grid: {
display: false,
},
title: {
display: true,
text: "Positions",
color: "white",
font: {
size: 16, // Hier die Schriftgröße ändern (z. B. 16px)
weight: "bold", // Falls du den Text fett machen möchtest
},
},
},
},
},
plugins: [],
} as any)
);
myChart.value = shallowRef(
new Chart(Chart1.value, {
type: 'bar',
data: {
labels: data1.map(row => row.id),
datasets: [
{
type: 'line',
label: 'TaxRate',
data: data1,
backgroundColor: ['#7550AE'],
borderColor: ['#7550AE'],
yAxisID: 'y',
parsing: {
yAxisKey: 'taxRatePercentage',
xAxisKey: 'id',
},
},
{
type: 'bar',
label: 'Amount',
data: data1,
backgroundColor: backgroundColors,
yAxisID: 'y1',
parsing: {
yAxisKey: 'amount',
xAxisKey: 'id',
},
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: {
duration: 1,
},
interaction: {
intersect: false,
mode: 'index',
},
plugins: {
legend: {
display: false,
},
tooltip: {
enabled: false,
position: 'nearest',
external: externalTooltipHandler,
},
zoom: {
pan: {
enabled: true,
mode: 'xy',
},
limits: {
y: { min: 0 },
},
zoom: {
wheel: {
enabled: true,
},
pinch: {
enabled: true,
},
mode: 'xy',
onZoomComplete(object: { chart?: Chart }) {
// This update is needed to display up to date zoom level in the title.
// Without this, previous zoom level is displayed.
// The reason is: title uses the same beforeUpdate hook, and is evaluated before zoom.
object.chart?.update('none');
},
},
},
},
scales: {
y: {
// type: "linear",
display: true,
position: 'left',
max: Math.max(...data.map(o => o.taxRate)) * 100 * 1.5,
// min: 0,
title: {
display: true,
text: 'Tax',
color: '#7550AE',
font: {
size: 16, // Hier die Schriftgröße ändern (z. B. 16px)
weight: 'bold', // Falls du den Text fett machen möchtest
},
},
},
y1: {
// type: "linear",
display: true,
position: 'right',
max: Math.max(...data.map(o => o.amount)) * 1.5,
title: {
display: true,
text: 'Slots',
color: 'white',
font: {
size: 16, // Hier die Schriftgröße ändern (z. B. 16px)
weight: 'bold', // Falls du den Text fett machen möchtest
},
},
grid: {
display: false,
},
// min: 0,
},
x: {
display: true,
ticks: {
display: false,
},
grid: {
display: false,
},
title: {
display: true,
text: 'Positions',
color: 'white',
font: {
size: 16, // Hier die Schriftgröße ändern (z. B. 16px)
weight: 'bold', // Falls du den Text fett machen möchtest
},
},
},
},
},
plugins: [],
} as unknown as ChartConfiguration)
);
}
onMounted(() => {
renderChart(props.positions);
renderChart(props.positions);
});
</script>

View file

@ -1,28 +1,24 @@
<template>
<button class="chart-button">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
class="stroke"
d="M10.8027 1.00195H16.002C16.3433 1.00195 16.6201 1.27869 16.6201 1.62006V6.81927"
stroke-width="1.85431"
/>
<path
class="stroke"
d="M16.6191 10.5273L16.6191 15.7266C16.6191 16.0679 16.3424 16.3447 16.001 16.3447L10.8018 16.3447"
stroke-width="1.85431"
/>
<path
class="stroke"
d="M7.0918 16.3457L1.89258 16.3457C1.55121 16.3457 1.27448 16.069 1.27448 15.7276L1.27448 10.5284"
stroke-width="1.85431"
/>
<path
class="stroke"
d="M1.27539 6.81836L1.27539 1.61914C1.27539 1.27777 1.55213 1.00104 1.8935 1.00104L7.09271 1.00104"
stroke-width="1.85431"
/>
</svg>
</button>
<button class="chart-button">
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path class="stroke" d="M10.8027 1.00195H16.002C16.3433 1.00195 16.6201 1.27869 16.6201 1.62006V6.81927" stroke-width="1.85431" />
<path
class="stroke"
d="M16.6191 10.5273L16.6191 15.7266C16.6191 16.0679 16.3424 16.3447 16.001 16.3447L10.8018 16.3447"
stroke-width="1.85431"
/>
<path
class="stroke"
d="M7.0918 16.3457L1.89258 16.3457C1.55121 16.3457 1.27448 16.069 1.27448 15.7276L1.27448 10.5284"
stroke-width="1.85431"
/>
<path
class="stroke"
d="M1.27539 6.81836L1.27539 1.61914C1.27539 1.27777 1.55213 1.00104 1.8935 1.00104L7.09271 1.00104"
stroke-width="1.85431"
/>
</svg>
</button>
</template>
<style lang="sass">

View file

@ -1,20 +1,20 @@
<template>
<button class="chart-button">
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="path-1-inside-1_3412_52485" fill="white">
<path
d="M12.9797 15.2305C11.7286 16.2335 10.2045 16.8366 8.60589 16.9613C7.00726 17.086 5.4081 16.7265 4.01664 15.9296C2.62518 15.1327 1.50588 13.9353 0.804464 12.4934C0.103046 11.0515 -0.14799 9.43173 0.0840395 7.84511C0.316069 6.2585 1.02041 4.77849 2.10537 3.59778C3.19033 2.41708 4.60564 1.59038 6.16702 1.22532C7.72841 0.860254 9.36354 0.973741 10.8595 1.551C12.3555 2.12826 13.643 3.14256 14.5545 4.46182L13.109 5.46048C12.3981 4.4315 11.3938 3.64038 10.227 3.19013C9.0602 2.73989 7.78485 2.65137 6.56702 2.93611C5.34918 3.22084 4.24529 3.86565 3.39905 4.78656C2.55282 5.70747 2.00345 6.86183 1.82248 8.09935C1.6415 9.33686 1.8373 10.6002 2.38439 11.7249C2.93147 12.8495 3.80449 13.7835 4.88979 14.405C5.97508 15.0266 7.22237 15.307 8.46926 15.2097C9.71615 15.1124 10.9049 14.642 11.8807 13.8597L12.9797 15.2305Z"
/>
</mask>
<path
d="M12.9797 15.2305C11.7286 16.2335 10.2045 16.8366 8.60589 16.9613C7.00726 17.086 5.4081 16.7265 4.01664 15.9296C2.62518 15.1327 1.50588 13.9353 0.804464 12.4934C0.103046 11.0515 -0.14799 9.43173 0.0840395 7.84511C0.316069 6.2585 1.02041 4.77849 2.10537 3.59778C3.19033 2.41708 4.60564 1.59038 6.16702 1.22532C7.72841 0.860254 9.36354 0.973741 10.8595 1.551C12.3555 2.12826 13.643 3.14256 14.5545 4.46182L13.109 5.46048C12.3981 4.4315 11.3938 3.64038 10.227 3.19013C9.0602 2.73989 7.78485 2.65137 6.56702 2.93611C5.34918 3.22084 4.24529 3.86565 3.39905 4.78656C2.55282 5.70747 2.00345 6.86183 1.82248 8.09935C1.6415 9.33686 1.8373 10.6002 2.38439 11.7249C2.93147 12.8495 3.80449 13.7835 4.88979 14.405C5.97508 15.0266 7.22237 15.307 8.46926 15.2097C9.71615 15.1124 10.9049 14.642 11.8807 13.8597L12.9797 15.2305Z"
stroke="white"
stroke-width="3.54886"
mask="url(#path-1-inside-1_3412_52485)"
/>
<path d="M16.0005 6.89955L13.89 0.404049L9.32 5.47956L16.0005 6.89955Z" fill="white" />
</svg>
</button>
<button class="chart-button">
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="path-1-inside-1_3412_52485" fill="white">
<path
d="M12.9797 15.2305C11.7286 16.2335 10.2045 16.8366 8.60589 16.9613C7.00726 17.086 5.4081 16.7265 4.01664 15.9296C2.62518 15.1327 1.50588 13.9353 0.804464 12.4934C0.103046 11.0515 -0.14799 9.43173 0.0840395 7.84511C0.316069 6.2585 1.02041 4.77849 2.10537 3.59778C3.19033 2.41708 4.60564 1.59038 6.16702 1.22532C7.72841 0.860254 9.36354 0.973741 10.8595 1.551C12.3555 2.12826 13.643 3.14256 14.5545 4.46182L13.109 5.46048C12.3981 4.4315 11.3938 3.64038 10.227 3.19013C9.0602 2.73989 7.78485 2.65137 6.56702 2.93611C5.34918 3.22084 4.24529 3.86565 3.39905 4.78656C2.55282 5.70747 2.00345 6.86183 1.82248 8.09935C1.6415 9.33686 1.8373 10.6002 2.38439 11.7249C2.93147 12.8495 3.80449 13.7835 4.88979 14.405C5.97508 15.0266 7.22237 15.307 8.46926 15.2097C9.71615 15.1124 10.9049 14.642 11.8807 13.8597L12.9797 15.2305Z"
/>
</mask>
<path
d="M12.9797 15.2305C11.7286 16.2335 10.2045 16.8366 8.60589 16.9613C7.00726 17.086 5.4081 16.7265 4.01664 15.9296C2.62518 15.1327 1.50588 13.9353 0.804464 12.4934C0.103046 11.0515 -0.14799 9.43173 0.0840395 7.84511C0.316069 6.2585 1.02041 4.77849 2.10537 3.59778C3.19033 2.41708 4.60564 1.59038 6.16702 1.22532C7.72841 0.860254 9.36354 0.973741 10.8595 1.551C12.3555 2.12826 13.643 3.14256 14.5545 4.46182L13.109 5.46048C12.3981 4.4315 11.3938 3.64038 10.227 3.19013C9.0602 2.73989 7.78485 2.65137 6.56702 2.93611C5.34918 3.22084 4.24529 3.86565 3.39905 4.78656C2.55282 5.70747 2.00345 6.86183 1.82248 8.09935C1.6415 9.33686 1.8373 10.6002 2.38439 11.7249C2.93147 12.8495 3.80449 13.7835 4.88979 14.405C5.97508 15.0266 7.22237 15.307 8.46926 15.2097C9.71615 15.1124 10.9049 14.642 11.8807 13.8597L12.9797 15.2305Z"
stroke="white"
stroke-width="3.54886"
mask="url(#path-1-inside-1_3412_52485)"
/>
<path d="M16.0005 6.89955L13.89 0.404049L9.32 5.47956L16.0005 6.89955Z" fill="white" />
</svg>
</button>
</template>
<style lang="sass">

View file

@ -1,120 +1,101 @@
<template>
<f-collapse class="f-collapse-active" @collapse:opened="loadActivePositionData" :loading="loading">
<template v-slot:header>
<div class="collapse-header">
<div class="collapse-header-row1">
<div><span class="subheader2">Tax</span> {{ props.taxRate }} %</div>
<f-button size="tiny" @click="payTax(props.id)">Pay Tax</f-button>
<div class="position-id">
<span class="subheader2">ID</span> <span class="number-small">{{ props.id }}</span>
</div>
</div>
<div class="collapse-header-row2">
<div>
<div class="profit-stats-item">
<div><b>Initial Stake</b></div>
<div>{{ compactNumber(props.amount) }} $KRK</div>
</div>
</div>
<div class="tags-list">
<f-tag v-if="tag">{{ tag }}</f-tag>
</div>
</div>
<!-- <div class="collapse-amount">
<FCollapse class="f-collapse-active" @collapse:opened="loadActivePositionData" :loading="loading">
<template v-slot:header>
<div class="collapse-header">
<div class="collapse-header-row1">
<div><span class="subheader2">Tax</span> {{ props.taxRate }} %</div>
<FButton size="tiny" @click="payTax(props.id)">Pay Tax</FButton>
<div class="position-id">
<span class="subheader2">ID</span> <span class="number-small">{{ props.id }}</span>
</div>
</div>
<div class="collapse-header-row2">
<div>
<div class="profit-stats-item">
<div><b>Initial Stake</b></div>
<div>{{ compactNumber(props.amount) }} $KRK</div>
</div>
</div>
<div class="tags-list">
<FTag v-if="tag">{{ tag }}</FTag>
</div>
</div>
<!-- <div class="collapse-amount">
<span class="number-small">{{ compactNumber(props.amount) }}</span>
<span class="caption"> $KRK</span>
</div> -->
</div>
</template>
<div class="collapsed-body">
<div class="profit-stats-wrapper">
<div class="profit-stats-item">
<div><b>Tax Paid</b></div>
<div>{{ taxPaidGes }} $KRK</div>
</div>
<div class="profit-stats-item">
<div><b>Issuance Earned</b></div>
<div>{{ profit }} $KRK</div>
</div>
<div class="profit-stats-item profit-stats-total">
<div><b>Total</b></div>
<div>{{ total.toFixed(5) }} $KRK</div>
</div>
</div>
</div>
<div class="collapsed-body--actions">
<div :class="{ 'collapse-menu-open': showTaxMenu }">
<f-button size="small" dense block outlined v-if="adjustTaxRate.state === 'SignTransaction'"
>Sign Transaction ...</f-button
>
<f-button
size="small"
dense
outlined
block
v-else-if="adjustTaxRate.state === 'Waiting'"
@click="unstakePosition"
>Waiting ...</f-button
>
<f-button
size="small"
dense
block
v-else-if="adjustTaxRate.state === 'Action' && !showTaxMenu"
@click="showTaxMenu = true"
>Adjust Tax Rate</f-button
>
<template v-else>
<div class="collapse-menu-input">
<f-select :items="filteredTaxRates" v-model="newTaxRate"> </f-select>
</div>
<div>
<f-button size="small" dense @click="changeTax(props.id, newTaxRate)">Confirm</f-button>
<f-button size="small" dense outlined @click="showTaxMenu = false">Cancel</f-button>
</div>
</template>
</div>
<div></div>
<div>
<f-button size="small" dense block outlined v-if="unstake.state === 'SignTransaction'"
>Sign Transaction ...</f-button
>
<f-button size="small" dense outlined block v-else-if="unstake.state === 'Waiting'"
>Waiting ...</f-button
>
<f-button size="small" dense block v-else-if="unstake.state === 'Unstakeable'" @click="unstakePosition"
>Unstake</f-button
>
</div>
</div>
</f-collapse>
</div>
</template>
<div class="collapsed-body">
<div class="profit-stats-wrapper">
<div class="profit-stats-item">
<div><b>Tax Paid</b></div>
<div>{{ taxPaidGes }} $KRK</div>
</div>
<div class="profit-stats-item">
<div><b>Issuance Earned</b></div>
<div>{{ profit }} $KRK</div>
</div>
<div class="profit-stats-item profit-stats-total">
<div><b>Total</b></div>
<div>{{ total.toFixed(5) }} $KRK</div>
</div>
</div>
</div>
<div class="collapsed-body--actions">
<div :class="{ 'collapse-menu-open': showTaxMenu }">
<FButton size="small" dense block outlined v-if="adjustTaxRate.state === 'SignTransaction'">Sign Transaction ...</FButton>
<FButton size="small" dense outlined block v-else-if="adjustTaxRate.state === 'Waiting'" @click="unstakePosition"
>Waiting ...</FButton
>
<FButton size="small" dense block v-else-if="adjustTaxRate.state === 'Action' && !showTaxMenu" @click="showTaxMenu = true"
>Adjust Tax Rate</FButton
>
<template v-else>
<div class="collapse-menu-input">
<FSelect :items="filteredTaxRates" v-model="newTaxRate"> </FSelect>
</div>
<div>
<FButton size="small" dense @click="changeTax(props.id, newTaxRate)">Confirm</FButton>
<FButton size="small" dense outlined @click="showTaxMenu = false">Cancel</FButton>
</div>
</template>
</div>
<div></div>
<div>
<FButton size="small" dense block outlined v-if="unstake.state === 'SignTransaction'">Sign Transaction ...</FButton>
<FButton size="small" dense outlined block v-else-if="unstake.state === 'Waiting'">Waiting ...</FButton>
<FButton size="small" dense block v-else-if="unstake.state === 'Unstakeable'" @click="unstakePosition">Unstake</FButton>
</div>
</div>
</FCollapse>
</template>
<script setup lang="ts">
import FButton from "@/components/fcomponents/FButton.vue";
import FTag from "@/components/fcomponents/FTag.vue";
import FSelect from "@/components/fcomponents/FSelect.vue";
import FCollapse from "@/components/fcomponents/FCollapse.vue";
import { compactNumber, formatBigNumber } from "@/utils/helper";
import { useUnstake } from "@/composables/useUnstake";
import { useAdjustTaxRate } from "@/composables/useAdjustTaxRates";
import { computed, ref, onMounted } from "vue";
import { getTaxDue, payTax } from "@/contracts/stake";
import { type Position, loadPositions } from "@/composables/usePositions";
import { useStatCollection } from "@/composables/useStatCollection";
import FButton from '@/components/fcomponents/FButton.vue';
import FTag from '@/components/fcomponents/FTag.vue';
import FSelect from '@/components/fcomponents/FSelect.vue';
import FCollapse from '@/components/fcomponents/FCollapse.vue';
import { compactNumber, formatBigNumber } from '@/utils/helper';
import { useUnstake } from '@/composables/useUnstake';
import { useAdjustTaxRate } from '@/composables/useAdjustTaxRates';
import { computed, ref, onMounted } from 'vue';
import { getTaxDue, payTax } from '@/contracts/stake';
import { type Position, loadPositions } from '@/composables/usePositions';
import { useStatCollection } from '@/composables/useStatCollection';
import { formatUnits } from "viem";
import { formatUnits } from 'viem';
const unstake = useUnstake();
const adjustTaxRate = useAdjustTaxRate();
const statCollection = useStatCollection();
const props = defineProps<{
taxRate: number;
treshold: number;
id: bigint;
amount: number;
position: Position;
taxRate: number;
treshold: number;
id: bigint;
amount: number;
position: Position;
}>();
const showTaxMenu = ref(false);
@ -125,62 +106,51 @@ const profit = ref<number>();
const loading = ref<boolean>(false);
const tag = computed(() => {
if (props.taxRate < props.treshold) {
return "Low Tax!";
}
return "";
if (props.taxRate < props.treshold) {
return 'Low Tax!';
}
return '';
});
const total = computed(() => props.amount + profit.value! + -taxPaidGes.value!);
async function changeTax(id: bigint, newTaxRate: number) {
await adjustTaxRate.changeTax(id, newTaxRate);
showTaxMenu.value = false;
await adjustTaxRate.changeTax(id, newTaxRate);
showTaxMenu.value = false;
}
async function unstakePosition() {
console.log(props.id);
await unstake.exitPosition(props.id);
loading.value = true;
await new Promise((resolve) => setTimeout(resolve, 5000));
console.log("loadPositions begin");
await loadPositions();
console.log("loadPositions end");
loading.value = false;
await unstake.exitPosition(props.id);
loading.value = true;
await new Promise(resolve => setTimeout(resolve, 5000));
await loadPositions();
loading.value = false;
}
async function loadActivePositionData() {
console.log("loadActivePositionData", props.position);
//loadTaxDue
taxDue.value = await getTaxDue(props.id);
taxPaidGes.value = formatBigNumber(taxDue.value + BigInt(props.position.taxPaid), 18);
//loadTaxDue
taxDue.value = await getTaxDue(props.id);
console.log("taxDue", taxDue.value);
taxPaidGes.value = formatBigNumber(taxDue.value + BigInt(props.position.taxPaid), 18);
console.log("loadActivePositionData", taxPaidGes.value);
//loadTotalSupply
//loadTotalSupply
const multiplier = Number(formatUnits(props.position.totalSupplyInit, 18)) / Number(formatUnits(statCollection.kraikenTotalSupply, 18));
const multiplier =
Number(formatUnits(props.position.totalSupplyInit, 18)) /
Number(formatUnits(statCollection.kraikenTotalSupply, 18));
console.log("props.position.totalSupplyInit", props.position.totalSupplyInit);
console.log("multiplier", multiplier);
profit.value =
Number(formatUnits(statCollection.kraikenTotalSupply, 18)) * multiplier -
Number(formatUnits(statCollection.kraikenTotalSupply, 18));
profit.value =
Number(formatUnits(statCollection.kraikenTotalSupply, 18)) * multiplier - Number(formatUnits(statCollection.kraikenTotalSupply, 18));
}
onMounted(() => {
const taxRate = adjustTaxRate.taxRates.find((obj) => obj.index === props.position.taxRateIndex + 1);
if (props.position.taxRateIndex !== undefined) {
const taxRate = adjustTaxRate.taxRates.find(obj => obj.index === props.position.taxRateIndex! + 1);
if (taxRate) {
newTaxRate.value = taxRate.year;
}
if (taxRate) {
newTaxRate.value = taxRate.year;
}
}
});
const filteredTaxRates = computed(() => adjustTaxRate.taxRates.filter((obj) => obj.year > props.taxRate));
const filteredTaxRates = computed(() => adjustTaxRate.taxRates.filter(obj => obj.year > props.taxRate));
</script>
<style lang="sass">

View file

@ -1,56 +1,49 @@
<template>
<f-collapse class="f-collapse-history">
<template v-slot:header>
<div class="collapse-header">
<div><span class="subheader2">Tax</span> {{ props.taxRate }} %</div>
<div>
<span class="subheader2">ID</span> <span class="number-small">{{ props.id }}</span>
</div>
<div class="collapse-amount">
<span class="number-small">{{ compactNumber(props.amount) }}</span>
<span class="caption"> $KRK</span>
</div>
</div>
</template>
<div class="collapsed-body history">
<div>
<span class="subheader2">Tax paid</span><span class="number-small">{{ props.taxPaid }}</span
><span class="caption"> $KRK</span>
</div>
<div>
<span class="subheader2">Profit</span><span class="number-small">{{ profit }}</span
><span class="caption"> $KRK</span>
</div>
</div>
</f-collapse>
<FCollapse class="f-collapse-history">
<template v-slot:header>
<div class="collapse-header">
<div><span class="subheader2">Tax</span> {{ props.taxRate }} %</div>
<div>
<span class="subheader2">ID</span> <span class="number-small">{{ props.id }}</span>
</div>
<div class="collapse-amount">
<span class="number-small">{{ compactNumber(props.amount) }}</span>
<span class="caption"> $KRK</span>
</div>
</div>
</template>
<div class="collapsed-body history">
<div>
<span class="subheader2">Tax paid</span><span class="number-small">{{ props.taxPaid }}</span
><span class="caption"> $KRK</span>
</div>
<div>
<span class="subheader2">Profit</span><span class="number-small">{{ profit }}</span
><span class="caption"> $KRK</span>
</div>
</div>
</FCollapse>
</template>
<script setup lang="ts">
import type { Position } from "@/composables/usePositions";
import FCollapse from "@/components/fcomponents/FCollapse.vue";
import { compactNumber } from "@/utils/helper";
import { formatUnits } from "viem";
import type { Position } from '@/composables/usePositions';
import FCollapse from '@/components/fcomponents/FCollapse.vue';
import { compactNumber } from '@/utils/helper';
import { formatUnits } from 'viem';
import { computed } from "vue";
import { computed } from 'vue';
const props = defineProps<{
taxRate: number;
taxPaid: string;
treshold: number;
id: bigint;
amount: number;
position: Position;
taxRate: number;
taxPaid: string;
treshold: number;
id: bigint;
amount: number;
position: Position;
}>();
const profit = computed(() => {
const multiplier =
Number(formatUnits(props.position.totalSupplyInit, 18)) /
Number(formatUnits(props.position.totalSupplyEnd!, 18));
console.log("multiplier", multiplier);
console.log("props.position.amount", props.position.amount);
return (
Number(formatUnits(props.position.totalSupplyEnd!, 18)) * multiplier -
Number(formatUnits(props.position.totalSupplyEnd!, 18))
);
const multiplier = Number(formatUnits(props.position.totalSupplyInit, 18)) / Number(formatUnits(props.position.totalSupplyEnd!, 18));
return Number(formatUnits(props.position.totalSupplyEnd!, 18)) * multiplier - Number(formatUnits(props.position.totalSupplyEnd!, 18));
});
</script>

View file

@ -1,48 +1,49 @@
<template>
<button class="f-btn" :class="classObject" :style="styleObject">
<slot></slot>
</button>
<button class="f-btn" :class="classObject" :style="styleObject">
<slot></slot>
</button>
</template>
<script setup lang="ts">
interface Props {
size?: string;
dense?: boolean;
disabled?: boolean;
invert?: boolean;
block?: boolean;
outlined?: boolean;
bgColor?: string;
light?: boolean;
dark?: boolean;
size?: string;
dense?: boolean;
disabled?: boolean;
invert?: boolean;
block?: boolean;
outlined?: boolean;
bgColor?: string;
light?: boolean;
dark?: boolean;
}
import { computed } from "vue";
import { computed } from 'vue';
const props = withDefaults(defineProps<Props>(), {
size: "medium",
size: 'medium',
bgColor: '',
});
const classObject = computed(() => ({
"f-btn--tiny": props.size === "tiny",
"f-btn--small": props.size === "small",
"f-btn--medium": props.size === "medium",
"f-btn--large": props.size === "large",
"f-btn--dense": props.dense,
"f-btn--disabled": props.disabled,
"f-btn--invert": props.invert,
"f-btn--block": props.block,
"f-btn--outlined": props.outlined,
"f-btn--light": props.light,
"f-btn--dark": props.dark,
'f-btn--tiny': props.size === 'tiny',
'f-btn--small': props.size === 'small',
'f-btn--medium': props.size === 'medium',
'f-btn--large': props.size === 'large',
'f-btn--dense': props.dense,
'f-btn--disabled': props.disabled,
'f-btn--invert': props.invert,
'f-btn--block': props.block,
'f-btn--outlined': props.outlined,
'f-btn--light': props.light,
'f-btn--dark': props.dark,
}));
const styleObject = computed(() => {
const returnObject: any = {};
if (props.bgColor) {
returnObject["background-color"] = props.bgColor;
}
return returnObject;
const returnObject: Record<string, string> = {};
if (props.bgColor) {
returnObject['background-color'] = props.bgColor;
}
return returnObject;
});
</script>

View file

@ -1,40 +1,45 @@
<template>
<div class="f-card" ref="fCard" :style="computedStyles">
<div v-if="props.title" class="f-card__title">
<h5>
{{ props.title }}
</h5>
</div>
<div class="f-card__body">
<slot></slot>
</div>
</div>
<div class="f-card" ref="fCard" :style="computedStyles">
<div v-if="props.title" class="f-card__title">
<h5>
{{ props.title }}
</h5>
</div>
<div class="f-card__body">
<slot></slot>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, computed } from "vue";
import { onMounted, ref, computed } from 'vue';
const fCard = ref();
interface Props {
bgColor?: string;
borderWidth?: number;
boxShadow?: string;
title?: string;
bgColor?: string;
borderWidth?: number;
boxShadow?: string;
title?: string;
}
const props = withDefaults(defineProps<Props>(), {});
const props = withDefaults(defineProps<Props>(), {
bgColor: '',
borderWidth: undefined,
boxShadow: '',
title: '',
});
const computedStyles = computed(() => ({
"border-width": props.borderWidth,
"background-color": props.bgColor,
"box-shadow": props.boxShadow,
'border-width': props.borderWidth,
'background-color': props.bgColor,
'box-shadow': props.boxShadow,
}));
onMounted(() => {
// if (props.bgcolor) {
// fCard.value.style["background-color"] = props.bgcolor;
// }
// if (props.bgcolor) {
// fCard.value.style["background-color"] = props.bgcolor;
// }
});
</script>

View file

@ -1,50 +1,55 @@
<template>
<div class="f-collapse">
<div class="f-collapse-wrapper" :class="{loading: props.loading}">
<template v-if="loading">
<f-loader></f-loader>
</template>
<div class="f-collapse-inner">
<slot name="header"></slot>
<!-- <img class="toggle-collapse" src="../assets/expand-less.svg?url" alt="expand less" /> -->
<Icon v-if="isShow" class="toggle-collapse" icon="mdi:chevron-down" @click="openClose"></Icon>
<Icon v-else icon="mdi:chevron-up" class="toggle-collapse" @click="openClose"></Icon>
<!-- <img
<div class="f-collapse">
<div class="f-collapse-wrapper" :class="{ loading: props.loading }">
<template v-if="loading">
<FLoader></FLoader>
</template>
<div class="f-collapse-inner">
<slot name="header"></slot>
<!-- <img class="toggle-collapse" src="../assets/expand-less.svg?url" alt="expand less" /> -->
<Icon v-if="isShow" class="toggle-collapse" icon="mdi:chevron-down" @click="openClose"></Icon>
<Icon v-else icon="mdi:chevron-up" class="toggle-collapse" @click="openClose"></Icon>
<!-- <img
class="toggle-collapse"
src="../assets/expand-more.svg?url"
alt="expand more"
v-else
/> -->
</div>
<Transition name="collapse">
<div v-if="isShow" class="collapsableDiv">
<slot></slot>
</div>
</Transition>
</div>
<Transition name="collapse">
<div v-if="isShow" class="collapsableDiv">
<slot></slot>
</div>
</div>
</Transition>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import {Icon} from "@iconify/vue";
import FLoader from "@/components/fcomponents/FLoader.vue";
import { ref } from 'vue';
import { Icon } from '@iconify/vue';
import FLoader from '@/components/fcomponents/FLoader.vue';
const emit = defineEmits(["collapse:opened", "collapse:closed"]);
interface Emits {
(event: 'collapse:opened'): void;
(event: 'collapse:closed'): void;
}
const emit = defineEmits<Emits>();
const props = defineProps<{
loading?: boolean;
loading?: boolean;
}>();
const isShow = ref(false);
const isShow = ref<boolean>(false);
const openClose = () => {
isShow.value = !isShow.value;
if(isShow.value){
emit("collapse:opened")
} else{
emit("collapse:closed")
}
isShow.value = !isShow.value;
if (isShow.value) {
emit('collapse:opened');
} else {
emit('collapse:closed');
}
};
</script>
<style lang="sass">
@ -92,6 +97,4 @@ const openClose = () => {
to
max-height: 0px
overflow: hidden
</style>

View file

@ -1,48 +1,47 @@
<template>
<div class="f-input" :class="classObject">
<div class="f-input-label subheader2">
<label v-if="props.label" :for="name">{{ props.label }}</label>
<icon>
<template v-slot:text v-if="slots.info">
<slot name="info"></slot>
</template>
</icon>
</div>
<div class="f-input__wrapper" ref="inputWrapper" @click="setFocus">
<input
:disabled="props.disabled"
:readonly="props.readonly"
:type="props.type"
:name="name"
:id="name"
@input="updateModelValue"
:value="props.modelValue"
/>
<div class="f-input--suffix" v-if="slots.suffix">
<slot name="suffix" >test </slot>
</div>
</div>
<div class="f-input--details" v-if="slots.details">
<slot name="details"> </slot>
</div>
</div>
<div class="f-input" :class="classObject">
<div class="f-input-label subheader2">
<label v-if="props.label" :for="name">{{ props.label }}</label>
<Icon>
<template v-slot:text v-if="slots.info">
<slot name="info"></slot>
</template>
</Icon>
</div>
<div class="f-input__wrapper" ref="inputWrapper" @click="setFocus">
<input
:disabled="props.disabled"
:readonly="props.readonly"
:type="props.type"
:name="name"
:id="name"
@input="updateModelValue"
:value="props.modelValue"
/>
<div class="f-input--suffix" v-if="slots.suffix">
<slot name="suffix">test </slot>
</div>
</div>
<div class="f-input--details" v-if="slots.details">
<slot name="details"> </slot>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, getCurrentInstance, useSlots, ref } from "vue";
import useClickOutside from "@/composables/useClickOutside"
import { computed, getCurrentInstance, useSlots, ref } from 'vue';
import useClickOutside from '@/composables/useClickOutside';
import Icon from "@/components/icons/IconInfo.vue";
import Icon from '@/components/icons/IconInfo.vue';
interface Props {
size?: string;
info?: string;
label?: string;
disabled?: boolean;
modelValue?: any;
type?: string;
readonly?: boolean
size?: string;
info?: string;
label?: string;
disabled?: boolean;
modelValue?: string | number;
type?: string;
readonly?: boolean;
}
const slots = useSlots();
@ -53,40 +52,47 @@ const instance = getCurrentInstance();
const name = `f-input-${instance!.uid}`;
const props = withDefaults(defineProps<Props>(), {
size: "normal",
disabled: false,
readonly: false,
type: "string",
size: 'normal',
disabled: false,
readonly: false,
type: 'string',
info: '',
label: '',
modelValue: '',
});
useClickOutside(inputWrapper, () => {
removeFocus();
})
removeFocus();
});
const emit = defineEmits(["update:modelValue"]);
interface Emits {
(e: 'update:modelValue', value: string): void;
}
const emit = defineEmits<Emits>();
function updateModelValue($event: any) {
emit("update:modelValue", $event.target.value);
function updateModelValue($event: Event) {
const target = $event.target as HTMLInputElement;
emit('update:modelValue', target.value);
}
const classObject = computed(() => ({
"f-input--normal": props.size === "normal",
"f-input--big": props.size === "big",
"f-input--disabled": props.disabled,
'f-input--normal': props.size === 'normal',
'f-input--big': props.size === 'big',
'f-input--disabled': props.disabled,
}));
function removeFocus(){
const target = inputWrapper.value as HTMLElement
target.classList.remove("f-input__wrapper--focused")
function removeFocus() {
const target = inputWrapper.value as HTMLElement;
target.classList.remove('f-input__wrapper--focused');
}
function setFocus(event:MouseEvent){
console.log("setFocus");
if(props.disabled){
return
}
const target = inputWrapper.value as HTMLElement
target.classList.add("f-input__wrapper--focused")
function setFocus(_event: MouseEvent) {
// console.log("setFocus");
if (props.disabled) {
return;
}
const target = inputWrapper.value as HTMLElement;
target.classList.add('f-input__wrapper--focused');
}
</script>
<style lang="sass">
@ -109,7 +115,7 @@ function setFocus(event:MouseEvent){
background-color: #2D2D2D
&.f-input__wrapper--focused
outline: none
border: 1px solid #7550AE
border: 1px solid #7550AE
box-shadow: 0 0 5px #7550AE
input
border: 0
@ -131,5 +137,4 @@ function setFocus(event:MouseEvent){
display: flex
align-items: center
margin-right: 10px
</style>

View file

@ -1,12 +1,12 @@
<template>
<div class="cube">
<div class="side"></div>
<div class="side"></div>
<div class="side"></div>
<div class="side"></div>
<div class="side"></div>
<div class="side"></div>
</div>
<div class="cube">
<div class="side"></div>
<div class="side"></div>
<div class="side"></div>
<div class="side"></div>
<div class="side"></div>
<div class="side"></div>
</div>
</template>
<style lang="sass">

View file

@ -1,37 +1,38 @@
<template>
<div
class="output-wrapper"
:class="{
'no-name': !props.name,
'output--variant-1': props.variant === 1,
'output--variant-2': props.variant === 2,
'output--variant-3': props.variant === 3,
}"
>
<div class="output-left">
<div class="output-text">{{ props.name }}</div>
<div class="output-price">
<slot name="price">
{{ props.price }}
</slot>
</div>
</div>
<div class="output-right" v-if="slots.end">
<slot name="end"></slot>
</div>
</div>
<div
class="output-wrapper"
:class="{
'no-name': !props.name,
'output--variant-1': props.variant === 1,
'output--variant-2': props.variant === 2,
'output--variant-3': props.variant === 3,
}"
>
<div class="output-left">
<div class="output-text">{{ props.name }}</div>
<div class="output-price">
<slot name="price">
{{ props.price }}
</slot>
</div>
</div>
<div class="output-right" v-if="slots.end">
<slot name="end"></slot>
</div>
</div>
</template>
<script setup lang="ts">
import { useSlots } from "vue";
const props = defineProps({
name: String,
price: [String, Number],
variant: {
type: Number,
required: false,
default: 1,
},
import { useSlots, withDefaults } from 'vue';
interface Props {
name?: string;
price?: string | number;
variant?: number;
}
const props = withDefaults(defineProps<Props>(), {
variant: 1,
name: '',
price: '',
});
const slots = useSlots();

View file

@ -1,163 +1,141 @@
<template>
<div class="o-select" @click="clickSelect" ref="componentRef">
<f-input hide-details :clearable="props.clearable" :label="props.label" :selectedable="false"
:focus="showList" :modelValue="`${year} % yearly`" readonly>
<template #info v-if="slots.info">
<slot name="info"></slot>
</template>
<template #suffix>
<Icon class="toggle-collapse" v-if="showList" icon="mdi:chevron-down"></Icon>
<Icon class="toggle-collapse" v-else icon="mdi:chevron-up"></Icon>
</template>
</f-input>
<div class="select-list-wrapper" v-show="showList" ref="selectList" >
<div class="select-list-inner" @click.stop>
<div class="select-list-item" v-for="(item, index) in props.items" :key="item.year" :class="{'active': year === item.year, 'hovered': activeIndex === index}"
@click.stop="clickItem(item)" @mouseenter="mouseEnter($event, index)" @mouseleave="mouseLeave($event, index)">
<div class="circle">
<div class="active" v-if="year === item.year"></div>
<div class="hovered" v-else-if="activeIndex === index"></div>
</div>
<div class="yearly">
<div class="value">{{ item.year }} %</div>
<div class="label">yearly</div>
</div>
<div class="daily">
<div class="value">{{ item.daily.toFixed(4) }} %</div>
<div class="label">daily</div>
</div>
</div>
</div>
<div class="o-select" @click="clickSelect" ref="componentRef">
<FInput
hide-details
:clearable="props.clearable"
:label="props.label ?? undefined"
:selectedable="false"
:focus="showList"
:modelValue="`${year} % yearly`"
readonly
>
<template #info v-if="slots.info">
<slot name="info"></slot>
</template>
<template #suffix>
<Icon class="toggle-collapse" v-if="showList" icon="mdi:chevron-down"></Icon>
<Icon class="toggle-collapse" v-else icon="mdi:chevron-up"></Icon>
</template>
</FInput>
<div class="select-list-wrapper" v-show="showList" ref="selectList">
<div class="select-list-inner" @click.stop>
<div
class="select-list-item"
v-for="(item, index) in props.items"
:key="item.year"
:class="{ active: year === item.year, hovered: activeIndex === index }"
@click.stop="clickItem(item)"
@mouseenter="mouseEnter($event, index)"
@mouseleave="mouseLeave($event, index)"
>
<div class="circle">
<div class="active" v-if="year === item.year"></div>
<div class="hovered" v-else-if="activeIndex === index"></div>
</div>
<div class="yearly">
<div class="value">{{ item.year }} %</div>
<div class="label">yearly</div>
</div>
<div class="daily">
<div class="value">{{ item.daily.toFixed(4) }} %</div>
<div class="label">daily</div>
</div>
</div>
</div>
</div>
<slot :style="[!props.editor ? {display: 'none'} : {}]"></slot>
</div>
<slot :style="[!props.editor ? { display: 'none' } : {}]"></slot>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, getCurrentInstance, type ComputedRef, useSlots } from "vue"
import FInput from "@/components/fcomponents/FInput.vue"
import useClickOutside from "@/composables/useClickOutside"
import {Icon} from "@iconify/vue";
const emit = defineEmits(['update:modelValue'])
const slots = useSlots();
import { ref, computed, onMounted, useSlots, withDefaults } from 'vue';
import FInput from '@/components/fcomponents/FInput.vue';
import useClickOutside from '@/composables/useClickOutside';
import { Icon } from '@iconify/vue';
interface Item {
year: number;
daily: number;
year: number;
daily: number;
}
const props = defineProps({
items: {
type: [Array<Item>],
required: false,
default: []
},
clearable: {
type: Boolean,
required: false,
default: false
},
itemTitle: {
type: String,
required: false,
default: "label"
},
itemValue: {
type: String,
required: false,
default: "value"
},
label: {
type: String,
required: false,
default: null
},
modelValue: {
type: Number,
required: false,
default: null
},
value: {
type: String,
required: false,
default: null
},
editor: {
type: Boolean,
required: false,
default: false
}
interface Props {
items?: Item[];
clearable?: boolean;
itemTitle?: string;
itemValue?: string;
label?: string | null;
modelValue?: number | null;
value?: string | null;
editor?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
items: () => [],
clearable: false,
itemTitle: 'label',
itemValue: 'value',
label: null,
modelValue: null,
value: null,
editor: false,
});
const showList = ref(<boolean>false);
const slotElements = ref(<any[]>[])
const componentRef = ref(<any>null);
const selectList = ref();
const activeIndex= ref();
interface Emits {
(e: 'update:modelValue', value: number): void;
}
const emit = defineEmits<Emits>();
const slots = useSlots();
const showList = ref<boolean>(false);
const _slotElements = ref<unknown[]>([]);
const componentRef = ref<HTMLElement | null>(null);
const selectList = ref<HTMLElement | null>(null);
const activeIndex = ref();
useClickOutside(componentRef, () => {
showList.value = false
})
showList.value = false;
});
const year= computed({
const year = computed({
// getter
get() {
return props.modelValue || props.items[0].year
return props.modelValue || props.items[0].year;
},
// setter
set(newValue: number) {
emit("update:modelValue", newValue)
emit('update:modelValue', newValue);
},
});
}
})
function mouseEnter(event:MouseEvent , index:number){
const target = event. target as HTMLElement;
activeIndex.value = index;
target.classList.add("active")
function mouseEnter(event: MouseEvent, index: number) {
const target = event.target as HTMLElement;
activeIndex.value = index;
target.classList.add('active');
}
function mouseLeave(event:MouseEvent, index:number){
const elements = selectList.value.querySelectorAll('.select-list-item');
elements.forEach((element:HTMLElement) => {
element.classList.remove("active")
});
function mouseLeave(_event: MouseEvent, _index: number) {
const elements = selectList.value?.querySelectorAll('.select-list-item');
elements?.forEach(element => {
(element as HTMLElement).classList.remove('active');
});
}
onMounted(() => {
})
onMounted(() => {});
function clickSelect(event: any) {
showList.value = !showList.value;
function clickSelect(_event: unknown) {
showList.value = !showList.value;
}
function clickItem(item: any) {
console.log("item", item);
year.value = item.year
showList.value = false;
console.log("showList.value", showList.value);
// emit('input', item)
function clickItem(item: { year: number }) {
// console.log("item", item);
year.value = item.year;
showList.value = false;
// console.log("showList.value", showList.value);
// emit('input', item)
}
</script>
<style lang="sass">
.o-select
position: relative
@ -228,7 +206,4 @@ function clickItem(item: any) {
&::-webkit-scrollbar-thumb
border-radius: 10px
background-color: lightgrey
</style>
</style>

View file

@ -1,148 +1,158 @@
<template>
<div>
<div class="slider-wrapper">
<div class="disabled-slider" :style="{ 'flex-basis': minPercentage + '%' }">
<div class="dot"></div>
</div>
<div class="range-slider">
<input
class="range-slider__range"
type="range"
ref="sliderInput"
:style="{
background: `linear-gradient(90deg, ${settings.fill} ${percentageDot}%, ${settings.background} ${percentageDot + 0.1}%)`,
}"
:value="props.modelValue"
:min="props.min"
:max="props.max"
@input="updateModelValue"
/>
<div
class="testbla"
@mousemove="testMove"
:style="{
left: percentageDot + '%',
}"
></div>
</div>
</div>
</div>
<div>
<div class="slider-wrapper">
<div class="disabled-slider" :style="{ 'flex-basis': minPercentage + '%' }">
<div class="dot"></div>
</div>
<div class="range-slider">
<input
class="range-slider__range"
type="range"
ref="sliderInput"
:style="{
background: `linear-gradient(90deg, ${settings.fill} ${percentageDot}%, ${settings.background} ${percentageDot + 0.1}%)`,
}"
:value="props.modelValue"
:min="props.min"
:max="props.max"
@input="updateModelValue"
/>
<div
class="testbla"
@mousemove="testMove"
:style="{
left: percentageDot + '%',
}"
></div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, ref, computed } from "vue";
import { onMounted, onUnmounted, ref, computed } from 'vue';
const sliderInput = ref();
const sliderInput = ref<HTMLInputElement | null>(null);
const emit = defineEmits(["update:modelValue"]);
interface Emits {
(event: 'update:modelValue', value: number): void;
}
const emit = defineEmits<Emits>();
const props = defineProps<{
modelValue: number;
min: number;
max: number;
modelValue: number;
min: number;
max: number;
}>();
const minPercentage = computed(() => {
if (!props.min || !props.max) {
return 0n;
}
return (props.min * 100) / props.max;
if (!props.min || !props.max) {
return 0;
}
return (props.min * 100) / props.max;
});
function updateModelValue(event: any) {
emit("update:modelValue", event.target.valueAsNumber);
function updateModelValue(event: Event) {
emit('update:modelValue', (event.target as HTMLInputElement).valueAsNumber);
}
const sliderValue = computed({
// getter
get() {
return props.modelValue || props.min;
},
// setter
set(newValue) {
emit("update:modelValue", newValue);
},
// getter
get() {
return Number.isFinite(props.modelValue) ? props.modelValue : props.min;
},
// setter
set(newValue: number) {
emit('update:modelValue', newValue);
},
});
const percentageDot = computed(() => {
let percentage = (100 * (sliderValue.value - props.min)) / (props.max - props.min);
if(percentage < 20){
percentage = percentage + 1;
} else if(percentage > 50){
percentage = percentage - 2;
} else if(percentage > 80){
percentage = percentage - 3;
}
return percentage
const range = props.max - props.min;
if (range <= 0) {
return 0;
}
let percentage = (100 * (sliderValue.value - props.min)) / range;
if (percentage < 20) {
percentage = percentage + 1;
} else if (percentage > 50) {
percentage = percentage - 2;
} else if (percentage > 80) {
percentage = percentage - 3;
}
return percentage;
});
const settings = {
fill: "#7550AE",
background: "#000000",
fill: '#7550AE',
background: '#000000',
};
onMounted(() => {
sliderInput.value.addEventListener("input", setSliderValue);
if (sliderInput.value) {
sliderInput.value.addEventListener('input', setSliderValue);
}
});
function setSliderValue(event: any){
sliderValue.value = event.target.value;
function setSliderValue(event: Event) {
const target = event.target as HTMLInputElement | null;
if (target) {
sliderValue.value = target.valueAsNumber;
}
}
function testMove(event: any) {
}
function testMove(_event: unknown) {}
onUnmounted(() => {
console.log("sliderInput.value", sliderInput.value);
if(sliderInput.value){
sliderInput.value.removeEventListener("input", setSliderValue);
}
})
if (sliderInput.value) {
sliderInput.value.removeEventListener('input', setSliderValue);
}
});
</script>
<style lang="scss">
.slider-wrapper {
display: flex;
display: flex;
}
.testbla {
position: absolute;
height: 25px;
width: 25px;
background-color: var(--color-based-blue);
user-select: none;
top: 50%;
transform: translate(-50%, -50%);
right: 50%;
border-radius: 25px;
pointer-events: none;
z-index: 10;
position: absolute;
height: 25px;
width: 25px;
background-color: var(--color-based-blue);
user-select: none;
top: 50%;
transform: translate(-50%, -50%);
right: 50%;
border-radius: 25px;
pointer-events: none;
z-index: 10;
}
.disabled-slider {
width: 60px;
height: 7px;
background-color: var(--color-border-main);
/* margin-top: auto; */
align-self: center;
margin-top: 2px;
position: relative;
z-index: 3;
pointer-events: none;
width: 60px;
height: 7px;
background-color: var(--color-border-main);
/* margin-top: auto; */
align-self: center;
margin-top: 2px;
position: relative;
z-index: 3;
pointer-events: none;
}
.dot {
border-radius: 50px;
height: 15px;
width: 15px;
background-color: var(--color-border-main);
position: absolute;
right: -14px;
/* bottom: 50%; */
top: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
border-radius: 50px;
height: 15px;
width: 15px;
background-color: var(--color-border-main);
position: absolute;
right: -14px;
/* bottom: 50%; */
top: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
}
// Base Colors
$shade-10: #4682b4 !default;
@ -164,96 +174,96 @@ $range-label-color: $shade-10 !default;
$range-label-width: 60px !default;
.range-slider {
width: $range-width;
position: relative;
accent-color: #7550AE;
width: $range-width;
position: relative;
accent-color: #7550ae;
}
.range-slider__range {
// width: calc(100% - (#{$range-label-width + 13px}));
width: 100%;
height: $range-track-height;
border-radius: 5px;
background: $range-track-color;
outline: none;
padding: 0;
margin: 0;
// width: calc(100% - (#{$range-label-width + 13px}));
width: 100%;
height: $range-track-height;
border-radius: 5px;
background: $range-track-color;
outline: none;
padding: 0;
margin: 0;
// Range Handle
&::-webkit-slider-thumb {
appearance: none;
width: 30px;
height: 30px;
opacity: 0;
padding: 5px;
border-radius: 50%;
cursor: pointer;
}
// Range Handle
&::-webkit-slider-thumb {
appearance: none;
width: 30px;
height: 30px;
opacity: 0;
padding: 5px;
border-radius: 50%;
cursor: pointer;
}
&:active::-webkit-slider-thumb {
background: $range-handle-color-hover;
}
&:active::-webkit-slider-thumb {
background: $range-handle-color-hover;
}
&::-moz-range-thumb {
width: $range-handle-size;
height: $range-handle-size;
border: 0;
border-radius: 50%;
background: $range-handle-color;
cursor: pointer;
transition: background 0.15s ease-in-out;
&::-moz-range-thumb {
width: $range-handle-size;
height: $range-handle-size;
border: 0;
border-radius: 50%;
background: $range-handle-color;
cursor: pointer;
transition: background 0.15s ease-in-out;
&:hover {
background: $range-handle-color-hover;
}
}
&:hover {
background: $range-handle-color-hover;
}
}
&:active::-moz-range-thumb {
background: $range-handle-color-hover;
}
&:active::-moz-range-thumb {
background: $range-handle-color-hover;
}
// Focus state
&:focus {
&::-webkit-slider-thumb {
background-color: transparent;
}
}
// Focus state
&:focus {
&::-webkit-slider-thumb {
background-color: transparent;
}
}
}
// Range Label
.range-slider__value {
display: inline-block;
position: relative;
width: $range-label-width;
color: $shade-0;
line-height: 20px;
text-align: center;
border-radius: 3px;
background: $range-label-color;
padding: 5px 10px;
margin-left: 8px;
display: inline-block;
position: relative;
width: $range-label-width;
color: $shade-0;
line-height: 20px;
text-align: center;
border-radius: 3px;
background: $range-label-color;
padding: 5px 10px;
margin-left: 8px;
&:after {
position: absolute;
top: 8px;
left: -7px;
width: 0;
height: 0;
border-top: 7px solid transparent;
border-right: 7px solid $range-label-color;
border-bottom: 7px solid transparent;
content: "";
}
&:after {
position: absolute;
top: 8px;
left: -7px;
width: 0;
height: 0;
border-top: 7px solid transparent;
border-right: 7px solid $range-label-color;
border-bottom: 7px solid transparent;
content: '';
}
}
// Firefox Overrides
::-moz-range-track {
background: $range-track-color;
border: 0;
background: $range-track-color;
border: 0;
}
input::-moz-focus-inner,
input::-moz-focus-outer {
border: 0;
border: 0;
}
</style>

View file

@ -1,7 +1,7 @@
<template>
<div class="f-tag">
<slot></slot>
</div>
<div class="f-tag">
<slot></slot>
</div>
</template>
<style lang="sass">

View file

@ -1,34 +1,41 @@
<template>
<section v-show="value === props.name" ref="tab" role="tabpanel" tabindex="-1">
<slot></slot>
</section>
<section v-show="value === props.name" ref="tab" role="tabpanel" tabindex="-1">
<slot></slot>
</section>
</template>
<script setup lang="ts">
import { onMounted, onBeforeMount, getCurrentInstance, ref, computed, watch, inject } from "vue";
const instance = ref<any>();
import { onMounted, onBeforeMount, getCurrentInstance, ref, watch, inject, type ComponentInternalInstance } from 'vue';
const isActive = ref<any>(null);
interface Tab {
uid: number;
name: string;
label: string;
}
const value = inject("value");
var updateTab: any = inject("updateTab");
const instance = ref<ComponentInternalInstance | null>();
const isActive = ref<unknown>(null);
const value = inject('value');
const updateTab: (tab: Tab) => void = inject('updateTab') as (tab: Tab) => void;
defineExpose({
isActive,
isActive,
});
onBeforeMount(() => {});
onMounted(() => {
instance.value = getCurrentInstance();
instance.value = getCurrentInstance();
var addTab: any = inject("addTab");
addTab({
uid: instance.value!.uid,
name: props.name,
label: props.label,
});
const addTab = inject('addTab') as (tab: Tab) => void;
addTab({
uid: instance.value?.uid ?? 0,
name: props.name,
label: props.label,
});
// resolved = {"test123": 123, "blub": "abc"}
// resolved = {"test123": 123, "blub": "abc"}
});
// const test = computed(() => {
@ -38,27 +45,20 @@ onMounted(() => {
// })
//Props
const props = defineProps({
label: {
type: String,
required: true,
default: null,
},
name: {
type: [String],
required: true,
default: null,
},
});
interface Props {
label: string;
name: string;
}
const props = defineProps<Props>();
watch(props, (newValue, oldValue) => {
console.log("newValue", newValue);
console.log("instance", instance.value);
watch(props, (_newValue, _oldValue) => {
// console.log("newValue", newValue);
// console.log("instance", instance.value);
updateTab({
uid: instance.value!.uid,
name: props.name,
label: props.label,
});
updateTab({
uid: instance.value?.uid ?? 0,
name: props.name,
label: props.label,
});
});
</script>

View file

@ -1,89 +1,96 @@
<template>
<div class="f-tabs" ref="tabsRef">
<div class="f-tabs__header" ref="headerRef">
<div
class="f-tab"
ref="tabsRef1"
:id="`f-tab-${tab.uid}`"
v-for="tab in tabs"
:key="tab.uid"
@click="setActive(tab)"
>
<h5>{{ tab.label }}</h5>
</div>
</div>
<div class="f-tabs__content">
<slot ref="tabsTest"></slot>
</div>
</div>
<div class="f-tabs" ref="tabsRef">
<div class="f-tabs__header" ref="headerRef">
<div class="f-tab" ref="tabsRef1" :id="`f-tab-${tab.uid}`" v-for="tab in tabs" :key="tab.uid" @click="setActive(tab)">
<h5>{{ tab.label }}</h5>
</div>
</div>
<div class="f-tabs__content">
<slot ref="tabsTest"></slot>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick, h, computed, useSlots, provide, getCurrentInstance } from "vue";
import { ref, onMounted, nextTick, useSlots, provide, withDefaults } from 'vue';
interface Tab {
uid: number;
name: string;
label: string;
}
defineOptions({
inheritAttrs: false,
inheritAttrs: false,
});
const props = defineProps({
modelValue: {
type: String,
required: false,
default: null,
},
interface Props {
modelValue?: string | null;
}
const props = withDefaults(defineProps<Props>(), {
modelValue: null,
});
const emit = defineEmits(["update:modelValue"]);
interface Emits {
(e: 'update:modelValue', value: string): void;
}
const emit = defineEmits<Emits>();
const slots1 = useSlots();
const _slots1 = useSlots();
const tabsRef = ref<HTMLElement>();
const tabsRef1 = ref<HTMLElement>();
const headerRef = ref<HTMLElement>();
const tabsArray = ref<Array<HTMLElement>>();
const tabsComponents = ref<Array<any>>();
const content = ref<any>();
const target = ref<any>();
const slots = ref<any>();
const value = ref<any>();
const tabsTest = ref<any>();
const tabs = ref<Array<any>>([]);
provide("value", value);
provide("addTab", (tab: any) => {
tabs.value.push(tab);
const _tabsArray = ref<Array<HTMLElement>>();
const _tabsComponents = ref<Array<unknown>>();
const _content = ref<unknown>();
const _target = ref<unknown>();
const _slots = ref<unknown>();
const value = ref<unknown>();
const tabsTest = ref<unknown>();
const tabs = ref<Array<Tab>>([]);
provide('value', value);
provide('addTab', (tab: Tab) => {
tabs.value.push(tab);
});
provide("updateTab", (tab: any) => {
let changedTabIndex = tabs.value.findIndex((obj) => obj.uid === tab.uid);
if (changedTabIndex > -1) {
tabs.value[changedTabIndex] = tab;
}
provide('updateTab', (tab: Tab) => {
const changedTabIndex = tabs.value.findIndex(obj => obj.uid === tab.uid);
if (changedTabIndex > -1) {
tabs.value[changedTabIndex] = tab;
}
});
// provide('addTab', tabs.value)
onMounted(() => {
nextTick(() => {
if(props.modelValue){
const tab = tabs.value.find((obj) => obj.name === props.modelValue)
setActive(tab);
} else {
setActive(tabs.value[0]);
}
});
nextTick(() => {
if (props.modelValue) {
const tab = tabs.value.find(obj => obj.name === props.modelValue);
if (tab) {
setActive(tab);
}
} else {
const firstTab = tabs.value[0];
if (firstTab) {
setActive(firstTab);
}
}
});
});
function setActive(tab: any) {
function setActive(tab: Tab) {
nextTick(() => {
const tabElement = headerRef.value?.querySelector(`#f-tab-${tab.uid}`) as HTMLElement | null;
const array = Array.prototype.slice.call(tabsRef.value?.children[0].children);
nextTick(() => {
const tabElement: any = headerRef.value?.querySelector(`#f-tab-${tab.uid}`);
var array = Array.prototype.slice.call(tabsRef.value?.children[0].children);
array.forEach((element: HTMLElement) => {
element.classList.remove("f-tab--active");
});
tabElement.classList.add("f-tab--active");
value.value = tab.name;
emit("update:modelValue", tab.name);
});
array.forEach((element: HTMLElement) => {
element.classList.remove('f-tab--active');
});
if (tabElement) {
tabElement.classList.add('f-tab--active');
}
value.value = tab.name;
emit('update:modelValue', tab.name);
});
}
</script>

View file

@ -41,10 +41,9 @@ describe('FTabs.vue', () => {
await wrapper.vm.$nextTick();
const sections = wrapper.findAll('section');
expect(sections[0].attributes('style')).not.toContain('display: none');
expect(sections[0].attributes('style')).not.toContain('display: none');
expect(sections[1].attributes('style')).toContain('display: none');
});
it('switches to the correct tab on click', async () => {
@ -66,7 +65,7 @@ describe('FTabs.vue', () => {
await tabs[1].trigger('click');
const sections = wrapper.findAll('section');
expect(sections[0].attributes('style')).toContain('display: none');
expect(sections[0].attributes('style')).toContain('display: none');
expect(sections[1].attributes('style')).not.toContain('display: none');
});
});

File diff suppressed because one or more lines are too long

View file

@ -1,29 +1,30 @@
<template>
<svg width="23" height="18" viewBox="0 0 23 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2590_494)">
<path
ref="svgPath"
d="M19.419 3.40166C16.7691 1.45664 14.2483 1.51053 14.2483 1.51053L13.9906 1.79865C17.1188 2.73512 18.5723 4.08593 18.5723 4.08593C16.6585 3.05941 14.7817 2.55501 13.0336 2.35694C11.7088 2.21276 10.4391 2.24892 9.3167 2.39287C9.20634 2.39287 9.11433 2.41083 9.00397 2.42879C8.35994 2.48292 6.79584 2.71692 4.82702 3.56357C4.14628 3.86966 3.74131 4.08593 3.74131 4.08593C3.74131 4.08593 5.2687 2.66303 8.58065 1.72656L8.39664 1.51053C8.39664 1.51053 5.87579 1.4564 3.22598 3.40166C3.22598 3.40166 0.576172 8.10242 0.576172 13.9018C0.576172 13.9018 2.12191 16.5134 6.18851 16.6393C6.18851 16.6393 6.86925 15.8289 7.42129 15.1446C5.08444 14.4601 4.20109 13.0195 4.20109 13.0195C4.20109 13.0195 4.3851 13.1454 4.71642 13.3256C4.73477 13.3435 4.75313 13.3615 4.79007 13.3797C4.84537 13.4156 4.90043 13.4338 4.95573 13.4697C5.41576 13.7219 5.87579 13.92 6.29911 14.0821C7.05351 14.3703 7.95521 14.6584 9.00397 14.8565C10.3841 15.1087 12.0032 15.1987 13.7697 14.8744C14.6344 14.7303 15.5178 14.4783 16.4378 14.0999C17.0819 13.8656 17.7996 13.5236 18.554 13.0372C18.554 13.0372 17.6339 14.514 15.2234 15.1805C15.7754 15.865 16.4378 16.6393 16.4378 16.6393C20.5047 16.5131 22.0688 13.9018 22.0688 13.9018C22.0688 8.10242 19.419 3.40166 19.419 3.40166ZM7.8818 12.2267C6.85138 12.2267 6.00498 11.3262 6.00498 10.2276C6.00498 9.12894 6.83303 8.22841 7.8818 8.22841C8.93056 8.22841 9.77697 9.12894 9.75861 10.2276C9.75861 11.3262 8.93056 12.2267 7.8818 12.2267ZM14.598 12.2267C13.5675 12.2267 12.7211 11.3262 12.7211 10.2276C12.7211 9.12894 13.5492 8.22841 14.598 8.22841C15.6467 8.22841 16.4748 9.12894 16.4748 10.2276C16.4748 11.3262 15.647 12.2267 14.598 12.2267Z"
:fill="props.color"
/>
</g>
<defs>
<clipPath id="clip0_2590_494">
<rect width="22" height="17" fill="white" transform="translate(0.333984 0.58667)" />
</clipPath>
</defs>
</svg>
<svg width="23" height="18" viewBox="0 0 23 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2590_494)">
<path
ref="svgPath"
d="M19.419 3.40166C16.7691 1.45664 14.2483 1.51053 14.2483 1.51053L13.9906 1.79865C17.1188 2.73512 18.5723 4.08593 18.5723 4.08593C16.6585 3.05941 14.7817 2.55501 13.0336 2.35694C11.7088 2.21276 10.4391 2.24892 9.3167 2.39287C9.20634 2.39287 9.11433 2.41083 9.00397 2.42879C8.35994 2.48292 6.79584 2.71692 4.82702 3.56357C4.14628 3.86966 3.74131 4.08593 3.74131 4.08593C3.74131 4.08593 5.2687 2.66303 8.58065 1.72656L8.39664 1.51053C8.39664 1.51053 5.87579 1.4564 3.22598 3.40166C3.22598 3.40166 0.576172 8.10242 0.576172 13.9018C0.576172 13.9018 2.12191 16.5134 6.18851 16.6393C6.18851 16.6393 6.86925 15.8289 7.42129 15.1446C5.08444 14.4601 4.20109 13.0195 4.20109 13.0195C4.20109 13.0195 4.3851 13.1454 4.71642 13.3256C4.73477 13.3435 4.75313 13.3615 4.79007 13.3797C4.84537 13.4156 4.90043 13.4338 4.95573 13.4697C5.41576 13.7219 5.87579 13.92 6.29911 14.0821C7.05351 14.3703 7.95521 14.6584 9.00397 14.8565C10.3841 15.1087 12.0032 15.1987 13.7697 14.8744C14.6344 14.7303 15.5178 14.4783 16.4378 14.0999C17.0819 13.8656 17.7996 13.5236 18.554 13.0372C18.554 13.0372 17.6339 14.514 15.2234 15.1805C15.7754 15.865 16.4378 16.6393 16.4378 16.6393C20.5047 16.5131 22.0688 13.9018 22.0688 13.9018C22.0688 8.10242 19.419 3.40166 19.419 3.40166ZM7.8818 12.2267C6.85138 12.2267 6.00498 11.3262 6.00498 10.2276C6.00498 9.12894 6.83303 8.22841 7.8818 8.22841C8.93056 8.22841 9.77697 9.12894 9.75861 10.2276C9.75861 11.3262 8.93056 12.2267 7.8818 12.2267ZM14.598 12.2267C13.5675 12.2267 12.7211 11.3262 12.7211 10.2276C12.7211 9.12894 13.5492 8.22841 14.598 8.22841C15.6467 8.22841 16.4748 9.12894 16.4748 10.2276C16.4748 11.3262 15.647 12.2267 14.598 12.2267Z"
:fill="props.color"
/>
</g>
<defs>
<clipPath id="clip0_2590_494">
<rect width="22" height="17" fill="white" transform="translate(0.333984 0.58667)" />
</clipPath>
</defs>
</svg>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { ref } from 'vue';
const svgPath = ref();
interface Props {
color?: string;
color?: string;
}
const props = withDefaults(defineProps<Props>(), {});
const props = withDefaults(defineProps<Props>(), {
color: 'currentColor',
});
</script>

View file

@ -1,8 +1,8 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<!-- Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc. -->
<path
d="M96 0C43 0 0 43 0 96L0 416c0 53 43 96 96 96l288 0 32 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l0-64c17.7 0 32-14.3 32-32l0-320c0-17.7-14.3-32-32-32L384 0 96 0zm0 384l256 0 0 64L96 448c-17.7 0-32-14.3-32-32s14.3-32 32-32zm32-240c0-8.8 7.2-16 16-16l192 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16zm16 48l192 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16s7.2-16 16-16z"
/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<!-- Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc. -->
<path
d="M96 0C43 0 0 43 0 96L0 416c0 53 43 96 96 96l288 0 32 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l0-64c17.7 0 32-14.3 32-32l0-320c0-17.7-14.3-32-32-32L384 0 96 0zm0 384l256 0 0 64L96 448c-17.7 0-32-14.3-32-32s14.3-32 32-32zm32-240c0-8.8 7.2-16 16-16l192 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16zm16 48l192 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-192 0c-8.8 0-16-7.2-16-16s7.2-16 16-16z"
/>
</svg>
</template>

View file

@ -1,7 +1,7 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<path
d="M575.8 255.5c0 18-15 32.1-32 32.1h-32l.7 160.2c0 2.7-.2 5.4-.5 8.1V472c0 22.1-17.9 40-40 40H456c-1.1 0-2.2 0-3.3-.1c-1.4 .1-2.8 .1-4.2 .1H416 392c-22.1 0-40-17.9-40-40V448 384c0-17.7-14.3-32-32-32H256c-17.7 0-32 14.3-32 32v64 24c0 22.1-17.9 40-40 40H160 128.1c-1.5 0-3-.1-4.5-.2c-1.2 .1-2.4 .2-3.6 .2H104c-22.1 0-40-17.9-40-40V360c0-.9 0-1.9 .1-2.8V287.6H32c-18 0-32-14-32-32.1c0-9 3-17 10-24L266.4 8c7-7 15-8 22-8s15 2 21 7L564.8 231.5c8 7 12 15 11 24z"
/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<path
d="M575.8 255.5c0 18-15 32.1-32 32.1h-32l.7 160.2c0 2.7-.2 5.4-.5 8.1V472c0 22.1-17.9 40-40 40H456c-1.1 0-2.2 0-3.3-.1c-1.4 .1-2.8 .1-4.2 .1H416 392c-22.1 0-40-17.9-40-40V448 384c0-17.7-14.3-32-32-32H256c-17.7 0-32 14.3-32 32v64 24c0 22.1-17.9 40-40 40H160 128.1c-1.5 0-3-.1-4.5-.2c-1.2 .1-2.4 .2-3.6 .2H104c-22.1 0-40-17.9-40-40V360c0-.9 0-1.9 .1-2.8V287.6H32c-18 0-32-14-32-32.1c0-9 3-17 10-24L266.4 8c7-7 15-8 22-8s15 2 21 7L564.8 231.5c8 7 12 15 11 24z"
/>
</svg>
</template>

View file

@ -1,42 +1,39 @@
<template>
<tippy v-if="slots.text" theme="my-theme" trigger="click">
<div class="info-icon">
<svg :width="props.size" :height="props.size" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M6.2 6.26416C6.2 5.84995 6.53579 5.51416 6.95 5.51416C7.36421 5.51416 7.7 5.84995 7.7 6.26416V9.76416C7.7 10.1784 7.36421 10.5142 6.95 10.5142C6.53579 10.5142 6.2 10.1784 6.2 9.76416V6.26416Z"
/>
<path
d="M7 3.01416C7.55228 3.01416 8 3.46188 8 4.01416C8 4.56644 7.55228 5.01416 7 5.01416C6.44772 5.01416 6 4.56644 6 4.01416C6 3.46188 6.44772 3.01416 7 3.01416Z"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M14 7.01416C14 10.8802 10.866 14.0142 7 14.0142C3.13401 14.0142 0 10.8802 0 7.01416C0 3.14817 3.13401 0.0141602 7 0.0141602C10.866 0.0141602 14 3.14817 14 7.01416ZM1.5 7.01416C1.5 10.0517 3.96243 12.5142 7 12.5142C10.0376 12.5142 12.5 10.0517 12.5 7.01416C12.5 3.97659 10.0376 1.51416 7 1.51416C3.96243 1.51416 1.5 3.97659 1.5 7.01416Z"
/>
</svg>
</div>
<template #content>
<slot name="text"></slot>
</template>
</tippy>
<Tippy v-if="slots.text" theme="my-theme" trigger="click">
<div class="info-icon">
<svg :width="props.size" :height="props.size" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M6.2 6.26416C6.2 5.84995 6.53579 5.51416 6.95 5.51416C7.36421 5.51416 7.7 5.84995 7.7 6.26416V9.76416C7.7 10.1784 7.36421 10.5142 6.95 10.5142C6.53579 10.5142 6.2 10.1784 6.2 9.76416V6.26416Z"
/>
<path
d="M7 3.01416C7.55228 3.01416 8 3.46188 8 4.01416C8 4.56644 7.55228 5.01416 7 5.01416C6.44772 5.01416 6 4.56644 6 4.01416C6 3.46188 6.44772 3.01416 7 3.01416Z"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M14 7.01416C14 10.8802 10.866 14.0142 7 14.0142C3.13401 14.0142 0 10.8802 0 7.01416C0 3.14817 3.13401 0.0141602 7 0.0141602C10.866 0.0141602 14 3.14817 14 7.01416ZM1.5 7.01416C1.5 10.0517 3.96243 12.5142 7 12.5142C10.0376 12.5142 12.5 10.0517 12.5 7.01416C12.5 3.97659 10.0376 1.51416 7 1.51416C3.96243 1.51416 1.5 3.97659 1.5 7.01416Z"
/>
</svg>
</div>
<template #content>
<slot name="text"></slot>
</template>
</Tippy>
</template>
<script setup lang="ts">
import { useSlots, ref, onMounted } from "vue";
import { Tippy } from "vue-tippy";
import "tippy.js/dist/tippy.css"; // optional for styling
import { useSlots } from 'vue';
import { Tippy } from 'vue-tippy';
import 'tippy.js/dist/tippy.css'; // optional for styling
const slots = useSlots();
interface Props {
size?: string;
size?: string;
}
const props = withDefaults(defineProps<Props>(), {
size: "15px",
size: '15px',
});
</script>
<style lang="sass">
@ -49,8 +46,8 @@ const props = withDefaults(defineProps<Props>(), {
fill: var(--color-font)
.tippy-arrow
color: rgba(15, 15, 15, 0.9) !important
.tippy-box[data-theme~='my-theme']
background-color: rgba(15, 15, 15, 0.9)
.tippy-box[data-theme~='my-theme']
background-color: rgba(15, 15, 15, 0.9)
height: fit-content
color: white
font-size: 12px
@ -58,5 +55,4 @@ const props = withDefaults(defineProps<Props>(), {
border-radius: var(--border-radius)
// @media (min-width: 768px)
// max-width: 400px
</style>

View file

@ -1,9 +1,9 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" class="login-icon" viewBox="0 0 512 512">
<path
d="M352 96l64 0c17.7 0 32 14.3 32 32l0 256c0 17.7-14.3 32-32 32l-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l64 0c53 0 96-43 96-96l0-256c0-53-43-96-96-96l-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32zm-9.4 182.6c12.5-12.5 12.5-32.8 0-45.3l-128-128c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L242.7 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l210.7 0-73.4 73.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l128-128z"
/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="login-icon" viewBox="0 0 512 512">
<path
d="M352 96l64 0c17.7 0 32 14.3 32 32l0 256c0 17.7-14.3 32-32 32l-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l64 0c53 0 96-43 96-96l0-256c0-53-43-96-96-96l-64 0c-17.7 0-32 14.3-32 32s14.3 32 32 32zm-9.4 182.6c12.5-12.5 12.5-32.8 0-45.3l-128-128c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L242.7 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l210.7 0-73.4 73.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l128-128z"
/>
</svg>
</template>
<style lang="sass">

View file

@ -1,33 +1,34 @@
<template>
<svg width="20" height="16" viewBox="0 0 20 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2590_499)">
<path
ref="svgPath"
d="M17.058 0.932576L1.74462 6.83727C0.699293 7.25659 0.705631 7.83977 1.55407 8.09947L5.48317 9.32593L6.83449 13.7733C7.01217 14.2637 6.92458 14.4583 7.43975 14.4583C7.83718 14.4583 8.01274 14.2765 8.2346 14.0608L10.1441 12.2041L14.1166 15.1392C14.8477 15.5426 15.3754 15.3336 15.5575 14.4606L18.1654 2.17133C18.4324 1.10065 17.7574 0.615059 17.058 0.932576Z"
:fill="props.color"
/>
<path
d="M7.45616 13.2757L6.16016 9.01067L16.1361 3.09253L8.76406 10.3509L7.45616 13.2757Z"
:fill="(props.color === 'black' ? 'white' : 'black')"
/>
</g>
<defs>
<clipPath id="clip0_2590_499">
<rect width="18.125" height="14.5" fill="white" transform="translate(0.9375 0.83667)" />
</clipPath>
</defs>
</svg>
<svg width="20" height="16" viewBox="0 0 20 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2590_499)">
<path
ref="svgPath"
d="M17.058 0.932576L1.74462 6.83727C0.699293 7.25659 0.705631 7.83977 1.55407 8.09947L5.48317 9.32593L6.83449 13.7733C7.01217 14.2637 6.92458 14.4583 7.43975 14.4583C7.83718 14.4583 8.01274 14.2765 8.2346 14.0608L10.1441 12.2041L14.1166 15.1392C14.8477 15.5426 15.3754 15.3336 15.5575 14.4606L18.1654 2.17133C18.4324 1.10065 17.7574 0.615059 17.058 0.932576Z"
:fill="props.color"
/>
<path
d="M7.45616 13.2757L6.16016 9.01067L16.1361 3.09253L8.76406 10.3509L7.45616 13.2757Z"
:fill="props.color === 'black' ? 'white' : 'black'"
/>
</g>
<defs>
<clipPath id="clip0_2590_499">
<rect width="18.125" height="14.5" fill="white" transform="translate(0.9375 0.83667)" />
</clipPath>
</defs>
</svg>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { ref } from 'vue';
const svgPath = ref();
interface Props {
color?: string;
color?: string;
}
const props = withDefaults(defineProps<Props>(), {});
const props = withDefaults(defineProps<Props>(), {
color: 'currentColor',
});
</script>

View file

@ -1,30 +1,31 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="20px"
height="18px"
viewBox="0 0 19 18"
version="1.1"
>
<g id="surface1">
<path
ref="svgPath"
d="M 14.945312 0 L 17.859375 0 L 11.464844 7.636719 L 18.9375 18 L 13.070312 18 L 8.480469 11.703125 L 3.222656 18 L 0.308594 18 L 7.085938 9.832031 L -0.0703125 0 L 5.941406 0 L 10.089844 5.753906 Z M 13.925781 16.207031 L 15.542969 16.207031 L 5.09375 1.726562 L 3.355469 1.726562 Z M 13.925781 16.207031 "
:fill="props.color"
/>
</g>
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="20px"
height="18px"
viewBox="0 0 19 18"
version="1.1"
>
<g id="surface1">
<path
ref="svgPath"
d="M 14.945312 0 L 17.859375 0 L 11.464844 7.636719 L 18.9375 18 L 13.070312 18 L 8.480469 11.703125 L 3.222656 18 L 0.308594 18 L 7.085938 9.832031 L -0.0703125 0 L 5.941406 0 L 10.089844 5.753906 Z M 13.925781 16.207031 L 15.542969 16.207031 L 5.09375 1.726562 L 3.355469 1.726562 Z M 13.925781 16.207031 "
:fill="props.color"
/>
</g>
</svg>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { ref } from 'vue';
const svgPath = ref();
interface Props {
color?: string;
color?: string;
}
const props = withDefaults(defineProps<Props>(), {});
const props = withDefaults(defineProps<Props>(), {
color: 'currentColor',
});
</script>

View file

@ -1,23 +1,23 @@
<template>
<template v-if="status === 'disconnected'">
<f-button class="connect-button--disconnected">Connect</f-button>
</template>
<template v-else-if="status === 'connected'">
<f-button class="connect-button connect-button--connected">
<img :src="getBlocky(address!)" alt="avatar" />
<div>{{ getAddressShortName(address!) }}</div>
</f-button>
</template>
<template v-else>
<f-button class="connect-button--loading">loading</f-button>
</template>
<template v-if="status === 'disconnected'">
<FButton class="connect-button--disconnected">Connect</FButton>
</template>
<template v-else-if="status === 'connected'">
<FButton class="connect-button connect-button--connected">
<img :src="getBlocky(address!)" alt="avatar" />
<div>{{ getAddressShortName(address!) }}</div>
</FButton>
</template>
<template v-else>
<FButton class="connect-button--loading">loading</FButton>
</template>
</template>
<script setup lang="ts">
import FButton from "@/components/fcomponents/FButton.vue";
import { getAddressShortName } from "@/utils/helper";
import { useAccount } from "@wagmi/vue";
import { getBlocky } from "@/utils/blockies";
import FButton from '@/components/fcomponents/FButton.vue';
import { getAddressShortName } from '@/utils/helper';
import { useAccount } from '@wagmi/vue';
import { getBlocky } from '@/utils/blockies';
const { address, status } = useAccount();
</script>

View file

@ -1,113 +1,103 @@
<template>
<div class="connect-wallet-wrapper">
<template v-if="status === 'connected'">
<div class="connected-header">
<div class="connected-header-avatar">
<img :src="getBlocky(address!)" alt="avatar" />
</div>
<div class="connected-header-address" :title="address">
{{ getAddressShortName(address!) }}
</div>
<div class="connected-header-logout" @click="() => disconnect()">
<img src="@/assets/logout.svg" alt="Logout" />
</div>
</div>
<h6 class="connected-tokens-headline">Your Tokens</h6>
<div class="connected-tokens">
<f-output name="$KRK" :price="harbAmount" :variant="3">
<template #end>
<f-button size="small" dense
><a :href="chain.chainData?.uniswap" target="_blank">Buy</a></f-button
>
</template>
</f-output>
<f-output name="Staked $KRK" :price="compactNumber(stakedAmount)" :variant="3">
<template #end>
<f-button size="small" dense @click="stakeLink">Stake</f-button>
</template>
</f-output>
</div>
</template>
<template v-else-if="status === 'disconnected'">
<h6 class="connect-wallet-headline low-mid-text">Connect your wallet</h6>
<div class="connectors-wrapper">
<div
class="connectors-element card"
:class="[[connector.id]]"
v-for="connector in connectors"
:key="connector.id"
@click="connectWallet(connector, chainId)"
>
<div class="connector-icon">
<img :src="loadConnectorImage(connector)" :alt="`${connector.name} Logo`" />
</div>
<div class="connector-name">
{{ connector.name }}
</div>
</div>
</div>
<div>Are you coming from Binance, Bybit, Kucoin or any other crypto exchange?</div>
<div><u>Follow these instructions first.</u></div>
</template>
<template v-else>
<div>loading</div>
</template>
</div>
<div class="connect-wallet-wrapper">
<template v-if="status === 'connected'">
<div class="connected-header">
<div class="connected-header-avatar">
<img :src="getBlocky(address!)" alt="avatar" />
</div>
<div class="connected-header-address" :title="address">
{{ getAddressShortName(address!) }}
</div>
<div class="connected-header-logout" @click="() => disconnect()">
<img src="@/assets/logout.svg" alt="Logout" />
</div>
</div>
<h6 class="connected-tokens-headline">Your Tokens</h6>
<div class="connected-tokens">
<FOutput name="$KRK" :price="harbAmount" :variant="3">
<template #end>
<FButton size="small" dense><a :href="chain.chainData?.uniswap" target="_blank">Buy</a></FButton>
</template>
</FOutput>
<FOutput name="Staked $KRK" :price="compactNumber(stakedAmount)" :variant="3">
<template #end>
<FButton size="small" dense @click="stakeLink">Stake</FButton>
</template>
</FOutput>
</div>
</template>
<template v-else-if="status === 'disconnected'">
<h6 class="connect-wallet-headline low-mid-text">Connect your wallet</h6>
<div class="connectors-wrapper">
<div
class="connectors-element card"
:class="[[connector.id]]"
v-for="connector in connectors"
:key="connector.id"
@click="connectWallet(connector, chainId)"
>
<div class="connector-icon">
<img :src="loadConnectorImage(connector)" :alt="`${connector.name} Logo`" />
</div>
<div class="connector-name">
{{ connector.name }}
</div>
</div>
</div>
<div>Are you coming from Binance, Bybit, Kucoin or any other crypto exchange?</div>
<div><u>Follow these instructions first.</u></div>
</template>
<template v-else>
<div>loading</div>
</template>
</div>
</template>
<script setup lang="ts">
import { getCurrentInstance, computed } from "vue";
import { useRouter } from "vue-router";
import { getAddressShortName, compactNumber, formatBigIntDivision } from "@/utils/helper";
import { getBlocky } from "@/utils/blockies";
import FButton from "@/components/fcomponents/FButton.vue";
import FOutput from "@/components/fcomponents/FOutput.vue";
import { getCurrentInstance, computed } from 'vue';
import { useRouter } from 'vue-router';
import { getAddressShortName, compactNumber, formatBigIntDivision } from '@/utils/helper';
import { getBlocky } from '@/utils/blockies';
import FButton from '@/components/fcomponents/FButton.vue';
import FOutput from '@/components/fcomponents/FOutput.vue';
// import { usePositions } from "@/composables/usePositions";
// import { useWallet } from "@/composables/useWallet";
import { usePositions } from "@/composables/usePositions";
import { useWallet } from "@/composables/useWallet";
import { useChain } from "@/composables/useChain";
import { usePositions } from '@/composables/usePositions';
import { useWallet } from '@/composables/useWallet';
import { useChain } from '@/composables/useChain';
import {
useAccount,
useDisconnect,
useConnect,
useChainId,
useBalance,
type CreateConnectorFn,
type Connector,
} from "@wagmi/vue";
import { useAccount, useDisconnect, useConnect, useChainId, type CreateConnectorFn, type Connector } from '@wagmi/vue';
const { address, status } = useAccount();
const { disconnect } = useDisconnect();
const { connectors, connect, error } = useConnect();
const { connectors, connect } = useConnect();
const router = useRouter();
const chainId = useChainId();
const { myActivePositions } = usePositions();
const wallet = useWallet();
const chain = useChain();
function loadConnectorImage(connector: Connector) {
if (connector.icon) {
return connector.icon;
} else {
return `./img/connectors/${connector.name}.svg`;
}
if (connector.icon) {
return connector.icon;
} else {
return `./img/connectors/${connector.name}.svg`;
}
}
// const goerliKey = import.meta.env.VITE_GOERLI_KEY;
const harbAmount = computed(() => {
if (wallet.balance?.value) {
return compactNumber(formatBigIntDivision(wallet.balance?.value, 10n ** BigInt(wallet.balance.decimals)));
} else {
return "0";
}
if (wallet.balance?.value) {
return compactNumber(formatBigIntDivision(wallet.balance?.value, 10n ** BigInt(wallet.balance.decimals)));
} else {
return '0';
}
});
const stakedAmount = computed(() =>
myActivePositions.value.reduce(function (a, b) {
return a + b.amount;
}, 0)
myActivePositions.value.reduce(function (a, b) {
return a + b.amount;
}, 0)
);
const instance = getCurrentInstance();
@ -142,20 +132,20 @@ const instance = getCurrentInstance();
// }
function closeModal() {
instance!.parent!.emit("update:modelValue", false);
instance!.parent!.emit('update:modelValue', false);
}
// //special case for metaMask, but I think that is the most used wallet
async function connectWallet(connector: CreateConnectorFn | Connector, chainId: any) {
console.log("connector", connector);
console.log("connector", connector.name);
connect({ connector, chainId });
closeModal();
async function connectWallet(connector: CreateConnectorFn | Connector, chainId: number) {
// console.log("connector", connector);
// console.log("connector", connector.name);
connect({ connector, chainId });
closeModal();
}
function stakeLink() {
router.push("/dashboard#stake");
closeModal();
router.push('/dashboard#stake');
closeModal();
}
</script>

View file

@ -1,24 +1,24 @@
<template>
<div class="footer" :style="{ color: (props.dark) ? 'white': 'black' }">
<div class="footer-inner">
<div class="footer-headline subheader2">Follow HARBERG Protocol</div>
<div class="social-links">
<social-button :dark="props.dark" type="discord" :href="discord"></social-button>
<social-button :dark="props.dark" type="telegram" :href="telegram"></social-button>
<social-button :dark="props.dark" type="twitter" :href="twitter"></social-button>
</div>
</div>
</div>
<div class="footer" :style="{ color: props.dark ? 'white' : 'black' }">
<div class="footer-inner">
<div class="footer-headline subheader2">Follow HARBERG Protocol</div>
<div class="social-links">
<SocialButton :dark="props.dark" type="discord" :href="discord"></SocialButton>
<SocialButton :dark="props.dark" type="telegram" :href="telegram"></SocialButton>
<SocialButton :dark="props.dark" type="twitter" :href="twitter"></SocialButton>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import socialButton from '@/components/socialButton.vue';
import SocialButton from '@/components/SocialButton.vue';
interface Props {
dark?: boolean;
dark?: boolean;
}
const discord = import.meta.env.VITE_DISCORD
const discord = import.meta.env.VITE_DISCORD;
const telegram = import.meta.env.VITE_TELEGRAM;
const twitter = import.meta.env.VITE_TWITTER;

View file

@ -1,69 +1,63 @@
<template>
<header>
<div class="navbar">
<div class="navbar-inner navigation-font">
<div class="navbar-left" @click="router.push('/')">
<div class="navbar-title">
<span>K</span>
<span class="big-spacing">r</span>
<span class="small-spacing">A</span>
<span class="big-spacing">I</span>
<span>ken</span>
</div>
</div>
<div class="navbar-center"></div>
<div class="navbar-end">
<nav v-if="!isMobile">
<RouterLink v-for="navbarRoute in navbarRoutes" :key="navbarRoute.name" :to="navbarRoute.path">
{{ navbarRoute.title }}
</RouterLink>
<a href="/#/docs">Docs</a>
</nav>
<template v-if="!isMobile">
<div class="vertical-line"></div>
<network-changer></network-changer>
<div class="vertical-line"></div>
</template>
<connect-button @click="showPanel = true" v-if="!isMobile"></connect-button>
<icon-login @click="showPanel = true" v-else-if="isMobile && !address"></icon-login>
<img
@click="showPanel = true"
v-else-if="isMobile && address"
:src="getBlocky(address)"
alt="avatar"
/>
</div>
</div>
</div>
</header>
<header>
<div class="navbar">
<div class="navbar-inner navigation-font">
<div class="navbar-left" @click="router.push('/')">
<div class="navbar-title">
<span>K</span>
<span class="big-spacing">r</span>
<span class="small-spacing">A</span>
<span class="big-spacing">I</span>
<span>ken</span>
</div>
</div>
<div class="navbar-center"></div>
<div class="navbar-end">
<nav v-if="!isMobile">
<RouterLink v-for="navbarRoute in navbarRoutes" :key="navbarRoute.name" :to="navbarRoute.path">
{{ navbarRoute.title }}
</RouterLink>
<a href="/#/docs">Docs</a>
</nav>
<template v-if="!isMobile">
<div class="vertical-line"></div>
<NetworkChanger></NetworkChanger>
<div class="vertical-line"></div>
</template>
<ConnectButton @click="showPanel = true" v-if="!isMobile"></ConnectButton>
<IconLogin @click="showPanel = true" v-else-if="isMobile && !address"></IconLogin>
<img @click="showPanel = true" v-else-if="isMobile && address" :src="getBlocky(address)" alt="avatar" />
</div>
</div>
</div>
</header>
</template>
<script setup lang="ts">
import { getBlocky } from "@/utils/blockies";
import { RouterLink, useRouter } from "vue-router";
import IconLogin from "@/components/icons/IconLogin.vue";
import ThemeToggle from "@/components/layouts/ThemeToggle.vue";
import NetworkChanger from "@/components/layouts/NetworkChanger.vue";
import ConnectButton from "@/components/layouts/ConnectButton.vue";
import { computed, inject, ref } from "vue";
import { useAccount } from "@wagmi/vue";
import { useDark } from "@/composables/useDark";
const {darkTheme} = useDark();
const {address} = useAccount();
import { getBlocky } from '@/utils/blockies';
import { RouterLink, useRouter } from 'vue-router';
import IconLogin from '@/components/icons/IconLogin.vue';
// import ThemeToggle from "@/components/layouts/ThemeToggle.vue";
import NetworkChanger from '@/components/layouts/NetworkChanger.vue';
import ConnectButton from '@/components/layouts/ConnectButton.vue';
import { inject, ref } from 'vue';
import { useAccount } from '@wagmi/vue';
import { useDark } from '@/composables/useDark';
const { darkTheme: _darkTheme } = useDark();
const { address } = useAccount();
const router = useRouter();
const showPanel = inject<boolean>('showPanel', false);
const isMobile = inject<boolean>('isMobile', false);
const showPanel = inject<boolean>("showPanel", false);
const isMobile = inject<boolean>("isMobile", false);
const dark1 = ref(false)
const _dark1 = ref(false);
const routes = router.getRoutes();
const navbarRoutes = routes.flatMap((obj) => {
if (obj.meta.group === "navbar") {
return { title: obj.meta.title, name: obj.name, path: obj.path };
} else {
return [];
}
const navbarRoutes = routes.flatMap(obj => {
if (obj.meta.group === 'navbar') {
return { title: obj.meta.title, name: obj.name, path: obj.path };
} else {
return [];
}
});
</script>

View file

@ -1,38 +1,38 @@
<template>
<div class="network-changer" v-click-outside="closeMenu">
<div class="network-changer-inner" @click="showMenu = !showMenu">
<icon-base></icon-base>
<Icon v-if="showMenu" class="toggle-icon" icon="mdi:chevron-down"></Icon>
<Icon v-else class="toggle-icon" icon="mdi:chevron-up"></Icon>
</div>
<Transition name="collapse-network">
<div v-show="showMenu" class="network-changer--list">
<div class="list-inner" ref="listInner">
<div class="list--element" v-for="chain in chains" :key="chain.id">
<template v-if="chain.id === wallet.account.chainId">
<icon-base></icon-base>
<div>{{ chain.name }}</div>
<div>Connected</div>
</template>
<template v-else>
<icon-base></icon-base>
<div>{{ chain.name }}</div>
<f-button outlined dense @click="switchNetwork(chain)">Switch Network</f-button>
</template>
</div>
</div>
</div>
</Transition>
</div>
<div class="network-changer" v-click-outside="closeMenu">
<div class="network-changer-inner" @click="showMenu = !showMenu">
<IconBase></IconBase>
<Icon v-if="showMenu" class="toggle-icon" icon="mdi:chevron-down"></Icon>
<Icon v-else class="toggle-icon" icon="mdi:chevron-up"></Icon>
</div>
<Transition name="collapse-network">
<div v-show="showMenu" class="network-changer--list">
<div class="list-inner" ref="listInner">
<div class="list--element" v-for="chain in chains" :key="chain.id">
<template v-if="chain.id === wallet.account.chainId">
<IconBase></IconBase>
<div>{{ chain.name }}</div>
<div>Connected</div>
</template>
<template v-else>
<IconBase></IconBase>
<div>{{ chain.name }}</div>
<FButton outlined dense @click="switchNetwork(chain)">Switch Network</FButton>
</template>
</div>
</div>
</div>
</Transition>
</div>
</template>
<script setup lang="ts">
import IconBase from "@/components/icons/IconBase.vue";
import { ref } from "vue";
import { useChains, useChainId, useSwitchChain } from "@wagmi/vue";
import {Icon} from "@iconify/vue";
import FButton from "@/components/fcomponents/FButton.vue";
import { useWallet } from "@/composables/useWallet";
import IconBase from '@/components/icons/IconBase.vue';
import { ref } from 'vue';
import { useChains, useSwitchChain } from '@wagmi/vue';
import { Icon } from '@iconify/vue';
import FButton from '@/components/fcomponents/FButton.vue';
import { useWallet } from '@/composables/useWallet';
const chains = useChains();
const wallet = useWallet();
const { switchChain } = useSwitchChain();
@ -41,15 +41,14 @@ const showMenu = ref<boolean>(false);
const listInner = ref();
function closeMenu() {
if (showMenu.value) {
console.log("tesat");
showMenu.value = false;
}
if (showMenu.value) {
// console.log("tesat");
showMenu.value = false;
}
}
async function switchNetwork(chain: any) {
console.log("chain", chain);
switchChain({ chainId: chain.id });
async function switchNetwork(chain: { id: number }) {
switchChain({ chainId: chain.id });
}
</script>

View file

@ -1,22 +1,26 @@
<template>
<div class="slideout-overlay" :class="{ open: props.modelValue }" @click="showPanel = false"></div>
<div :class="{ 'slideout-wrapper': true, open: props.modelValue }">
<div class="slideout-panel">
<slot></slot>
</div>
</div>
<div class="slideout-overlay" :class="{ open: props.modelValue }" @click="showPanel = false"></div>
<div :class="{ 'slideout-wrapper': true, open: props.modelValue }">
<div class="slideout-panel">
<slot></slot>
</div>
</div>
</template>
<script setup lang="ts">
import { inject, watchEffect } from "vue";
import { inject } from 'vue';
const props = defineProps({
modelValue: Boolean,
});
interface Props {
modelValue?: boolean;
}
const props = defineProps<Props>();
defineEmits(["update:modelValue"]);
interface Emits {
(e: 'update:modelValue', value: boolean): void;
}
defineEmits<Emits>();
const showPanel = inject("showPanel");
const showPanel = inject('showPanel');
</script>
<style lang="sass">

View file

@ -1,150 +1,85 @@
<template>
<div class="toggle-dark-light">
<input type="checkbox" id="darkmode-toggle" @click="toggleTheme" :checked="props.modelValue" />
<label for="darkmode-toggle">
<svg
version="1.1"
class="sun"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 496 496"
style="enable-background: new 0 0 496 496"
xml:space="preserve"
>
<rect
x="152.994"
y="58.921"
transform="matrix(0.3827 0.9239 -0.9239 0.3827 168.6176 -118.5145)"
width="40.001"
height="16"
/>
<rect
x="46.9"
y="164.979"
transform="matrix(0.9239 0.3827 -0.3827 0.9239 71.29 -12.4346)"
width="40.001"
height="16"
/>
<rect
x="46.947"
y="315.048"
transform="matrix(0.9239 -0.3827 0.3827 0.9239 -118.531 50.2116)"
width="40.001"
height="16"
/>
<div class="toggle-dark-light">
<input type="checkbox" id="darkmode-toggle" @click="toggleTheme" :checked="props.modelValue" />
<label for="darkmode-toggle">
<svg
version="1.1"
class="sun"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 496 496"
style="enable-background: new 0 0 496 496"
xml:space="preserve"
>
<rect x="152.994" y="58.921" transform="matrix(0.3827 0.9239 -0.9239 0.3827 168.6176 -118.5145)" width="40.001" height="16" />
<rect x="46.9" y="164.979" transform="matrix(0.9239 0.3827 -0.3827 0.9239 71.29 -12.4346)" width="40.001" height="16" />
<rect x="46.947" y="315.048" transform="matrix(0.9239 -0.3827 0.3827 0.9239 -118.531 50.2116)" width="40.001" height="16" />
<rect
x="164.966"
y="409.112"
transform="matrix(-0.9238 -0.3828 0.3828 -0.9238 168.4872 891.7491)"
width="16"
height="39.999"
/>
<rect x="164.966" y="409.112" transform="matrix(-0.9238 -0.3828 0.3828 -0.9238 168.4872 891.7491)" width="16" height="39.999" />
<rect
x="303.031"
y="421.036"
transform="matrix(-0.3827 -0.9239 0.9239 -0.3827 50.2758 891.6655)"
width="40.001"
height="16"
/>
<rect x="303.031" y="421.036" transform="matrix(-0.3827 -0.9239 0.9239 -0.3827 50.2758 891.6655)" width="40.001" height="16" />
<rect
x="409.088"
y="315.018"
transform="matrix(-0.9239 -0.3827 0.3827 -0.9239 701.898 785.6559)"
width="40.001"
height="16"
/>
<rect x="409.088" y="315.018" transform="matrix(-0.9239 -0.3827 0.3827 -0.9239 701.898 785.6559)" width="40.001" height="16" />
<rect
x="409.054"
y="165.011"
transform="matrix(-0.9239 0.3827 -0.3827 -0.9239 891.6585 168.6574)"
width="40.001"
height="16"
/>
<rect
x="315.001"
y="46.895"
transform="matrix(0.9238 0.3828 -0.3828 0.9238 50.212 -118.5529)"
width="16"
height="39.999"
/>
<path
d="M248,88c-88.224,0-160,71.776-160,160s71.776,160,160,160s160-71.776,160-160S336.224,88,248,88z M248,392
<rect x="409.054" y="165.011" transform="matrix(-0.9239 0.3827 -0.3827 -0.9239 891.6585 168.6574)" width="40.001" height="16" />
<rect x="315.001" y="46.895" transform="matrix(0.9238 0.3828 -0.3828 0.9238 50.212 -118.5529)" width="16" height="39.999" />
<path
d="M248,88c-88.224,0-160,71.776-160,160s71.776,160,160,160s160-71.776,160-160S336.224,88,248,88z M248,392
c-79.4,0-144-64.6-144-144s64.6-144,144-144s144,64.6,144,144S327.4,392,248,392z"
/>
<rect x="240" width="16" height="72" />
<rect
x="62.097"
y="90.096"
transform="matrix(0.7071 0.7071 -0.7071 0.7071 98.0963 -40.6334)"
width="71.999"
height="16"
/>
<rect y="240" width="72" height="16" />
/>
<rect x="240" width="16" height="72" />
<rect x="62.097" y="90.096" transform="matrix(0.7071 0.7071 -0.7071 0.7071 98.0963 -40.6334)" width="71.999" height="16" />
<rect y="240" width="72" height="16" />
<rect
x="90.091"
y="361.915"
transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 -113.9157 748.643)"
width="16"
height="71.999"
/>
<rect x="240" y="424" width="16" height="72" />
<rect x="90.091" y="361.915" transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 -113.9157 748.643)" width="16" height="71.999" />
<rect x="240" y="424" width="16" height="72" />
<rect
x="361.881"
y="389.915"
transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 397.8562 960.6281)"
width="71.999"
height="16"
/>
<rect x="424" y="240" width="72" height="16" />
<rect
x="389.911"
y="62.091"
transform="matrix(0.7071 0.7071 -0.7071 0.7071 185.9067 -252.6357)"
width="16"
height="71.999"
/>
</svg>
<svg
version="1.1"
class="moon"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 49.739 49.739"
style="enable-background: new 0 0 49.739 49.739"
xml:space="preserve"
>
<path
d="M25.068,48.889c-9.173,0-18.017-5.06-22.396-13.804C-3.373,23.008,1.164,8.467,13.003,1.979l2.061-1.129l-0.615,2.268
<rect x="361.881" y="389.915" transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 397.8562 960.6281)" width="71.999" height="16" />
<rect x="424" y="240" width="72" height="16" />
<rect x="389.911" y="62.091" transform="matrix(0.7071 0.7071 -0.7071 0.7071 185.9067 -252.6357)" width="16" height="71.999" />
</svg>
<svg
version="1.1"
class="moon"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 49.739 49.739"
style="enable-background: new 0 0 49.739 49.739"
xml:space="preserve"
>
<path
d="M25.068,48.889c-9.173,0-18.017-5.06-22.396-13.804C-3.373,23.008,1.164,8.467,13.003,1.979l2.061-1.129l-0.615,2.268
c-1.479,5.459-0.899,11.25,1.633,16.306c2.75,5.493,7.476,9.587,13.305,11.526c5.831,1.939,12.065,1.492,17.559-1.258v0
c0.25-0.125,0.492-0.258,0.734-0.391l2.061-1.13l-0.585,2.252c-1.863,6.873-6.577,12.639-12.933,15.822
C32.639,48.039,28.825,48.888,25.068,48.889z M12.002,4.936c-9.413,6.428-12.756,18.837-7.54,29.253
c5.678,11.34,19.522,15.945,30.864,10.268c5.154-2.582,9.136-7.012,11.181-12.357c-5.632,2.427-11.882,2.702-17.752,0.748
c-6.337-2.108-11.473-6.557-14.463-12.528C11.899,15.541,11.11,10.16,12.002,4.936z"
/>
</svg>
</label>
</div>
/>
</svg>
</label>
</div>
</template>
<script setup lang="ts">
const emit = defineEmits(["update:modelValue"]);
interface Emits {
(event: 'update:modelValue', value: boolean): void;
}
const emit = defineEmits<Emits>();
const props = defineProps<{
modelValue: boolean;
modelValue: boolean;
}>();
function toggleTheme(event: any) {
emit("update:modelValue", event.target.checked);
function toggleTheme(event: Event) {
const target = event.target as HTMLInputElement | null;
if (target) {
emit('update:modelValue', target.checked);
}
}
</script>

View file

@ -1,97 +1,97 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { defineAsyncComponent } from "vue";
import { mount, flushPromises } from "@vue/test-utils";
import SocialBadge from "./socialButton.vue";
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { mount } from '@vue/test-utils';
import { defineComponent } from 'vue';
import SocialBadge from './SocialButton.vue';
const mockIconComponent = (name:string) => ({
default: {
function mockIconComponent(name: string) {
return {
__esModule: true,
default: defineComponent({
name,
template: `<svg class='${name.toLowerCase()}'></svg>`,
},
});
vi.mock("../components/icons/IconDiscord.vue", () => mockIconComponent("IconDiscord"));
vi.mock("../components/icons/IconTwitter.vue", () => mockIconComponent("IconTwitter"));
vi.mock("../components/icons/IconTelegram.vue", () => mockIconComponent("IconTelegram"));
}),
};
}
vi.mock('@/components/icons/IconDiscord.vue', () => mockIconComponent('IconDiscord'));
vi.mock('@/components/icons/IconTwitter.vue', () => mockIconComponent('IconTwitter'));
vi.mock('@/components/icons/IconTelegram.vue', () => mockIconComponent('IconTelegram'));
describe("SocialBadge.vue", () => {
describe('SocialBadge.vue', () => {
let wrapper;
beforeEach(() => {
wrapper = null;
});
it("renders the correct link", () => {
it('renders the correct link', () => {
wrapper = mount(SocialBadge, {
props: {
href: "https://example.com",
href: 'https://example.com',
},
});
const link = wrapper.find("a");
const link = wrapper.find('a');
expect(link.exists()).toBe(true);
expect(link.attributes("href")).toBe("https://example.com");
expect(link.attributes("target")).toBe("_blank");
expect(link.attributes('href')).toBe('https://example.com');
expect(link.attributes('target')).toBe('_blank');
});
it("applies the correct color for light mode", () => {
it('applies the correct color for light mode', () => {
wrapper = mount(SocialBadge, {
props: {
dark: false,
},
});
const badge = wrapper.find(".social-badge");
expect(badge.attributes("style")).toContain("color: black");
expect(badge.attributes("style")).toContain("border-color: black");
const badge = wrapper.find('.social-badge');
expect(badge.attributes('style')).toContain('color: black');
expect(badge.attributes('style')).toContain('border-color: black');
});
it("applies the correct color for dark mode", () => {
it('applies the correct color for dark mode', () => {
wrapper = mount(SocialBadge, {
props: {
dark: true,
},
});
const badge = wrapper.find(".social-badge");
expect(badge.attributes("style")).toContain("color: white");
expect(badge.attributes("style")).toContain("border-color: white");
const badge = wrapper.find('.social-badge');
expect(badge.attributes('style')).toContain('color: white');
expect(badge.attributes('style')).toContain('border-color: white');
});
// it("renders the correct icon based on the type prop", async () => {
// wrapper = mount(SocialBadge, {
// props: {
// type: "discord",
// },
// });
// await flushPromises();
// const current = wrapper.getCurrentComponent()?.setupState?.img.__asyncResolved
// console.log("current", current.default.name);
// expect(current.default.name).toBe("IconDiscord");
// it("renders the correct icon based on the type prop", async () => {
// wrapper = mount(SocialBadge, {
// props: {
// type: "discord",
// },
// });
// await flushPromises();
// const current = wrapper.getCurrentComponent()?.setupState?.img.__asyncResolved
// console.log("current", current.default.name);
// // expect(icon.exists()).toBe(true);
// });
// expect(current.default.name).toBe("IconDiscord");
it("does not render an icon if the type is unsupported", async () => {
// // expect(icon.exists()).toBe(true);
// });
it('does not render an icon if the type is unsupported', async () => {
wrapper = mount(SocialBadge, {
props: {
type: "unsupported",
type: 'unsupported',
},
});
await wrapper.vm.$nextTick();
const icon = wrapper.find(".social-badge-icon").find("component");
const icon = wrapper.find('.social-badge-icon').find('component');
expect(icon.exists()).toBe(false);
});
it("renders without crashing when no props are provided", () => {
it('renders without crashing when no props are provided', () => {
wrapper = mount(SocialBadge);
expect(wrapper.exists()).toBe(true);
});
});

View file

@ -1,64 +0,0 @@
<template>
<a :href="props.href" target="_blank">
<div
class="social-badge"
:style="{ color: color, 'border-color': color }"
:class="{ 'social-badge--dark': props.dark }"
>
<div class="social-badge-icon">
<component :color="color" :is="img" />
</div>
</div>
</a>
</template>
<script setup lang="ts">
import { defineAsyncComponent, computed } from "vue";
interface Props {
type?: string;
dark?: boolean;
href?: string;
}
const props = withDefaults(defineProps<Props>(), {});
const color = computed(() => (props.dark ? "white" : "black"));
const img = computed(() => {
let img;
switch (props.type) {
case "discord":
img = defineAsyncComponent(() => import(`../components/icons/IconDiscord.vue`));
break;
case "twitter":
img = defineAsyncComponent(() => import(`../components/icons/IconTwitter.vue`));
break;
case "telegram":
img = defineAsyncComponent(() => import(`../components/icons/IconTelegram.vue`));
break;
default:
break;
}
return img;
});
</script>
<style lang="sass">
.social-badge
border-radius: 14px
display: flex
border: 1px solid var(--color-social-border)
padding: 6px 20px
align-items: center
flex: 0 1 0
color: black
&:hover,&:active,&:focus
background-color: var(--color-white-hovered)
cursor: pointer
&.social-badge--dark
&:hover, &:active, &:focus
background-color: var(--color-black-hovered)
// font-size: 0
</style>