feat(subscriptions): refactor subscription handling and update related queries
- Renamed `hasUserTrialSubscription` to `usedTrialSubscription` for clarity in the SubscriptionsService. - Updated subscription-related queries and fragments to use `active` instead of `isActive` for consistency. - Enhanced the ProPage component to utilize the new subscription checks and improve trial usage logic. - Removed unused subscription history query to streamline the codebase. - Adjusted the SubscriptionInfoBar to reflect the new subscription state handling.
This commit is contained in:
parent
eab6da5e89
commit
a6d05bcf69
@ -18,11 +18,11 @@ export async function subscription(conversation: Conversation<Context, Context>,
|
||||
|
||||
const subscriptionsService = new SubscriptionsService({ telegramId });
|
||||
|
||||
const hasUserTrial = await subscriptionsService.hasUserTrialSubscription();
|
||||
const hasUserTrial = await subscriptionsService.usedTrialSubscription();
|
||||
|
||||
const { subscriptionPrices } = await subscriptionsService.getSubscriptionPrices({
|
||||
filters: {
|
||||
isActive: {
|
||||
active: {
|
||||
eq: true,
|
||||
},
|
||||
period: {
|
||||
|
||||
@ -4,25 +4,16 @@ import { PageHeader } from '@/components/navigation';
|
||||
import { TryFreeButton } from '@/components/subscription';
|
||||
import { env } from '@/config/env';
|
||||
import { Button } from '@repo/ui/components/ui/button';
|
||||
import { ArrowRight, Crown, Infinity as InfinityIcon, Star, Users } from 'lucide-react';
|
||||
import { ArrowRight, Crown, Infinity as InfinityIcon } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default async function ProPage() {
|
||||
const { telegramId } = await getSessionUser();
|
||||
const subscriptionData = await getSubscription({ telegramId });
|
||||
const { hasActiveSubscription, usedTrialSubscription } = await getSubscription({
|
||||
telegramId,
|
||||
});
|
||||
|
||||
// Простая логика для проверки статуса подписки
|
||||
const subscription = subscriptionData?.subscription;
|
||||
const isActive =
|
||||
subscription?.isActive &&
|
||||
subscription?.expiresAt &&
|
||||
new Date() < new Date(subscription.expiresAt);
|
||||
|
||||
// Проверка возможности использования пробного периода
|
||||
const hasUsedTrial = subscription?.subscriptionHistories?.some(
|
||||
(item) => item && item.period === 'trial' && item.state === 'success',
|
||||
);
|
||||
const canUseTrial = !isActive && !hasUsedTrial;
|
||||
const canUseTrial = !usedTrialSubscription;
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900">
|
||||
@ -47,10 +38,12 @@ export default async function ProPage() {
|
||||
</h1>
|
||||
|
||||
<p className="mx-auto mb-8 max-w-2xl text-xl text-gray-600 dark:text-gray-300">
|
||||
{isActive ? 'Ваша подписка Pro активна!' : 'Разблокируйте больше возможностей'}
|
||||
{hasActiveSubscription
|
||||
? 'Ваша подписка Pro активна!'
|
||||
: 'Разблокируйте больше возможностей'}
|
||||
</p>
|
||||
|
||||
{!isActive && (
|
||||
{!hasActiveSubscription && (
|
||||
<div className="flex flex-col items-center justify-center gap-4 sm:flex-row">
|
||||
{canUseTrial && <TryFreeButton />}
|
||||
|
||||
@ -86,23 +79,14 @@ export default async function ProPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-3 rounded-lg border border-gray-200 bg-white/50 p-4 dark:border-gray-700 dark:bg-slate-800/50">
|
||||
<div className="mt-1 shrink-0">
|
||||
<Users className="size-5 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<p className="text-left text-base leading-relaxed text-gray-700 dark:text-gray-300">
|
||||
Ваш профиль доступен всем пользователям в поиске
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-3 rounded-lg border border-gray-200 bg-white/50 p-4 dark:border-gray-700 dark:bg-slate-800/50">
|
||||
{/* <div className="flex items-start gap-3 rounded-lg border border-gray-200 bg-white/50 p-4 dark:border-gray-700 dark:bg-slate-800/50">
|
||||
<div className="mt-1 shrink-0">
|
||||
<Star className="size-5 text-purple-600 dark:text-purple-400" />
|
||||
</div>
|
||||
<p className="text-left text-base leading-relaxed text-gray-700 dark:text-gray-300">
|
||||
Профиль и аватар выделяются цветом
|
||||
</p>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -5,7 +5,6 @@ import { useCustomerQuery } from '@/hooks/api/customers';
|
||||
import { useSubscriptionQuery } from '@/hooks/api/subscriptions';
|
||||
import { Enum_Customer_Role } from '@repo/graphql/types';
|
||||
import { cn } from '@repo/ui/lib/utils';
|
||||
import { getRemainingDays } from '@repo/utils/datetime-format';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
|
||||
@ -14,10 +13,10 @@ export function SubscriptionInfoBar() {
|
||||
|
||||
const { data: { customer } = {} } = useCustomerQuery();
|
||||
|
||||
const isActive = data?.subscription?.isActive;
|
||||
const isActive = data?.hasActiveSubscription;
|
||||
const remainingOrdersCount = data?.remainingOrdersCount;
|
||||
const remainingDays = data?.remainingDays;
|
||||
const maxOrdersPerMonth = data?.maxOrdersPerMonth;
|
||||
const expiresAt = data?.subscription?.expiresAt;
|
||||
|
||||
if (customer?.role === Enum_Customer_Role.Client) return null;
|
||||
|
||||
@ -31,8 +30,8 @@ export function SubscriptionInfoBar() {
|
||||
description = `Доступно ${remainingOrdersCount} из ${maxOrdersPerMonth} записей в этом месяце`;
|
||||
}
|
||||
|
||||
if (isActive && expiresAt) {
|
||||
description = `Осталось ${getRemainingDays(expiresAt)} дней`;
|
||||
if (isActive) {
|
||||
description = `Осталось ${remainingDays} дней`;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -6,7 +6,6 @@ import {
|
||||
createSubscriptionHistory,
|
||||
createTrialSubscription,
|
||||
getSubscription,
|
||||
getSubscriptionHistory,
|
||||
getSubscriptionPrices,
|
||||
getSubscriptionSettings,
|
||||
updateSubscription,
|
||||
@ -43,16 +42,6 @@ export const useSubscriptionPricesQuery = (
|
||||
});
|
||||
};
|
||||
|
||||
export const useSubscriptionHistoryQuery = (
|
||||
variables: Parameters<typeof getSubscriptionHistory>[0],
|
||||
) => {
|
||||
return useQuery({
|
||||
enabled: Boolean(variables.subscriptionId),
|
||||
queryFn: () => getSubscriptionHistory(variables),
|
||||
queryKey: ['subscriptionHistory', variables.subscriptionId],
|
||||
});
|
||||
};
|
||||
|
||||
export const useSubscriptionMutation = () => {
|
||||
const { data: session } = useSession();
|
||||
const telegramId = session?.user?.telegramId;
|
||||
|
||||
@ -53,6 +53,8 @@ export class BaseService {
|
||||
|
||||
const customer = result.data.customers.at(0);
|
||||
|
||||
if (!customer) throw new Error(ERRORS.NOT_FOUND_CUSTOMER);
|
||||
|
||||
if (customer && isCustomerBanned(customer)) {
|
||||
throw new Error(SHARED_ERRORS.NO_PERMISSION);
|
||||
}
|
||||
|
||||
@ -74,7 +74,7 @@ export class OrdersService extends BaseService {
|
||||
const isMasterCreating = slot.master.documentId === customer?.documentId;
|
||||
|
||||
// Если у мастера слота нет активной подписки и не осталось доступных заказов
|
||||
if (!subscription?.isActive && remainingOrdersCount <= 0) {
|
||||
if (!subscription?.active && remainingOrdersCount <= 0) {
|
||||
throw new Error(
|
||||
isMasterCreating ? ERRORS.ORDER_LIMIT_EXCEEDED_MASTER : ERRORS.ORDER_LIMIT_EXCEEDED_CLIENT,
|
||||
);
|
||||
|
||||
@ -4,6 +4,11 @@ import { BaseService } from './base';
|
||||
import { OrdersService } from './orders';
|
||||
import { type VariablesOf } from '@graphql-typed-document-node/core';
|
||||
import dayjs from 'dayjs';
|
||||
import minMax from 'dayjs/plugin/minMax';
|
||||
|
||||
if (!dayjs.prototype.minMax) {
|
||||
dayjs.extend(minMax);
|
||||
}
|
||||
|
||||
export const ERRORS = {
|
||||
FAILED_TO_CREATE_TRIAL_SUBSCRIPTION: 'Не удалось создать пробную подписку',
|
||||
@ -16,7 +21,6 @@ export const ERRORS = {
|
||||
|
||||
export class SubscriptionsService extends BaseService {
|
||||
async createOrUpdateSubscription(payload: { period: GQL.Enum_Subscriptionprice_Period }) {
|
||||
// ищем цену по выбранному периоду
|
||||
const { subscriptionPrices } = await this.getSubscriptionPrices({
|
||||
filters: { period: { eq: payload.period } },
|
||||
});
|
||||
@ -24,75 +28,50 @@ export class SubscriptionsService extends BaseService {
|
||||
const subscriptionPrice = subscriptionPrices[0];
|
||||
if (!subscriptionPrice) throw new Error('Subscription price not found');
|
||||
|
||||
// получаем текущую подписку
|
||||
const { subscription: existingSubscription } = await this.getSubscription({
|
||||
telegramId: this._user.telegramId,
|
||||
});
|
||||
|
||||
let expiresAt: string;
|
||||
const newExpiresAt = dayjs
|
||||
.max(dayjs(existingSubscription?.expiresAt), dayjs())
|
||||
.add(subscriptionPrice.days, 'day');
|
||||
|
||||
if (existingSubscription?.expiresAt) {
|
||||
// --- продлеваем подписку ---
|
||||
expiresAt = dayjs(existingSubscription.expiresAt)
|
||||
.add(subscriptionPrice.days, 'day')
|
||||
.toISOString();
|
||||
const { customer } = await this.checkIsBanned();
|
||||
|
||||
const result = await this.updateSubscription({
|
||||
data: { expiresAt },
|
||||
documentId: existingSubscription.documentId,
|
||||
});
|
||||
const result = await this.createSubscription({
|
||||
data: {
|
||||
active: true,
|
||||
customer: customer.documentId,
|
||||
expiresAt: newExpiresAt.toISOString(),
|
||||
},
|
||||
});
|
||||
|
||||
// создаём запись в истории
|
||||
await this.createSubscriptionHistory({
|
||||
// Добавляем в последнюю подписку ссылку на новую только что созданную
|
||||
if (result?.createSubscription && existingSubscription)
|
||||
await this.updateSubscription({
|
||||
data: {
|
||||
amount: subscriptionPrice.amount,
|
||||
currency: 'RUB',
|
||||
description: subscriptionPrice.description ?? 'Продление подписки',
|
||||
endDate: expiresAt,
|
||||
period: subscriptionPrice.period,
|
||||
source: GQL.Enum_Subscriptionhistory_Source.Renewal,
|
||||
startDate: existingSubscription.expiresAt,
|
||||
state: GQL.Enum_Subscriptionhistory_State.Success,
|
||||
subscription: result?.updateSubscription?.documentId,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// --- создаём новую подписку ---
|
||||
const { customer } = await this.checkIsBanned();
|
||||
if (!customer?.documentId) throw new Error('Customer not found');
|
||||
|
||||
const expiresAtNew = dayjs().add(subscriptionPrice.days, 'day').toISOString();
|
||||
|
||||
const result = await this.createSubscription({
|
||||
data: {
|
||||
autoRenew: true,
|
||||
customer: customer.documentId,
|
||||
expiresAt: expiresAtNew,
|
||||
isActive: true,
|
||||
active: true,
|
||||
nextSubscription: result.createSubscription.documentId,
|
||||
},
|
||||
documentId: existingSubscription?.documentId,
|
||||
});
|
||||
|
||||
expiresAt = result?.createSubscription?.expiresAt ?? expiresAtNew;
|
||||
|
||||
// создаём запись в истории
|
||||
await this.createSubscriptionHistory({
|
||||
data: {
|
||||
amount: subscriptionPrice.amount,
|
||||
currency: 'RUB',
|
||||
description: subscriptionPrice.description ?? 'Новая подписка',
|
||||
endDate: expiresAt,
|
||||
period: subscriptionPrice.period,
|
||||
source: GQL.Enum_Subscriptionhistory_Source.Payment,
|
||||
startDate: dayjs().toISOString(),
|
||||
state: GQL.Enum_Subscriptionhistory_State.Success,
|
||||
subscription: result?.createSubscription?.documentId,
|
||||
},
|
||||
});
|
||||
}
|
||||
await this.createSubscriptionHistory({
|
||||
data: {
|
||||
amount: subscriptionPrice.amount,
|
||||
currency: 'RUB',
|
||||
description: existingSubscription ? 'Продление подписки' : 'Новая подписка',
|
||||
period: subscriptionPrice.period,
|
||||
source: GQL.Enum_Subscriptionhistory_Source.Payment,
|
||||
state: GQL.Enum_Subscriptionhistory_State.Success,
|
||||
subscription: result?.createSubscription?.documentId,
|
||||
subscription_price: subscriptionPrice.documentId,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
expiresAt,
|
||||
formattedDate: dayjs(expiresAt).toDate().toLocaleDateString('ru-RU', {
|
||||
expiresAt: newExpiresAt.toDate(),
|
||||
formattedDate: newExpiresAt.toDate().toLocaleDateString('ru-RU', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
@ -139,13 +118,13 @@ export class SubscriptionsService extends BaseService {
|
||||
const { customer } = await this.checkIsBanned();
|
||||
|
||||
// Проверяем, не использовал ли пользователь уже пробный период
|
||||
const hasUserTrial = await this.hasUserTrialSubscription();
|
||||
const hasUserTrial = await this.usedTrialSubscription();
|
||||
if (hasUserTrial) throw new Error(ERRORS.TRIAL_PERIOD_ALREADY_USED);
|
||||
|
||||
// Получаем цены подписки для определения длительности пробного периода
|
||||
const { subscriptionPrices } = await this.getSubscriptionPrices({
|
||||
filters: {
|
||||
isActive: {
|
||||
active: {
|
||||
eq: true,
|
||||
},
|
||||
},
|
||||
@ -157,7 +136,7 @@ export class SubscriptionsService extends BaseService {
|
||||
(price) => price?.period === GQL.Enum_Subscriptionprice_Period.Trial,
|
||||
);
|
||||
if (!trialPrice) throw new Error(ERRORS.TRIAL_PERIOD_NOT_FOUND);
|
||||
if (!trialPrice.isActive) throw new Error(ERRORS.TRIAL_PERIOD_NOT_ACTIVE);
|
||||
if (!trialPrice.active) throw new Error(ERRORS.TRIAL_PERIOD_NOT_ACTIVE);
|
||||
|
||||
const trialPeriodDays = trialPrice?.days;
|
||||
const now = dayjs();
|
||||
@ -166,10 +145,9 @@ export class SubscriptionsService extends BaseService {
|
||||
// Создаем пробную подписку
|
||||
const subscriptionData = await this.createSubscription({
|
||||
data: {
|
||||
autoRenew: false,
|
||||
active: true,
|
||||
customer: customer?.documentId,
|
||||
expiresAt: expiresAt.toISOString(),
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
@ -185,10 +163,8 @@ export class SubscriptionsService extends BaseService {
|
||||
amount: 0,
|
||||
currency: 'RUB',
|
||||
description: `Пробный период на ${trialPeriodDays} дней`,
|
||||
endDate: expiresAt.toISOString(),
|
||||
period: GQL.Enum_Subscriptionhistory_Period.Trial,
|
||||
period: trialPrice.period,
|
||||
source: GQL.Enum_Subscriptionhistory_Source.Trial,
|
||||
startDate: now.toISOString(),
|
||||
state: GQL.Enum_Subscriptionhistory_State.Success,
|
||||
subscription: subscription.documentId,
|
||||
},
|
||||
@ -197,32 +173,41 @@ export class SubscriptionsService extends BaseService {
|
||||
return subscriptionData;
|
||||
}
|
||||
|
||||
async getSubscription(variables: VariablesOf<typeof GQL.GetSubscriptionDocument>) {
|
||||
async getSubscription({
|
||||
telegramId,
|
||||
}: Pick<VariablesOf<typeof GQL.GetCustomerDocument>, 'telegramId'>) {
|
||||
await this.checkIsBanned();
|
||||
|
||||
const { query } = await getClientWithToken();
|
||||
|
||||
const result = await query({
|
||||
query: GQL.GetSubscriptionDocument,
|
||||
variables,
|
||||
const data = await this.getSubscriptions({
|
||||
filters: {
|
||||
active: {
|
||||
eq: true,
|
||||
},
|
||||
customer: {
|
||||
telegramId: { eq: telegramId },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const subscription = result.data.subscriptions.at(0);
|
||||
|
||||
const hasActiveSubscription = Boolean(
|
||||
subscription?.isActive &&
|
||||
subscription?.expiresAt &&
|
||||
new Date() < new Date(subscription.expiresAt),
|
||||
);
|
||||
const subscription = data.subscriptions.find((x) => !x?.nextSubscription?.documentId);
|
||||
const remainingDays = subscription ? this.getRemainingDays(subscription) : 0;
|
||||
const hasActiveSubscription = subscription?.active && remainingDays > 0;
|
||||
|
||||
const { maxOrdersPerMonth, remainingOrdersCount } = await this.getRemainingOrdersCount();
|
||||
|
||||
return { hasActiveSubscription, maxOrdersPerMonth, remainingOrdersCount, subscription };
|
||||
const usedTrialSubscription = await this.usedTrialSubscription();
|
||||
|
||||
return {
|
||||
hasActiveSubscription,
|
||||
maxOrdersPerMonth,
|
||||
remainingDays,
|
||||
remainingOrdersCount,
|
||||
subscription,
|
||||
usedTrialSubscription,
|
||||
};
|
||||
}
|
||||
|
||||
async getSubscriptionHistory(variables: VariablesOf<typeof GQL.GetSubscriptionHistoryDocument>) {
|
||||
await this.checkIsBanned();
|
||||
|
||||
const { query } = await getClientWithToken();
|
||||
|
||||
const result = await query({
|
||||
@ -248,6 +233,19 @@ export class SubscriptionsService extends BaseService {
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async getSubscriptions(variables?: VariablesOf<typeof GQL.GetSubscriptionsDocument>) {
|
||||
await this.checkIsBanned();
|
||||
|
||||
const { query } = await getClientWithToken();
|
||||
|
||||
const result = await query({
|
||||
query: GQL.GetSubscriptionsDocument,
|
||||
variables,
|
||||
});
|
||||
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async getSubscriptionSettings() {
|
||||
await this.checkIsBanned();
|
||||
|
||||
@ -260,27 +258,6 @@ export class SubscriptionsService extends BaseService {
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async hasUserTrialSubscription() {
|
||||
const { customer } = await this.checkIsBanned();
|
||||
|
||||
const { subscription: existingSubscription } = await this.getSubscription({
|
||||
telegramId: customer?.telegramId,
|
||||
});
|
||||
|
||||
if (!existingSubscription) return false;
|
||||
|
||||
const { subscriptionHistories } = await this.getSubscriptionHistory({
|
||||
filters: {
|
||||
period: { eq: GQL.Enum_Subscriptionhistory_Period.Trial },
|
||||
subscription: { documentId: { eq: existingSubscription.documentId } },
|
||||
},
|
||||
});
|
||||
|
||||
return subscriptionHistories?.some(
|
||||
(history) => history?.state === GQL.Enum_Subscriptionhistory_State.Success,
|
||||
);
|
||||
}
|
||||
|
||||
async updateSubscription(variables: VariablesOf<typeof GQL.UpdateSubscriptionDocument>) {
|
||||
await this.checkIsBanned();
|
||||
|
||||
@ -315,6 +292,49 @@ export class SubscriptionsService extends BaseService {
|
||||
return mutationResult.data;
|
||||
}
|
||||
|
||||
async usedTrialSubscription() {
|
||||
const { customer } = await this._getUser();
|
||||
|
||||
const { subscriptionHistories } = await this.getSubscriptionHistory({
|
||||
filters: {
|
||||
or: [
|
||||
{
|
||||
source: {
|
||||
eq: GQL.Enum_Subscriptionhistory_Source.Trial,
|
||||
},
|
||||
},
|
||||
{
|
||||
period: {
|
||||
eq: GQL.Enum_Subscriptionprice_Period.Trial,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
subscription: {
|
||||
customer: {
|
||||
documentId: {
|
||||
eq: customer?.documentId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return subscriptionHistories?.some(
|
||||
(history) => history?.state === GQL.Enum_Subscriptionhistory_State.Success,
|
||||
);
|
||||
}
|
||||
|
||||
private getRemainingDays(subscription: GQL.SubscriptionFieldsFragment) {
|
||||
if (!subscription) return 0;
|
||||
|
||||
const remainingDays = dayjs(subscription?.expiresAt).diff(dayjs(), 'day', true);
|
||||
|
||||
if (remainingDays <= 0) return 0;
|
||||
|
||||
return Math.ceil(remainingDays);
|
||||
}
|
||||
|
||||
private async getRemainingOrdersCount() {
|
||||
const ordersService = new OrdersService(this._user);
|
||||
|
||||
|
||||
@ -1,21 +1,24 @@
|
||||
fragment SubscriptionFields on Subscription {
|
||||
documentId
|
||||
isActive
|
||||
active
|
||||
expiresAt
|
||||
autoRenew
|
||||
nextSubscription {
|
||||
documentId
|
||||
}
|
||||
}
|
||||
|
||||
fragment SubscriptionHistoryFields on SubscriptionHistory {
|
||||
documentId
|
||||
period
|
||||
startDate
|
||||
endDate
|
||||
amount
|
||||
currency
|
||||
state
|
||||
paymentId
|
||||
source
|
||||
description
|
||||
subscription {
|
||||
...SubscriptionFields
|
||||
}
|
||||
}
|
||||
|
||||
fragment SubscriptionSettingFields on SubscriptionSetting {
|
||||
@ -30,7 +33,7 @@ fragment SubscriptionPriceFields on SubscriptionPrice {
|
||||
days
|
||||
amount
|
||||
currency
|
||||
isActive
|
||||
active
|
||||
description
|
||||
}
|
||||
|
||||
@ -48,8 +51,8 @@ fragment SubscriptionRewardFields on SubscriptionReward {
|
||||
}
|
||||
}
|
||||
|
||||
query GetSubscription($telegramId: Long) {
|
||||
subscriptions(filters: { customer: { telegramId: { eq: $telegramId } } }) {
|
||||
query GetSubscriptions($filters: SubscriptionFiltersInput) {
|
||||
subscriptions(filters: $filters) {
|
||||
...SubscriptionFields
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1,6 +1,5 @@
|
||||
/* eslint-disable import/no-unassigned-import */
|
||||
import { type OpUnitType } from 'dayjs';
|
||||
import dayjs, { type ConfigType } from 'dayjs';
|
||||
import dayjs, { type ConfigType, type OpUnitType } from 'dayjs';
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import 'dayjs/locale/ru';
|
||||
@ -85,10 +84,6 @@ export function getMinutes(time: string) {
|
||||
return Number.parseInt(hours, 10) * 60 + Number.parseInt(minutes, 10);
|
||||
}
|
||||
|
||||
export function getRemainingDays(date: DateTime) {
|
||||
return dayjs(date).diff(dayjs(), 'day');
|
||||
}
|
||||
|
||||
export function getTimeZoneLabel(tz: string = DEFAULT_TZ): string {
|
||||
if (tz === DEFAULT_TZ) return 'МСК';
|
||||
const offset = dayjs().tz(tz).format('Z');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user