Files
desktop/app/views/Introduction/Introduction.tsx
rosetta 83f38dc63f 'init'
2026-01-30 05:01:05 +02:00

160 lines
4.2 KiB
TypeScript

import { useEffect, useState } from 'react';
import { Button, Group, Text, Flex } from '@mantine/core';
import { IconShieldLock, IconBolt, IconDeviceMobile } from '@tabler/icons-react';
import classes from './Introduction.module.css';
import { useNavigate } from 'react-router-dom';
import useWindow, { ElectronTheme } from '@/app/hooks/useWindow';
import { AnimatedButton } from '@/app/components/AnimatedButton/AnimatedButton';
const slides = [
{
title: 'Security',
description: 'Your data is protected by modern encryption and stays only on your device',
icon: IconShieldLock,
color: '#4CAF50',
},
{
title: 'Speed',
description: 'Instant synchronization and fast performance without delays',
icon: IconBolt,
color: '#FF9800',
},
{
title: 'Simple Interface',
description: 'Intuitive design that is clear at first glance',
icon: IconDeviceMobile,
color: '#2196F3',
},
];
export function Introduction() {
const navigate = useNavigate();
const {setSize, setResizeble, setTheme} = useWindow();
const [activeSlide, setActiveSlide] = useState(0);
const [touchStart, setTouchStart] = useState<number | null>(null);
const [touchEnd, setTouchEnd] = useState<number | null>(null);
// Минимальное расстояние свайпа (в px)
const minSwipeDistance = 50;
useEffect(() => {
setSize(385, 555);
setResizeble(false);
setTheme(ElectronTheme.SYSTEM);
}, []);
const handleGetStarted = () => {
navigate('/create-seed');
};
const goToSlide = (index: number) => {
setActiveSlide(index);
};
const nextSlide = () => {
setActiveSlide((prev) => (prev + 1) % slides.length);
};
const prevSlide = () => {
setActiveSlide((prev) => (prev - 1 + slides.length) % slides.length);
};
const onTouchStart = (e: React.TouchEvent) => {
setTouchEnd(null);
setTouchStart(e.targetTouches[0].clientX);
};
const onTouchMove = (e: React.TouchEvent) => {
setTouchEnd(e.targetTouches[0].clientX);
};
const onTouchEnd = () => {
if (!touchStart || !touchEnd) return;
const distance = touchStart - touchEnd;
const isLeftSwipe = distance > minSwipeDistance;
const isRightSwipe = distance < -minSwipeDistance;
if (isLeftSwipe) {
nextSlide();
} else if (isRightSwipe) {
prevSlide();
}
};
const handleCarouselClick = () => {
nextSlide();
};
const CurrentIcon = slides[activeSlide].icon;
return (
<div className={classes.wrapper}>
<Flex h={'100%'} w={'100%'} direction={'column'} justify={'space-between'} align={'center'}>
<div>
<div
className={classes.carousel}
onTouchStart={onTouchStart}
onTouchMove={onTouchMove}
onTouchEnd={onTouchEnd}
onClick={handleCarouselClick}
style={{ cursor: 'pointer', userSelect: 'none' }}
>
<div className={classes.slideContent} key={activeSlide}>
<div
className={classes.iconWrapper}
style={{ backgroundColor: `${slides[activeSlide].color}20` }}
>
<CurrentIcon
size={80}
stroke={1.5}
color={slides[activeSlide].color}
/>
</div>
<Text size={'sm'} className={classes.title} fw={500}>
{slides[activeSlide].title}
</Text>
<Text mt={'xs'} size={'xs'} c={'dimmed'} className={classes.description}>
{slides[activeSlide].description}
</Text>
</div>
<Group justify="center" className={classes.dots}>
{slides.map((_, index) => (
<button
key={index}
className={`${classes.dot} ${index === activeSlide ? classes.dotActive : ''}`}
onClick={(e) => {
e.stopPropagation();
goToSlide(index);
}}
aria-label={`Go to slide ${index + 1}`}
/>
))}
</Group>
</div>
</div>
<Flex gap={'xs'} direction={'column'} w={'100%'}>
<AnimatedButton
fullWidth
size="md"
animated={['#2DA5FF', '#87DBFF']}
onClick={handleGetStarted}
>
Get Started
</AnimatedButton>
<Button
fullWidth
variant={'transparent'}
size="md"
onClick={() => navigate('/exists-seed')}
>
Already have an account
</Button>
</Flex>
</Flex>
</div>
);
}