add image upload, fix styles
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useRef } from 'react';
|
||||
import { fetchFruits } from '@/app/lib/action';
|
||||
import { fetchFruits } from '@/app/actions/action';
|
||||
import { useClickOutside } from '@/app/hooks/useClickOutside';
|
||||
import Link from 'next/link';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useState, useRef } from 'react';
|
||||
import { useClickOutside } from '@/app/hooks/useClickOutside';
|
||||
import Link from 'next/link';
|
||||
import { getUserData } from '@/app/lib/action';
|
||||
import { getUserData } from '@/app/actions/action';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { logout } from '@/app/actions/auth';
|
||||
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
'use client'
|
||||
|
||||
import { Config, OneTap } from '@vkid/sdk';
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
const VKAuthDynamic = () => {
|
||||
const containerRef = useRef(null);
|
||||
const isInitialized = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Динамическая загрузка SDK
|
||||
const loadVKIDSDK = async () => {
|
||||
try {
|
||||
// Проверяем, не загружен ли уже SDK
|
||||
if (window.VKIDSDK) return window.VKIDSDK;
|
||||
|
||||
const { default: VKID } = await import('@vkid/sdk');
|
||||
window.VKIDSDK = VKID;
|
||||
return VKID;
|
||||
} catch (error) {
|
||||
console.error('Failed to load VKID SDK:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const initialize = async () => {
|
||||
if (isInitialized.current) return;
|
||||
|
||||
const VKID = await loadVKIDSDK();
|
||||
if (!VKID || !containerRef.current) return;
|
||||
|
||||
isInitialized.current = true;
|
||||
|
||||
try {
|
||||
// Конфигурация
|
||||
VKID.Config.init({
|
||||
app: Number(process.env.NEXT_PUBLIC_VK_APP_ID) || 1111111,
|
||||
redirectUrl: process.env.NEXT_PUBLIC_REDIRECT_URL || window.location.origin,
|
||||
responseMode: VKID.ConfigResponseMode.Callback,
|
||||
source: VKID.ConfigSource.LOWCODE,
|
||||
scope: '',
|
||||
});
|
||||
|
||||
// Создание и рендеринг виджета
|
||||
const oneTap = new VKID.OneTap();
|
||||
|
||||
oneTap
|
||||
.render({
|
||||
container: containerRef.current,
|
||||
showAlternativeLogin: true,
|
||||
})
|
||||
.on(VKID.WidgetEvents.ERROR, handleError)
|
||||
.on(VKID.OneTapInternalEvents.LOGIN_SUCCESS, async (payload) => {
|
||||
const { code, device_id } = payload;
|
||||
|
||||
try {
|
||||
const authData = await VKID.Auth.exchangeCode(code, device_id);
|
||||
handleSuccess(authData);
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('VKID initialization error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
initialize();
|
||||
|
||||
return () => {
|
||||
// Очистка
|
||||
isInitialized.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleSuccess = async (data) => {
|
||||
try {
|
||||
// Отправка данных на ваш API endpoint
|
||||
const response = await fetch('/api/auth/vk-callback', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
// Успешная авторизация
|
||||
console.log('Authentication successful:', result);
|
||||
// Перенаправление или обновление состояния
|
||||
window.location.href = '/dashboard'; // Пример редиректа
|
||||
} else {
|
||||
handleError(result);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Server request failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleError = (error) => {
|
||||
console.error('Authentication error:', error);
|
||||
// Можно показать уведомление пользователю
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div ref={containerRef} />
|
||||
vk
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VKAuthDynamic;
|
||||
@@ -23,7 +23,7 @@ export default function LoginForm() {
|
||||
defaultValue={state?.previousState?.email ? state?.previousState?.email : ''}
|
||||
/>
|
||||
{state?.error?.email && (
|
||||
<p className="text-sm text-red-500">{state?.error?.email}</p>
|
||||
<p className={`${styles['form-error']}`}>{state?.error?.email}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className={`${styles['form-group']}`}>
|
||||
@@ -36,11 +36,11 @@ export default function LoginForm() {
|
||||
placeholder="Введите пароль"
|
||||
/>
|
||||
{state?.error?.password && (
|
||||
<p className="text-sm text-red-500">{state?.error?.password}</p>
|
||||
<p className={`${styles['form-error']}`}>{state?.error?.password}</p>
|
||||
)}
|
||||
</div>
|
||||
{state?.error.server && (
|
||||
<p className="text-sm text-red-500">{state?.error.server}</p>
|
||||
<p className={`${styles['form-error']}`}>{state?.error.server}</p>
|
||||
)}
|
||||
<div className={`${styles['form-options']}`}>
|
||||
<div className={`${styles['form-checkbox']}`}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import style from "./ui.module.scss"
|
||||
import style from "@/app/styles/ui.module.scss"
|
||||
|
||||
export default function LogoIcon() {
|
||||
return (
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import styles from './ui.module.scss';
|
||||
import styles from '@/app/styles/ui.module.scss';
|
||||
|
||||
export default function Logo() {
|
||||
return (
|
||||
|
||||
@@ -9,15 +9,11 @@ export default function ProtectionSummary() {
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-number">0</div>
|
||||
<div className="stat-label">Защищено</div>
|
||||
<div className="stat-label">Общий размер</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-number">0</div>
|
||||
<div className="stat-label">Изображений</div>
|
||||
</div>
|
||||
<div className="stat-card">
|
||||
<div className="stat-number">100%</div>
|
||||
<div className="stat-label">Уровень защиты</div>
|
||||
<div className="stat-label">Проверки/нарушения</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
'use client'
|
||||
|
||||
import React, { useRef, useState, useCallback, ChangeEvent, DragEvent } from 'react';
|
||||
|
||||
// Типы для выбранного файла
|
||||
interface SelectedFile {
|
||||
file: File;
|
||||
preview: string;
|
||||
name: string;
|
||||
size: string;
|
||||
dimensions?: {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
}
|
||||
|
||||
type SupportedFormat = 'image/jpeg' | 'image/png' | 'image/gif' | 'image/bmp';
|
||||
type FileExtension = 'jpg' | 'jpeg' | 'png' | 'gif' | 'bmp';
|
||||
|
||||
export default function UploadSectionImage() {
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [isDragging, setIsDragging] = useState<boolean>(false);
|
||||
const [selectedFile, setSelectedFile] = useState<SelectedFile | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const SUPPORTED_FORMATS: SupportedFormat[] = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'image/bmp'
|
||||
];
|
||||
|
||||
const ALLOWED_EXTENSIONS: FileExtension[] = ['jpg', 'jpeg', 'png', 'gif', 'bmp'];
|
||||
|
||||
|
||||
const validateFile = (file: File): { isValid: boolean; errorMessage?: string } => {
|
||||
if (!SUPPORTED_FORMATS.includes(file.type as SupportedFormat)) {
|
||||
const extension = file.name.split('.').pop()?.toLowerCase();
|
||||
if (!extension || !ALLOWED_EXTENSIONS.includes(extension as FileExtension)) {
|
||||
return {
|
||||
isValid: false,
|
||||
errorMessage: 'Неподдерживаемый формат файла.'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_SIZE = 10 * 1024 * 1024;
|
||||
if (file.size > MAX_SIZE) {
|
||||
return {
|
||||
isValid: false,
|
||||
errorMessage: 'Файл слишком большой. Максимальный размер: 10MB'
|
||||
};
|
||||
}
|
||||
|
||||
return { isValid: true };
|
||||
};
|
||||
|
||||
const getImageDimensions = (file: File): Promise<{ width: number; height: number }> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
const objectUrl = URL.createObjectURL(file);
|
||||
|
||||
img.onload = () => {
|
||||
const dimensions = {
|
||||
width: img.width,
|
||||
height: img.height
|
||||
};
|
||||
URL.revokeObjectURL(objectUrl);
|
||||
resolve(dimensions);
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
URL.revokeObjectURL(objectUrl);
|
||||
reject(new Error('Не удалось загрузить изображение'));
|
||||
};
|
||||
|
||||
img.src = objectUrl;
|
||||
});
|
||||
};
|
||||
|
||||
const handleFileSelect = useCallback(async (file: File | null): Promise<void> => {
|
||||
if (!file) {
|
||||
setError(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setError(null);
|
||||
|
||||
const validation = validateFile(file);
|
||||
if (!validation.isValid) {
|
||||
setError(validation.errorMessage || 'Неизвестная ошибка валидации');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const dimensions = await getImageDimensions(file);
|
||||
|
||||
if (dimensions.width < 150 || dimensions.height < 150 ||
|
||||
dimensions.width > 4000 || dimensions.height > 4000) {
|
||||
setError(`Размер изображения: ${dimensions.width}x${dimensions.height}. Допустимый диапазон: 150x150 - 4000x4000 пикселей.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Создание превью
|
||||
const objectUrl = URL.createObjectURL(file);
|
||||
|
||||
setSelectedFile({
|
||||
file,
|
||||
preview: objectUrl,
|
||||
name: file.name,
|
||||
size: `${(file.size / 1024 / 1024).toFixed(2)} MB`,
|
||||
dimensions
|
||||
});
|
||||
|
||||
console.log('Файл обработан:', {
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
dimensions
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Ошибка при обработке файла');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleFileInputChange = (event: ChangeEvent<HTMLInputElement>): void => {
|
||||
const file = event.target.files?.[0] || null;
|
||||
handleFileSelect(file);
|
||||
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
const handleButtonClick = (): void => {
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.click();
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragOver = (event: DragEvent<HTMLDivElement>): void => {
|
||||
event.preventDefault();
|
||||
setIsDragging(true);
|
||||
};
|
||||
|
||||
const handleDragLeave = (): void => {
|
||||
setIsDragging(false);
|
||||
};
|
||||
|
||||
const handleDrop = (event: DragEvent<HTMLDivElement>): void => {
|
||||
event.preventDefault();
|
||||
setIsDragging(false);
|
||||
|
||||
const file = event.dataTransfer.files[0];
|
||||
handleFileSelect(file);
|
||||
};
|
||||
|
||||
const handleClearFile = (): void => {
|
||||
if (selectedFile) {
|
||||
URL.revokeObjectURL(selectedFile.preview);
|
||||
}
|
||||
setSelectedFile(null);
|
||||
setError(null);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
if (selectedFile) {
|
||||
URL.revokeObjectURL(selectedFile.preview);
|
||||
}
|
||||
};
|
||||
}, [selectedFile]);
|
||||
|
||||
return (
|
||||
<div className="upload-section">
|
||||
<h3>📷 Загрузить изображение для защиты</h3>
|
||||
|
||||
<div
|
||||
className={`drag-drop-zone ${isDragging ? 'dragging' : ''}`}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
style={{
|
||||
border: isDragging ? '2px dashed #3b82f6' : '2px dashed #d1d5db',
|
||||
backgroundColor: isDragging ? '#eff6ff' : '#f9fafb',
|
||||
padding: '2rem',
|
||||
borderRadius: '.75rem',
|
||||
textAlign: 'center',
|
||||
transition: 'all .2s ease-in-out'
|
||||
}}
|
||||
>
|
||||
<h4>Перетащите изображения сюда</h4>
|
||||
<p className="description">
|
||||
Размер: 150x150 - 4000x4000 пикселей, форматы: JPG, PNG, GIF, BMP (WEBP НЕ поддерживается)
|
||||
</p>
|
||||
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept=".jpg,.jpeg,.png,.gif,.bmp"
|
||||
style={{ display: 'none' }}
|
||||
onChange={handleFileInputChange}
|
||||
aria-label="Выбор файла для защиты"
|
||||
/>
|
||||
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={handleButtonClick}
|
||||
type="button"
|
||||
>
|
||||
Выбрать файлы для защиты
|
||||
</button>
|
||||
|
||||
{error && (
|
||||
<div className="mt-4 p-3 bg-red-50 border border-red-200 rounded-lg">
|
||||
<p className="text-red-600 font-medium">❌ {error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedFile && (
|
||||
<div
|
||||
className="selected-file"
|
||||
>
|
||||
<div className="selected-file-file-info">
|
||||
<p className="text-gray-700">
|
||||
<span className="font-medium">Файл:</span> {selectedFile.name}
|
||||
</p>
|
||||
<p className="text-gray-700">
|
||||
<span className="font-medium">Размер:</span> {selectedFile.size}
|
||||
</p>
|
||||
<p className="text-gray-700">
|
||||
<span className="font-medium">Стоимость защиты:</span> 0
|
||||
</p>
|
||||
<p className="text-gray-700">
|
||||
<span className="font-medium">Текущий баланс:</span> 0
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<img
|
||||
src={selectedFile.preview}
|
||||
alt="Предпросмотр загруженного изображения"
|
||||
className="uploaded-image"
|
||||
onError={() => setError('Не удалось загрузить превью изображения')}
|
||||
/>
|
||||
|
||||
<div className="mt-4 flex gap-3 justify-center">
|
||||
<button
|
||||
className="btn btn-cancel"
|
||||
onClick={handleClearFile}
|
||||
type="button"
|
||||
>
|
||||
Отмена
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-confirm"
|
||||
onClick={() => {
|
||||
console.log('Обработка файла:', selectedFile.file);
|
||||
console.log('Файл отправлен на защиту!');
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
Подтвердить защиту
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
export default function UploadSection() {
|
||||
return (
|
||||
<div className="upload-section">
|
||||
<h3>
|
||||
📷 Загрузить изображение для защиты
|
||||
</h3>
|
||||
<div className="drag-drop-zone">
|
||||
<div className="text-[48px] mb-[15px]">🛡️</div>
|
||||
<h4>
|
||||
Перетащите изображения сюда
|
||||
</h4>
|
||||
<p>
|
||||
Размер: 150x150 - 4000x4000 пикселей, форматы: JPG, PNG, GIF, BMP (WEBP НЕ поддерживается)
|
||||
</p>
|
||||
<button className="btn btn-primary">
|
||||
🛡️ Выбрать файлы для защиты
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import styles from '@/app/ui/ui.module.scss';
|
||||
import { useState } from 'react';
|
||||
import styles from '@/app/styles/ui.module.scss';
|
||||
import { useState, useRef } from 'react';
|
||||
import Link from 'next/link';
|
||||
import clsx from 'clsx';
|
||||
import { usePathname } from 'next/navigation';
|
||||
@@ -19,42 +19,32 @@ export default function DropDownList() {
|
||||
},
|
||||
{
|
||||
name: 'Маркировка видео',
|
||||
href: '/pages/emptypage'
|
||||
href: '/pages/marking-video'
|
||||
},
|
||||
{
|
||||
name: 'Маркировка аудио',
|
||||
href: '/pages/emptypage'
|
||||
},
|
||||
{
|
||||
name: 'Маркировка PDF',
|
||||
href: '/pages/emptypage'
|
||||
},
|
||||
{
|
||||
name: 'Маркировка PHP/JS',
|
||||
href: '/pages/emptypage'
|
||||
},
|
||||
{
|
||||
name: 'Маркировка Текста',
|
||||
href: '/pages/emptypage'
|
||||
},
|
||||
href: '/pages/marking-audio'
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
${styles['nav-dropdown']}
|
||||
${dropDownExpanded ? styles['expanded'] : ''}
|
||||
`}
|
||||
onClick={dropDownHandler}
|
||||
>
|
||||
<div className={`flex ${styles['nav-link']} cursor-pointer select-none`}>
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M17.63 5.84C17.27 5.33 16.67 5 16 5L5 5.01C3.9 5.01 3 5.9 3 7v10c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2l-5.37.84z"></path>
|
||||
</svg>
|
||||
<p>Маркировка</p>
|
||||
<svg className={`${styles['dropdown-arrow']}`} viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M7 10l5 5 5-5z"></path>
|
||||
</svg>
|
||||
onClick={dropDownHandler}
|
||||
>
|
||||
<div className={`flex ${styles['nav-link']} cursor-pointer select-none`}>
|
||||
<svg viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M17.63 5.84C17.27 5.33 16.67 5 16 5L5 5.01C3.9 5.01 3 5.9 3 7v10c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2l-5.37.84z"></path>
|
||||
</svg>
|
||||
<p>Маркировка</p>
|
||||
<svg className={`${styles['dropdown-arrow']}`} viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M7 10l5 5 5-5z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<ul className={`
|
||||
${styles['sub-menu']}
|
||||
@@ -77,6 +67,6 @@ export default function DropDownList() {
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import clsx from 'clsx';
|
||||
import styles from './ui.module.scss'
|
||||
import styles from '@/app/styles/ui.module.scss'
|
||||
import Logo from '@/app/ui/logo';
|
||||
import DropDownList from '@/app/ui/nav-link-dropdown';
|
||||
import { logout } from '@/app/actions/auth';
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getUserData } from '@/app/lib/action';
|
||||
import { getUserData } from '@/app/actions/action';
|
||||
|
||||
export default function HomePage() {
|
||||
const {
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
.sidebar {
|
||||
width: 280px;
|
||||
background: linear-gradient(180deg, #6366f1 0%, #8b5cf6 100%);
|
||||
padding: 30px 0;
|
||||
border-radius: 0 30px 30px 0;
|
||||
position: fixed;
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px 30px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
text-decoration: none;
|
||||
transition: all 0.3s;
|
||||
border-radius: 0 25px 25px 0;
|
||||
margin-right: 20px;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: white;
|
||||
transform: translateX(5px)
|
||||
}
|
||||
|
||||
svg {
|
||||
margin-right: 15px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.sub-link {
|
||||
padding: 12px 50px;
|
||||
font-size: 14px;
|
||||
border-radius: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.nav-dropdown {
|
||||
.dropdown-arrow {
|
||||
margin-left: auto;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
.dropdown-arrow {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sub-menu.expanded {
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
.sub-menu {
|
||||
list-style: none;
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
margin-right: 20px;
|
||||
border-radius: 0 15px 15px 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 30px 40px;
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
|
||||
svg {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background: linear-gradient(135deg, #6366f1, #8b5cf6);
|
||||
border-radius: 15px;
|
||||
margin-bottom: 15px;
|
||||
|
||||
svg {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
fill: white;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user