mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
769 lines
18 KiB
TypeScript
769 lines
18 KiB
TypeScript
// User types
|
||
export interface User {
|
||
id: number;
|
||
telegram_id: number | null; // Nullable для email-only пользователей
|
||
username: string | null;
|
||
first_name: string | null;
|
||
last_name: string | null;
|
||
email: string | null;
|
||
email_verified: boolean;
|
||
balance_kopeks: number;
|
||
balance_rubles: number;
|
||
referral_code: string | null;
|
||
language: string;
|
||
created_at: string;
|
||
auth_type: 'telegram' | 'email' | 'google' | 'yandex' | 'discord' | 'vk'; // Тип аутентификации
|
||
}
|
||
|
||
// OAuth types
|
||
export interface OAuthProvider {
|
||
name: string;
|
||
display_name: string;
|
||
}
|
||
|
||
// Campaign bonus info (returned during auth)
|
||
export interface CampaignBonusInfo {
|
||
campaign_name: string;
|
||
bonus_type: 'balance' | 'subscription' | 'tariff' | 'none';
|
||
balance_kopeks: number;
|
||
subscription_days: number | null;
|
||
tariff_name: string | null;
|
||
}
|
||
|
||
// Auth types
|
||
export interface AuthResponse {
|
||
access_token: string;
|
||
refresh_token: string;
|
||
token_type: string;
|
||
expires_in: number;
|
||
user: User;
|
||
campaign_bonus?: CampaignBonusInfo | null;
|
||
}
|
||
|
||
export interface TokenResponse {
|
||
access_token: string;
|
||
refresh_token: string;
|
||
token_type: string;
|
||
expires_in: number;
|
||
}
|
||
|
||
export interface RegisterResponse {
|
||
message: string;
|
||
email: string;
|
||
requires_verification: boolean;
|
||
}
|
||
|
||
// Subscription types
|
||
export interface ServerInfo {
|
||
uuid: string;
|
||
name: string;
|
||
country_code: string | null;
|
||
}
|
||
|
||
export interface TrafficPurchase {
|
||
id: number;
|
||
traffic_gb: number;
|
||
expires_at: string;
|
||
created_at: string;
|
||
days_remaining: number;
|
||
progress_percent: number;
|
||
}
|
||
|
||
export interface Subscription {
|
||
id: number;
|
||
status: string;
|
||
is_trial: boolean;
|
||
start_date: string;
|
||
end_date: string;
|
||
days_left: number;
|
||
hours_left: number;
|
||
minutes_left: number;
|
||
time_left_display: string;
|
||
traffic_limit_gb: number;
|
||
traffic_used_gb: number;
|
||
traffic_used_percent: number;
|
||
device_limit: number;
|
||
connected_squads: string[];
|
||
servers: ServerInfo[];
|
||
autopay_enabled: boolean;
|
||
autopay_days_before: number;
|
||
subscription_url: string | null;
|
||
hide_subscription_link: boolean;
|
||
is_active: boolean;
|
||
is_expired: boolean;
|
||
is_limited: boolean;
|
||
traffic_purchases?: TrafficPurchase[];
|
||
// Daily tariff fields
|
||
is_daily?: boolean;
|
||
is_daily_paused?: boolean;
|
||
daily_price_kopeks?: number;
|
||
next_daily_charge_at?: string; // ISO datetime string
|
||
// Tariff info
|
||
tariff_id?: number;
|
||
tariff_name?: string;
|
||
traffic_reset_mode?: string;
|
||
}
|
||
|
||
// Response wrapper for subscription status endpoint
|
||
export interface SubscriptionStatusResponse {
|
||
has_subscription: boolean;
|
||
subscription: Subscription | null;
|
||
}
|
||
|
||
// Multi-tariff subscription list item (from GET /cabinet/subscriptions)
|
||
export interface SubscriptionListItem {
|
||
id: number;
|
||
status: string;
|
||
tariff_id: number | null;
|
||
tariff_name: string | null;
|
||
traffic_limit_gb: number;
|
||
traffic_used_gb: number;
|
||
device_limit: number;
|
||
end_date: string | null;
|
||
subscription_url: string | null;
|
||
subscription_crypto_link: string | null;
|
||
is_trial: boolean;
|
||
is_daily?: boolean;
|
||
is_daily_paused?: boolean;
|
||
autopay_enabled: boolean;
|
||
connected_squads: string[] | null;
|
||
}
|
||
|
||
// Response from GET /cabinet/subscriptions (multi-tariff)
|
||
export interface SubscriptionsListResponse {
|
||
subscriptions: SubscriptionListItem[];
|
||
multi_tariff_enabled: boolean;
|
||
}
|
||
|
||
// Device types
|
||
export interface Device {
|
||
hwid: string;
|
||
platform: string;
|
||
device_model: string;
|
||
created_at: string | null;
|
||
/**
|
||
* User-set local alias persisted in the bot DB (`user_device_aliases`).
|
||
* `null` when the user hasn't renamed the device — clients fall back
|
||
* to `device_model` / `platform` for display.
|
||
*/
|
||
local_name?: string | null;
|
||
}
|
||
|
||
export interface DevicesResponse {
|
||
devices: Device[];
|
||
total: number;
|
||
device_limit: number;
|
||
}
|
||
|
||
// Tariff switch preview
|
||
export interface TariffSwitchPreview {
|
||
can_switch: boolean;
|
||
current_tariff_id: number | null;
|
||
current_tariff_name: string | null;
|
||
new_tariff_id: number;
|
||
new_tariff_name: string;
|
||
remaining_days: number;
|
||
upgrade_cost_kopeks: number;
|
||
upgrade_cost_label: string;
|
||
balance_kopeks: number;
|
||
balance_label: string;
|
||
has_enough_balance: boolean;
|
||
missing_amount_kopeks: number;
|
||
missing_amount_label: string;
|
||
is_upgrade: boolean;
|
||
// Discount fields (from promo group)
|
||
base_upgrade_cost_kopeks?: number;
|
||
discount_percent?: number;
|
||
discount_kopeks?: number;
|
||
}
|
||
|
||
export interface RenewalOption {
|
||
period_days: number;
|
||
price_kopeks: number;
|
||
price_rubles: number;
|
||
discount_percent: number;
|
||
original_price_kopeks: number | null;
|
||
}
|
||
|
||
export interface TrafficPackage {
|
||
gb: number;
|
||
price_kopeks: number;
|
||
price_rubles: number;
|
||
is_unlimited: boolean;
|
||
// Discount fields (from promo group)
|
||
base_price_kopeks?: number;
|
||
discount_percent?: number;
|
||
discount_kopeks?: number;
|
||
}
|
||
|
||
export interface TrialInfo {
|
||
is_available: boolean;
|
||
duration_days: number;
|
||
traffic_limit_gb: number;
|
||
device_limit: number;
|
||
requires_payment: boolean;
|
||
price_kopeks: number;
|
||
price_rubles: number;
|
||
reason_unavailable: string | null;
|
||
}
|
||
|
||
// Purchase options types
|
||
export interface TrafficOption {
|
||
value: number;
|
||
label: string;
|
||
price_kopeks: number;
|
||
price_label: string;
|
||
original_price_kopeks?: number;
|
||
original_price_label?: string;
|
||
discount_percent?: number;
|
||
is_available: boolean;
|
||
is_default?: boolean;
|
||
}
|
||
|
||
export interface ServerOption {
|
||
uuid: string;
|
||
name: string;
|
||
price_kopeks: number;
|
||
price_label: string;
|
||
original_price_kopeks?: number;
|
||
original_price_label?: string;
|
||
discount_percent?: number;
|
||
is_available: boolean;
|
||
}
|
||
|
||
export interface DevicesConfig {
|
||
min: number;
|
||
max: number;
|
||
default: number;
|
||
current: number;
|
||
price_per_device_kopeks: number;
|
||
price_per_device_label: string;
|
||
price_per_device_original_kopeks?: number;
|
||
discount_percent?: number;
|
||
}
|
||
|
||
export interface TrafficConfig {
|
||
selectable: boolean;
|
||
mode: string;
|
||
options: TrafficOption[];
|
||
default?: number;
|
||
current?: number;
|
||
}
|
||
|
||
export interface ServersConfig {
|
||
options: ServerOption[];
|
||
min: number;
|
||
max: number;
|
||
default: string[];
|
||
selected: string[];
|
||
}
|
||
|
||
export interface PeriodOption {
|
||
id: string;
|
||
period_days: number;
|
||
months: number;
|
||
label: string;
|
||
price_kopeks: number;
|
||
price_label: string;
|
||
per_month_price_kopeks: number;
|
||
per_month_price_label: string;
|
||
discount_percent?: number;
|
||
original_price_kopeks?: number;
|
||
original_price_label?: string;
|
||
is_available: boolean;
|
||
traffic: TrafficConfig;
|
||
servers: ServersConfig;
|
||
devices: DevicesConfig;
|
||
}
|
||
|
||
// Tariff types for tariffs mode
|
||
export interface TariffPeriod {
|
||
days: number;
|
||
months: number;
|
||
label: string;
|
||
price_kopeks: number;
|
||
price_label: string;
|
||
price_per_month_kopeks: number;
|
||
price_per_month_label: string;
|
||
// Discount info (if promo group discount applied)
|
||
original_price_kopeks?: number;
|
||
original_price_label?: string;
|
||
original_per_month_kopeks?: number;
|
||
original_per_month_label?: string;
|
||
discount_percent?: number;
|
||
discount_amount_kopeks?: number;
|
||
discount_label?: string;
|
||
// Extra devices info (additional devices beyond tariff base)
|
||
extra_devices_count?: number;
|
||
extra_devices_cost_kopeks?: number;
|
||
extra_devices_cost_label?: string;
|
||
base_tariff_price_kopeks?: number;
|
||
base_tariff_price_label?: string;
|
||
}
|
||
|
||
export interface TariffServer {
|
||
uuid: string;
|
||
name: string;
|
||
}
|
||
|
||
export interface Tariff {
|
||
id: number;
|
||
name: string;
|
||
description: string | null;
|
||
tier_level: number;
|
||
traffic_limit_gb: number;
|
||
traffic_limit_label: string;
|
||
is_unlimited_traffic: boolean;
|
||
device_limit: number;
|
||
base_device_limit?: number;
|
||
extra_devices_count: number;
|
||
servers_count: number;
|
||
servers: TariffServer[];
|
||
periods: TariffPeriod[];
|
||
is_current: boolean;
|
||
is_available: boolean;
|
||
// Custom days options
|
||
custom_days_enabled?: boolean;
|
||
price_per_day_kopeks?: number;
|
||
min_days?: number;
|
||
max_days?: number;
|
||
// Custom traffic options
|
||
custom_traffic_enabled?: boolean;
|
||
traffic_price_per_gb_kopeks?: number;
|
||
min_traffic_gb?: number;
|
||
max_traffic_gb?: number;
|
||
// Device price
|
||
device_price_kopeks?: number;
|
||
// Traffic topup options
|
||
traffic_topup_enabled?: boolean;
|
||
traffic_topup_packages?: number[];
|
||
max_topup_traffic_gb?: number;
|
||
// Daily tariff options
|
||
is_daily?: boolean;
|
||
daily_price_kopeks?: number;
|
||
// Promo group discount info
|
||
promo_group_name?: string;
|
||
original_device_price_kopeks?: number;
|
||
device_discount_percent?: number;
|
||
original_daily_price_kopeks?: number;
|
||
daily_discount_percent?: number;
|
||
original_price_per_day_kopeks?: number;
|
||
custom_days_discount_percent?: number;
|
||
// Traffic reset
|
||
traffic_reset_mode?: string;
|
||
// Multi-tariff: already purchased by user
|
||
is_purchased?: boolean;
|
||
}
|
||
|
||
export interface TariffsPurchaseOptions {
|
||
sales_mode: 'tariffs';
|
||
tariffs: Tariff[];
|
||
current_tariff_id: number | null;
|
||
balance_kopeks: number;
|
||
balance_label: string;
|
||
// New fields for expired subscription handling
|
||
subscription_status?: string;
|
||
subscription_is_expired?: boolean;
|
||
has_subscription?: boolean;
|
||
// Multi-tariff: all available tariffs already purchased
|
||
all_tariffs_purchased?: boolean;
|
||
}
|
||
|
||
export interface ClassicPurchaseOptions {
|
||
sales_mode: 'classic';
|
||
currency: string;
|
||
balance_kopeks: number;
|
||
balance_label: string;
|
||
subscription_id: number | null;
|
||
periods: PeriodOption[];
|
||
traffic: TrafficConfig;
|
||
servers: ServersConfig;
|
||
devices: DevicesConfig;
|
||
selection: {
|
||
period_id: string;
|
||
period_days: number;
|
||
traffic_value: number;
|
||
servers: string[];
|
||
devices: number;
|
||
};
|
||
}
|
||
|
||
export type PurchaseOptions = TariffsPurchaseOptions | ClassicPurchaseOptions;
|
||
|
||
export interface PurchaseSelection {
|
||
period_id?: string;
|
||
period_days?: number;
|
||
traffic_value?: number;
|
||
servers?: string[];
|
||
devices?: number;
|
||
}
|
||
|
||
export interface PurchasePreview {
|
||
total_price_kopeks: number;
|
||
total_price_label: string;
|
||
original_price_kopeks?: number;
|
||
original_price_label?: string;
|
||
discount_percent?: number;
|
||
discount_label?: string;
|
||
per_month_price_kopeks: number;
|
||
per_month_price_label: string;
|
||
breakdown: { label: string; value: string }[];
|
||
balance_kopeks: number;
|
||
balance_label: string;
|
||
missing_amount_kopeks: number;
|
||
missing_amount_label?: string;
|
||
can_purchase: boolean;
|
||
status_message?: string;
|
||
}
|
||
|
||
// Balance types
|
||
export interface Balance {
|
||
balance_kopeks: number;
|
||
balance_rubles: number;
|
||
}
|
||
|
||
export interface Transaction {
|
||
id: number;
|
||
type: string;
|
||
amount_kopeks: number;
|
||
amount_rubles: number;
|
||
description: string | null;
|
||
payment_method: string | null;
|
||
is_completed: boolean;
|
||
created_at: string;
|
||
completed_at: string | null;
|
||
}
|
||
|
||
export interface PaymentMethodOption {
|
||
id: string;
|
||
name: string;
|
||
description?: string | null;
|
||
}
|
||
|
||
export interface PaymentMethod {
|
||
id: string;
|
||
name: string;
|
||
description: string | null;
|
||
min_amount_kopeks: number;
|
||
max_amount_kopeks: number;
|
||
is_available: boolean;
|
||
options?: PaymentMethodOption[] | null;
|
||
quick_amounts?: number[];
|
||
// Если true — после получения payment_url кабинет сразу делает
|
||
// window.location.href вместо показа панели с кнопкой "Открыть".
|
||
open_url_direct?: boolean;
|
||
}
|
||
|
||
// Referral types
|
||
export interface ReferralInfo {
|
||
referral_code: string;
|
||
referral_link: string;
|
||
bot_referral_link?: string;
|
||
total_referrals: number;
|
||
active_referrals: number;
|
||
total_earnings_kopeks: number;
|
||
total_earnings_rubles: number;
|
||
commission_percent: number;
|
||
available_balance_kopeks: number;
|
||
available_balance_rubles: number;
|
||
withdrawn_kopeks: number;
|
||
}
|
||
|
||
export interface ReferralTerms {
|
||
is_enabled: boolean;
|
||
commission_percent: number;
|
||
minimum_topup_kopeks: number;
|
||
minimum_topup_rubles: number;
|
||
first_topup_bonus_kopeks: number;
|
||
first_topup_bonus_rubles: number;
|
||
inviter_bonus_kopeks: number;
|
||
inviter_bonus_rubles: number;
|
||
max_commission_payments: number;
|
||
partner_section_visible?: boolean;
|
||
}
|
||
|
||
// Ticket types
|
||
export interface TicketMediaItem {
|
||
type: 'photo' | 'video' | 'document';
|
||
file_id: string;
|
||
caption?: string | null;
|
||
/** Signed, expiring download token (response only). */
|
||
token?: string | null;
|
||
}
|
||
|
||
export interface TicketMessage {
|
||
id: number;
|
||
message_text: string;
|
||
is_from_admin: boolean;
|
||
has_media: boolean;
|
||
media_type: string | null;
|
||
media_file_id: string | null;
|
||
/** Signed, expiring download token for the legacy single media_file_id. */
|
||
media_token?: string | null;
|
||
media_caption: string | null;
|
||
media_items?: TicketMediaItem[] | null;
|
||
created_at: string;
|
||
}
|
||
|
||
export interface Ticket {
|
||
id: number;
|
||
title: string;
|
||
status: string;
|
||
priority: string;
|
||
created_at: string;
|
||
updated_at: string;
|
||
closed_at: string | null;
|
||
messages_count: number;
|
||
last_message: TicketMessage | null;
|
||
}
|
||
|
||
export interface TicketDetail extends Omit<Ticket, 'messages_count' | 'last_message'> {
|
||
is_reply_blocked: boolean;
|
||
messages: TicketMessage[];
|
||
}
|
||
|
||
export interface SupportConfig {
|
||
tickets_enabled: boolean;
|
||
support_type: 'tickets' | 'profile' | 'url' | 'both';
|
||
support_url?: string | null;
|
||
support_username?: string | null;
|
||
}
|
||
|
||
// Paginated response
|
||
export interface PaginatedResponse<T> {
|
||
items: T[];
|
||
total: number;
|
||
page: number;
|
||
per_page: number;
|
||
pages: number;
|
||
}
|
||
|
||
// App config types (for connection setup)
|
||
export interface LocalizedText {
|
||
[key: string]: string;
|
||
}
|
||
|
||
// Remnawave format types
|
||
export interface RemnawaveButtonClient {
|
||
url?: string;
|
||
link?: string;
|
||
text: LocalizedText;
|
||
type?: 'external' | 'subscriptionLink' | 'copyButton';
|
||
svgIconKey?: string;
|
||
resolvedUrl?: string;
|
||
}
|
||
|
||
export interface RemnawaveBlockClient {
|
||
title: LocalizedText;
|
||
description: LocalizedText;
|
||
buttons?: RemnawaveButtonClient[];
|
||
svgIconKey?: string;
|
||
svgIconColor?: string;
|
||
}
|
||
|
||
export interface RemnawaveAppClient {
|
||
name: string;
|
||
featured?: boolean;
|
||
deepLink?: string | null;
|
||
svgIconKey?: string;
|
||
blocks: RemnawaveBlockClient[];
|
||
}
|
||
|
||
export interface RemnawavePlatformData {
|
||
svgIconKey?: string;
|
||
displayName?: LocalizedText;
|
||
apps: RemnawaveAppClient[];
|
||
}
|
||
|
||
export interface AppConfig {
|
||
platformNames: Record<string, LocalizedText>;
|
||
hasSubscription: boolean;
|
||
subscriptionUrl: string | null;
|
||
hideLink?: boolean;
|
||
branding?: {
|
||
name?: string;
|
||
logoUrl?: string;
|
||
supportUrl?: string;
|
||
};
|
||
|
||
// Remnawave
|
||
isRemnawave?: boolean;
|
||
svgLibrary?: Record<string, string | { svgString: string }>;
|
||
baseTranslations?: Record<string, LocalizedText>;
|
||
baseSettings?: { isShowTutorialButton: boolean; tutorialUrl: string };
|
||
uiConfig?: {
|
||
installationGuidesBlockType?: 'cards' | 'timeline' | 'accordion' | 'minimal';
|
||
};
|
||
|
||
platforms: Record<string, RemnawavePlatformData>;
|
||
}
|
||
|
||
// Pending payment types
|
||
export interface PendingPayment {
|
||
id: number;
|
||
method: string;
|
||
method_display: string;
|
||
identifier: string;
|
||
amount_kopeks: number;
|
||
amount_rubles: number;
|
||
status: string;
|
||
status_emoji: string;
|
||
status_text: string;
|
||
is_paid: boolean;
|
||
is_checkable: boolean;
|
||
created_at: string;
|
||
expires_at: string | null;
|
||
payment_url: string | null;
|
||
user_id?: number;
|
||
user_telegram_id?: number;
|
||
user_username?: string | null;
|
||
user_email?: string | null;
|
||
}
|
||
|
||
export interface ManualCheckResponse {
|
||
success: boolean;
|
||
message: string;
|
||
payment: PendingPayment | null;
|
||
status_changed: boolean;
|
||
old_status: string | null;
|
||
new_status: string | null;
|
||
}
|
||
|
||
// Saved payment method (card) for recurrent payments
|
||
export interface SavedCard {
|
||
id: number;
|
||
method_type: string;
|
||
card_last4: string | null;
|
||
card_type: string | null;
|
||
title: string | null;
|
||
created_at: string;
|
||
}
|
||
|
||
export interface SavedCardsResponse {
|
||
cards: SavedCard[];
|
||
recurrent_enabled: boolean;
|
||
}
|
||
|
||
// Ticket notifications types
|
||
export interface TicketNotification {
|
||
id: number;
|
||
ticket_id: number;
|
||
notification_type: 'new_ticket' | 'admin_reply' | 'user_reply';
|
||
message: string | null;
|
||
is_read: boolean;
|
||
created_at: string;
|
||
read_at: string | null;
|
||
}
|
||
|
||
export interface TicketNotificationList {
|
||
items: TicketNotification[];
|
||
unread_count: number;
|
||
}
|
||
|
||
export interface UnreadCountResponse {
|
||
unread_count: number;
|
||
}
|
||
|
||
export interface TicketSettings {
|
||
sla_enabled: boolean;
|
||
sla_minutes: number;
|
||
sla_check_interval_seconds: number;
|
||
sla_reminder_cooldown_minutes: number;
|
||
support_system_mode: string;
|
||
cabinet_user_notifications_enabled: boolean;
|
||
cabinet_admin_notifications_enabled: boolean;
|
||
}
|
||
|
||
// Payment method config types (admin)
|
||
export interface PaymentMethodSubOptionInfo {
|
||
id: string;
|
||
name: string;
|
||
}
|
||
|
||
export interface PaymentMethodConfig {
|
||
method_id: string;
|
||
sort_order: number;
|
||
is_enabled: boolean;
|
||
display_name: string | null;
|
||
default_display_name: string;
|
||
sub_options: Record<string, boolean> | null;
|
||
available_sub_options: PaymentMethodSubOptionInfo[] | null;
|
||
quick_amounts: number[] | null;
|
||
default_quick_amounts: number[];
|
||
min_amount_kopeks: number | null;
|
||
max_amount_kopeks: number | null;
|
||
default_min_amount_kopeks: number;
|
||
default_max_amount_kopeks: number;
|
||
user_type_filter: 'all' | 'telegram' | 'email';
|
||
first_topup_filter: 'any' | 'yes' | 'no';
|
||
promo_group_filter_mode: 'all' | 'selected';
|
||
allowed_promo_group_ids: number[];
|
||
open_url_direct: boolean;
|
||
is_provider_configured: boolean;
|
||
created_at: string | null;
|
||
updated_at: string | null;
|
||
}
|
||
|
||
export interface PromoGroupSimple {
|
||
id: number;
|
||
name: string;
|
||
}
|
||
|
||
// Account Linking
|
||
export interface LinkedProvider {
|
||
provider: string;
|
||
linked: boolean;
|
||
identifier: string | null;
|
||
}
|
||
|
||
export interface LinkedProvidersResponse {
|
||
providers: LinkedProvider[];
|
||
}
|
||
|
||
export interface LinkCallbackResponse {
|
||
success: boolean;
|
||
message: string | null;
|
||
merge_required: boolean;
|
||
merge_token: string | null;
|
||
}
|
||
|
||
export interface ServerCompleteResponse extends LinkCallbackResponse {
|
||
provider: string;
|
||
}
|
||
|
||
// Account Merge
|
||
export interface MergeSubscriptionPreview {
|
||
status: string;
|
||
is_trial: boolean;
|
||
end_date: string | null;
|
||
traffic_limit_gb: number;
|
||
traffic_used_gb: number;
|
||
device_limit: number;
|
||
tariff_name: string | null;
|
||
autopay_enabled: boolean;
|
||
}
|
||
|
||
export interface MergeAccountPreview {
|
||
id: number;
|
||
username: string | null;
|
||
first_name: string | null;
|
||
email: string | null;
|
||
auth_methods: string[];
|
||
balance_kopeks: number;
|
||
subscription: MergeSubscriptionPreview | null;
|
||
created_at: string | null;
|
||
}
|
||
|
||
export interface MergePreviewResponse {
|
||
primary: MergeAccountPreview;
|
||
secondary: MergeAccountPreview;
|
||
expires_in_seconds: number;
|
||
}
|
||
|
||
export interface MergeResponse {
|
||
success: boolean;
|
||
access_token: string | null;
|
||
refresh_token: string | null;
|
||
user: User | null;
|
||
}
|