Add bonus program link generation

This commit is contained in:
Ruslan Bakiev
2026-04-07 10:48:07 +07:00
parent 722dbb89cb
commit 86eee08d87
4 changed files with 160 additions and 4 deletions

View File

@@ -47,6 +47,14 @@ export type AuthSession = {
user: User;
};
export type BonusProgramLink = {
__typename?: 'BonusProgramLink';
expiresAt: Scalars['DateTime']['output'];
token: Scalars['String']['output'];
url: Scalars['String']['output'];
userId: Scalars['ID']['output'];
};
export type BonusTransaction = {
__typename?: 'BonusTransaction';
amount: Scalars['Float']['output'];
@@ -300,6 +308,7 @@ export type Mutation = {
clientReviewOrder: Order;
connectMessenger: MessengerConnection;
consumeLoginToken: AuthSession;
createBonusProgramLink: BonusProgramLink;
createInvitation: Invitation;
createMyDeliveryAddress: DeliveryAddress;
createReferral: ReferralLink;
@@ -355,6 +364,11 @@ export type MutationConsumeLoginTokenArgs = {
};
export type MutationCreateBonusProgramLinkArgs = {
userId: Scalars['ID']['input'];
};
export type MutationCreateInvitationArgs = {
input: CreateInvitationInput;
};
@@ -830,6 +844,13 @@ export type VerifyLoginCodeMutationVariables = Exact<{
export type VerifyLoginCodeMutation = { __typename?: 'Mutation', verifyLoginCode: { __typename?: 'AuthSession', accessToken: string, expiresAt: any, user: { __typename?: 'User', id: string, email: string, fullName: string, role: UserRole, company?: { __typename?: 'Company', id: string } | null } } };
export type CreateBonusProgramLinkMutationVariables = Exact<{
userId: Scalars['ID']['input'];
}>;
export type CreateBonusProgramLinkMutation = { __typename?: 'Mutation', createBonusProgramLink: { __typename?: 'BonusProgramLink', userId: string, token: string, url: string, expiresAt: any } };
export type RequestRewardWithdrawalMutationVariables = Exact<{
input: RequestRewardWithdrawalInput;
}>;
@@ -1283,6 +1304,38 @@ export function useVerifyLoginCodeMutation(options: VueApolloComposable.UseMutat
return VueApolloComposable.useMutation<VerifyLoginCodeMutation, VerifyLoginCodeMutationVariables>(VerifyLoginCodeDocument, options);
}
export type VerifyLoginCodeMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<VerifyLoginCodeMutation, VerifyLoginCodeMutationVariables>;
export const CreateBonusProgramLinkDocument = gql`
mutation CreateBonusProgramLink($userId: ID!) {
createBonusProgramLink(userId: $userId) {
userId
token
url
expiresAt
}
}
`;
/**
* __useCreateBonusProgramLinkMutation__
*
* To run a mutation, you first call `useCreateBonusProgramLinkMutation` within a Vue component and pass it any options that fit your needs.
* When your component renders, `useCreateBonusProgramLinkMutation` returns an object that includes:
* - A mutate function that you can call at any time to execute the mutation
* - Several other properties: https://v4.apollo.vuejs.org/api/use-mutation.html#return
*
* @param options that will be passed into the mutation, supported options are listed on: https://v4.apollo.vuejs.org/guide-composable/mutation.html#options;
*
* @example
* const { mutate, loading, error, onDone } = useCreateBonusProgramLinkMutation({
* variables: {
* userId: // value for 'userId'
* },
* });
*/
export function useCreateBonusProgramLinkMutation(options: VueApolloComposable.UseMutationOptions<CreateBonusProgramLinkMutation, CreateBonusProgramLinkMutationVariables> | ReactiveFunction<VueApolloComposable.UseMutationOptions<CreateBonusProgramLinkMutation, CreateBonusProgramLinkMutationVariables>> = {}) {
return VueApolloComposable.useMutation<CreateBonusProgramLinkMutation, CreateBonusProgramLinkMutationVariables>(CreateBonusProgramLinkDocument, options);
}
export type CreateBonusProgramLinkMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<CreateBonusProgramLinkMutation, CreateBonusProgramLinkMutationVariables>;
export const RequestRewardWithdrawalDocument = gql`
mutation RequestRewardWithdrawal($input: RequestRewardWithdrawalInput!) {
requestRewardWithdrawal(input: $input) {

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { useQuery } from '@vue/apollo-composable';
import { useMutation, useQuery } from '@vue/apollo-composable';
import {
CreateBonusProgramLinkDocument,
ManagerBonusAccountDocument,
type ManagerBonusAccountQuery,
} from '~/composables/graphql/generated';
@@ -16,6 +17,10 @@ type PendingWithdrawalItem = ManagerBonusAccountQuery['managerBonusAccount']['pe
const route = useRoute();
const userId = computed(() => String(route.params.userId || ''));
const createBonusProgramLinkMutation = useMutation(CreateBonusProgramLinkDocument, { throws: 'never' });
const bonusProgramLink = ref('');
const bonusProgramLinkExpiresAt = ref('');
const bonusProgramLinkFeedback = ref('');
const bonusAccountQuery = useQuery(ManagerBonusAccountDocument, () => ({
userId: userId.value,
@@ -55,6 +60,33 @@ function withdrawalStatusClass(status: string) {
}
return 'bg-[#fff3d8] text-[#9a6100]';
}
async function generateBonusProgramLink() {
bonusProgramLinkFeedback.value = '';
const response = await createBonusProgramLinkMutation.mutate({
userId: userId.value,
});
const payload = response?.data?.createBonusProgramLink;
if (!payload?.url) {
bonusProgramLinkFeedback.value = createBonusProgramLinkMutation.error.value?.message || 'Не удалось сгенерировать ссылку.';
return;
}
bonusProgramLink.value = payload.url;
bonusProgramLinkExpiresAt.value = payload.expiresAt;
bonusProgramLinkFeedback.value = 'Ссылка готова. Её можно переслать клиенту.';
}
async function copyBonusProgramLink() {
if (!bonusProgramLink.value) {
return;
}
await navigator.clipboard.writeText(bonusProgramLink.value);
bonusProgramLinkFeedback.value = 'Ссылка скопирована.';
}
</script>
<template>
@@ -76,13 +108,68 @@ function withdrawalStatusClass(status: string) {
:subtitle="bonusAccount.companyName || bonusAccount.email || undefined"
>
<template #actions>
<div class="text-left md:text-right">
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Доступный бонус</p>
<p class="mt-2 text-3xl font-black leading-none text-[#123824]">{{ formatAmount(bonusAccount.balance) }}</p>
<div class="flex flex-col gap-3 md:items-end">
<div class="text-left md:text-right">
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Доступный бонус</p>
<p class="mt-2 text-3xl font-black leading-none text-[#123824]">{{ formatAmount(bonusAccount.balance) }}</p>
</div>
<button
class="btn rounded-full border-0 bg-[#123824] px-5 text-white hover:bg-[#0f2f20]"
:disabled="createBonusProgramLinkMutation.loading.value"
@click="generateBonusProgramLink"
>
{{ createBonusProgramLinkMutation.loading.value ? 'Генерируем...' : 'Сгенерировать ссылку' }}
</button>
</div>
</template>
</UiBackHeader>
<article v-if="bonusProgramLink" class="surface-card rounded-[28px] px-5 py-4">
<div class="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
<div class="min-w-0 flex-1 space-y-2">
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-[#6a8a76]">Ссылка в бонусный кабинет</p>
<p class="text-sm text-[#355947]">
Эту ссылку менеджер может отправить клиенту. Дальше клиент авторизуется через отдельный Telegram-бот бонусной программы.
</p>
<div class="rounded-[20px] bg-[#f8fbf9] px-4 py-3 text-sm font-semibold text-[#123824] break-all">
{{ bonusProgramLink }}
</div>
<p v-if="bonusProgramLinkExpiresAt" class="text-xs text-[#5c7b69]">
Действует до {{ formatDateTime(bonusProgramLinkExpiresAt) }}
</p>
</div>
<div class="flex gap-3">
<a
:href="bonusProgramLink"
target="_blank"
rel="noreferrer"
class="btn rounded-full border border-[#d7e9de] bg-white px-5 text-[#123824] hover:bg-[#f3f8f5]"
>
Открыть
</a>
<button
class="btn rounded-full border-0 bg-[#139957] px-5 text-white hover:bg-[#0d854a]"
@click="copyBonusProgramLink"
>
Скопировать
</button>
</div>
</div>
<p v-if="bonusProgramLinkFeedback" class="mt-4 text-sm font-semibold text-[#0d854a]">
{{ bonusProgramLinkFeedback }}
</p>
</article>
<p
v-else-if="bonusProgramLinkFeedback"
class="text-sm font-semibold text-[#0d854a]"
>
{{ bonusProgramLinkFeedback }}
</p>
<div v-if="pendingWithdrawals.length" class="space-y-3">
<div class="space-y-1">
<p class="text-lg font-bold text-[#123824]">Заявки на выплату</p>

View File

@@ -0,0 +1,8 @@
mutation CreateBonusProgramLink($userId: ID!) {
createBonusProgramLink(userId: $userId) {
userId
token
url
expiresAt
}
}

View File

@@ -213,6 +213,13 @@ type IntegrationSyncDashboard {
items: [IntegrationSyncItem!]!
}
type BonusProgramLink {
userId: ID!
token: String!
url: String!
expiresAt: DateTime!
}
type Warehouse {
id: ID!
code: String!
@@ -563,6 +570,7 @@ type Mutation {
clientReviewOrder(orderId: ID!, decision: Decision!): Order!
createReferral(input: CreateReferralInput!): ReferralLink!
createBonusProgramLink(userId: ID!): BonusProgramLink!
addBonusTransaction(input: AddBonusTransactionInput!): BonusTransaction!
requestRewardWithdrawal(input: RequestRewardWithdrawalInput!): RewardWithdrawalRequest!
reviewRewardWithdrawal(input: ReviewRewardWithdrawalInput!): RewardWithdrawalRequest!