tonproof fix, notification
This commit is contained in:
parent
1b96d049c7
commit
91d9c916e2
|
|
@ -1,27 +1,29 @@
|
|||
import "~/app/styles/globals.css";
|
||||
import '~/app/styles/globals.css';
|
||||
import '~/shared/libs/buffer';
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useExpand, useWebApp } from "@vkruglikov/react-telegram-web-app";
|
||||
import { useEffect } from 'react';
|
||||
import { useExpand, useWebApp } from '@vkruglikov/react-telegram-web-app';
|
||||
|
||||
import { Providers } from "~/app/providers";
|
||||
import { AppRouter } from "~/app/router";
|
||||
import { Providers } from '~/app/providers';
|
||||
import { AppRouter } from '~/app/router';
|
||||
import { Notification } from '~/shared/ui/notification';
|
||||
|
||||
export const App = () => {
|
||||
const WebApp = useWebApp();
|
||||
const [, expand] = useExpand();
|
||||
const WebApp = useWebApp();
|
||||
const [, expand] = useExpand();
|
||||
|
||||
useEffect(() => {
|
||||
WebApp.enableClosingConfirmation();
|
||||
expand();
|
||||
useEffect(() => {
|
||||
WebApp.enableClosingConfirmation();
|
||||
expand();
|
||||
|
||||
WebApp.setHeaderColor("#1d1d1b");
|
||||
WebApp.setBackgroundColor("#1d1d1b");
|
||||
}, []);
|
||||
WebApp.setHeaderColor('#1d1d1b');
|
||||
WebApp.setBackgroundColor('#1d1d1b');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Providers>
|
||||
<AppRouter />
|
||||
</Providers>
|
||||
);
|
||||
return (
|
||||
<Providers>
|
||||
<AppRouter />
|
||||
<Notification />
|
||||
</Providers>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -36,42 +36,47 @@ export const ViewContentPage = () => {
|
|||
|
||||
const handleBuyContentTON = useCallback(async () => {
|
||||
try {
|
||||
// Helper function to wait for wallet connection
|
||||
const waitForConnection = async (timeoutMs = 30000, intervalMs = 500) => {
|
||||
const startTime = Date.now();
|
||||
|
||||
while (Date.now() - startTime < timeoutMs) {
|
||||
if (tonConnectUI.connected) {
|
||||
return true;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
||||
}
|
||||
|
||||
return false; // Timed out
|
||||
};
|
||||
|
||||
// If not connected, start connection process
|
||||
// Если не подключен, начинаем процесс подключения через auth
|
||||
if (!tonConnectUI.connected) {
|
||||
console.log('DEBUG: Wallet not connected, opening modal');
|
||||
console.log('DEBUG: Wallet not connected, using auth flow first');
|
||||
|
||||
// Open connection modal
|
||||
await tonConnectUI.openModal();
|
||||
// Вызываем auth.mutateAsync() до открытия модального окна
|
||||
// Это настроит параметры подключения с TonProof
|
||||
try {
|
||||
await auth.mutateAsync();
|
||||
|
||||
// Wait for connection
|
||||
const connected = await waitForConnection();
|
||||
if (!connected) {
|
||||
console.log('DEBUG: Connection timed out or was cancelled');
|
||||
// Проверяем, установилось ли подключение после auth
|
||||
if (!tonConnectUI.connected) {
|
||||
console.log('DEBUG: Auth did not establish connection, returning');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('DEBUG: Connection and authentication successful');
|
||||
} catch (error) {
|
||||
console.log('Ошибка подключения кошелька', 'danger');
|
||||
console.error('DEBUG: Auth failed during connection:', error);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('DEBUG: Connection successful, authenticating');
|
||||
await auth.mutateAsync();
|
||||
} else {
|
||||
// Already connected, just authenticate
|
||||
// Если уже подключены, просто аутентифицируемся
|
||||
console.log('DEBUG: Already connected, authenticating');
|
||||
await auth.mutateAsync();
|
||||
}
|
||||
|
||||
// Proceed with purchase
|
||||
// Проверка наличия TonProof
|
||||
if (
|
||||
tonConnectUI.wallet?.connectItems?.tonProof &&
|
||||
!('error' in tonConnectUI.wallet.connectItems.tonProof)
|
||||
) {
|
||||
console.log(
|
||||
'DEBUG: TonProof available:',
|
||||
tonConnectUI.wallet.connectItems.tonProof
|
||||
);
|
||||
} else {
|
||||
console.warn('DEBUG: TonProof not available after connection');
|
||||
}
|
||||
|
||||
// Теперь продолжаем с покупкой
|
||||
console.log('DEBUG: Proceeding with purchase');
|
||||
const contentResponse = await purchaseContent({
|
||||
content_address: WebApp.initDataUnsafe?.start_param,
|
||||
|
|
|
|||
|
|
@ -129,6 +129,26 @@ export const useAuth = () => {
|
|||
return true;
|
||||
};
|
||||
|
||||
// Helper для ожидания подключения с таймаутом
|
||||
const waitForConnection = async (timeoutMs = 30000, checkIntervalMs = 500) => {
|
||||
console.log('DEBUG: Waiting for wallet connection');
|
||||
const startTime = Date.now();
|
||||
|
||||
while (Date.now() - startTime < timeoutMs) {
|
||||
if (tonConnectUI.connected) {
|
||||
console.log('DEBUG: Connection detected');
|
||||
// Даем дополнительное время для инициализации connectItems
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
return true;
|
||||
}
|
||||
// Проверяем статус каждые checkIntervalMs
|
||||
await new Promise((resolve) => setTimeout(resolve, checkIntervalMs));
|
||||
}
|
||||
|
||||
console.log('DEBUG: Connection wait timed out');
|
||||
return false;
|
||||
};
|
||||
|
||||
return useMutation(['auth'], async () => {
|
||||
clearInterval(interval.current);
|
||||
let authResult;
|
||||
|
|
@ -150,13 +170,34 @@ export const useAuth = () => {
|
|||
// Start periodic refresh of the payload
|
||||
interval.current = setInterval(prepareConnectParams, payloadTTLMS);
|
||||
|
||||
// Open the modal - this will not resolve until connection or cancellation
|
||||
await tonConnectUI.openModal();
|
||||
// Open the modal and wait for connection
|
||||
try {
|
||||
console.log('DEBUG: Opening wallet connect modal');
|
||||
tonConnectUI.openModal();
|
||||
|
||||
// Check if connection was successful
|
||||
if (!tonConnectUI.connected) {
|
||||
console.log('DEBUG: Connection cancelled or failed');
|
||||
throw new Error('Wallet connection cancelled or failed');
|
||||
// Ждем подключения кошелька
|
||||
const connected = await waitForConnection();
|
||||
|
||||
if (!connected || !tonConnectUI.connected) {
|
||||
console.log('DEBUG: Connection cancelled or failed after waiting');
|
||||
throw new Error('Wallet connection cancelled or failed');
|
||||
}
|
||||
|
||||
// Даем дополнительное время для полной инициализации wallet
|
||||
if (!tonConnectUI.wallet || !tonConnectUI.wallet.connectItems) {
|
||||
console.log('DEBUG: Wallet object not fully initialized, waiting...');
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
}
|
||||
|
||||
console.log('DEBUG: Connection successful, wallet details:', {
|
||||
connected: tonConnectUI.connected,
|
||||
hasWallet: !!tonConnectUI.wallet,
|
||||
hasConnectItems: !!tonConnectUI.wallet?.connectItems,
|
||||
hasTonProof: !!tonConnectUI.wallet?.connectItems?.tonProof,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('DEBUG: Error during wallet connection:', error);
|
||||
throw new Error('Wallet connection process failed');
|
||||
}
|
||||
|
||||
// Check if we have a proof after connection
|
||||
|
|
|
|||
|
|
@ -1,100 +1,135 @@
|
|||
import { create } from "zustand";
|
||||
import { create } from 'zustand';
|
||||
|
||||
export type Royalty = {
|
||||
address: string;
|
||||
value: number;
|
||||
address: string;
|
||||
value: number;
|
||||
};
|
||||
|
||||
type NotificationType = 'success' | 'danger' | 'warning' | 'info';
|
||||
|
||||
interface Notification {
|
||||
id: string;
|
||||
message: string;
|
||||
type: NotificationType;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
type RootStore = {
|
||||
name: string;
|
||||
setName: (name: string) => void;
|
||||
name: string;
|
||||
setName: (name: string) => void;
|
||||
|
||||
author: string;
|
||||
setAuthor: (author: string) => void;
|
||||
author: string;
|
||||
setAuthor: (author: string) => void;
|
||||
|
||||
file: File | null;
|
||||
setFile: (file: File | null) => void;
|
||||
file: File | null;
|
||||
setFile: (file: File | null) => void;
|
||||
|
||||
fileType: string;
|
||||
setFileType: (type: string) => void;
|
||||
fileType: string;
|
||||
setFileType: (type: string) => void;
|
||||
|
||||
fileSrc: string;
|
||||
setFileSrc: (fileSrc: string) => void;
|
||||
fileSrc: string;
|
||||
setFileSrc: (fileSrc: string) => void;
|
||||
|
||||
allowCover: boolean;
|
||||
setAllowCover: (allowCover: boolean) => void;
|
||||
allowCover: boolean;
|
||||
setAllowCover: (allowCover: boolean) => void;
|
||||
|
||||
allowDwnld: boolean;
|
||||
setAllowDwnld: (allowDwnld: boolean) => void;
|
||||
allowDwnld: boolean;
|
||||
setAllowDwnld: (allowDwnld: boolean) => void;
|
||||
|
||||
cover: File | null;
|
||||
setCover: (cover: File | null) => void;
|
||||
cover: File | null;
|
||||
setCover: (cover: File | null) => void;
|
||||
|
||||
isPercentHintOpen: boolean;
|
||||
setPercentHintOpen: (isPercentHintOpen: boolean) => void;
|
||||
isPercentHintOpen: boolean;
|
||||
setPercentHintOpen: (isPercentHintOpen: boolean) => void;
|
||||
|
||||
authors: string[];
|
||||
setAuthors: (authors: string[]) => void;
|
||||
authors: string[];
|
||||
setAuthors: (authors: string[]) => void;
|
||||
|
||||
royalty: Royalty[];
|
||||
setRoyalty: (authors: Royalty[]) => void;
|
||||
royalty: Royalty[];
|
||||
setRoyalty: (authors: Royalty[]) => void;
|
||||
|
||||
price: number;
|
||||
setPrice: (price: number) => void;
|
||||
price: number;
|
||||
setPrice: (price: number) => void;
|
||||
|
||||
allowResale: boolean;
|
||||
setAllowResale: (allowResale: boolean) => void;
|
||||
allowResale: boolean;
|
||||
setAllowResale: (allowResale: boolean) => void;
|
||||
|
||||
licenseResalePrice: number;
|
||||
setLicenseResalePrice: (licenseResalePrice: number) => void;
|
||||
licenseResalePrice: number;
|
||||
setLicenseResalePrice: (licenseResalePrice: number) => void;
|
||||
|
||||
hashtags: string[];
|
||||
setHashtags: (hashtags: string[]) => void;
|
||||
hashtags: string[];
|
||||
setHashtags: (hashtags: string[]) => void;
|
||||
|
||||
notifications: Notification[];
|
||||
addNotification: (message: string, type: NotificationType) => void;
|
||||
removeNotification: (id: string) => void;
|
||||
};
|
||||
|
||||
export const useRootStore = create<RootStore>((set) => ({
|
||||
name: "",
|
||||
setName: (name) => set({ name }),
|
||||
name: '',
|
||||
setName: (name) => set({ name }),
|
||||
|
||||
author: "",
|
||||
setAuthor: (author) => set({ author }),
|
||||
author: '',
|
||||
setAuthor: (author) => set({ author }),
|
||||
|
||||
file: null,
|
||||
setFile: (file) => set({ file }),
|
||||
file: null,
|
||||
setFile: (file) => set({ file }),
|
||||
|
||||
fileType: "",
|
||||
setFileType: (fileType) => set({ fileType }),
|
||||
fileType: '',
|
||||
setFileType: (fileType) => set({ fileType }),
|
||||
|
||||
fileSrc: "",
|
||||
setFileSrc: (fileSrc) => set({ fileSrc }),
|
||||
fileSrc: '',
|
||||
setFileSrc: (fileSrc) => set({ fileSrc }),
|
||||
|
||||
allowCover: false,
|
||||
setAllowCover: (allowCover) => set({ allowCover }),
|
||||
allowCover: false,
|
||||
setAllowCover: (allowCover) => set({ allowCover }),
|
||||
|
||||
allowDwnld: false,
|
||||
setAllowDwnld: (allowDwnld) => set({ allowDwnld }),
|
||||
|
||||
cover: null,
|
||||
setCover: (cover) => set({ cover }),
|
||||
allowDwnld: false,
|
||||
setAllowDwnld: (allowDwnld) => set({ allowDwnld }),
|
||||
|
||||
isPercentHintOpen: true,
|
||||
setPercentHintOpen: (isPercentHintOpen) => set({ isPercentHintOpen }),
|
||||
cover: null,
|
||||
setCover: (cover) => set({ cover }),
|
||||
|
||||
authors: [],
|
||||
setAuthors: (authors) => set({ authors }),
|
||||
isPercentHintOpen: true,
|
||||
setPercentHintOpen: (isPercentHintOpen) => set({ isPercentHintOpen }),
|
||||
|
||||
royalty: [],
|
||||
setRoyalty: (royalty) => set({ royalty }),
|
||||
authors: [],
|
||||
setAuthors: (authors) => set({ authors }),
|
||||
|
||||
price: 0.15,
|
||||
setPrice: (price: number) => set({ price }),
|
||||
royalty: [],
|
||||
setRoyalty: (royalty) => set({ royalty }),
|
||||
|
||||
allowResale: false,
|
||||
setAllowResale: (allowResale) => set({ allowResale }),
|
||||
price: 0.15,
|
||||
setPrice: (price: number) => set({ price }),
|
||||
|
||||
licenseResalePrice: 0,
|
||||
setLicenseResalePrice: (licenseResalePrice) => set({ licenseResalePrice }),
|
||||
allowResale: false,
|
||||
setAllowResale: (allowResale) => set({ allowResale }),
|
||||
|
||||
hashtags: [],
|
||||
setHashtags: (hashtags: string[]) => set({ hashtags }),
|
||||
licenseResalePrice: 0,
|
||||
setLicenseResalePrice: (licenseResalePrice) => set({ licenseResalePrice }),
|
||||
|
||||
hashtags: [],
|
||||
setHashtags: (hashtags: string[]) => set({ hashtags }),
|
||||
|
||||
notifications: [],
|
||||
|
||||
addNotification: (message: string, type: NotificationType = 'info') => {
|
||||
const id = Date.now().toString();
|
||||
const notification = {
|
||||
id,
|
||||
message,
|
||||
type,
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
|
||||
set((state) => ({
|
||||
notifications: [...state.notifications, notification],
|
||||
}));
|
||||
},
|
||||
|
||||
removeNotification: (id: string) => {
|
||||
set((state) => ({
|
||||
notifications: state.notifications.filter((notification) => notification.id !== id),
|
||||
}));
|
||||
},
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { useRootStore } from '~/shared/stores/root';
|
||||
|
||||
interface AnimatedNotification {
|
||||
id: string;
|
||||
message: string;
|
||||
type: 'success' | 'danger' | 'warning' | 'info';
|
||||
isExiting: boolean;
|
||||
}
|
||||
|
||||
export const Notification = () => {
|
||||
const { notifications, removeNotification } = useRootStore();
|
||||
const [animatedNotifications, setAnimatedNotifications] = useState<AnimatedNotification[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentIds = animatedNotifications.map((n) => n.id);
|
||||
const newNotifications = notifications
|
||||
.filter((n) => !currentIds.includes(n.id))
|
||||
.map((n) => ({ ...n, isExiting: false }));
|
||||
|
||||
if (newNotifications.length > 0) {
|
||||
newNotifications.forEach((notification) => {
|
||||
setTimeout(() => {
|
||||
handleRemove(notification.id);
|
||||
}, 4000);
|
||||
});
|
||||
|
||||
setAnimatedNotifications((prev) => [...prev, ...newNotifications]);
|
||||
}
|
||||
}, [notifications]);
|
||||
|
||||
// Функция для начала анимации удаления
|
||||
const handleRemove = (id: string) => {
|
||||
setAnimatedNotifications((prev) =>
|
||||
prev.map((n) => (n.id === id ? { ...n, isExiting: true } : n))
|
||||
);
|
||||
setTimeout(() => {
|
||||
setAnimatedNotifications((prev) => prev.filter((n) => n.id !== id));
|
||||
removeNotification(id);
|
||||
}, 300);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-x-0 top-2 z-50 flex flex-col items-center gap-2 pointer-events-none">
|
||||
{animatedNotifications.map((notification) => (
|
||||
<div
|
||||
key={notification.id}
|
||||
onClick={() => handleRemove(notification.id)}
|
||||
className={`
|
||||
p-2 rounded shadow-md relative transition-all duration-300 max-w-sm
|
||||
transform origin-top pointer-events-auto cursor-pointer
|
||||
${notification.isExiting ? 'animate-fade-out' : 'animate-slide-in-top'}
|
||||
${getNotificationClass(notification.type)}
|
||||
`}
|
||||
>
|
||||
<div className="text-center">{notification.message}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getNotificationClass = (type: 'success' | 'danger' | 'warning' | 'info'): string => {
|
||||
const baseStyle = 'border border-white bg-opacity-70 backdrop-blur-sm';
|
||||
|
||||
switch (type) {
|
||||
case 'success':
|
||||
return `${baseStyle} bg-primary text-white`;
|
||||
case 'danger':
|
||||
return `${baseStyle} bg-primary text-white`;
|
||||
case 'warning':
|
||||
return `${baseStyle} bg-primary text-white`;
|
||||
case 'info':
|
||||
default:
|
||||
return `${baseStyle} bg-primary text-white`;
|
||||
}
|
||||
};
|
||||
|
|
@ -1,13 +1,33 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
gray: "#1d1d1b",
|
||||
primary: "#e40615",
|
||||
},
|
||||
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
gray: '#1d1d1b',
|
||||
primary: '#e40615',
|
||||
},
|
||||
keyframes: {
|
||||
'slide-in-top': {
|
||||
'0%': {
|
||||
transform: 'translateY(-1rem)',
|
||||
opacity: '0',
|
||||
},
|
||||
'100%': {
|
||||
transform: 'translateY(0)',
|
||||
opacity: '1',
|
||||
},
|
||||
},
|
||||
'fade-out': {
|
||||
'0%': { opacity: '1' },
|
||||
'100%': { opacity: '0' },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
'slide-in-top': 'slide-in-top 0.3s ease-out forwards',
|
||||
'fade-out': 'fade-out 0.3s ease-out forwards',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
plugins: [],
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue