This commit is contained in:
Nadar
2026-03-17 13:24:22 +03:00
commit 82e5ac9d81
554 changed files with 29637 additions and 0 deletions

View File

@@ -0,0 +1,165 @@
<template>
<div class="asset-card" :class="{ 'is-active': isActive, 'is-disabled': disabled }">
<UiCoin
class="asset-card__coin"
:code="code"
/>
<div class="asset-card__name-wrapper">
<p class="asset-card__code">
{{ code }}
</p>
<p class="asset-card__name">
{{ name }}
</p>
</div>
<div class="asset-card__money">
<p class="asset-card__balance">
<span
v-if="showConverted"
class="asset-card__converted"
>
{{ convertedBalance }}
</span>
<span>{{ balance }}</span>
</p>
<p
v-if="withdraw"
class="asset-card__withdraw"
>
<span
v-if="showConverted"
class="asset-card__converted"
>
{{ convertedWithdraw }}
</span>
<span>{{ withdraw }}</span>
<UiIconSUpRight class="asset-card__withdraw-icon" />
</p>
</div>
</div>
</template>
<script setup lang="ts">
import type Decimal from 'decimal.js'
interface Props {
code: string
name?: string
balance: Decimal.Value
withdraw?: Decimal.Value
rate: Decimal.Value
isActive?: boolean
disabled?: boolean
}
const props = defineProps<Props>()
const showConverted = computed(() => !!props.rate && props.rate !== 1)
const convertedBalance = computed(() => $money.fullFormat($money.convert(props.balance, props.rate), 'USDT'))
const convertedWithdraw = computed(() => $money.fullFormat($money.convert(props.balance, props.rate), 'USDT'))
</script>
<style lang="scss">
.asset-card {
display: grid;
grid-template-columns: auto 1fr 1fr;
align-items: center;
padding: 24px;
border-radius: 12px;
background-color: $clr-grey-100;
outline: 2px solid transparent;
outline-offset: -2px;
cursor: pointer;
transition: .2s ease-out;
transition-property: outline-color, background-color;
min-height: 97px;
&:hover {
outline-color: $clr-grey-300;
}
&.is-active,
&:active {
background-color: $clr-grey-200;
}
&.is-active {
outline-color: $clr-cyan-300;
}
&.is-disabled {
opacity: 0.3;
pointer-events: none;
}
&__name-wrapper {
min-width: 0;
}
&__coin {
margin-right: 16px;
}
&__code {
@include txt-l-sb;
text-transform: uppercase;
}
&__name {
@include txt-r-m;
margin-top: 2px;
color: $clr-grey-400;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
&__money {
text-align: right;
}
&__balance {
@include txt-l-sb;
}
&__withdraw {
@include txt-i-sb;
margin-top: 8px;
}
&__balance,
&__withdraw {
vertical-align: middle;
white-space: nowrap;
span {
vertical-align: middle;
}
}
&__withdraw-icon {
color: $clr-cyan-500;
margin-left: 4px;
}
&__converted {
@include txt-i-m;
vertical-align: middle;
color: $clr-grey-400;
&::after {
content: '/';
margin-inline: 4px;
color: $clr-grey-300;
}
}
}
</style>

View File

@@ -0,0 +1,73 @@
<template>
<Sidebar id="asset" class="asset-sidebar" @close="$emit('close')">
<UiCoin :code="asset.code" class="asset-sidebar__coin" />
<div>
<MoneyAmount class="asset-sidebar__balance" :value="asset.balance" :currency="asset.code" />
</div>
<div class="asset-sidebar__actions">
<UiButton type="ghost" size="small" icon="s-up-right" :href="`/create/withdraw/${projectId}/${asset.code}`">
Отправить
</UiButton>
<UiButton type="ghost" size="small" icon="s-down-left" :href="`/create/invoice/${projectId}`">
Получить
</UiButton>
<UiButton type="ghost" color="secondary" size="small" icon="s-exchange" disabled>
Обменять
</UiButton>
</div>
</Sidebar>
</template>
<script setup lang="ts">
import { Sidebar } from '#components'
defineProps({
projectId: {
type: String,
required: true,
},
asset: {
type: Object,
required: true,
},
})
defineEmits(['close'])
</script>
<style lang="scss">
.asset-sidebar {
text-align: center;
&__coin {
border-radius: 9px;
height: 48px;
width: 48px;
margin-bottom: 16px;
}
&__balance {
@include h3('money-amount-value', true);
}
&__actions {
--button-ghost-primary-color: #{$clr-black};
--button-icon-color: #{$clr-cyan-500};
--button-icon-disabled-color: #{$clr-grey-400};
display: flex;
padding: 4px;
background-color: $clr-grey-100;
border-radius: 12px;
margin-top: 40px;
> * {
flex: 1;
}
}
}
</style>

View File

@@ -0,0 +1,107 @@
<template>
<div class="email-confirmation">
<img src="/email-confirmation.svg" alt="logo" draggable="false">
<h1 class="email-confirmation__title">
{{ $t('check_email') }}
</h1>
<div class="email-confirmation__content">
<i18n-t
scope="global"
keypath="we_have_sent_you_an_email_to"
tag="p"
class="email-confirmation__text"
>
<strong class="email-confirmation__email">{{ email }}</strong>
</i18n-t>
<div class="email-confirmation__instructions">
<slot />
</div>
</div>
<div class="email-confirmation__upper-text">
<i18n-t class="mb-18" keypath="check_spam_folder" tag="p" scope="global">
<strong class="text-grey-600">«{{ $t('spam') }}»</strong>
</i18n-t>
</div>
<div v-if="resendFn" class="email-confirmation__resend">
<span>{{ $t('did_not_get_mail') }}</span>
<UiButton class="ml-4" type="link" @click="resendFn">
{{ $t('send_again') }}
</UiButton>
</div>
</div>
</template>
<script setup lang="ts">
defineProps({
email: {
required: true,
type: String,
},
resendFn: { type: Function },
})
</script>
<style lang="scss">
.email-confirmation {
width: 516px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin: 0 auto;
&__title {
margin-bottom: 30px;
}
&__content {
background-color: $clr-grey-200;
padding: 24px;
border-radius: 12px;
}
&__text {
@include txt-l;
display: inline-block;
text-align: center;
margin-bottom: 20px;
width: 100%;
}
&__instructions {
@include txt-l-sb;
display: inline-block;
text-align: center;
width: 100%;
}
&__email {
color: $clr-grey-500;
}
&__upper-text {
@include txt-m;
display: inline-block;
text-align: center;
margin-top: 40px;
color: $clr-grey-500;
}
&__resend {
@include txt-i-m;
color: $clr-grey-600;
}
&__under-text {
@include txt-i-m;
}
}
</style>

View File

@@ -0,0 +1,48 @@
<template>
<UiAccordion>
<UiAccordionItem
:title="$t('forgot_password_questions.forgot_password.title')"
>
<i18n-t
scope="global"
keypath="forgot_password_questions.forgot_password.content"
tag="p"
>
<UiButton type="link" href="/reset-password">
{{ $t('can_reset_password') }}
</UiButton>
<UiButton type="link">
{{ $t('write_us') }}
</UiButton>
</i18n-t>
</UiAccordionItem>
<UiAccordionItem
:title="$t('forgot_password_questions.access_to_mail.title')"
>
<i18n-t
scope="global"
keypath="forgot_password_questions.access_to_mail.content"
tag="p"
>
<UiButton type="link">
{{ $t('fill_the_form') }}
</UiButton>
</i18n-t>
</UiAccordionItem>
<UiAccordionItem :title="$t('forgot_password_questions.no_mail.title')">
<i18n-t
keypath="forgot_password_questions.no_mail.content"
tag="p"
scope="global"
>
<UiButton type="link">
{{ $t('write_us') }}
</UiButton>
</i18n-t>
</UiAccordionItem>
</UiAccordion>
</template>
<script setup lang="ts"></script>

View File

@@ -0,0 +1,131 @@
<template>
<div
:class="[
cn.b(),
cn.is('checked', checked),
cn.is('invalid', invalid),
cn.is('disabled', disabled),
cn.is('focused', focused),
]"
@click="handleChange"
>
<UiIconSCheck :class="[cn.e('checkmark')]" />
<p :class="[cn.e('label')]">
<slot v-bind="{ checked }">
{{ label }}
</slot>
</p>
</div>
</template>
<script setup lang="ts">
export interface Props {
id: string
label?: string
disabled?: boolean
modelValue?: boolean | string | number
trueValue?: boolean | string | number
falseValue?: boolean | string | number
required?: boolean
}
defineOptions({
name: 'CheckboxButton',
})
const props = withDefaults(defineProps<Props>(), {
disabled: false,
trueValue: true,
falseValue: false,
required: false,
modelValue: undefined,
})
const slots = useSlots()
const { checked, invalid, focused, handleChange } = useCheckbox(props, slots)
const cn = useClassname('checkbox-button')
</script>
<style lang="scss">
.checkbox-button {
$self: &;
@include txt-i-m;
--border-color: #{$clr-grey-300};
--border-width: 1px;
display: inline-flex;
align-items: center;
height: 40px;
padding: 8px 16px;
border-radius: 12px;
outline: var(--border-width) solid var(--border-color);
outline-offset: calc(var(--border-width) * -1);
cursor: pointer;
transition: .2s ease-out;
transition-property: outline-color, background-color, color;
user-select: none;
&:hover {
--border-color: transparent;
background-color: $clr-grey-200;
}
&.is-checked {
--border-color: transparent;
background-color: $clr-cyan-500;
color: $clr-white;
&:hover {
background-color: $clr-cyan-400;
}
}
//&.is-invalid {
// --border-color: var(--checkbox-invalid-border-color);
//}
&.is-disabled {
cursor: not-allowed;
}
&__checkmark {
--border-color: var(--checkbox-border-color);
--border-width: 1px;
width: var(--size);
height: var(--size);
border-radius: 4px;
outline: var(--border-width) solid var(--border-color);
outline-offset: calc(var(--border-width) * -1);
color: transparent;
cursor: pointer;
transition: .2s ease-out;
transition-property: outline-color, background-color, color;
margin-right: 8px;
#{$self}.has-label & {
margin-top: 1px;
}
#{$self}.is-checked & {
--border-width: 0;
color: var(--checkbox-checked-color);
background-color: var(--checkbox-checked-background);
}
#{$self}.is-disabled & {
--border-color: var(--checkbox-disabled-border-color);
}
#{$self}.is-disabled.is-checked & {
color: var(--checkbox-disabled-checked-color);
}
}
}
</style>

View File

@@ -0,0 +1,73 @@
<template>
<div
class="currency-name"
:class="`currency-name--${size}`"
>
<UiCoin
class="currency-name__coin"
:code="code"
/>
<span
v-if="size !== 'small'"
class="currency-name__name"
>
{{ name || code }}
</span>
<span class="currency-name__code">{{ code }}</span>
</div>
</template>
<script setup lang="ts">
export interface Props {
code: string
name?: string
size?: 'small' | 'medium'
}
defineOptions({
name: 'CurrencyName',
})
const props = withDefaults(defineProps<Props>(), {
size: 'medium',
})
</script>
<style lang="scss">
.currency-name {
&--medium {
display: grid;
grid-template-columns: 32px auto;
gap: var(--currency-name-gap, 4px 8px);
align-items: center;
}
&--small {
--coin-size: 20px;
display: inline-flex;
vertical-align: middle;
justify-content: flex-end;
align-items: center;
gap: var(--currency-name-gap, 4px);
}
&__coin {
grid-column: 1;
grid-row: span 2;
}
&__name {
@include txt-r-sb('currency-name');
color: $clr-black;
}
&__code {
@include txt-s-m('currency-name-code');
color: $clr-grey-500;
}
}
</style>

View File

@@ -0,0 +1,35 @@
<template>
<div class="form-header">
<UiButton
icon="chevron-left"
color="secondary"
type="link"
class="form-header__back"
:href="backLink"
>
{{ backText }}
</UiButton>
<slot name="title">
<h1 class="form-header__title">
{{ title }}
</h1>
</slot>
</div>
</template>
<script setup>
defineProps({
backLink: { type: String, required: true },
backText: { type: String, required: true },
title: { type: String, required: true },
})
</script>
<style lang="scss">
.form-header {
&__back {
margin-bottom: 8px;
}
}
</style>

View File

@@ -0,0 +1,44 @@
<template>
<div class="formatted-date">
<p class="formatted-date__date">
{{ date }} <span class="formatted-date__time">{{ time }}</span>
</p>
<p class="formatted-date__zone">
(UTC+3)
</p>
</div>
</template>
<script setup>
import dayjs from 'dayjs'
const props = defineProps({
value: { type: String, required: true },
})
const dayObj = computed(() => dayjs(props.value))
const date = computed(() => dayObj.value.format('DD.MM.YY'))
const time = computed(() => dayObj.value.format('HH:mm'))
</script>
<style lang="scss">
.formatted-date {
&__date {
@include txt-r-sb;
color: $clr-black;
white-space: nowrap;
}
&__time {
color: $clr-grey-400;
}
&__zone {
@include txt-s-m;
color: $clr-grey-500;
margin-top: 4px;
}
}
</style>

View File

@@ -0,0 +1,46 @@
<template>
<div class="invoices-empty">
<img
src="/flag.svg"
class="mb-40"
>
<h2>
{{ $t('your_invoices_will_be_displayed_here.title') }}
</h2>
<h4 class="invoices-empty__text">
{{ $t('your_invoices_will_be_displayed_here.content') }}
</h4>
<UiButton
size="large"
class="mt-40 w-100"
:href="`/create/invoice/${id}`"
>
{{ $t('create_an_invoice') }}
</UiButton>
</div>
</template>
<script setup>
defineProps({ id: { type: String, required: true } })
defineEmits(['create'])
</script>
<style lang="scss">
.invoices-empty {
display: flex;
align-items: center;
flex-direction: column;
width: 422px;
align-self: center;
justify-self: center;
&__text {
color: $clr-grey-400;
margin-top: 8px;
}
}
</style>

View File

@@ -0,0 +1,79 @@
<template>
<NuxtLink
class="nav-item"
:class="{
'nav-item--active': isActive,
}"
:to="to"
>
<slot name="icon">
<Component
:is="resolveComponent(`ui-icon-${icon}`)"
class="nav-item__icon"
/>
</slot>
<span class="nav-item__title">
{{ title }}
</span>
</NuxtLink>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import type { UiIcon } from '#build/types/ui/icons'
interface Props {
to: string
icon: UiIcon
title: string
matcher?: () => boolean
}
const props = defineProps<Props>()
const isActive = computed(() => (props.matcher ? props.matcher() : false))
</script>
<style lang="scss">
.nav-item {
--link-color: #{$clr-white};
@include txt-i-sb;
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
color: var(--link-color);
transition: color 0.2s ease-out;
&:hover {
--link-color: #{$clr-grey-500};
}
&--active,
&:active {
--link-color: #{$clr-cyan-300} !important;
}
&__icon {
margin-bottom: 5px;
position: relative;
}
&__notification-icon {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
border-radius: 20px;
width: 20px;
height: 20px;
top: -8px;
right: -24px;
background: $clr-white;
color: $clr-white;
}
}
</style>

View File

@@ -0,0 +1,10 @@
<template>
<NuxtLink to="/projects">
<img
class="app-logo__img"
src="/logo-with-text.svg"
alt="logo"
draggable="false"
>
</NuxtLink>
</template>

View File

@@ -0,0 +1,46 @@
<template>
<nav class="nav-sidebar">
<div
v-if="$slots.logo"
class="nav-sidebar__logo"
>
<slot name="logo" />
</div>
<div class="nav-sidebar__content">
<slot />
</div>
<div class="nav-sidebar__bottom">
<slot name="bottom" />
</div>
</nav>
</template>
<style lang="scss">
.nav-sidebar {
display: flex;
flex-direction: column;
background: #{$clr-black};
color: #{$clr-white};
padding: 32px 27px 32px 27px;
width: 120px;
height: 100%;
text-align: center;
overflow: hidden;
&__logo {
margin-bottom: 60px;
}
&__content {
display: flex;
flex-direction: column;
overflow-y: auto;
gap: 20px;
margin-inline: -27px;
flex: 1;
}
}
</style>

View File

@@ -0,0 +1,183 @@
<template>
<div
class="network-select network-select--secondary network-select--large"
:class="{
'has-value': !!field.value.value,
'is-disabled': disabled,
'is-invalid': invalid,
}"
v-bind="$attrs"
@click="show"
>
<label
ref="wrapper"
class="network-select__wrapper"
tabindex="0"
@keydown.enter="show"
@keydown.space="show"
>
<span class="network-select__content">
<div
v-if="!!field.value.value"
class="network-select__value"
>
<UiCoin
:code="assetCode"
class="network-select__coin"
/>
<span>{{ modelValue }}</span>
</div>
<p class="network-select__label">{{ $t('network') }}</p>
<UiButton
class="network-select__action"
type="ghost"
size="small"
disabled
>
{{ actionText }}
</UiButton>
</span>
</label>
<div
v-if="invalid"
class="network-select__bottom"
>
<div class="network-select__validation-message">
{{ field.errorMessage.value }}
</div>
</div>
</div>
</template>
<script setup>
import { computed, ref, toRef, watch } from 'vue'
import { useField } from 'vee-validate'
const props = defineProps({
id: {
type: String,
required: true,
},
assetCode: {
type: String,
required: true,
},
modelValue: {
type: String,
},
disabled: {
type: Boolean,
default: false,
},
})
const id = toRef(props, 'id')
const { t } = useI18n()
const field = useField(id, 'required', {
validateOnValueUpdate: false,
syncVModel: true,
initialValue: !isEmptyValue(props.modelValue) ? props.modelValue : undefined,
})
const wrapper = ref()
const active = ref(false)
const invalid = computed(() => !props.disabled && !!field.errorMessage.value)
const actionText = computed(() =>
field.value.value ? t('change') : t('select'),
)
watch(field.value, hide)
watch(active, (value) => {
if (!value)
wrapper.value?.focus()
})
function show() {
active.value = true
}
function hide() {
active.value = false
}
function isEmptyValue(value) {
return [null, undefined, ''].includes(value)
}
</script>
<style lang="scss">
.network-select {
$self: &;
&__wrapper {
display: block;
border-radius: 12px;
outline: 1px solid $clr-grey-300;
outline-offset: -1px;
padding-inline: 16px;
background-color: $clr-white;
height: 48px;
}
&__content {
position: relative;
display: flex;
align-items: center;
height: 100%;
}
&__label {
@include txt-i-m;
position: absolute;
pointer-events: none;
top: 15px;
left: 0;
color: $clr-grey-400;
transform-origin: 0 0;
#{$self}.has-value & {
transform: translateY(-7px) scale(0.78);
color: $clr-grey-500;
}
}
&__value {
@include txt-i-m;
display: flex;
align-items: center;
gap: 4px;
padding-block: 22px 8px;
flex: 1;
}
&__coin {
--coin-size: 16px;
--coin-border-radius: 3px;
}
&__action {
margin-left: auto;
}
&__bottom {
@include txt-s-m;
margin-top: 4px;
padding-inline: 16px;
}
&__validation-message {
color: var(--input-validation-message-color);
}
}
</style>

View File

@@ -0,0 +1,110 @@
<template>
<div class="notification-card" :class="{ 'not-read': !read }">
<div class="notification-card__icon">
<Component :is="resolveComponent(`ui-icon-${icon}`)" />
</div>
<div class="notification-card__content">
<p class="notification-card__title">
{{ title }}
</p>
<div class="notification-card__subtitle-wrapper">
<span class="notification-card__subtitle">
{{ subtitle }}
</span>
<span class="notification-card__date">
Сегодня в 14:40
</span>
</div>
<slot />
</div>
</div>
</template>
<script setup lang="ts">
import type { UiIcon } from '#build/types/ui/icons'
export interface Props {
icon: UiIcon
title: string
subtitle: string
read: boolean
}
defineProps<Props>()
</script>
<style lang="scss">
.notification-card {
position: relative;
display: grid;
grid-template-columns: 32px auto;
gap: 8px;
padding: 8px;
border-radius: 12px;
cursor: pointer;
outline: 1px solid transparent;
outline-offset: -1px;
transition: .2s ease-out;
transition-property: background-color, outline-color;
&:hover {
background-color: #F7F9FF;
}
&:active {
outline-color: $clr-cyan-300;
}
&.not-read {
&::after {
content: '';
position: absolute;
top: 12px;
right: 8px;
width: 6px;
height: 6px;
background-color: $clr-red-500;
border-radius: 50%;
}
}
&__icon {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 8px;
color: var(--notification-card-icon-color, $clr-grey-500);
background-color: var(--notification-card-icon-background, $clr-grey-200);
}
&__content {
}
&__title {
@include txt-r-sb;
margin-bottom: 4px;
}
&__subtitle-wrapper {
@include txt-t-m;
display: flex;
justify-content: space-between;
}
&__subtitle {
color: $clr-grey-600;
}
&__date {
color: $clr-grey-400;
}
}
</style>

View File

@@ -0,0 +1,38 @@
<template>
<NotificationCardBase icon="s-up-right" v-bind="props" class="card">
<p class="amount">
<span>Валюта: <strong>USDT</strong></span>
<span>Сумма: <strong>500</strong></span>
</p>
</NotificationCardBase>
</template>
<script setup lang="ts">
import type { Props } from './base.vue'
const props = defineProps<Omit<Props, 'icon'>>()
</script>
<style lang="scss" scoped>
.card {
--notification-card-icon-color: #{$clr-cyan-600};
}
.amount {
@include txt-s;
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px;
border-radius: 6px;
color: $clr-grey-500;
margin-top: 8px;
background-color: $clr-grey-200;
height: 32px;
strong {
font-weight: 600;
}
}
</style>

View File

@@ -0,0 +1,39 @@
<template>
<NotificationCardBase icon="s-down-left" v-bind="props" class="card">
<p class="amount">
<span>Валюта: <strong>USDT</strong></span>
<span>Сумма: <strong>500</strong></span>
</p>
</NotificationCardBase>
</template>
<script setup lang="ts">
import type { Props } from './base.vue'
const props = defineProps<Omit<Props, 'icon'>>()
</script>
<style lang="scss" scoped>
.card {
--notification-card-icon-color: #{$clr-green-500};
--notification-card-icon-background: #{$clr-green-100};
}
.amount {
@include txt-s;
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px;
border-radius: 6px;
color: $clr-grey-500;
margin-top: 8px;
background-color: $clr-grey-200;
height: 32px;
strong {
font-weight: 600;
}
}
</style>

View File

@@ -0,0 +1,43 @@
<template>
<NotificationCardBase icon="s-exit" v-bind="props">
<p class="location">
Местоположение: Moscow, Russia
</p>
<p class="alert">
Если это были не вы срочно
<UiButton type="link" size="small">
смените пароль
</UiButton>
или
<UiButton type="link" size="small">
свяжитесь с поддержкой
</UiButton>
</p>
</NotificationCardBase>
</template>
<script setup lang="ts">
import type { Props } from './base.vue'
const props = defineProps<Omit<Props, 'icon'>>()
</script>
<style lang="scss" scoped>
.location {
@include txt-s-m;
color: $clr-grey-400;
margin-top: 4px;
margin-bottom: 8px;
}
.alert {
@include txt-r-m;
padding: 8px;
border-radius: 6px;
background-color: $clr-grey-200;
color: $clr-grey-500;
}
</style>

View File

@@ -0,0 +1,51 @@
<template>
<NotificationCardBase icon="s-clock" v-bind="props" class="card">
<div class="row">
<p class="amount">
<span>Валюта: <strong>USDT</strong></span>
<span>Сумма: <strong>500</strong></span>
</p>
<UiButton class="support" size="small" type="outlined" color="secondary">
Support
</UiButton>
</div>
</NotificationCardBase>
</template>
<script setup lang="ts">
import type { Props } from './base.vue'
const props = defineProps<Omit<Props, 'icon'>>()
</script>
<style lang="scss" scoped>
.card {
--notification-card-icon-color: #{$clr-warn-500};
--notification-card-icon-background: #{$clr-warn-200};
}
.row {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 8px;
}
.amount {
@include txt-s;
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px;
border-radius: 6px;
color: $clr-grey-500;
background-color: $clr-grey-200;
height: 32px;
strong {
font-weight: 600;
}
}
</style>

View File

@@ -0,0 +1,107 @@
<template>
<UiDropdown
class="notifications-dropdown"
dropdown-class="notifications-dropdown__content"
placement="bottom-end"
trigger="hover"
:offset="12"
teleport
>
<template #default="{ isActive }">
<NotifyButton
icon="bell"
:count="0"
class="notifications-dropdown__trigger"
:state="isActive ? 'hover' : undefined"
/>
</template>
<template #dropdown>
<div class="notifications-dropdown__header">
<h3>Notifications</h3>
<UiButton class="ml-a" size="small" icon="s-check-seen" type="ghost">
Mark all as read
</UiButton>
<UiButton class="ml-4" icon="s-kebab-android" type="link" size="small" color="secondary" />
</div>
<form class="notifications-dropdown__filter">
<UiSwitcher
id="notification_type"
:options="[
{ label: 'All', value: 'all' },
{ label: 'Поступления', value: 'p' },
{ label: 'Списания', value: 's' },
]"
size="small"
/>
</form>
<div class="notifications-dropdown__list">
<NotificationCardWithdrawCreated title="Новый вывод средств создан" subtitle="Адрес: 7u5RQ9g....bkNfG" />
<NotificationCardDeposit title="На ваш кошелек поступил новый платеж" subtitle="Адрес: 7u5RQ9g....bkNfG" />
<NotificationCardCompletedWithdraw title="Вывод средств был проведен успешно" subtitle="Адрес: 7u5RQ9g....bkNfG" />
<NotificationCardSignIn title="Вход в учетную запись" subtitle="IP: 185.218.108.156" read />
</div>
</template>
</UiDropdown>
</template>
<script setup>
import { useForm } from 'vee-validate'
useForm({
keepValuesOnUnmount: true,
initialValues: {
notification_type: 'all',
},
})
</script>
<style lang="scss">
.notifications-dropdown {
cursor: pointer;
&__header {
display: flex;
align-items: center;
margin-bottom: 8px;
}
&__filter {
margin-bottom: 8px;
}
&__list {
//margin-inline: -16px;
//padding-inline: 16px;
//max-height: 150px;
//overflow: auto;
> *:not(:last-child) {
margin-bottom: 8px;
}
}
&__content {
position: relative;
width: 406px;
padding: 16px !important;
box-shadow: 0px 4px 4px 0px #6C86AD40;
&::before {
content: '';
width: 16px;
height: 16px;
position: absolute;
top: -8px;
right: 12px;
background-color: $clr-white;
border-radius: 2px;
transform: rotateZ(45deg);
}
}
}
</style>

View File

@@ -0,0 +1,57 @@
<template>
<UiButton
:class="[cn.b(), cn.has('one', count === 1), cn.has('few', count > 1)]"
:icon="icon"
type="ghost"
color="secondary"
:data-count="countContent"
/>
</template>
<script setup>
const props = defineProps({ icon: { type: String }, count: { type: Number } })
const cn = useClassname('notify-button')
const countContent = computed(() => {
return props.count > 9 ? '9+' : props.count
})
</script>
<style lang="scss">
.notify-button {
position: relative;
&::after {
@include txt-s-m;
display: block;
position: absolute;
background-color: $clr-red-500;
color: $clr-white;
}
&.has-one {
&::after {
content: '';
width: 6px;
height: 6px;
border-radius: 3px;
top: 7px;
right: 7px;
}
}
&.has-few {
&::after {
content: attr(data-count);
top: 0px;
right: 0px;
padding-inline: 4px;
border-radius: 7px;
}
}
}
</style>

View File

@@ -0,0 +1,21 @@
<template>
<div class="operation-type">
<span class="mr-4">{{ type }}</span>
<UiIconSUpRight class="text-clr-cyan-500" />
</div>
</template>
<script setup>
defineProps({
type: {
type: String,
required: true,
},
})
</script>
<style lang="scss">
.operation-type{
@include txt-r-sb;
}
</style>

View File

@@ -0,0 +1,77 @@
<template>
<form
class="page-form"
@submit="onSubmit"
>
<UiButton
icon="chevron-left"
color="secondary"
type="link"
class="mb-8"
:href="backLink"
>
{{ backText }}
</UiButton>
<slot name="title">
<h1 :class="titleOffset ? 'mb-32' : ''">
{{ title }}
</h1>
</slot>
<div class="page-form__summary">
<StaticError class="mb-16" />
<slot />
</div>
<slot name="submit" v-bind="{ isSubmitting }">
<UiButton
class="page-form__submit"
size="large"
native-type="submit"
:loading="isSubmitting"
>
{{ submitText }}
</UiButton>
</slot>
</form>
</template>
<script setup>
import { useForm } from 'vee-validate'
const props = defineProps({
title: { type: String, required: true },
backLink: { type: String, required: true },
backText: { type: String, required: true },
submitText: { type: String, required: true },
titleOffset: { type: Boolean, default: true },
handler: {
type: Function,
required: true,
},
})
const { handleSubmit, isSubmitting } = useForm()
const onSubmit = handleSubmit(async (values) => {
await props.handler(values)
})
</script>
<style lang="scss">
.page-form {
margin: 0 auto;
width: 500px;
&__summary {
padding-top: var(--page-form-padding-top, 24px);
padding-bottom: var(--page-form-padding-bottom, 16px);
}
&__submit {
width: 100%;
}
}
</style>

View File

@@ -0,0 +1,51 @@
<template>
<div class="page-block">
<div class="page-block__header">
<div class="d-flex flex-column">
<div class="d-flex align-items-center" style="gap: 16px">
<h3 v-if="title">
{{ title }}
</h3>
<slot name="badge" />
</div>
<span v-if="subTitle" class="page-block__subtitle">{{ subTitle }}</span>
</div>
<slot name="actions" />
</div>
<slot />
</div>
</template>
<script setup>
defineProps({
title: { type: String },
subTitle: { type: String },
})
</script>
<style lang="scss">
.page-block {
display: flex;
flex-direction: column;
gap: var(--page-block-gap, 8px);
border-radius: 12px;
padding: var(--page-block-padding, 16px);
background-color: $clr-white;
&__header{
display: flex;
align-items: center;
justify-content: space-between;
}
&__subtitle{
@include txt-i-m;
margin-top: 4px;
color: $clr-grey-500;
}
}
</style>

View File

@@ -0,0 +1,78 @@
<template>
<div class="footer-info-block">
<div class="footer-info-block__left">
<UiIconAskForDiscountFilled class="footer-info-block__icon" />
<div>
<h3 class="footer-info-block__title">
{{ title }}
</h3>
<p class="footer-info-block__text">
{{ text }}
</p>
</div>
</div>
<div class="footer-info-block__right">
<UiButton
color="secondary"
right-icon="s-chevron-right"
>
{{ action }}
</UiButton>
</div>
</div>
</template>
<script setup>
defineProps({
title: { type: String, required: true },
text: { type: String, required: true },
action: { type: String, required: true },
})
</script>
<style lang="scss">
.footer-info-block {
display: flex;
&__title {
color: $clr-grey-600;
margin-bottom: 8px;
}
&__text {
color: $clr-grey-500;
width: 512px;
}
&__icon {
color: $clr-market-500 !important;
margin-right: 32px;
padding: 9px;
box-sizing: content-box;
}
&__left {
display: flex;
flex: 1;
border-bottom-left-radius: 12px;
border-top-left-radius: 12px;
background-color: $clr-grey-200;
padding: 24px 20px;
}
&__right {
display: flex;
align-items: center;
justify-content: center;
border-bottom-right-radius: 12px;
border-top-right-radius: 12px;
background-color: $clr-grey-300;
width: 286px;
}
}
</style>

View File

@@ -0,0 +1,90 @@
<template>
<header class="page-header">
<div class="page-header__title">
<UiButton
v-if="withBackButton"
icon="arrow-left"
color="secondary"
class="mr-24"
href="/projects"
type="outlined"
/>
<h1>{{ title }}</h1>
<div
v-if="$slots.default"
class="page-header__default"
>
<slot />
</div>
</div>
<div class="page-header__right">
<slot name="right">
<div class="d-flex align-items-center" style="gap: 24px;">
<NotificationsDropdown />
<ProfileDropdown />
</div>
</slot>
</div>
</header>
</template>
<script setup>
defineProps({
title: {
type: String,
},
withBackButton: {
type: Boolean,
default: false,
},
})
const notify = useNotify()
</script>
<style lang="scss">
@use 'sass:color';
.page-header {
position: sticky;
backdrop-filter: blur(4px);
background-size: 4px 4px;
background-image: radial-gradient(
color.change($clr-grey-100, $alpha: 0.7) 2px,
$clr-cyan-200
);
border-bottom: 1px solid #f4f6ff;
margin-inline: -16px;
padding-inline: 16px;
top: 0;
z-index: 6000;
display: flex;
align-items: center;
justify-content: space-between;
//margin-bottom: 32px;
padding-block: 32px;
&__title {
display: flex;
align-items: center;
}
&__default {
display: flex;
align-items: center;
gap: 24px;
margin-left: 24px;
}
&__right {
display: flex;
align-items: center;
gap: 24px;
}
}
</style>

View File

@@ -0,0 +1,97 @@
<template>
<div class="info-block">
<div class="d-flex justify-content-between mb-16">
<p class="info-block__title">
{{ title }}
</p>
<UiBadge v-if="badge" type="marketing">
{{ badge }}
</UiBadge>
</div>
<slot name="info" />
<span class="info-block__text">{{ text }}</span>
<UiButton
class="info-block__action"
type="outlined"
:href="link"
right-icon="s-chevron-right"
size="small"
>
{{ action }}
</UiButton>
</div>
</template>
<script setup>
defineProps({
title: {
type: String,
required: true,
},
text: {
type: String,
required: true,
},
badge: {
type: String,
},
action: {
type: String,
required: true,
},
link: {
type: String,
},
})
</script>
<style lang="scss">
.info-block {
display: flex;
flex-direction: column;
position: relative;
width: 270px;
min-height: 179px;
border-radius: 12px;
padding: 16px;
background-color: $clr-grey-100;
&__title {
@include txt-m-sb;
}
&__text {
@include txt-s;
margin-bottom: 16px;
color: $clr-grey-500;
}
&__action {
margin-top: auto;
align-self: flex-start;
}
}
.info-block-addInfo {
@include txt-s;
margin-bottom: 16px;
&__title {
margin-bottom: 4px;
color: $clr-grey-500;
}
&__sum {
@include txt-m-sb;
}
&__text {
@include txt-r;
color: $clr-grey-400;
}
}
</style>

View File

@@ -0,0 +1,32 @@
<template>
<div class="page-toolbar">
<slot name="prefix" />
<UiSearch
class="flex-1"
size="large"
:label="searchLabel"
:model-value="search"
@update:model-value="$emit('update:search', $event)"
/>
<slot />
</div>
</template>
<script setup>
defineProps({
searchLabel: { type: String, required: true },
search: { type: String },
})
defineEmits(['update:search'])
</script>
<style lang="scss">
.page-toolbar {
display: flex;
gap: 16px;
align-items: center;
}
</style>

View File

@@ -0,0 +1,92 @@
<template>
<UiDropdown
v-if="authenticated"
class="profile-dropdown"
dropdown-class="profile-dropdown__content"
placement="bottom-end"
trigger="hover"
:offset="12"
teleport
>
<template #default="{ isActive }">
<UiButton class="profile-dropdown__trigger" icon="circle-userpic" :state="isActive ? 'hover' : undefined" />
</template>
<template #dropdown>
<UiDropdownItem class="mb-12" @click="navigateTo('/2fa')">
<template #icon>
<UiIconSProtection class="text-clr-cyan-400" />
</template>
<span>Безопасность</span>
</UiDropdownItem>
<UiDropdownItem class="mb-12" @click="navigateTo('/verification/status')">
<template #icon>
<UiIconSOverview class="text-clr-cyan-400" />
</template>
<span>Лимиты</span>
</UiDropdownItem>
<UiDropdownItem @click="navigateTo('/settings')">
<template #icon>
<UiIconSSettings class="text-clr-cyan-400" />
</template>
<span>Настройки</span>
</UiDropdownItem>
<UiDropdownSeparator />
<UiDropdownItem @click="logout">
<template #icon>
<UiIconSExit class="text-clr-grey-400" />
</template>
<span>Logout</span>
</UiDropdownItem>
</template>
</UiDropdown>
</template>
<script setup>
const { logout, authenticated } = useAuth()
</script>
<style lang="scss">
.profile-dropdown {
cursor: pointer;
&__trigger {
--button-color: #{$clr-grey-500};
--button-background: #{$clr-grey-200};
--button-hover-color: #{$clr-white};
--button-active-color: #{$clr-white};
}
&__content {
@include txt-s-m('dropdown-item', true);
--dropdown-item-padding: 8px 12px;
position: relative;
color: $clr-grey-600;
width: 233px;
padding: 16px !important;
box-shadow: 0px 4px 4px 0px #6C86AD40;
&::before {
content: '';
width: 16px;
height: 16px;
transform: rotateZ(45deg);
position: absolute;
top: -8px;
right: 12px;
background-color: $clr-white;
border-radius: 2px;
}
}
}
</style>

View File

@@ -0,0 +1,35 @@
<template>
<div class="project-create">
<img src="/flag.svg" class="mb-40">
<i18n-t scope="global" keypath="create_your_project.title" tag="h2">
<span class="text-clr-cyan-500">
{{ $t('create_your_project.create') }}
</span>
</i18n-t>
<h4 class="project-create__text">
{{ $t('create_your_project.content') }}
</h4>
<UiButton size="large" class="mt-40 w-100" href="/projects/create">
{{ $t('create') }}
</UiButton>
</div>
</template>
<style lang="scss">
.project-create {
display: flex;
align-items: center;
flex-direction: column;
width: 422px;
align-self: center;
justify-self: center;
&__text {
color: $clr-grey-400;
margin-top: 8px;
}
}
</style>

View File

@@ -0,0 +1,104 @@
<template>
<div class="info-columns">
<PageInfoBlock
title="More limits"
text="Get increased limits and advanced features by providing a little more profile information"
action="Increase the limit"
link="/verification/status"
badge="BASIC"
style="
background-image: url('/block2.jpg');
background-size: cover;
background-position: bottom right;
"
>
<template #info>
<div
v-if="!maxState"
class="limits"
>
<p class="limits__date">
Updated on <strong>{{ dayjs().format('DD.MM.YYYY') }}</strong>
</p>
<div>
<p class="limits__title">
Remaining limit
</p>
<div class="mb-4 d-flex justify-content-between">
<span class="limits__current">{{ dayLimit }} <span class="limits__current-text">/ per day</span></span>
<span class="limits__total">{{ curDayLimit }} USDT</span>
</div>
<UiProgressBar :progress="(curDayLimit / dayLimit) * 100" />
<div class="mb-4 mt-24 d-flex justify-content-between">
<span class="limits__current">{{ monthLimit }} <span class="limits__current-text">/ per month</span></span>
<span class="limits__total">{{ curMonthLimit }} USDT</span>
</div>
<UiProgressBar :progress="(curMonthLimit / monthLimit) * 100" />
</div>
</div>
</template>
</PageInfoBlock>
<PageInfoBlock
title="2FA authentication"
text="Complete the verification process to remove withdrawal limits."
action="Add 2FA"
link="/2fa"
/>
</div>
</template>
<script setup>
import dayjs from 'dayjs'
const maxState = ref(false)
const curDayLimit = ref(100)
const curMonthLimit = ref(5200)
const dayLimit = ref(1000)
const monthLimit = ref(10000)
</script>
<style lang="scss">
.info-columns {
> *:not(:last-child) {
margin-bottom: 8px;
}
}
.limits {
margin-bottom: 24px;
&__date {
@include txt-s;
color: $clr-grey-500;
margin-bottom: 16px;
}
&__title {
@include txt-s-m;
color: $clr-grey-400;
text-align: right;
}
&__total {
@include txt-s-m;
color: $clr-grey-500;
}
&__current {
@include txt-m-sb;
}
&__current-text{
@include txt-r-sb;
color: $clr-grey-400;
}
}
</style>

View File

@@ -0,0 +1,162 @@
<template>
<UiPlainTable class="projects-table" :columns="columns" :data="projects">
<template #cell(name)="{ row }">
<div class="name-cell">
<div class="name-cell__initials">
{{ getProjectInitials(row.original) }}
</div>
<div class="name-cell__name">
{{ row.original.name }}
</div>
<div class="name-cell__id">
id {{ row.original.id }}
</div>
</div>
</template>
<template #cell(balance)="{ row }">
<MoneyAmount :value="row.original.account.balance" currency="USDT" />
</template>
<template #cell(withdraw)="{ row }">
<MoneyAmount value="0" currency="USDT" />
</template>
<template #cell(actions)="{ row }">
<UiButton class="mr-16" color="secondary">
API
</UiButton>
<UiButton class="mr-16" color="secondary">
Settings
</UiButton>
<UiButton :href="`/projects/${row.original.id}`">
Open
</UiButton>
</template>
</UiPlainTable>
</template>
<script setup lang="ts">
import { createColumnHelper } from '@tanstack/vue-table'
interface ProjectListItem {
id: number
name: string
}
defineProps({
projects: {
type: Array,
required: true,
},
})
const columnHelper = createColumnHelper<ProjectListItem>()
const columns = [
columnHelper.accessor('name', {
header: 'Name',
}),
columnHelper.display({
id: 'balance',
header: 'Balance',
}),
columnHelper.display({
id: 'withdraw',
header: 'In sending',
}),
columnHelper.display({
id: 'actions',
}),
]
function getProjectInitials(project: ProjectListItem) {
return project.name.slice(0, 1)
}
</script>
<style lang="scss">
.projects-table {
@include txt-i-sb('money-amount-value', true);
width: 100%;
border-collapse: separate;
border-spacing: 0 8px;
text-align: left;
margin-block: -8px;
td,
th {
margin: 0;
padding-inline: 16px;
background-color: $clr-grey-100;
&:first-child {
border-top-left-radius: 12px;
border-bottom-left-radius: 12px;
}
&:last-child {
border-top-right-radius: 12px;
border-bottom-right-radius: 12px;
}
}
th {
@include txt-i-m;
color: $clr-grey-500;
padding-block: 16px;
&.name {
padding-left: 64px;
}
}
td {
padding-block: 24px;
height: 95.8px;
&.actions {
background-color: $clr-grey-200;
padding-left: 32px;
width: 1%;
white-space: nowrap;
}
}
}
.name-cell {
display: grid;
align-items: center;
grid-template-columns: 32px auto;
gap: 4px 16px;
&__initials {
@include font(20px, 500, 32px);
background-color: $clr-grey-400;
height: 32px;
text-align: center;
border-radius: 6px;
color: $clr-white;
text-transform: uppercase;
grid-column: 1;
grid-row: span 2;
}
&__name {
@include txt-i-sb;
grid-column: 2;
color: $clr-black;
}
&__id {
@include txt-s-m;
grid-column: 2;
color: $clr-grey-500;
}
}
</style>

View File

@@ -0,0 +1,106 @@
<template>
<div
:class="[
cn.b(),
cn.is('checked', checked),
cn.is('disabled', disabled),
cn.is('focused', focused),
cn.is('disabled', disabled),
]"
@click="handleChange"
>
<p :class="[cn.e('label')]">
<slot>
{{ label }}
</slot>
</p>
<p v-if="caption || $slots.caption" :class="[cn.e('caption')]">
<slot name="caption">
{{ caption }}
</slot>
</p>
</div>
</template>
<script setup lang="ts">
export interface Props {
id: string
value: string | number
label?: string
caption?: string
disabled?: boolean
modelValue?: string | number
required?: boolean
}
defineOptions({
name: 'RadioButton',
})
const props = withDefaults(defineProps<Props>(), {
disabled: false,
trueValue: true,
falseValue: false,
required: false,
modelValue: undefined,
})
const slots = useSlots()
const { checked, focused, handleChange } = useRadio(props, slots)
const cn = useClassname('radio-button')
</script>
<style lang="scss">
.radio-button {
$self: &;
@include txt-i-m;
--border-color: #{$clr-grey-300};
--border-width: 1px;
display: inline-flex;
justify-content: center;
align-items: center;
flex-direction: column;
text-align: center;
height: 48px;
padding: 8px 16px;
border-radius: 12px;
outline: var(--border-width) solid var(--border-color);
outline-offset: calc(var(--border-width) * -1);
cursor: pointer;
transition: .2s ease-out;
transition-property: outline-color, background-color, color;
user-select: none;
&:hover {
--border-color: transparent;
background-color: $clr-grey-200;
}
&.is-checked {
--border-color: transparent;
background-color: $clr-cyan-500;
color: $clr-white;
}
&.is-disabled {
opacity: 0.4;
pointer-events: none;
}
&__caption {
@include txt-r-m;
color: $clr-cyan-400;
#{$self}.is-checked & {
color: $clr-cyan-300;
}
}
}
</style>

View File

@@ -0,0 +1,145 @@
<template>
<UiDropdown
class="resource-filter"
position="bottom-start"
:offset="0"
dropdown-class="resource-filter__dropdown"
transition-name="ui-select"
>
<template #default>
<div class="resource-filter__wrapper">
<p class="resource-filter__label">
{{ label }}
</p>
<div class="resource-filter__content">
<p class="resource-filter__value">
{{ value }}
</p>
<i
v-if="filled"
class="resource-filter__clear icon-s-cross-compact"
tabindex="0"
@click.stop="$emit('clear')"
/>
</div>
<UiIconChevronDown class="resource-filter__chevron" />
</div>
</template>
<template #dropdown>
<slot />
</template>
</UiDropdown>
</template>
<script setup lang="ts">
export interface Props {
label: string
value: string
filled?: boolean
}
withDefaults(defineProps<Props>(), {
filled: false,
})
defineEmits(['clear'])
</script>
<style lang="scss">
.resource-filter {
$self: &;
&__wrapper {
display: grid;
grid-template-areas: 'label chevron' 'content chevron';
grid-template-columns: 1fr auto;
column-gap: 8px;
align-items: center;
padding: 8px 16px;
border-radius: 12px;
cursor: pointer;
background-color: var(--select-background);
transition: 0.2s ease-out;
transition-property: background-color, border-radius, box-shadow;
outline: none;
width: 169px;
&:focus-visible,
&:hover {
background-color: var(--select-hover-background);
}
&:active {
background-color: var(--select-active-background);
}
#{$self}.is-active & {
border-radius: 12px 12px 0 0;
box-shadow: 0 4px 4px 0 #6c86ad40;
}
}
&__label {
@include txt-s-m;
grid-area: label;
color: var(--select-label-color);
transition: color 0.2s ease-out;
user-select: none;
#{$self}.is-active &,
#{$self}__wrapper:hover &,
#{$self}__wrapper:focus-visible & {
color: var(--select-label-hover-color);
}
}
&__content {
display: flex;
grid-area: content;
}
&__value {
@include txt-i-m;
background: none;
color: var(--select-color);
padding: 0;
border: none;
width: 100%;
outline: none;
cursor: pointer;
user-select: none;
pointer-events: none;
appearance: none;
white-space: nowrap;
&::placeholder {
color: var(--select-color);
}
}
&__clear {
color: var(--select-clear-color);
transition: color 0.2s ease-out;
&:hover {
color: var(--select-clear-hover-color);
}
}
&__chevron {
color: var(--select-chevron-color);
grid-area: chevron;
}
&__dropdown {
border-radius: 0 0 12px 12px;
box-shadow: 0 4px 4px 0 #6c86ad40;
}
}
</style>

View File

@@ -0,0 +1,51 @@
<template>
<ResourceFilterBase
:label="label"
:value="value"
:filled="modelValue && modelValue.length > 0"
@clear="$emit('update:modelValue', [])"
>
<UiCalendar
:model-value="modelValue"
@update:model-value="$emit('update:modelValue', $event)"
/>
</ResourceFilterBase>
</template>
<script setup lang="ts">
import dayjs from 'dayjs'
export interface Props {
label: string
placeholder: string
modelValue?: [from?: number, to?: number]
}
defineOptions({
name: 'ResourceFilterCalendar',
})
const props = defineProps<Props>()
defineEmits(['update:modelValue'])
const value = computed(() => {
if (!props.modelValue)
return props.placeholder
const [start, end] = props.modelValue
if (!start)
return props.placeholder
if (start && end) {
const from = dayjs(start).format('DD.MM')
const to = dayjs(end).format('DD.MM.YY')
return `${from}${to}`
}
else if (start) {
return dayjs(start).format('DD.MM.YY')
}
})
</script>

View File

@@ -0,0 +1,23 @@
<template>
<UiSelect
class="resource-filter resource-filter--select"
v-bind="props"
:model-value="modelValue"
clearable
emit-value
map-options
@update:model-value="$emit('update:modelValue', $event)"
/>
</template>
<script setup lang="ts">
import type { Props } from 'ui-layer/components/select/types'
defineOptions({
name: 'ResourceFilterSelect',
})
const props = defineProps<Props>()
defineEmits(['update:modelValue'])
</script>

View File

@@ -0,0 +1,68 @@
<template>
<div class="resource-filters">
<div class="resource-filters__filters">
<Component
:is="getFilterComponent(filter)"
v-for="filter in schema as Filters"
:id="`resource_filter.${filter.key}`"
v-bind="filter"
:key="filter.key"
:model-value="appliedFiltersRaw[filter.key]"
@update:model-value="apply(filter.key, $event)"
/>
</div>
<UiButton
v-if="!empty"
type="outlined"
icon="s-cross"
class="resource-filters__clear"
@click="reset"
>
Clear filters
</UiButton>
</div>
</template>
<script setup lang="ts">
import type { Filter, Filters } from '#imports'
const { schema, appliedFiltersRaw, empty, apply, reset } = inject(
filtersContextKey,
{
schema: [],
appliedFiltersRaw: {},
appliedFilters: computed(() => ({})),
empty: true,
apply: () => {},
reset: () => {},
},
)
function getFilterComponent(filter: Filter) {
switch (filter.type) {
case 'calendar':
return resolveComponent('ResourceFilterCalendar')
case 'select':
default:
return resolveComponent('ResourceFilterSelect')
}
}
</script>
<style lang="scss">
.resource-filters {
display: flex;
align-items: center;
gap: 16px;
&__filters {
display: flex;
gap: 8px;
}
&__clear {
margin-left: auto;
}
}
</style>

View File

@@ -0,0 +1,61 @@
<template>
<SettingsRawTable :columns="columns" :data="data">
<template #cell(finance_notifications)="{ row }">
<div class="d-flex align-items-center text-clr-grey-600">
<Component :is="resolveComponent(`ui-icon-${row.original.icon}`)" :style="`color: ${row.original.color}`" />
<h4 class="ml-10">
{{ row.original.finance_notifications }}
</h4>
</div>
</template>
<template #cell(telegram)="{ row }">
<div class="d-flex justify-content-center">
<UiCheckbox id="telegram" />
</div>
</template>
<template #cell(mail)="{ row }">
<div class="d-flex justify-content-center">
<UiCheckbox id="mail" :model-value="true" disabled />
</div>
</template>
<template #cell(push)="{ row }">
<div class="d-flex justify-content-center">
<UiCheckbox id="push" :model-value="true" />
</div>
</template>
</SettingsRawTable>
</template>
<script setup>
import { createColumnHelper } from '@tanstack/vue-table'
const columnHelper = createColumnHelper()
const columns = [
columnHelper.display({
id: 'finance_notifications',
header: 'Finance Notifications',
}),
columnHelper.display({
id: 'telegram',
header: 'Telegram',
}),
columnHelper.display({
id: 'mail',
header: 'Mail',
}),
columnHelper.display({
id: 'push',
header: 'Push',
}),
]
const data = [
{ finance_notifications: 'Поступления средств', icon: 'ArrowReceive', color: '#10C44C' },
{ finance_notifications: 'Выводы', icon: 'ArrowSend', color: '#1464D3' },
{ finance_notifications: 'Счет частично оплачен', icon: 'InstallmentPlan' },
]
</script>

View File

@@ -0,0 +1,45 @@
<template>
<div class="block">
<span class="text-small">Spent</span>
<span class="text-small">Remaining limit </span>
</div>
<UiProgressBar class="progress" :progress="(amount / maxAmount) * 100" />
<div class="block">
<span class="text-amount">{{ amount }} <span class="text-currency">{{ currency }}</span></span>
<span class="text-amount">{{ maxAmount - amount }} <span class="text-currency">{{ currency }}</span> </span>
</div>
</template>
<script setup>
defineProps({
currency: { type: String, required: true },
amount: { type: Number, required: true },
maxAmount: { type: Number, required: true },
})
</script>
<style lang="scss" scoped>
.block{
display: flex;
justify-content: space-between;
}
.progress{
margin-block: 8px;
}
.text-small {
@include txt-r;
color: $clr-grey-500;
}
.text-currency {
@include txt-i-sb;
color: $clr-grey-400;
}
.text-amount{
@include txt-i-sb;
}
</style>

View File

@@ -0,0 +1,50 @@
<template>
<div class="settings-property-item">
<Component
:is="resolveComponent(`ui-icon-${icon}`)"
class="settings-property-item__icon"
/>
<div class="settings-property-item-content">
<p class="settings-property-item-content__title">
{{ title }}
</p>
<p v-if="text" class="settings-property-item-content__text">
{{ text }}
</p>
</div>
</div>
</template>
<script setup>
defineProps({
icon: { type: String, required: true },
title: { type: String, required: true },
text: { type: String },
})
</script>
<style lang="scss">
.settings-property-item {
display: flex;
align-items: center;
&__icon{
color: $clr-cyan-500;
margin-right: 10px;
}
}
.settings-property-item-content{
&__title {
@include txt-i-sb;
}
&__text {
@include txt-s-m;
margin-top: 4px;
color: $clr-grey-500;
}
}
</style>

View File

@@ -0,0 +1,24 @@
<template>
<div class="settings-property">
<SettingsPropertyItem :icon="icon" :text="text" :title="title" />
<slot />
</div>
</template>
<script setup>
defineProps({
icon: { type: String, required: true },
title: { type: String, required: true },
text: { type: String },
})
</script>
<style lang="scss">
.settings-property {
display: flex;
align-items: center;
justify-content: space-between;
height: 62px;
}
</style>

View File

@@ -0,0 +1,112 @@
<template>
<table class="settings-table">
<thead>
<tr v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
<th
v-for="header in headerGroup.headers"
:key="header.id"
:colSpan="header.colSpan"
:class="[header.id]"
:style="{
width: `${header.getSize()}px`,
}"
>
<template v-if="!header.isPlaceholder">
<FlexRender
:render="header.column.columnDef.header"
:props="header.getContext()"
/>
</template>
</th>
</tr>
</thead>
<slot>
<tbody>
<tr v-for="row in table.getRowModel().rows" :key="row.id">
<td
v-for="cell in row.getVisibleCells()"
:key="cell.id"
:class="[cell.column.id]"
>
<slot :name="`cell(${cell.column.id})`" v-bind="cell.getContext()">
<FlexRender
:render="cell.column.columnDef.cell"
:props="cell.getContext()"
/>
</slot>
</td>
</tr>
</tbody>
</slot>
</table>
</template>
<script setup lang="ts">
import { FlexRender, getCoreRowModel, useVueTable } from '@tanstack/vue-table'
import type { ColumnDef } from '@tanstack/vue-table'
export interface Props {
columns: ColumnDef<unknown>[]
data: unknown[]
}
defineOptions({
name: 'SettingsTable',
})
const props = defineProps<Props>()
const table = useVueTable({
get data() {
return props.data
},
get columns() {
return props.columns
},
getCoreRowModel: getCoreRowModel(),
})
</script>
<style lang="scss">
.settings-table {
width: 100%;
border-collapse: separate;
border-spacing: 0 8px;
margin-block: -8px;
th {
@include h5;
background-color: $clr-grey-100;
color: $clr-grey-500;
&:first-child {
border-top-left-radius: 12px;
border-bottom-left-radius: 12px;
}
&:last-child {
border-top-right-radius: 12px;
border-bottom-right-radius: 12px;
}
}
td{
}
td,
th {
padding: 16px 24px;
&:first-child {
width: 300px;
text-align: left;
}
}
}
</style>

View File

@@ -0,0 +1,59 @@
<template>
<SettingsRawTable :columns="columns" :data="data">
<template #cell(service_notifications)="{ row }">
<div class="d-flex align-items-center text-clr-grey-600">
<Component :is="resolveComponent(`ui-icon-${row.original.icon}`)" />
<h4 class="ml-10">
{{ row.original.service_notifications }}
</h4>
</div>
</template>
<template #cell(telegram)="{ row }">
<div class="d-flex justify-content-center">
<UiCheckbox id="telegram" />
</div>
</template>
<template #cell(mail)="{ row }">
<div class="d-flex justify-content-center">
<UiCheckbox id="mail" :model-value="true" />
</div>
</template>
<template #cell(push)="{ row }">
<div class="d-flex justify-content-center">
<UiCheckbox id="push" :model-value="true" />
</div>
</template>
</SettingsRawTable>
</template>
<script setup>
import { createColumnHelper } from '@tanstack/vue-table'
const columnHelper = createColumnHelper()
const columns = [
columnHelper.display({
id: 'service_notifications',
header: 'Service Notifications',
}),
columnHelper.display({
id: 'telegram',
header: 'Telegram',
}),
columnHelper.display({
id: 'mail',
header: 'Mail',
}),
columnHelper.display({
id: 'push',
header: 'Push',
}),
]
const data = [
{ service_notifications: 'Вход в аккаунт', icon: 'signin' },
]
</script>

View File

@@ -0,0 +1,48 @@
<template>
<div class="settings-tariff-card">
<div class="settings-tariff-card__header">
<div>
<slot name="icon" />
<h4 class="d-inline-block ml-8 text-clr-grey-600">
{{ title }}
</h4>
</div>
<slot name="subtitle" />
</div>
<div class="settings-tariff-card__content">
<slot name="content" />
</div>
</div>
</template>
<script setup>
defineProps({
title: { type: String, required: true },
})
</script>
<style lang="scss">
.settings-tariff-card {
&__header{
padding: 24px;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
background-color: $clr-grey-100;
display: flex;
justify-content: space-between;
align-items: center;
}
&__content{
padding: 24px;
border-bottom-left-radius: 12px;
border-bottom-right-radius: 12px;
border: 2px solid $clr-grey-100;
display: flex;
gap: 32px;
}
}
</style>

View File

@@ -0,0 +1,87 @@
<template>
<VueFinalModal
:modal-id="modalId"
class="sidebar"
content-class="sidebar__content"
content-transition="sidebar"
hide-overlay
background="interactive"
:click-to-close="false"
:z-index-fn="({ index }) => 6000 + 2 * index"
>
<div class="sidebar__top">
<UiButton icon="arrow-left" type="outlined" color="secondary" @click="vfm.close(modalId)" />
<slot name="top" />
</div>
<div v-if="$slots.default" class="sidebar__middle">
<slot />
</div>
<div v-if="$slots.bottom" class="sidebar__bottom">
<slot name="bottom" />
</div>
</VueFinalModal>
</template>
<script setup>
import { VueFinalModal, useVfm } from 'vue-final-modal'
const props = defineProps({
id: {
type: String,
},
})
const vfm = useVfm()
const modalId = computed(() => {
if (props.id)
return `sidebar-${props.id}`
return 'sidebar'
})
</script>
<style lang="scss">
.sidebar {
&__content {
width: 353px;
position: absolute;
right: 0;
display: flex;
flex-direction: column;
background-color: $clr-white;
height: 100%;
gap: 32px;
padding: 16px;
}
&__top {
display: flex;
align-items: center;
gap: 16px;
}
&__middle {
flex: 1;
overflow-y: auto;
}
&-enter-active,
&-leave-active {
transition: transform .2s ease-in-out;
}
&-enter-to,
&-leave-from {
//transform: rotateZ(360deg);
}
&-enter-from,
&-leave-to {
transform: translateX(100%);
}
}
</style>

View File

@@ -0,0 +1,9 @@
<template>
<Transition name="fade">
<UiAlert v-if="staticError" type="negative" :text="staticError.message" />
</Transition>
</template>
<script setup lang="ts">
const staticError = useStaticError()
</script>

View File

@@ -0,0 +1,139 @@
<template>
<div class="form-stepper">
<template
v-for="(label, index) in items"
:key="index"
>
<div
class=" form-stepper__step"
:class="{
'form-stepper__step--current': index === step,
'form-stepper__step--passed': index < step,
}"
>
<div class="form-stepper__index">
<span v-if="index > step - 1 ">
{{ index + 1 }}
</span>
<UiIconCheck
v-else
class="form-stepper__icon"
/>
</div>
<div
class="form-stepper__label"
v-html="label"
/>
</div>
<div
v-if="index !== items.length - 1"
class="form-stepper__line"
:class="{
'form-stepper__line--passed': index > step - 1,
}"
/>
</template>
</div>
</template>
<script setup>
const props = defineProps({
items: {
type: Array,
required: true,
},
step: {
type: Number,
default: 0,
},
})
const emit = defineEmits(['set'])
</script>
<style lang="scss">
.form-stepper {
$self: &;
width: 100%;
display: flex;
align-items: center;
&__line {
$line: &;
height: 3px;
flex: 1;
background-image: linear-gradient(90deg, #a5e9bc 0%, #a7b9d5 100%);
margin-inline: 4px;
border-radius: 2px;
&--passed {
background-image: linear-gradient(90deg, #a7b9d5 0%, #dfe5ff 100%);
}
}
&__step {
display: flex;
align-items: center;
flex-direction: column;
position: relative;
user-select: none;
&:first-child {
justify-self: flex-start;
}
&:last-child {
justify-self: flex-end;
}
&--passed {
--form-stepper-index-background: #{$clr-green-500};
--form-stepper-label-color: #{$clr-green-500};
//color: var(--form-stepper-index-color, #{$clr-white});
cursor: initial;
}
&--current {
--form-stepper-index-background: #{$clr-grey-300};
--form-stepper-label-color: #{$clr-grey-600};
--form-stepper-index-color: #{$clr-grey-600};
cursor: initial;
}
}
&__icon {
--icon-size: 20px;
line-height: 18px;
color: $clr-white;
}
&__index {
@include txt-i-m;
width: 32px;
height: 32px;
line-height: 32px;
border-radius: 8px;
text-align: center;
background: var(--form-stepper-index-background, #{$clr-grey-200});
color: var(--form-stepper-index-color, #{$clr-grey-400});
cursor: pointer;
}
&__label {
@include txt-r;
color: var(--form-stepper-label-color, #{$clr-grey-600});
margin-top: 16px;
text-align: center;
}
}
</style>

View File

@@ -0,0 +1,140 @@
<template>
<UiPlainTable
class="stripped-table"
:class="{ 'is-loading': loading }"
:columns="columns"
:data="data"
>
<template v-if="loading" #default>
<tbody>
<tr v-for="i in 6" :key="i">
<td :colspan="columns.length">
<div class="stripped-table__skeleton">
<div
v-for="j in i % 2 === 0 ? 7 : 3"
:key="j"
class="stripped-table__skeleton-cell"
/>
</div>
</td>
</tr>
</tbody>
</template>
<template v-else-if="!data.length" #default>
<tbody>
<tr>
<td class="stripped-table__no-data" :colspan="columns.length">
<img src="/no-data.svg" alt="No Data">
<h4>No Data</h4>
</td>
</tr>
</tbody>
</template>
<template v-for="(_, slot) of $slots" #[slot]="scope">
<slot :name="slot" v-bind="scope" />
</template>
</UiPlainTable>
</template>
<script setup lang="ts">
import type { ColumnDef } from '@tanstack/vue-table'
export interface Props {
columns: ColumnDef<unknown>[]
data: unknown[]
loading?: boolean
}
withDefaults(defineProps<Props>(), {
loading: false,
})
</script>
<style lang="scss">
.stripped-table {
width: 100%;
border-collapse: separate;
border-spacing: 0 4px;
text-align: left;
margin-block: -4px;
td,
th {
margin: 0;
padding: 16px 8px;
&:first-child {
border-top-left-radius: 12px;
border-bottom-left-radius: 12px;
padding-left: 24px;
}
&:last-child {
border-top-right-radius: 12px;
border-bottom-right-radius: 12px;
padding-right: 24px;
}
}
th {
@include h5;
background-color: $clr-grey-100;
color: $clr-grey-500;
}
&.is-loading {
td {
background-color: $clr-grey-100;
}
}
&:not(.is-loading) {
tbody {
tr:nth-child(even) {
td {
background-color: $clr-grey-100;
}
}
}
}
&__no-data {
height: 494px;
text-align: center;
h4 {
margin-top: 8px;
color: $clr-grey-400;
}
}
&__skeleton {
display: flex;
align-items: center;
gap: 16px;
}
&__skeleton-cell {
border-radius: 12px;
background-color: $clr-white;
background-image: linear-gradient(
110deg,
$clr-white 8%,
$clr-grey-100 18%,
$clr-white 33%
);
background-size: 200% 100%;
height: 36px;
animation: 1.5s shine linear infinite;
width: 100%;
}
}
@keyframes shine {
to {
background-position-x: -200%;
}
}
</style>

View File

@@ -0,0 +1,163 @@
<template>
<div
class="verification-card"
:class="{
'verification-card--passed': passed,
}"
>
<div class="verification-card__header">
<UiBadge
v-if="badge"
:type="badge.type"
class="mb-24"
>
{{ badge.title }}
</UiBadge>
<p class="verification-card-info__title">
Withdrawal
</p>
<div
v-if="info"
class="verification-card-info"
>
<div>
<span class="verification-card-info__per-month-amount">
{{ info.perMonth }}
</span>
<span class="verification-card-info__per-month-text"> / month </span>
</div>
<div class="mt-4">
<span class="verification-card-info__per-day-amount">
{{ info.perDay }}
</span>
<span class="verification-card-info__per-day-text"> / day </span>
</div>
</div>
<div
v-else
class="verification-card-info"
>
<span class="verification-card-info__per-month-amount">
No limit
</span>
<span class="verification-card-info__per-month-text"> / month & day </span>
</div>
</div>
<div class="verification-card-body">
<slot />
</div>
<div class="verification-card__action">
<UiAlert
v-if="passed"
type="positive"
text="Your current status"
/>
<UiButton
v-else
class="w-100"
size="large"
:href="link"
>
Start verification
</UiButton>
</div>
</div>
</template>
<script setup>
defineProps({
passed: {
type: Boolean,
default: false,
},
badge: {
type: Object,
required: true,
},
info: {
type: Object,
default: null,
},
link: {
type: String,
default: '#',
},
})
</script>
<style lang="scss">
.verification-card {
$self: &;
display: flex;
flex-direction: column;
width: var(--verification-card-width, 405px);
//height: 585px;
padding: 24px;
border-radius: 12px;
background-color: $clr-grey-200;
&__header {
height: 157px;
border-bottom: 1px solid $clr-grey-400;
margin-bottom: 16px;
}
&__action {
margin-top: auto;
}
&--passed {
background-color: $clr-white;
outline: $clr-green-500 solid 2px;
outline-offset: -2px;
.verification-card__title {
color: $clr-black;
}
}
}
.verification-card-info {
&__per-month-amount {
@include h2;
}
&__per-month-text {
@include txt-l-sb;
color: $clr-grey-400;
}
&__per-day-amount {
@include txt-l-sb;
}
&__per-day-text {
@include txt-l-sb;
color: $clr-grey-400;
}
&__title {
@include txt-l-m;
color: $clr-grey-500;
margin-bottom: 4px;
}
}
.verification-card-body {
display: flex;
flex-direction: column;
gap: 16px;
margin-bottom: 24px;
}
</style>

View File

@@ -0,0 +1,55 @@
<template>
<div>
<div
v-for="block in propertyBlocks"
:key="block.title"
class="elements"
>
<span class="title">
{{ block.title }}
</span>
<h5 class="text-clr-grey-500">
{{ block.underTitle }}
</h5>
<VerificationProperty
v-for="property in block.properties"
:key="property.title"
:title="property.title"
:text="property.text"
/>
</div>
</div>
</template>
<script setup>
const propertyBlocks=[
{
title: 'Restrictions',
underTitle: 'Invoices',
properties:[
{title:'Minimum amount in the invoice is', text:' 5 USDT'},
{title:'Commission for creating an invoice is', text:'0.5 USDT'},
{title:'Limit for creating invoices is', text:'200 per day, 1000 per month'},
]},
{
underTitle: 'Withdrawal of funds',
properties:[
{title:'Minimum withdrawal amount is', text:'5 USDT'},
{title:'Commission for withdrawal is', text:'2.5 USDT + 1%'}
]},
]
</script>
<style lang="scss" scoped>
.elements {
display: flex;
flex-direction: column;
gap: 16px;
}
.title{
@include txt-m-b;
}
</style>

View File

@@ -0,0 +1,78 @@
<template>
<div>
<div class="elements mb-16">
<span class="requirements"> Requirements </span>
<div>
<UiIconSSocialCard class="mr-4 text-clr-grey-400 d-inline-block" />
<span class="text"> Organization data </span>
</div>
<div>
<UiIconSExcursion class="mr-4 text-clr-grey-400" />
<span class="text">Identity verification </span>
</div>
</div>
<div
v-for="block in propertyBlocks"
:key="block.title"
class="elements"
>
<span class="title">
{{ block.title }}
</span>
<h5 class="text-clr-grey-500">
{{ block.underTitle }}
</h5>
<VerificationProperty
v-for="property in block.properties"
:key="property.title"
:title="property.title"
:text="property.text"
/>
</div>
</div>
</template>
<script setup>
const propertyBlocks = [
{
title: 'Restrictions',
underTitle: 'Invoices',
properties: [
{ title: 'Invoice creation', text: 'unlimited' },
{ title: 'Commission for creating an invoice is', text: '0.5 USDT' },
],
},
{
underTitle: 'Withdrawal of funds',
properties: [
{ title: 'Commission for withdrawal is', text: '2.5 USDT + 1%' },
],
},
]
</script>
<style lang="scss" scoped>
.elements {
display: flex;
flex-direction: column;
gap: 16px;
}
.title{
@include txt-m-b;
}
.text{
@include txt-r-m;
color: $clr-grey-500;
}
.requirements{
@include txt-m-b;
}
</style>

View File

@@ -0,0 +1,49 @@
<template>
<div class="varification-property">
<Component
:is="resolveComponent(`ui-icon-${icon}`)"
class="mr-4"
:class="icon === 'SCheck' ? 'text-clr-green-500' : ''"
/>
<span class="varification-property__title">
{{ title }}
<span class="varification-property__text">
{{ text }}
</span>
</span>
</div>
</template>
<script setup>
defineProps({
title: {
type: String,
default: '',
},
text: {
type: String,
default: '',
},
icon: {
type: String,
default: 'SCheck',
},
})
</script>
<style lang="scss">
.varification-property {
color: $clr-grey-500;
&__title {
@include txt-r;
}
&__text {
@include txt-r-b;
}
}
</style>

View File

@@ -0,0 +1,40 @@
<template>
<div>
<p class="mb-16">
<strong> We accept: </strong>
</p>
<ul class="accept-list">
<li
v-for="listItem in list"
:key="listItem.title"
>
<span> {{ listItem.title }} </span>
<p class="text-clr-grey-500">
{{ listItem.underTitle }}
</p>
</li>
</ul>
</div>
</template>
<script setup>
defineProps({
list: {
type: Array,
required: true
}
})
</script>
<style lang="scss">
.accept-list{
list-style-type: disc;
padding: 0 0 0 24px;
margin: 0;
display: flex;
flex-direction: column;
gap: 16px;
}
</style>