mirror of
https://github.com/Motion-Games/MotionWebStudio.git
synced 2026-04-21 09:00:53 +02:00
init
This commit is contained in:
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
96
App.tsx
Normal file
96
App.tsx
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { HashRouter as Router, Routes, Route } from 'react-router-dom';
|
||||||
|
import { Navbar } from './components/Navbar';
|
||||||
|
import { Footer } from './components/Footer';
|
||||||
|
import { Home } from './pages/Home';
|
||||||
|
import { Services } from './pages/Services';
|
||||||
|
import { Products } from './pages/Products';
|
||||||
|
import { References } from './pages/References';
|
||||||
|
import { Contact } from './pages/Contact';
|
||||||
|
import { Login } from './pages/auth/Login';
|
||||||
|
import { Register } from './pages/auth/Register';
|
||||||
|
import { ForgotPassword } from './pages/auth/ForgotPassword';
|
||||||
|
import { ResetPassword } from './pages/auth/ResetPassword';
|
||||||
|
import { Dashboard } from './pages/Dashboard';
|
||||||
|
import { Admin } from './pages/Admin';
|
||||||
|
import { Privacy } from './pages/Privacy';
|
||||||
|
import { Terms } from './pages/Terms';
|
||||||
|
import { FAQ } from './pages/FAQ';
|
||||||
|
import { ProtectedRoute } from './components/ProtectedRoute';
|
||||||
|
import { AuthProvider } from './context/AuthContext';
|
||||||
|
import ScrollToTop from './components/ScrollToTop';
|
||||||
|
import { AnalyticsTracker } from './components/AnalyticsTracker';
|
||||||
|
import { ProfileCompleter } from './components/ProfileCompleter';
|
||||||
|
import { CookieBanner } from './components/CookieBanner';
|
||||||
|
|
||||||
|
// Demo Pages
|
||||||
|
import { SweetCraving } from './pages/demos/SweetCraving';
|
||||||
|
import { BlueWave } from './pages/demos/BlueWave';
|
||||||
|
import { Steelguard } from './pages/demos/Steelguard';
|
||||||
|
|
||||||
|
// Helper component to scroll to top on route change
|
||||||
|
const ScrollToTopHelper = () => {
|
||||||
|
return <ScrollToTop />;
|
||||||
|
};
|
||||||
|
|
||||||
|
const App: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<AuthProvider>
|
||||||
|
<Router>
|
||||||
|
<div className="min-h-screen bg-white flex flex-col font-sans text-gray-900">
|
||||||
|
<ScrollToTopHelper />
|
||||||
|
<AnalyticsTracker />
|
||||||
|
<ProfileCompleter />
|
||||||
|
<CookieBanner />
|
||||||
|
|
||||||
|
<Routes>
|
||||||
|
{/* Main Application Routes */}
|
||||||
|
<Route path="/" element={<><Navbar /><main className="flex-grow"><Home /></main><Footer /></>} />
|
||||||
|
<Route path="/services" element={<><Navbar /><main className="flex-grow"><Services /></main><Footer /></>} />
|
||||||
|
<Route path="/products" element={<><Navbar /><main className="flex-grow"><Products /></main><Footer /></>} />
|
||||||
|
<Route path="/references" element={<><Navbar /><main className="flex-grow"><References /></main><Footer /></>} />
|
||||||
|
<Route path="/contact" element={<><Navbar /><main className="flex-grow"><Contact /></main><Footer /></>} />
|
||||||
|
|
||||||
|
{/* Info Pages */}
|
||||||
|
<Route path="/privacy" element={<><Navbar /><main className="flex-grow"><Privacy /></main><Footer /></>} />
|
||||||
|
<Route path="/terms" element={<><Navbar /><main className="flex-grow"><Terms /></main><Footer /></>} />
|
||||||
|
<Route path="/faq" element={<><Navbar /><main className="flex-grow"><FAQ /></main><Footer /></>} />
|
||||||
|
|
||||||
|
{/* Auth Routes */}
|
||||||
|
<Route path="/auth/login" element={<><Navbar /><main className="flex-grow"><Login /></main><Footer /></>} />
|
||||||
|
<Route path="/auth/register" element={<><Navbar /><main className="flex-grow"><Register /></main><Footer /></>} />
|
||||||
|
<Route path="/auth/forgot-password" element={<><Navbar /><main className="flex-grow"><ForgotPassword /></main><Footer /></>} />
|
||||||
|
<Route path="/auth/reset-password" element={<><Navbar /><main className="flex-grow"><ResetPassword /></main><Footer /></>} />
|
||||||
|
|
||||||
|
{/* Protected Routes */}
|
||||||
|
<Route
|
||||||
|
path="/dashboard"
|
||||||
|
element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<><Navbar /><main className="flex-grow"><Dashboard /></main><Footer /></>
|
||||||
|
</ProtectedRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/admin"
|
||||||
|
element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<><Navbar /><main className="flex-grow"><Admin /></main><Footer /></>
|
||||||
|
</ProtectedRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Demo Routes - These have their own custom layouts/navbars */}
|
||||||
|
<Route path="/demos/sweetcraving" element={<SweetCraving />} />
|
||||||
|
<Route path="/demos/bluewave" element={<BlueWave />} />
|
||||||
|
<Route path="/demos/steelguard" element={<Steelguard />} />
|
||||||
|
</Routes>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</Router>
|
||||||
|
</AuthProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
20
README.md
Normal file
20
README.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<div align="center">
|
||||||
|
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
# Run and deploy your AI Studio app
|
||||||
|
|
||||||
|
This contains everything you need to run your app locally.
|
||||||
|
|
||||||
|
View your app in AI Studio: https://ai.studio/apps/drive/1-FVDkCL1ZZR8qhPZRheiLI83H5WiQpXi
|
||||||
|
|
||||||
|
## Run Locally
|
||||||
|
|
||||||
|
**Prerequisites:** Node.js
|
||||||
|
|
||||||
|
|
||||||
|
1. Install dependencies:
|
||||||
|
`npm install`
|
||||||
|
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
|
||||||
|
3. Run the app:
|
||||||
|
`npm run dev`
|
||||||
0
SUPABASE_AUTO_PROFILES.sql
Normal file
0
SUPABASE_AUTO_PROFILES.sql
Normal file
1
SUPABASE_SETUP.sql
Normal file
1
SUPABASE_SETUP.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
鏸n槨bイヌ+鴨b槨^J鷸mォIトv+hョレ,zロゥハ境uォZmォ
|
||||||
57
components/AnalyticsTracker.tsx
Normal file
57
components/AnalyticsTracker.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { supabase, isSupabaseConfigured } from '../lib/supabaseClient';
|
||||||
|
import { useAuth } from '../context/AuthContext';
|
||||||
|
|
||||||
|
export const AnalyticsTracker: React.FC = () => {
|
||||||
|
const location = useLocation();
|
||||||
|
const { user, loading } = useAuth();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Wait for auth to initialize so we don't log "null" user then "user id" immediately after
|
||||||
|
if (loading) return;
|
||||||
|
|
||||||
|
const trackVisit = async () => {
|
||||||
|
// 1. Check for cookie consent
|
||||||
|
// If user hasn't allowed or has explicitly denied, do not track.
|
||||||
|
const consent = localStorage.getItem('cookie_consent');
|
||||||
|
if (consent !== 'true') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Don't track if Supabase isn't configured (Demo mode)
|
||||||
|
if (!isSupabaseConfigured) return;
|
||||||
|
|
||||||
|
// 3. Check if we already logged this session
|
||||||
|
// sessionStorage persists while the tab is open, ensuring 1 count per session
|
||||||
|
const hasLoggedSession = sessionStorage.getItem('motionweb_visit_logged');
|
||||||
|
|
||||||
|
if (hasLoggedSession) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Mark immediately to prevent race conditions if effect fires multiple times rapidly
|
||||||
|
sessionStorage.setItem('motionweb_visit_logged', 'true');
|
||||||
|
|
||||||
|
await supabase.from('page_visits').insert({
|
||||||
|
page_path: location.pathname + location.hash,
|
||||||
|
user_agent: navigator.userAgent,
|
||||||
|
user_id: user?.id || null
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// Silently fail for analytics to not disrupt user experience
|
||||||
|
console.error('Analytics tracking error:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
trackVisit();
|
||||||
|
|
||||||
|
// Add listener for storage events to re-check tracking if consent changes mid-session
|
||||||
|
const handleStorageChange = () => trackVisit();
|
||||||
|
window.addEventListener('storage', handleStorageChange);
|
||||||
|
|
||||||
|
return () => window.removeEventListener('storage', handleStorageChange);
|
||||||
|
}, [location, user, loading]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
42
components/Button.tsx
Normal file
42
components/Button.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
|
variant?: 'primary' | 'secondary' | 'outline' | 'white';
|
||||||
|
size?: 'sm' | 'md' | 'lg';
|
||||||
|
fullWidth?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Button: React.FC<ButtonProps> = ({
|
||||||
|
children,
|
||||||
|
variant = 'primary',
|
||||||
|
size = 'md',
|
||||||
|
fullWidth = false,
|
||||||
|
className = '',
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const baseStyles = "inline-flex items-center justify-center font-semibold transition-all duration-300 rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2";
|
||||||
|
|
||||||
|
const variants = {
|
||||||
|
primary: "bg-gradient-to-r from-primary to-secondary hover:from-primary-dark hover:to-secondary-dark text-white shadow-lg hover:shadow-xl border-transparent focus:ring-primary",
|
||||||
|
secondary: "bg-secondary hover:bg-secondary-dark text-white shadow-md focus:ring-secondary",
|
||||||
|
outline: "bg-transparent border-2 border-primary text-primary hover:bg-primary hover:text-white focus:ring-primary",
|
||||||
|
white: "bg-white text-primary hover:bg-gray-100 shadow-md focus:ring-white"
|
||||||
|
};
|
||||||
|
|
||||||
|
const sizes = {
|
||||||
|
sm: "px-4 py-2 text-sm",
|
||||||
|
md: "px-6 py-3 text-base",
|
||||||
|
lg: "px-8 py-4 text-lg",
|
||||||
|
};
|
||||||
|
|
||||||
|
const widthClass = fullWidth ? "w-full" : "";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={`${baseStyles} ${variants[variant]} ${sizes[size]} ${widthClass} ${className}`}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
132
components/CookieBanner.tsx
Normal file
132
components/CookieBanner.tsx
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { X, Check, Cookie } from 'lucide-react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { useAuth } from '../context/AuthContext';
|
||||||
|
import { supabase, isSupabaseConfigured } from '../lib/supabaseClient';
|
||||||
|
|
||||||
|
export const CookieBanner: React.FC = () => {
|
||||||
|
const { user } = useAuth();
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
const [hasChecked, setHasChecked] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const checkAndSyncConsent = async () => {
|
||||||
|
// 1. Check Local Storage choice
|
||||||
|
const localConsent = localStorage.getItem('cookie_consent');
|
||||||
|
|
||||||
|
// If a choice was already made locally, we hide the banner immediately
|
||||||
|
if (localConsent !== null) {
|
||||||
|
setIsVisible(false);
|
||||||
|
setHasChecked(true);
|
||||||
|
|
||||||
|
// AUTO-SYNC: If user is logged in, ensure the local choice is synced to the database
|
||||||
|
if (user && isSupabaseConfigured) {
|
||||||
|
try {
|
||||||
|
const consentValue = localConsent === 'true';
|
||||||
|
await supabase
|
||||||
|
.from('profiles')
|
||||||
|
.update({ cookie_consent: consentValue })
|
||||||
|
.eq('id', user.id);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error auto-syncing cookie consent to DB:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. If NO local choice but user IS logged in, try to fetch from DB
|
||||||
|
if (user && isSupabaseConfigured) {
|
||||||
|
try {
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('profiles')
|
||||||
|
.select('cookie_consent')
|
||||||
|
.eq('id', user.id)
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
if (!error && data && data.cookie_consent !== null) {
|
||||||
|
// Save DB choice to local storage and hide
|
||||||
|
localStorage.setItem('cookie_consent', data.cookie_consent.toString());
|
||||||
|
setIsVisible(false);
|
||||||
|
setHasChecked(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error checking cookie consent in DB:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. If NO choice found anywhere, show the banner
|
||||||
|
setIsVisible(true);
|
||||||
|
setHasChecked(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
checkAndSyncConsent();
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
const handleConsent = async (allowed: boolean) => {
|
||||||
|
// Save to local storage immediately to hide banner
|
||||||
|
localStorage.setItem('cookie_consent', allowed.toString());
|
||||||
|
setIsVisible(false);
|
||||||
|
|
||||||
|
// If logged in, also update the database
|
||||||
|
if (user && isSupabaseConfigured) {
|
||||||
|
try {
|
||||||
|
await supabase
|
||||||
|
.from('profiles')
|
||||||
|
.update({ cookie_consent: allowed })
|
||||||
|
.eq('id', user.id);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error saving cookie consent to DB manually:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger storage event for AnalyticsTracker and other listeners
|
||||||
|
window.dispatchEvent(new Event('storage'));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isVisible || !hasChecked) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed bottom-6 right-6 z-[100] max-w-sm w-full animate-fade-in-up px-4 sm:px-0">
|
||||||
|
<div className="bg-white/95 backdrop-blur-md rounded-2xl shadow-[0_20px_50px_rgba(0,0,0,0.15)] border border-gray-100 p-6 flex flex-col gap-4">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="p-3 bg-primary/10 rounded-xl text-primary flex-shrink-0">
|
||||||
|
<Cookie className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
<div className="flex-grow">
|
||||||
|
<h3 className="text-sm font-bold text-gray-900">Sütik és Adatvédelem</h3>
|
||||||
|
<p className="text-xs text-gray-500 mt-1 leading-relaxed">
|
||||||
|
Az élmény fokozása érdekében sütiket használunk. Az elfogadással hozzájárul az anonim látogatottsági adatok gyűjtéséhez.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsVisible(false)}
|
||||||
|
className="text-gray-400 hover:text-gray-600 transition-colors p-1"
|
||||||
|
title="Bezárás"
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button
|
||||||
|
onClick={() => handleConsent(true)}
|
||||||
|
className="flex-1 bg-primary text-white py-2.5 px-4 rounded-xl text-xs font-bold hover:bg-primary-dark transition-all flex items-center justify-center gap-2 shadow-lg shadow-primary/20 hover:scale-[1.02] active:scale-95"
|
||||||
|
>
|
||||||
|
<Check className="w-3.5 h-3.5" /> Elfogadom
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleConsent(false)}
|
||||||
|
className="flex-1 bg-gray-100 text-gray-600 py-2.5 px-4 rounded-xl text-xs font-bold hover:bg-gray-200 transition-all active:scale-95"
|
||||||
|
>
|
||||||
|
Elutasítom
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-[10px] text-center text-gray-400">
|
||||||
|
További információkért olvassa el az <Link to="/privacy" className="underline hover:text-primary transition-colors">Adatkezelési tájékoztatónkat</Link>.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
289
components/FeedbackModal.tsx
Normal file
289
components/FeedbackModal.tsx
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { X, Info, CheckCircle, AlertTriangle, MessageSquare, Calendar, ChevronDown, ChevronUp } from 'lucide-react';
|
||||||
|
import { Button } from './Button';
|
||||||
|
|
||||||
|
interface FeedbackModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (feedbackData: any) => Promise<void>;
|
||||||
|
loading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FeedbackModal: React.FC<FeedbackModalProps> = ({ isOpen, onClose, onSubmit, loading }) => {
|
||||||
|
// --- STATE ---
|
||||||
|
const [mainDecision, setMainDecision] = useState<'approved' | 'minor' | 'major' | null>(null);
|
||||||
|
|
||||||
|
// Approval Flow
|
||||||
|
const [approvalConfirmed, setApprovalConfirmed] = useState(false);
|
||||||
|
|
||||||
|
// Revision Flow
|
||||||
|
const [designCheckboxes, setDesignCheckboxes] = useState<string[]>([]);
|
||||||
|
const [designText, setDesignText] = useState('');
|
||||||
|
|
||||||
|
const [contentCheckboxes, setContentCheckboxes] = useState<string[]>([]);
|
||||||
|
const [contentText, setContentText] = useState('');
|
||||||
|
|
||||||
|
const [structureCheckboxes, setStructureCheckboxes] = useState<string[]>([]);
|
||||||
|
const [structureText, setStructureText] = useState('');
|
||||||
|
|
||||||
|
const [funcCheckboxes, setFuncCheckboxes] = useState<string[]>([]);
|
||||||
|
const [funcText, setFuncText] = useState('');
|
||||||
|
|
||||||
|
const [priorityText, setPriorityText] = useState('');
|
||||||
|
|
||||||
|
const [deadlineType, setDeadlineType] = useState<'none' | 'date' | 'discuss'>('none');
|
||||||
|
const [deadlineDate, setDeadlineDate] = useState('');
|
||||||
|
|
||||||
|
const [extraNotes, setExtraNotes] = useState('');
|
||||||
|
|
||||||
|
const [revisionConfirmed, setRevisionConfirmed] = useState(false);
|
||||||
|
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
// --- HANDLERS ---
|
||||||
|
const handleCheckbox = (
|
||||||
|
currentList: string[],
|
||||||
|
setList: React.Dispatch<React.SetStateAction<string[]>>,
|
||||||
|
value: string,
|
||||||
|
noneValue: string
|
||||||
|
) => {
|
||||||
|
if (value === noneValue) {
|
||||||
|
if (currentList.includes(noneValue)) {
|
||||||
|
setList([]);
|
||||||
|
} else {
|
||||||
|
setList([noneValue]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let newList = currentList.filter(item => item !== noneValue);
|
||||||
|
if (newList.includes(value)) {
|
||||||
|
newList = newList.filter(item => item !== value);
|
||||||
|
} else {
|
||||||
|
newList = [...newList, value];
|
||||||
|
}
|
||||||
|
setList(newList);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isRevision = mainDecision === 'minor' || mainDecision === 'major';
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
const feedbackData = {
|
||||||
|
decision: mainDecision,
|
||||||
|
submittedAt: new Date().toISOString(),
|
||||||
|
approval: mainDecision === 'approved' ? {
|
||||||
|
confirmed: approvalConfirmed
|
||||||
|
} : null,
|
||||||
|
revision: isRevision ? {
|
||||||
|
design: { selected: designCheckboxes, comment: designText },
|
||||||
|
content: { selected: contentCheckboxes, comment: contentText },
|
||||||
|
structure: { selected: structureCheckboxes, comment: structureText },
|
||||||
|
functionality: { selected: funcCheckboxes, comment: funcText },
|
||||||
|
priority: priorityText,
|
||||||
|
deadline: { type: deadlineType, date: deadlineDate },
|
||||||
|
extraNotes: extraNotes,
|
||||||
|
confirmed: revisionConfirmed
|
||||||
|
} : null
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmit(feedbackData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const commonInputStyles = "w-full p-4 border border-gray-300 rounded-xl text-sm font-medium text-black placeholder-gray-400 focus:ring-4 focus:ring-primary/10 focus:border-primary outline-none bg-white transition-all shadow-sm";
|
||||||
|
|
||||||
|
const renderFeedbackCategory = (
|
||||||
|
title: string,
|
||||||
|
options: string[],
|
||||||
|
selected: string[],
|
||||||
|
setSelected: React.Dispatch<React.SetStateAction<string[]>>,
|
||||||
|
textValue: string,
|
||||||
|
setTextValue: React.Dispatch<React.SetStateAction<string>>,
|
||||||
|
placeholder: string
|
||||||
|
) => {
|
||||||
|
const noneOption = options[options.length - 1];
|
||||||
|
const showTextarea = selected.length > 0 && !selected.includes(noneOption);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-6 bg-gray-50/50 p-6 rounded-2xl border border-gray-100 shadow-sm">
|
||||||
|
<h4 className="font-bold text-gray-800 mb-4 text-sm uppercase tracking-widest">{title}</h4>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-4">
|
||||||
|
{options.map(opt => (
|
||||||
|
<label key={opt} className={`flex items-center space-x-3 p-3 rounded-xl cursor-pointer border-2 transition-all ${selected.includes(opt) ? 'bg-white border-primary shadow-md' : 'bg-white/50 border-transparent hover:border-gray-200'}`}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selected.includes(opt)}
|
||||||
|
onChange={() => handleCheckbox(selected, setSelected, opt, noneOption)}
|
||||||
|
className="rounded text-primary focus:ring-primary w-5 h-5 border-gray-300"
|
||||||
|
/>
|
||||||
|
<span className={`text-sm font-semibold ${selected.includes(opt) ? 'text-primary' : 'text-gray-600'}`}>{opt}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{showTextarea && (
|
||||||
|
<textarea
|
||||||
|
value={textValue}
|
||||||
|
onChange={(e) => setTextValue(e.target.value)}
|
||||||
|
className={`${commonInputStyles} animate-fade-in mt-2`}
|
||||||
|
rows={3}
|
||||||
|
placeholder={placeholder}
|
||||||
|
></textarea>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-gray-900/75 backdrop-blur-sm overflow-y-auto">
|
||||||
|
<div className="bg-white rounded-3xl shadow-2xl w-full max-w-3xl flex flex-col my-8 relative animate-fade-in-up border border-white/20">
|
||||||
|
|
||||||
|
<button onClick={onClose} className="absolute top-6 right-6 p-2 hover:bg-gray-100 rounded-full transition-colors z-10 bg-white shadow-sm border border-gray-100">
|
||||||
|
<X className="w-5 h-5 text-gray-500" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="p-8 border-b border-gray-100 bg-gradient-to-r from-blue-50/50 to-purple-50/50 rounded-t-3xl">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="bg-white p-3 rounded-2xl shadow-md text-primary border border-gray-100">
|
||||||
|
<MessageSquare className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-black text-gray-900 tracking-tighter">Visszajelzés a Weboldalról</h2>
|
||||||
|
<p className="text-sm text-gray-500 font-medium mt-1">A bemutató verzió alapján kérjük jelezd, ha valamit módosítanál.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-8 md:p-10 space-y-10">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-bold text-gray-900 mb-5 flex items-center gap-2">
|
||||||
|
Döntés a továbblépésről <span className="text-red-500">*</span>
|
||||||
|
</h3>
|
||||||
|
<div className="grid grid-cols-1 gap-4">
|
||||||
|
{[
|
||||||
|
{ id: 'approved', label: 'Igen, jóváhagyom a terveket', color: 'green', icon: '✅' },
|
||||||
|
{ id: 'minor', label: 'Alapvetően jó, de kisebb javításokat kérek', color: 'yellow', icon: '🔧' },
|
||||||
|
{ id: 'major', label: 'Nem megfelelő, jelentős módosításra van szükség', color: 'red', icon: '❌' }
|
||||||
|
].map(opt => (
|
||||||
|
<label key={opt.id} className={`flex items-center p-5 border-2 rounded-2xl cursor-pointer transition-all ${mainDecision === opt.id ? `border-${opt.color}-500 bg-${opt.color}-50 shadow-lg ring-1 ring-${opt.color}-500` : 'border-gray-100 hover:border-gray-200'}`}>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="decision"
|
||||||
|
checked={mainDecision === opt.id}
|
||||||
|
onChange={() => setMainDecision(opt.id as any)}
|
||||||
|
className={`w-6 h-6 text-${opt.color}-600 focus:ring-${opt.color}-500 border-gray-300`}
|
||||||
|
/>
|
||||||
|
<span className="ml-4 font-bold text-gray-900">{opt.icon} {opt.label}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{mainDecision === 'approved' && (
|
||||||
|
<div className="animate-fade-in bg-green-50 p-6 rounded-2xl border-2 border-green-200 shadow-inner">
|
||||||
|
<h3 className="text-green-900 font-black mb-4 flex items-center gap-2 uppercase text-sm tracking-widest">
|
||||||
|
<CheckCircle className="w-5 h-5" /> Megerősítés
|
||||||
|
</h3>
|
||||||
|
<label className="flex items-start cursor-pointer group">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={approvalConfirmed}
|
||||||
|
onChange={(e) => setApprovalConfirmed(e.target.checked)}
|
||||||
|
className="mt-1 w-6 h-6 text-green-600 rounded-lg focus:ring-green-500 border-green-300"
|
||||||
|
/>
|
||||||
|
<span className="ml-4 text-sm text-green-800 font-bold leading-relaxed">
|
||||||
|
Tudomásul veszem, hogy a jóváhagyás után a végleges fejlesztés megkezdődik a tervek alapján.
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isRevision && (
|
||||||
|
<div className="animate-fade-in space-y-8 border-t border-gray-100 pt-10">
|
||||||
|
|
||||||
|
{renderFeedbackCategory(
|
||||||
|
"Dizájn módosítások",
|
||||||
|
["Színek", "Betűtípusok", "Elrendezés", "Képek / Illusztrációk", "Nem szeretnék dizájn módosítást"],
|
||||||
|
designCheckboxes, setDesignCheckboxes,
|
||||||
|
designText, setDesignText,
|
||||||
|
"Írd le pontosan, mit változtatnál a megjelenésen..."
|
||||||
|
)}
|
||||||
|
|
||||||
|
{renderFeedbackCategory(
|
||||||
|
"Tartalmi változtatások",
|
||||||
|
["Szövegek stílusa", "Adatok pontosítása", "Hiányzó tartalom", "Nem szeretnék tartalmi módosítást"],
|
||||||
|
contentCheckboxes, setContentCheckboxes,
|
||||||
|
contentText, setContentText,
|
||||||
|
"Írd le a szöveges módosításokat..."
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="text-sm font-black text-gray-900 mb-3 uppercase tracking-widest">Mi a legfontosabb kérésed? <span className="text-red-500">*</span></h3>
|
||||||
|
<textarea
|
||||||
|
value={priorityText}
|
||||||
|
onChange={(e) => setPriorityText(e.target.value)}
|
||||||
|
className={commonInputStyles}
|
||||||
|
rows={2}
|
||||||
|
placeholder="A legkritikusabb pont, amin változtatni kell..."
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="text-sm font-black text-gray-900 mb-3 uppercase tracking-widest">Egyéb észrevételek</h3>
|
||||||
|
<textarea
|
||||||
|
value={extraNotes}
|
||||||
|
onChange={(e) => setExtraNotes(e.target.value)}
|
||||||
|
className={commonInputStyles}
|
||||||
|
rows={4}
|
||||||
|
placeholder="Bármi egyéb megjegyzés..."
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-yellow-50 p-6 rounded-2xl border-2 border-yellow-200 shadow-inner">
|
||||||
|
<label className="flex items-start cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={revisionConfirmed}
|
||||||
|
onChange={(e) => setRevisionConfirmed(e.target.checked)}
|
||||||
|
className="mt-1 w-6 h-6 text-yellow-600 rounded-lg focus:ring-yellow-500 border-yellow-300"
|
||||||
|
/>
|
||||||
|
<span className="ml-4 text-sm text-yellow-900 font-bold leading-relaxed">
|
||||||
|
Tudomásul veszem, hogy a kért módosítások feldolgozása után kollégáik keresni fognak az újabb verzióval.
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-8 border-t border-gray-100 bg-gray-50/50 rounded-b-3xl flex flex-col sm:flex-row justify-end gap-4">
|
||||||
|
<Button variant="white" onClick={onClose} disabled={loading} className="px-10 border-gray-200">Mégse</Button>
|
||||||
|
|
||||||
|
{mainDecision === 'approved' && (
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={loading || !approvalConfirmed}
|
||||||
|
className="bg-green-600 hover:bg-green-700 text-white font-black uppercase tracking-widest px-10 shadow-lg shadow-green-200"
|
||||||
|
>
|
||||||
|
{loading ? 'Küldés...' : 'Végleges Jóváhagyás'}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isRevision && (
|
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={loading || !revisionConfirmed || !priorityText.trim()}
|
||||||
|
className="font-black uppercase tracking-widest px-10 shadow-lg shadow-primary/20"
|
||||||
|
>
|
||||||
|
{loading ? 'Küldés...' : 'Visszajelzés Elküldése'}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!mainDecision && (
|
||||||
|
<Button disabled className="opacity-50 cursor-not-allowed font-black uppercase tracking-widest px-10">
|
||||||
|
Válasszon opciót
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
72
components/Footer.tsx
Normal file
72
components/Footer.tsx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Facebook, Instagram, Code2, Mail } from 'lucide-react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
export const Footer: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<footer className="bg-gray-900 text-white pt-16 pb-8 border-t border-gray-800">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12 mb-12">
|
||||||
|
{/* Brand Info */}
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<div className="p-2 bg-gradient-to-br from-primary to-secondary rounded-lg">
|
||||||
|
<Code2 className="h-6 w-6 text-white" />
|
||||||
|
</div>
|
||||||
|
<span className="text-2xl font-bold">MotionWeb</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-400 text-sm leading-relaxed">
|
||||||
|
Innovatív webes megoldások, amelyek üzleti eredményeket hoznak. Teljeskörű digitális partner cégek számára.
|
||||||
|
</p>
|
||||||
|
<div className="flex space-x-4">
|
||||||
|
<a href="#" className="w-10 h-10 rounded-full bg-gray-800 flex items-center justify-center text-gray-400 hover:bg-primary hover:text-white transition-all duration-300">
|
||||||
|
<Facebook size={18} />
|
||||||
|
</a>
|
||||||
|
<a href="#" className="w-10 h-10 rounded-full bg-gray-800 flex items-center justify-center text-gray-400 hover:bg-primary hover:text-white transition-all duration-300">
|
||||||
|
<Instagram size={18} />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Services */}
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-bold mb-6 text-white">Szolgáltatások</h3>
|
||||||
|
<ul className="space-y-3">
|
||||||
|
<li className="text-gray-400 text-sm">Egyedi Weboldal</li>
|
||||||
|
<li className="text-gray-400 text-sm">Reszponzív Design</li>
|
||||||
|
<li className="text-gray-400 text-sm">SEO Optimalizálás</li>
|
||||||
|
<li className="text-gray-400 text-sm">UI/UX Design</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Contact Info - Modified */}
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-bold mb-6 text-white">Elérhetőség</h3>
|
||||||
|
<ul className="space-y-4">
|
||||||
|
<li className="flex items-center space-x-3">
|
||||||
|
<Mail className="w-5 h-5 text-primary flex-shrink-0" />
|
||||||
|
<a href="mailto:motionstudiohq@gmail.com" className="text-gray-400 hover:text-white transition-colors text-sm">motionstudiohq@gmail.com</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Information (Replaced Newsletter) */}
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-bold mb-6 text-white">Információk</h3>
|
||||||
|
<ul className="space-y-3">
|
||||||
|
<li><Link to="/privacy" className="text-gray-400 hover:text-primary transition-colors text-sm">Adatkezelési tájékoztató</Link></li>
|
||||||
|
<li><Link to="/terms" className="text-gray-400 hover:text-primary transition-colors text-sm">Általános Szerződési Feltételek</Link></li>
|
||||||
|
<li><Link to="/faq" className="text-gray-400 hover:text-primary transition-colors text-sm">Gyakori Kérdések</Link></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="border-t border-gray-800 pt-8 flex flex-col md:flex-row justify-between items-center gap-4">
|
||||||
|
<p className="text-gray-500 text-sm text-center md:text-left">
|
||||||
|
© {new Date().getFullYear()} Motion Web Stúdió. Minden jog fenntartva.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
};
|
||||||
236
components/Navbar.tsx
Normal file
236
components/Navbar.tsx
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import { Menu, X, Code2, User, LogIn, UserPlus, LogOut, LayoutDashboard, ShieldAlert } from 'lucide-react';
|
||||||
|
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
import { Button } from './Button';
|
||||||
|
import { useAuth } from '../context/AuthContext';
|
||||||
|
|
||||||
|
export const Navbar: React.FC = () => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [isProfileOpen, setIsProfileOpen] = useState(false);
|
||||||
|
const [scrolled, setScrolled] = useState(false);
|
||||||
|
const location = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const profileRef = useRef<HTMLDivElement>(null);
|
||||||
|
const { user, signOut, isAdmin } = useAuth();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleScroll = () => {
|
||||||
|
setScrolled(window.scrollY > 20);
|
||||||
|
};
|
||||||
|
window.addEventListener('scroll', handleScroll);
|
||||||
|
|
||||||
|
// Close dropdown when clicking outside
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (profileRef.current && !profileRef.current.contains(event.target as Node)) {
|
||||||
|
setIsProfileOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('scroll', handleScroll);
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsOpen(false);
|
||||||
|
setIsProfileOpen(false);
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
await signOut();
|
||||||
|
navigate('/');
|
||||||
|
setIsProfileOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper to determine if a link is active based on hash or path
|
||||||
|
const isActive = (path: string) => {
|
||||||
|
if (path === '/') return location.pathname === '/' && !location.hash;
|
||||||
|
if (path.startsWith('/#')) return location.hash === path.substring(1);
|
||||||
|
return location.pathname === path;
|
||||||
|
};
|
||||||
|
|
||||||
|
const navLinks = [
|
||||||
|
{ name: 'Kezdőlap', path: '/' },
|
||||||
|
{ name: 'Szolgáltatások', path: '/#services' },
|
||||||
|
{ name: 'Referenciák', path: '/#references' },
|
||||||
|
{ name: 'Csomagok', path: '/#products' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const linkClass = (path: string) => `text-sm font-medium transition-colors hover:text-primary ${
|
||||||
|
isActive(path) ? 'text-primary' : (
|
||||||
|
!scrolled && location.pathname === '/' ? 'text-gray-100 hover:text-white' : 'text-gray-700'
|
||||||
|
)
|
||||||
|
}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav className={`fixed w-full z-50 transition-all duration-300 ${scrolled ? 'bg-white/90 backdrop-blur-md shadow-md py-2' : 'bg-transparent py-4'}`}>
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex justify-between items-center h-16">
|
||||||
|
{/* Logo */}
|
||||||
|
<Link to="/" className="flex items-center space-x-2 group">
|
||||||
|
<div className="p-2 bg-gradient-to-br from-primary to-secondary rounded-lg group-hover:shadow-lg transition-all duration-300">
|
||||||
|
<Code2 className="h-6 w-6 text-white" />
|
||||||
|
</div>
|
||||||
|
<span className={`text-2xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-primary to-secondary ${!scrolled && location.pathname === '/' ? 'text-white' : ''}`}>
|
||||||
|
MotionWeb
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* Desktop Navigation Links */}
|
||||||
|
<div className="hidden md:flex items-center space-x-8">
|
||||||
|
{navLinks.map((link) => (
|
||||||
|
<Link
|
||||||
|
key={link.path}
|
||||||
|
to={link.path}
|
||||||
|
className={linkClass(link.path)}
|
||||||
|
>
|
||||||
|
{link.name}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Side Actions */}
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
{/* Desktop Order Button */}
|
||||||
|
<div className="hidden md:block">
|
||||||
|
<Link to="/#rendeles">
|
||||||
|
<Button variant={!scrolled && location.pathname === '/' ? 'white' : 'primary'} size="sm">
|
||||||
|
Rendelés
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Profile Dropdown */}
|
||||||
|
<div className="relative" ref={profileRef}>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsProfileOpen(!isProfileOpen)}
|
||||||
|
className={`p-2 rounded-full transition-colors duration-300 flex items-center justify-center ${
|
||||||
|
!scrolled && location.pathname === '/'
|
||||||
|
? 'text-white hover:bg-white/10'
|
||||||
|
: 'text-gray-700 hover:bg-gray-100'
|
||||||
|
} ${user ? 'ring-2 ring-primary/20 bg-primary/5' : ''}`}
|
||||||
|
aria-label="Felhasználói fiók"
|
||||||
|
>
|
||||||
|
<User className="w-6 h-6" />
|
||||||
|
{isAdmin && <div className="absolute top-1 right-1 w-2.5 h-2.5 bg-red-500 rounded-full border-2 border-white"></div>}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{isProfileOpen && (
|
||||||
|
<div className="absolute right-0 mt-3 w-64 bg-white rounded-xl shadow-2xl py-2 border border-gray-100 transform origin-top-right animate-fade-in z-50">
|
||||||
|
<div className="px-4 py-3 border-b border-gray-100 bg-gray-50/50">
|
||||||
|
<p className="text-sm font-semibold text-gray-900">Fiókom</p>
|
||||||
|
<p className="text-xs text-gray-500 truncate">{user ? user.email : 'Vendég felhasználó'}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="py-2">
|
||||||
|
{user ? (
|
||||||
|
<>
|
||||||
|
<Link to="/dashboard" className="flex items-center px-4 py-2.5 text-sm text-gray-700 hover:bg-purple-50 hover:text-primary transition-colors">
|
||||||
|
<LayoutDashboard className="w-4 h-4 mr-3" /> Vezérlőpult
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{isAdmin && (
|
||||||
|
<Link to="/admin" className="flex items-center px-4 py-2.5 text-sm text-red-600 hover:bg-red-50 hover:text-red-700 transition-colors font-semibold">
|
||||||
|
<ShieldAlert className="w-4 h-4 mr-3" /> Admin Felület
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="border-t border-gray-100 my-1"></div>
|
||||||
|
<button onClick={handleLogout} className="w-full flex items-center px-4 py-2.5 text-sm text-red-600 hover:bg-red-50 transition-colors">
|
||||||
|
<LogOut className="w-4 h-4 mr-3" /> Kijelentkezés
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Link to="/auth/login" className="flex items-center px-4 py-2.5 text-sm text-gray-700 hover:bg-purple-50 hover:text-primary transition-colors">
|
||||||
|
<LogIn className="w-4 h-4 mr-3" /> Bejelentkezés
|
||||||
|
</Link>
|
||||||
|
<Link to="/auth/register" className="flex items-center px-4 py-2.5 text-sm text-gray-700 hover:bg-purple-50 hover:text-primary transition-colors">
|
||||||
|
<UserPlus className="w-4 h-4 mr-3" /> Regisztráció
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Menu Toggle */}
|
||||||
|
<div className="md:hidden">
|
||||||
|
<button
|
||||||
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
|
className={`p-2 rounded-md ${!scrolled && location.pathname === '/' ? 'text-white' : 'text-gray-700'}`}
|
||||||
|
>
|
||||||
|
{isOpen ? <X className="h-6 w-6" /> : <Menu className="h-6 w-6" />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Menu */}
|
||||||
|
{isOpen && (
|
||||||
|
<div className="md:hidden bg-white border-t border-gray-100 shadow-xl absolute w-full animate-fade-in">
|
||||||
|
<div className="px-2 pt-2 pb-3 space-y-1 sm:px-3">
|
||||||
|
{navLinks.map((link) => (
|
||||||
|
<Link
|
||||||
|
key={link.path}
|
||||||
|
to={link.path}
|
||||||
|
className={`block px-3 py-2 rounded-md text-base font-medium ${
|
||||||
|
isActive(link.path)
|
||||||
|
? 'text-primary bg-purple-50'
|
||||||
|
: 'text-gray-700 hover:text-primary hover:bg-gray-50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{link.name}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
<div className="pt-4 pb-2 px-3 space-y-3">
|
||||||
|
<Link to="/#rendeles" className="block w-full">
|
||||||
|
<Button fullWidth>Rendelés</Button>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{!user && (
|
||||||
|
<div className="grid grid-cols-2 gap-3 pt-2">
|
||||||
|
<Link to="/auth/login">
|
||||||
|
<Button variant="outline" fullWidth size="sm">Belépés</Button>
|
||||||
|
</Link>
|
||||||
|
<Link to="/auth/register">
|
||||||
|
<Button variant="secondary" fullWidth size="sm">Regisztráció</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{user && (
|
||||||
|
<div className="pt-2 border-t border-gray-100">
|
||||||
|
<div className="px-1 py-2 flex items-center gap-3 mb-2">
|
||||||
|
<div className="w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center text-primary">
|
||||||
|
<User className="w-4 h-4" />
|
||||||
|
</div>
|
||||||
|
<div className="overflow-hidden">
|
||||||
|
<p className="text-sm font-medium text-gray-900 truncate">{user.email}</p>
|
||||||
|
{isAdmin && <p className="text-[10px] text-red-500 font-bold uppercase">Adminisztrátor</p>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Link to="/dashboard" className="block w-full text-center py-2 text-sm font-medium text-gray-700 bg-gray-50 rounded-lg mb-2">
|
||||||
|
Vezérlőpult megnyitása
|
||||||
|
</Link>
|
||||||
|
{isAdmin && (
|
||||||
|
<Link to="/admin" className="block w-full text-center py-2 text-sm font-medium text-red-600 bg-red-50 rounded-lg mb-2 border border-red-100">
|
||||||
|
Admin Felület
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
<button onClick={handleLogout} className="block w-full text-center py-2 text-sm font-medium text-red-600 border border-red-100 rounded-lg hover:bg-red-50">
|
||||||
|
Kijelentkezés
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
};
|
||||||
876
components/OrderForm.tsx
Normal file
876
components/OrderForm.tsx
Normal file
@@ -0,0 +1,876 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Send, CheckCircle, AlertCircle, Globe, Server, Check,
|
||||||
|
ArrowRight, ArrowLeft, User, FileText, Target, Layout,
|
||||||
|
Palette, Zap, Lightbulb, Settings, ClipboardCheck, Lock,
|
||||||
|
Cloud, Upload
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { Button } from './Button';
|
||||||
|
import { useAuth } from '../context/AuthContext';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { supabase, isSupabaseConfigured } from '../lib/supabaseClient';
|
||||||
|
|
||||||
|
interface Inspiration {
|
||||||
|
url: string;
|
||||||
|
comment: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OrderForm: React.FC = () => {
|
||||||
|
const { user, loading } = useAuth();
|
||||||
|
const [currentStep, setCurrentStep] = useState(1);
|
||||||
|
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
const [submitError, setSubmitError] = useState<string | null>(null);
|
||||||
|
const [errors, setErrors] = useState<string[]>([]);
|
||||||
|
const [privacyAccepted, setPrivacyAccepted] = useState(false);
|
||||||
|
const [aszfAccepted, setAszfAccepted] = useState(false);
|
||||||
|
const totalSteps = 9;
|
||||||
|
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
// 1. Kapcsolattartás
|
||||||
|
name: '',
|
||||||
|
company: '',
|
||||||
|
email: user?.email || '',
|
||||||
|
phone: '',
|
||||||
|
package: 'Pro Web', // Default selection updated
|
||||||
|
|
||||||
|
// 2. Bemutatkozás
|
||||||
|
description: '',
|
||||||
|
|
||||||
|
// 3. Célok
|
||||||
|
goals: [] as string[],
|
||||||
|
goalOther: '',
|
||||||
|
successCriteria: '',
|
||||||
|
|
||||||
|
// 4. Tartalom
|
||||||
|
content: [] as string[],
|
||||||
|
contentOther: '',
|
||||||
|
existingAssets: 'Nincs' as 'Igen' | 'Nem' | 'Részben',
|
||||||
|
contentLink: '', // New field for drive link
|
||||||
|
|
||||||
|
// 5. Design
|
||||||
|
primaryColor: '',
|
||||||
|
secondaryColor: '',
|
||||||
|
balanceColor: '',
|
||||||
|
style: [] as string[],
|
||||||
|
targetAudience: '',
|
||||||
|
|
||||||
|
// 6. Funkciók
|
||||||
|
features: [] as string[],
|
||||||
|
|
||||||
|
// 7. Inspirációk
|
||||||
|
inspirations: [
|
||||||
|
{ url: '', comment: '' },
|
||||||
|
{ url: '', comment: '' },
|
||||||
|
{ url: '', comment: '' }
|
||||||
|
] as Inspiration[],
|
||||||
|
|
||||||
|
// 8. Extrák & Megjegyzések
|
||||||
|
extras: [] as string[],
|
||||||
|
domainName: '',
|
||||||
|
notes: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
// Helper arrays
|
||||||
|
const colorOptions = [
|
||||||
|
{ name: 'Piros', value: '#ef4444' },
|
||||||
|
{ name: 'Kék', value: '#3b82f6' },
|
||||||
|
{ name: 'Zöld', value: '#22c55e' },
|
||||||
|
{ name: 'Lila', value: '#a855f7' },
|
||||||
|
{ name: 'Fekete', value: '#171717' },
|
||||||
|
{ name: 'Fehér', value: '#ffffff' },
|
||||||
|
{ name: 'Szürke', value: '#6b7280' },
|
||||||
|
{ name: 'Narancs', value: '#f97316' },
|
||||||
|
{ name: 'Sárga', value: '#eab308' },
|
||||||
|
{ name: 'Türkiz', value: '#14b8a6' },
|
||||||
|
{ name: 'Barna', value: '#78350f' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const styleOptions = [
|
||||||
|
'Modern és letisztult', 'Üzleti és professzionális', 'Fiatalos és energikus',
|
||||||
|
'Spirituális / nyugodt', 'Természetes / barátságos', 'Luxus / prémium'
|
||||||
|
];
|
||||||
|
|
||||||
|
const steps = [
|
||||||
|
{ id: 1, title: 'Kapcsolat', icon: User },
|
||||||
|
{ id: 2, title: 'Bemutatkozás', icon: FileText },
|
||||||
|
{ id: 3, title: 'Célok', icon: Target },
|
||||||
|
{ id: 4, title: 'Tartalom', icon: Layout },
|
||||||
|
{ id: 5, title: 'Design', icon: Palette },
|
||||||
|
{ id: 6, title: 'Funkciók', icon: Zap },
|
||||||
|
{ id: 7, title: 'Inspirációk', icon: Lightbulb },
|
||||||
|
{ id: 8, title: 'Extrák', icon: Settings },
|
||||||
|
{ id: 9, title: 'Összegzés', icon: ClipboardCheck },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Auto-fill user data (Name and Email) when user loads
|
||||||
|
React.useEffect(() => {
|
||||||
|
const prefillUserData = async () => {
|
||||||
|
if (!user) return;
|
||||||
|
|
||||||
|
let emailToSet = user.email || '';
|
||||||
|
let nameToSet = '';
|
||||||
|
|
||||||
|
// 1. Try metadata (from session - fastest)
|
||||||
|
const meta = user.user_metadata;
|
||||||
|
if (meta?.last_name && meta?.first_name) {
|
||||||
|
// Hungarian order: Lastname Firstname
|
||||||
|
nameToSet = `${meta.last_name} ${meta.first_name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Fallback to DB if Supabase is configured and metadata is missing name
|
||||||
|
if (!nameToSet && isSupabaseConfigured) {
|
||||||
|
try {
|
||||||
|
const { data } = await supabase
|
||||||
|
.from('profiles')
|
||||||
|
.select('first_name, last_name')
|
||||||
|
.eq('id', user.id)
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
if (data?.first_name && data?.last_name) {
|
||||||
|
nameToSet = `${data.last_name} ${data.first_name}`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching user profile for order form:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFormData(prev => {
|
||||||
|
// Only update if fields are empty to avoid overwriting user input
|
||||||
|
if (prev.name && prev.email) return prev;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
email: prev.email || emailToSet,
|
||||||
|
name: prev.name || nameToSet
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
prefillUserData();
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
// If loading auth state, show a skeleton or loader
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div id="order-form-container" className="bg-white rounded-3xl shadow-xl p-12 text-center border border-gray-100 min-h-[400px] flex items-center justify-center">
|
||||||
|
<div className="animate-pulse text-gray-400">Betöltés...</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth Protection - Lock Screen
|
||||||
|
if (!user) {
|
||||||
|
return (
|
||||||
|
<div id="order-form-container" className="bg-white rounded-3xl shadow-xl overflow-hidden border border-gray-100 scroll-mt-24 max-w-4xl mx-auto">
|
||||||
|
<div className="bg-gradient-to-br from-[#eef2ff] to-[#f5f3ff] pt-12 pb-8 px-6 md:px-12 border-b border-white shadow-sm text-center">
|
||||||
|
<div className="w-20 h-20 bg-white rounded-full flex items-center justify-center mx-auto mb-6 shadow-md text-[#4e6bff]">
|
||||||
|
<Lock className="w-10 h-10" />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-3xl font-extrabold text-[#111827] mb-3">Jelentkezzen be a rendeléshez</h2>
|
||||||
|
<p className="text-[#4b5563] text-lg max-w-xl mx-auto">
|
||||||
|
Weboldal rendelés leadásához kérjük, jelentkezzen be fiókjába, vagy regisztráljon egyet ingyenesen.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="p-12 text-center bg-white">
|
||||||
|
<div className="flex flex-col sm:flex-row gap-4 justify-center max-w-md mx-auto">
|
||||||
|
<Link to="/auth/login" className="w-full">
|
||||||
|
<Button fullWidth size="lg">Bejelentkezés</Button>
|
||||||
|
</Link>
|
||||||
|
<Link to="/auth/register" className="w-full">
|
||||||
|
<Button variant="outline" fullWidth size="lg">Regisztráció</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<p className="mt-8 text-sm text-gray-500">
|
||||||
|
A regisztráció mindössze 1 percet vesz igénybe, és segít a projekt későbbi nyomon követésében.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setFormData(prev => ({ ...prev, [name]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCheckboxChange = (category: 'goals' | 'content' | 'style' | 'features' | 'extras', value: string) => {
|
||||||
|
setFormData(prev => {
|
||||||
|
const list = prev[category];
|
||||||
|
if (list.includes(value)) {
|
||||||
|
return { ...prev, [category]: list.filter(item => item !== value) };
|
||||||
|
} else {
|
||||||
|
return { ...prev, [category]: [...list, value] };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInspirationChange = (index: number, field: 'url' | 'comment', value: string) => {
|
||||||
|
const newInspirations = [...formData.inspirations];
|
||||||
|
newInspirations[index] = { ...newInspirations[index], [field]: value };
|
||||||
|
setFormData(prev => ({ ...prev, inspirations: newInspirations }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateStep = (step: number): boolean => {
|
||||||
|
const newErrors: string[] = [];
|
||||||
|
|
||||||
|
if (step === 1) {
|
||||||
|
if (!formData.name) newErrors.push('A név megadása kötelező.');
|
||||||
|
if (!formData.email) newErrors.push('Az e-mail cím megadása kötelező.');
|
||||||
|
if (!formData.phone) newErrors.push('A telefonszám megadása kötelező.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newErrors.length > 0) {
|
||||||
|
setErrors(newErrors);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setErrors([]);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const nextStep = () => {
|
||||||
|
if (validateStep(currentStep)) {
|
||||||
|
if (currentStep < totalSteps) {
|
||||||
|
setCurrentStep(prev => prev + 1);
|
||||||
|
window.scrollTo({ top: document.getElementById('order-form-container')?.offsetTop || 0, behavior: 'smooth' });
|
||||||
|
} else {
|
||||||
|
handleSubmit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const prevStep = () => {
|
||||||
|
if (currentStep > 1) {
|
||||||
|
setCurrentStep(prev => prev - 1);
|
||||||
|
window.scrollTo({ top: document.getElementById('order-form-container')?.offsetTop || 0, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
setIsSubmitting(true);
|
||||||
|
setSubmitError(null);
|
||||||
|
|
||||||
|
if (!privacyAccepted) {
|
||||||
|
setSubmitError('A rendelés leadásához el kell fogadnia az Adatkezelési tájékoztatót.');
|
||||||
|
setIsSubmitting(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!aszfAccepted) {
|
||||||
|
setSubmitError('A rendelés leadásához el kell fogadnia az Általános Szerződési Feltételeket (ÁSZF).');
|
||||||
|
setIsSubmitting(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine amount text
|
||||||
|
const amount =
|
||||||
|
formData.package === 'Landing Page' ? '190.000 Ft' :
|
||||||
|
formData.package === 'Pro Web' ? '350.000 Ft' :
|
||||||
|
'Egyedi Árazás';
|
||||||
|
|
||||||
|
if (isSupabaseConfigured && user) {
|
||||||
|
try {
|
||||||
|
const { error } = await supabase.from('orders').insert({
|
||||||
|
user_id: user.id,
|
||||||
|
customer_name: formData.name,
|
||||||
|
customer_email: formData.email,
|
||||||
|
package: formData.package,
|
||||||
|
status: 'new',
|
||||||
|
amount: amount,
|
||||||
|
details: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSubmitted(true);
|
||||||
|
window.scrollTo({ top: document.getElementById('order-form-container')?.offsetTop || 0, behavior: 'smooth' });
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('Error submitting order:', err);
|
||||||
|
setSubmitError('Hiba történt a rendelés elküldésekor: ' + err.message);
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback for Demo Mode
|
||||||
|
console.log('Demo Mode: Order Data:', formData);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1500)); // Simulate delay
|
||||||
|
setIsSubmitted(true);
|
||||||
|
setIsSubmitting(false);
|
||||||
|
window.scrollTo({ top: document.getElementById('order-form-container')?.offsetTop || 0, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Modern Input Style Class - MotionWeb Style
|
||||||
|
const inputClass = "w-full px-4 py-3 rounded-lg border border-gray-200 bg-white text-[#111111] placeholder-gray-400 focus:ring-4 focus:ring-[#4e6bff]/10 focus:border-[#4e6bff] outline-none transition-all shadow-sm";
|
||||||
|
const labelClass = "text-sm font-semibold text-gray-800 block mb-2";
|
||||||
|
const checkboxClass = "w-5 h-5 text-[#4e6bff] rounded focus:ring-[#4e6bff] border-gray-300 flex-shrink-0 transition-colors cursor-pointer";
|
||||||
|
|
||||||
|
const ColorPickerSection = ({ label, selected, onChange }: { label: string, selected: string, onChange: (color: string) => void }) => (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className={labelClass}>{label}</label>
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{colorOptions.map((color) => (
|
||||||
|
<button
|
||||||
|
key={color.name}
|
||||||
|
type="button"
|
||||||
|
onClick={() => onChange(color.name)}
|
||||||
|
className={`w-9 h-9 rounded-full border flex items-center justify-center transition-all hover:scale-110 hover:shadow-md ${selected === color.name ? 'border-gray-800 ring-2 ring-offset-2 ring-gray-200 scale-110' : 'border-gray-200'}`}
|
||||||
|
style={{ backgroundColor: color.value }}
|
||||||
|
title={color.name}
|
||||||
|
>
|
||||||
|
{selected === color.name && <Check className={`w-4 h-4 ${['Fehér', 'Sárga'].includes(color.name) ? 'text-black' : 'text-white'}`} />}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-gray-500 min-h-[1.25rem]">{selected ? `Választott: ${selected}` : ''}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isSubmitted) {
|
||||||
|
return (
|
||||||
|
<div id="order-form-container" className="bg-white rounded-3xl shadow-xl p-12 text-center border border-gray-100 animate-fade-in-up scroll-mt-24 max-w-4xl mx-auto">
|
||||||
|
<div className="w-24 h-24 bg-green-50 rounded-full flex items-center justify-center mx-auto mb-6 shadow-sm">
|
||||||
|
<CheckCircle className="w-12 h-12 text-green-500" />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-3xl font-extrabold text-gray-900 mb-4">Rendelését sikeresen rögzítettük!</h2>
|
||||||
|
<p className="text-xl text-gray-600 mb-8 max-w-2xl mx-auto leading-relaxed">
|
||||||
|
Átirányítjuk az előlegfizetési oldalra. Amennyiben ez nem történik meg, kollégáink hamarosan felveszik Önnel a kapcsolatot.
|
||||||
|
</p>
|
||||||
|
<Button onClick={() => { setIsSubmitted(false); setCurrentStep(1); }} variant="outline">Új űrlap kitöltése</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id="order-form-container" className="bg-white rounded-3xl shadow-xl overflow-hidden border border-gray-100 scroll-mt-24 transition-all duration-500 flex flex-col relative">
|
||||||
|
{/* Light Gradient Header & Step Navigation */}
|
||||||
|
<div className="bg-gradient-to-br from-[#eef2ff] to-[#f5f3ff] pt-12 pb-8 px-6 md:px-12 border-b border-white shadow-sm">
|
||||||
|
<div className="text-center mb-10">
|
||||||
|
<h2 className="text-3xl md:text-4xl font-extrabold text-[#111827] mb-3">Rendelés Leadása</h2>
|
||||||
|
<p className="text--[#4b5563] text-lg max-w-2xl mx-auto">Töltse ki az űrlapot a pontos ajánlatadáshoz, és segítünk megvalósítani elképzeléseit.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Desktop Stepper Navigation */}
|
||||||
|
<div className="hidden lg:flex justify-between items-center relative max-w-6xl mx-auto px-4 mb-4">
|
||||||
|
{/* Background Line */}
|
||||||
|
<div className="absolute left-4 right-4 top-5 transform -translate-y-1/2 h-1 bg-gray-200 rounded-full -z-10" />
|
||||||
|
{/* Active Progress Line */}
|
||||||
|
<div
|
||||||
|
className="absolute left-4 top-5 transform -translate-y-1/2 h-1 bg-[#4e6bff] rounded-full -z-10 transition-all duration-500 ease-out"
|
||||||
|
style={{ width: `${((currentStep - 1) / (totalSteps - 1)) * 96}%` }} // 96% to account for padding
|
||||||
|
/>
|
||||||
|
|
||||||
|
{steps.map((step) => {
|
||||||
|
const isActive = step.id === currentStep;
|
||||||
|
const isCompleted = step.id < currentStep;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={step.id} className="relative flex flex-col items-center group">
|
||||||
|
<div className={`w-10 h-10 rounded-full flex items-center justify-center transition-all duration-300 border-2 z-10 ${
|
||||||
|
isActive
|
||||||
|
? 'bg-[#4e6bff] border-[#4e6bff] text-white shadow-[0_0_0_4px_rgba(78,107,255,0.2)] scale-110'
|
||||||
|
: isCompleted
|
||||||
|
? 'bg-[#4e6bff] border-[#4e6bff] text-white'
|
||||||
|
: 'bg-white border-gray-300 text-gray-400'
|
||||||
|
}`}>
|
||||||
|
{isCompleted ? <Check className="w-5 h-5" /> : <step.icon className="w-5 h-5" />}
|
||||||
|
</div>
|
||||||
|
<div className={`absolute top-12 text-xs font-semibold whitespace-nowrap transition-all duration-300 ${
|
||||||
|
isActive ? 'text-[#4e6bff] translate-y-0 opacity-100' : 'text-[#6b7280] translate-y-1'
|
||||||
|
}`}>
|
||||||
|
{step.title}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile/Tablet Progress Bar */}
|
||||||
|
<div className="lg:hidden max-w-2xl mx-auto">
|
||||||
|
<div className="flex justify-between text-xs font-bold text-gray-500 mb-2 uppercase tracking-wider">
|
||||||
|
<span>Lépés {currentStep} / {totalSteps}</span>
|
||||||
|
<span className="text-[#4e6bff]">{steps[currentStep - 1].title}</span>
|
||||||
|
</div>
|
||||||
|
<div className="h-2 w-full bg-gray-200 rounded-full overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="h-full bg-[#4e6bff] shadow-[0_0_10px_rgba(78,107,255,0.5)] transition-all duration-500 ease-out"
|
||||||
|
style={{ width: `${(currentStep / totalSteps) * 100}%` }}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-6 md:p-12 min-h-[400px] bg-white">
|
||||||
|
{errors.length > 0 && (
|
||||||
|
<div className="bg-red-50 border border-red-100 text-red-600 px-4 py-3 rounded-xl flex items-start gap-3 mb-8 animate-fade-in shadow-sm">
|
||||||
|
<AlertCircle className="w-5 h-5 mt-0.5 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<p className="font-bold">Kérjük, javítsa a következő hibákat:</p>
|
||||||
|
<ul className="list-disc list-inside text-sm mt-1 opacity-80">
|
||||||
|
{errors.map((err, idx) => <li key={idx}>{err}</li>)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{submitError && (
|
||||||
|
<div className="bg-red-50 border border-red-100 text-red-600 px-4 py-3 rounded-xl flex items-start gap-3 mb-8 animate-fade-in shadow-sm">
|
||||||
|
<AlertCircle className="w-5 h-5 mt-0.5 flex-shrink-0" />
|
||||||
|
<div>{submitError}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Step 1: Kapcsolattartási adatok */}
|
||||||
|
{currentStep === 1 && (
|
||||||
|
<div className="space-y-8 animate-fade-in max-w-3xl mx-auto">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<div className="w-16 h-16 bg-blue-50 rounded-2xl flex items-center justify-center mx-auto mb-4 text-[#4e6bff]">
|
||||||
|
<User className="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900">Kapcsolattartási adatok</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<label className={labelClass}>Név <span className="text-red-500">*</span></label>
|
||||||
|
<input type="text" name="name" value={formData.name} onChange={handleInputChange} placeholder="Add meg a neved" className={inputClass} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className={labelClass}>Cég neve <span className="text-gray-400 font-normal">(opcionális)</span></label>
|
||||||
|
<input type="text" name="company" value={formData.company} onChange={handleInputChange} placeholder="Ha van céged, írd ide a nevét" className={inputClass} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className={labelClass}>E-mail cím <span className="text-red-500">*</span></label>
|
||||||
|
<input type="email" name="email" value={formData.email} onChange={handleInputChange} placeholder="valami@valami.hu" className={inputClass} readOnly />
|
||||||
|
<p className="text-xs text-gray-500 mt-1">A bejelentkezett e-mail címed.</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className={labelClass}>Telefonszám <span className="text-red-500">*</span></label>
|
||||||
|
<input type="tel" name="phone" value={formData.phone} onChange={handleInputChange} placeholder="+36..." className={inputClass} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-4">
|
||||||
|
<label className={labelClass}>Választott csomag</label>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||||
|
{['Landing Page', 'Pro Web', 'Enterprise'].map((pkg) => (
|
||||||
|
<label key={pkg} className={`relative flex items-center gap-3 px-4 py-4 rounded-xl border-2 cursor-pointer transition-all ${formData.package === pkg ? 'border-[#4e6bff] bg-blue-50/30' : 'border-gray-100 bg-gray-50 hover:border-gray-200'}`}>
|
||||||
|
<input type="radio" name="package" value={pkg} checked={formData.package === pkg} onChange={(e) => setFormData({...formData, package: e.target.value})} className="sr-only" />
|
||||||
|
<div className={`w-5 h-5 rounded-full border-2 flex-shrink-0 flex items-center justify-center ${formData.package === pkg ? 'border-[#4e6bff]' : 'border-gray-300'}`}>
|
||||||
|
{formData.package === pkg && <div className="w-2.5 h-2.5 rounded-full bg-[#4e6bff]" />}
|
||||||
|
</div>
|
||||||
|
<span className={`font-bold text-sm sm:text-base ${formData.package === pkg ? 'text-[#4e6bff]' : 'text-gray-700'}`}>{pkg}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentStep === 2 && (
|
||||||
|
<div className="space-y-8 animate-fade-in max-w-3xl mx-auto">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<div className="w-16 h-16 bg-blue-50 rounded-2xl flex items-center justify-center mx-auto mb-4 text-[#4e6bff]">
|
||||||
|
<FileText className="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900">A vállalkozás rövid bemutatása</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className={labelClass}>Mivel foglalkozik a cég?</label>
|
||||||
|
<textarea name="description" rows={6} value={formData.description} onChange={handleInputChange} placeholder="Pl.: vízszereléssel és háztartási gépek javításával foglalkozunk." className={inputClass}></textarea>
|
||||||
|
<p className="text-sm text-gray-500 mt-2 bg-gray-50 p-3 rounded-lg border border-gray-100 inline-block">💡 Tipp: Írjon le mindent, amit fontosnak tart a tevékenységével kapcsolatban.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentStep === 3 && (
|
||||||
|
<div className="space-y-8 animate-fade-in max-w-3xl mx-auto">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<div className="w-16 h-16 bg-blue-50 rounded-2xl flex items-center justify-center mx-auto mb-4 text-[#4e6bff]">
|
||||||
|
<Target className="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900">Mi a weboldal fő célja?</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
{['Cég bemutatása', 'Termék / szolgáltatás értékesítés', 'Időpontfoglalás vagy ügyfélszerzés', 'Hírlevél / e-mail lista építése', 'Közösségépítés', 'Egyéb'].map((goal) => (
|
||||||
|
<div key={goal}>
|
||||||
|
<label className={`flex items-center space-x-3 cursor-pointer p-4 rounded-xl border transition-all h-full shadow-sm ${formData.goals.includes(goal) ? 'border-[#4e6bff] bg-blue-50/20' : 'border-gray-200 bg-white hover:border-gray-300'}`}>
|
||||||
|
<input type="checkbox" checked={formData.goals.includes(goal)} onChange={() => handleCheckboxChange('goals', goal)} className={checkboxClass} />
|
||||||
|
<span className="text-gray-900 font-medium">{goal}</span>
|
||||||
|
</label>
|
||||||
|
{goal === 'Egyéb' && formData.goals.includes('Egyéb') && (
|
||||||
|
<div className="mt-2 animate-fade-in">
|
||||||
|
<input type="text" name="goalOther" value={formData.goalOther} onChange={handleInputChange} placeholder="Kérjük fejtse ki..." className={`${inputClass} text-sm py-2`} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2 pt-4">
|
||||||
|
<label className={labelClass}>Mi számít sikernek a weboldal esetében? <span className="text-gray-400 font-normal">(opcionális)</span></label>
|
||||||
|
<input type="text" name="successCriteria" value={formData.successCriteria} onChange={handleInputChange} placeholder="Több megrendelés, több hívás, több látogató..." className={inputClass} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentStep === 4 && (
|
||||||
|
<div className="space-y-8 animate-fade-in max-w-3xl mx-auto">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<div className="w-16 h-16 bg-blue-50 rounded-2xl flex items-center justify-center mx-auto mb-4 text-[#4e6bff]">
|
||||||
|
<Layout className="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900">Tartalom és szerkezet</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className={labelClass}>Milyen aloldalakat tervez?</label>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
|
{['Rólunk', 'Szolgáltatások', 'Termékek', 'Referenciák / Vélemények', 'Blog / Hírek', 'Kapcsolat', 'Egyéb'].map((item) => (
|
||||||
|
<div key={item}>
|
||||||
|
<label className={`flex items-center space-x-3 cursor-pointer p-3 rounded-lg border transition-all ${formData.content.includes(item) ? 'border-[#4e6bff] bg-blue-50/20' : 'border-transparent hover:bg-gray-50'}`}>
|
||||||
|
<input type="checkbox" checked={formData.content.includes(item)} onChange={() => handleCheckboxChange('content', item)} className={checkboxClass} />
|
||||||
|
<span className="text-gray-800 font-medium">{item}</span>
|
||||||
|
</label>
|
||||||
|
{item === 'Egyéb' && formData.content.includes('Egyéb') && (
|
||||||
|
<input type="text" name="contentOther" value={formData.contentOther} onChange={handleInputChange} placeholder="Egyéb oldalak..." className={`${inputClass} mt-1 py-2 text-sm`} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-8 border-t border-gray-100">
|
||||||
|
<label className={labelClass}>Van már meglévő szöveg, kép vagy logó?</label>
|
||||||
|
<div className="flex flex-wrap gap-4 mt-3">
|
||||||
|
{['Igen', 'Nem', 'Részben'].map((opt) => (
|
||||||
|
<label key={opt} className={`flex items-center gap-3 px-5 py-3 rounded-lg border cursor-pointer transition-all ${formData.existingAssets === opt ? 'border-[#4e6bff] bg-blue-50/20 text-[#4e6bff]' : 'border-gray-200 bg-white hover:bg-gray-50 text-gray-700'}`}>
|
||||||
|
<input type="radio" name="existingAssets" value={opt} checked={formData.existingAssets === opt} onChange={(e) => setFormData({...formData, existingAssets: e.target.value as any})} className="w-4 h-4 text-[#4e6bff] focus:ring-[#4e6bff]" />
|
||||||
|
<span className="font-semibold">{opt}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content Upload / Link Section */}
|
||||||
|
{(formData.existingAssets === 'Igen' || formData.existingAssets === 'Részben') && (
|
||||||
|
<div className="mt-6 bg-blue-50 border border-blue-100 p-6 rounded-xl animate-fade-in">
|
||||||
|
<h4 className="font-bold text-blue-900 mb-3 flex items-center gap-2">
|
||||||
|
<Cloud className="w-5 h-5" /> Tartalom megosztása
|
||||||
|
</h4>
|
||||||
|
<p className="text-sm text-blue-800 mb-4">
|
||||||
|
Kérjük, töltse fel a meglévő tartalmakat (képek, szövegek, logó) egy felhő tárhelyre (pl. Google Drive) és ossza meg a linket, vagy töltsön fel fájlt (max 1GB).
|
||||||
|
</p>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className={labelClass}>Felhő mappa linkje</label>
|
||||||
|
<input
|
||||||
|
type="url"
|
||||||
|
name="contentLink"
|
||||||
|
value={formData.contentLink}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
placeholder="https://drive.google.com/..."
|
||||||
|
className={inputClass}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className={labelClass}>Vagy fájl feltöltése</label>
|
||||||
|
<div className="border-2 border-dashed border-blue-200 rounded-lg p-6 bg-white text-center hover:bg-blue-50/50 transition-colors cursor-pointer relative group">
|
||||||
|
<Upload className="w-8 h-8 text-blue-400 mx-auto mb-2" />
|
||||||
|
<p className="text-sm text-gray-500">Kattintson a feltöltéshez vagy húzza ide a fájlt</p>
|
||||||
|
<p className="text-xs text-gray-400 mt-1">(Max 1GB)</p>
|
||||||
|
<input type="file" className="absolute inset-0 w-full h-full opacity-0 cursor-pointer" title="Fájl feltöltése" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentStep === 5 && (
|
||||||
|
<div className="space-y-8 animate-fade-in max-w-4xl mx-auto">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<div className="w-16 h-16 bg-blue-50 rounded-2xl flex items-center justify-center mx-auto mb-4 text-[#4e6bff]">
|
||||||
|
<Palette className="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900">Design és stílus</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 p-8 bg-gray-50/50 rounded-3xl border border-gray-100">
|
||||||
|
<ColorPickerSection label="Főszín" selected={formData.primaryColor} onChange={(c) => setFormData(prev => ({ ...prev, primaryColor: c }))} />
|
||||||
|
<ColorPickerSection label="Mellékszín" selected={formData.secondaryColor} onChange={(c) => setFormData(prev => ({ ...prev, secondaryColor: c }))} />
|
||||||
|
<ColorPickerSection label="Kiegyensúlyozó szín" selected={formData.balanceColor} onChange={(c) => setFormData(prev => ({ ...prev, balanceColor: c }))} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3 pt-4">
|
||||||
|
<label className={labelClass}>Weboldal stílusa:</label>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
||||||
|
{styleOptions.map((style) => (
|
||||||
|
<label key={style} className={`flex items-center space-x-3 cursor-pointer p-4 rounded-xl border shadow-sm transition-all ${formData.style.includes(style) ? 'border-[#4e6bff] bg-blue-50/20' : 'border-gray-200 bg-white hover:border-gray-300'}`}>
|
||||||
|
<input type="checkbox" checked={formData.style.includes(style)} onChange={() => handleCheckboxChange('style', style)} className={checkboxClass} />
|
||||||
|
<span className="text-gray-900 text-sm font-semibold">{style}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className={labelClass}>Célcsoport <span className="text-gray-400 font-normal">(opcionális)</span></label>
|
||||||
|
<input type="text" name="targetAudience" value={formData.targetAudience} onChange={handleInputChange} placeholder="Pl.: 18–30 éves fiatalok, középkorú nők, vállalkozók stb." className={inputClass} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentStep === 6 && (
|
||||||
|
<div className="space-y-8 animate-fade-in max-w-3xl mx-auto">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<div className="w-16 h-16 bg-blue-50 rounded-2xl flex items-center justify-center mx-auto mb-4 text-[#4e6bff]">
|
||||||
|
<Zap className="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900">Funkciók és technikai igények</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
{['Kapcsolatfelvételi űrlap', 'Időpontfoglalás rendszer', 'Hírlevél-feliratkozás', 'Webshop / fizetési lehetőség', 'Blog / hírek', 'Többnyelvűség', 'Képgaléria / videógaléria', 'Google Térkép integráció', 'Chat gomb (Messenger / WhatsApp)'].map((feat) => (
|
||||||
|
<label key={feat} className={`flex items-center space-x-3 cursor-pointer p-4 rounded-xl border shadow-sm transition-all ${formData.features.includes(feat) ? 'border-[#4e6bff] bg-blue-50/20' : 'border-gray-200 bg-white hover:border-gray-300'}`}>
|
||||||
|
<input type="checkbox" checked={formData.features.includes(feat)} onChange={() => handleCheckboxChange('features', feat)} className={checkboxClass} />
|
||||||
|
<span className="text-gray-900 font-medium">{feat}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentStep === 7 && (
|
||||||
|
<div className="space-y-8 animate-fade-in max-w-3xl mx-auto">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<div className="w-16 h-16 bg-blue-50 rounded-2xl flex items-center justify-center mx-auto mb-4 text-[#4e6bff]">
|
||||||
|
<Lightbulb className="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900">Inspirációk</h3>
|
||||||
|
<p className="text-gray-500 mt-2">Segítsen megérteni az ízlését!</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-blue-50 border border-blue-100 p-5 rounded-xl text-blue-800 text-sm mb-6 flex items-start gap-3">
|
||||||
|
<div className="bg-white p-1 rounded-full"><Lightbulb className="w-4 h-4 text-[#4e6bff]" /></div>
|
||||||
|
<p>Osszon meg velünk 3 weboldalt, ami tetszik, és írja le röviden, miért (pl. színek, elrendezés, hangulat).</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
{[0, 1, 2].map((i) => (
|
||||||
|
<div key={i} className="grid grid-cols-1 md:grid-cols-2 gap-6 bg-gray-50/50 p-6 rounded-2xl border border-gray-100 hover:shadow-md transition-all">
|
||||||
|
<div>
|
||||||
|
<label className="text-xs font-bold text-gray-400 uppercase tracking-wider mb-2 block">Weboldal {i+1} URL</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Globe className="absolute left-3 top-3 w-4 h-4 text-gray-400" />
|
||||||
|
<input
|
||||||
|
type="url"
|
||||||
|
value={formData.inspirations[i].url}
|
||||||
|
onChange={(e) => handleInspirationChange(i, 'url', e.target.value)}
|
||||||
|
placeholder="https://pelda.hu"
|
||||||
|
className={`${inputClass} pl-9`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-xs font-bold text-gray-400 uppercase tracking-wider mb-2 block">Mi tetszik benne?</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formData.inspirations[i].comment}
|
||||||
|
onChange={(e) => handleInspirationChange(i, 'comment', e.target.value)}
|
||||||
|
placeholder="Színek, elrendezés, animációk..."
|
||||||
|
className={inputClass}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentStep === 8 && (
|
||||||
|
<div className="space-y-8 animate-fade-in max-w-3xl mx-auto">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<div className="w-16 h-16 bg-blue-50 rounded-2xl flex items-center justify-center mx-auto mb-4 text-[#4e6bff]">
|
||||||
|
<Settings className="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900">Extra szolgáltatások és Megjegyzések</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<label className={labelClass}>Igényelt extra szolgáltatások:</label>
|
||||||
|
<div className="flex flex-wrap gap-4">
|
||||||
|
{['SEO optimalizálás', 'Szövegírás', 'Domain ügyintézés', 'Tárhely ügyintézés'].map((extra) => (
|
||||||
|
<label key={extra} className={`flex items-center space-x-3 cursor-pointer select-none p-3 rounded-lg border transition-all ${formData.extras.includes(extra) ? 'border-[#4e6bff] bg-blue-50/20' : 'border-gray-200 bg-white hover:border-gray-300'}`}>
|
||||||
|
<input type="checkbox" checked={formData.extras.includes(extra)} onChange={() => handleCheckboxChange('extras', extra)} className={checkboxClass} />
|
||||||
|
<span className="text-gray-900 font-medium">{extra}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Dynamic Content */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
{formData.extras.includes('Domain ügyintézés') && (
|
||||||
|
<div className="bg-blue-50 border border-blue-100 p-6 rounded-xl animate-fade-in">
|
||||||
|
<label className="text-sm font-semibold text-blue-900 block mb-2">Add meg a kívánt domain nevet:</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Globe className="absolute left-3 top-3 w-5 h-5 text-blue-400" />
|
||||||
|
<input type="text" name="domainName" value={formData.domainName} onChange={handleInputChange} placeholder="azencegem.hu" className={`${inputClass} pl-10 border-blue-200 focus:ring-blue-300`} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Domain Warning */}
|
||||||
|
{!formData.extras.includes('Domain ügyintézés') && (
|
||||||
|
<div className="bg-yellow-50 border border-yellow-100 p-6 rounded-xl flex items-start gap-4 animate-fade-in text-yellow-900 mt-2">
|
||||||
|
<div className="bg-white p-2 rounded-full shadow-sm"><Globe className="w-5 h-5 text-yellow-600" /></div>
|
||||||
|
<div className="text-sm leading-relaxed">
|
||||||
|
<strong>Figyelem:</strong> Nem jelölte be a domain ügyintézést. Kérjük győződjön meg róla, hogy rendelkezik saját domain névvel, vagy a későbbiekben tudja biztosítani a DNS beállításokat.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!formData.extras.includes('Tárhely ügyintézés') && (
|
||||||
|
<div className="bg-orange-50 border border-orange-100 p-6 rounded-xl flex items-start gap-4 animate-fade-in text-orange-900">
|
||||||
|
<div className="bg-white p-2 rounded-full shadow-sm"><Server className="w-5 h-5 text-orange-500" /></div>
|
||||||
|
<div className="text-sm leading-relaxed">
|
||||||
|
<strong>Figyelem:</strong> Mivel nem jelölte be a tárhely ügyintézést, a weboldalt átadjuk (forráskód), de az üzemeltetést és a szerver beállítását nem a MotionWeb végzi.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-8 border-t border-gray-100">
|
||||||
|
<label className={labelClass}>Megjegyzések, további kérések</label>
|
||||||
|
<textarea name="notes" rows={5} value={formData.notes} onChange={handleInputChange} placeholder="További ötletek, kérések, megjegyzések..." className={inputClass}></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Step 9: Összegzés */}
|
||||||
|
{currentStep === 9 && (
|
||||||
|
<div className="space-y-8 animate-fade-in max-w-3xl mx-auto">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<div className="w-16 h-16 bg-blue-50 rounded-2xl flex items-center justify-center mx-auto mb-4 text-[#4e6bff]">
|
||||||
|
<ClipboardCheck className="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900">Rendelés összesítése</h3>
|
||||||
|
<p className="text-gray-500">Ellenőrizze az adatokat a véglegesítés előtt.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 text-sm">
|
||||||
|
<div className="bg-gray-50 p-6 rounded-2xl border border-gray-200 space-y-4">
|
||||||
|
<div>
|
||||||
|
<h4 className="font-bold text-gray-400 uppercase tracking-wide text-xs mb-2">Kapcsolattartó</h4>
|
||||||
|
<p className="font-bold text-xl text-gray-900">{formData.name}</p>
|
||||||
|
<p className="text-gray-600">{formData.email}</p>
|
||||||
|
<p className="text-gray-600">{formData.phone}</p>
|
||||||
|
{formData.company && <p className="text-gray-600 font-medium mt-1">{formData.company}</p>}
|
||||||
|
</div>
|
||||||
|
<div className="pt-4 border-t border-gray-200">
|
||||||
|
<h4 className="font-bold text-gray-400 uppercase tracking-wide text-xs mb-2">Választott Csomag</h4>
|
||||||
|
<span className="inline-block bg-[#4e6bff] text-white px-4 py-1.5 rounded-full font-bold text-sm shadow-sm">{formData.package}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-gray-50 p-6 rounded-2xl border border-gray-200 space-y-4">
|
||||||
|
<div>
|
||||||
|
<h4 className="font-bold text-gray-400 uppercase tracking-wide text-xs mb-2">Célok</h4>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{formData.goals.length > 0 ? formData.goals.map(g => <span key={g} className="bg-white border border-gray-200 px-3 py-1 rounded-lg text-xs font-semibold text-gray-700 shadow-sm">{g}</span>) : <span className="text-gray-400 italic">Nincs megadva</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="pt-4 border-t border-gray-200">
|
||||||
|
<h4 className="font-bold text-gray-400 uppercase tracking-wide text-xs mb-2">Színvilág</h4>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
{formData.primaryColor && <div className="w-8 h-8 rounded-full border border-gray-200 shadow-sm" style={{background: colorOptions.find(c => c.name === formData.primaryColor)?.value}} title="Főszín"></div>}
|
||||||
|
{formData.secondaryColor && <div className="w-8 h-8 rounded-full border border-gray-200 shadow-sm" style={{background: colorOptions.find(c => c.name === formData.secondaryColor)?.value}} title="Mellékszín"></div>}
|
||||||
|
{formData.balanceColor && <div className="w-8 h-8 rounded-full border border-gray-200 shadow-sm" style={{background: colorOptions.find(c => c.name === formData.balanceColor)?.value}} title="Kiegyensúlyozó"></div>}
|
||||||
|
{!formData.primaryColor && !formData.secondaryColor && !formData.balanceColor && <span className="text-gray-400 italic">Nincs színválasztás</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-[#4e6bff]/10 border border-[#4e6bff]/20 p-6 rounded-xl text-blue-900 text-sm">
|
||||||
|
<p className="font-bold mb-2">Mi történik a rendelés leadása után?</p>
|
||||||
|
<p className="opacity-90 leading-relaxed">
|
||||||
|
A "Rendelés leadása" gombra kattintva átirányítjuk az előlegfizetési oldalra. A sikeres tranzakciót követően kollégáink 48 órán belül felveszik Önnel a kapcsolatot a megadott elérhetőségeken a projekt indításához.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-start">
|
||||||
|
<div className="flex items-center h-5">
|
||||||
|
<input
|
||||||
|
id="privacy-order"
|
||||||
|
name="privacy"
|
||||||
|
type="checkbox"
|
||||||
|
required
|
||||||
|
checked={privacyAccepted}
|
||||||
|
onChange={(e) => setPrivacyAccepted(e.target.checked)}
|
||||||
|
className="h-4 w-4 text-primary focus:ring-primary border-gray-300 rounded cursor-pointer"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="ml-3 text-sm">
|
||||||
|
<label htmlFor="privacy-order" className="font-medium text-gray-700">
|
||||||
|
Elfogadom az <Link to="/privacy" target="_blank" className="text-primary hover:underline">Adatkezelési tájékoztatót</Link>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-start">
|
||||||
|
<div className="flex items-center h-5">
|
||||||
|
<input
|
||||||
|
id="aszf-order"
|
||||||
|
name="aszf"
|
||||||
|
type="checkbox"
|
||||||
|
required
|
||||||
|
checked={aszfAccepted}
|
||||||
|
onChange={(e) => setAszfAccepted(e.target.checked)}
|
||||||
|
className="h-4 w-4 text-primary focus:ring-primary border-gray-300 rounded cursor-pointer"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="ml-3 text-sm">
|
||||||
|
<label htmlFor="aszf-order" className="font-medium text-gray-700">
|
||||||
|
Elfogadom az <Link to="/terms" target="_blank" className="text-primary hover:underline">Általános Szerződési Feltételeket (ÁSZF)</Link>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer Navigation */}
|
||||||
|
<div className="bg-white border-t border-gray-100 p-8 flex justify-between items-center rounded-b-2xl">
|
||||||
|
<Button
|
||||||
|
onClick={prevStep}
|
||||||
|
variant="white"
|
||||||
|
disabled={currentStep === 1 || isSubmitting}
|
||||||
|
className={`${currentStep === 1 ? 'opacity-0 pointer-events-none' : ''} border border-gray-200 text-gray-600 hover:bg-gray-50 px-6`}
|
||||||
|
>
|
||||||
|
<ArrowLeft className="w-4 h-4 mr-2" /> Vissza
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{currentStep < totalSteps ? (
|
||||||
|
<Button onClick={nextStep} className="bg-[#4e6bff] hover:bg-[#3d54cc] text-white shadow-lg shadow-blue-200 px-8 py-3 rounded-full hover:scale-105 transition-all">
|
||||||
|
Következő <ArrowRight className="w-4 h-4 ml-2" />
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button onClick={handleSubmit} disabled={isSubmitting} className="bg-gradient-to-r from-[#4e6bff] to-[#7c3aed] text-white shadow-xl hover:shadow-2xl hover:scale-105 transition-all px-10 py-4 text-lg rounded-full disabled:opacity-70 disabled:cursor-not-allowed">
|
||||||
|
{isSubmitting ? 'Küldés...' : (
|
||||||
|
<>Rendelés leadása <Send className="w-5 h-5 ml-2" /></>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
97
components/ProcessSection.tsx
Normal file
97
components/ProcessSection.tsx
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
UserPlus,
|
||||||
|
FileText,
|
||||||
|
CreditCard,
|
||||||
|
Layout,
|
||||||
|
MessageSquare,
|
||||||
|
CheckCircle,
|
||||||
|
Globe,
|
||||||
|
ShieldCheck
|
||||||
|
} from 'lucide-react';
|
||||||
|
|
||||||
|
export const ProcessSection: React.FC = () => {
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: 'Regisztráció & fiók létrehozása',
|
||||||
|
description: 'Első lépésként létrehozod a fiókodat, ahol később minden rendelést és értesítést kezelni tudsz.',
|
||||||
|
icon: UserPlus
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: 'Kérdőív kitöltése',
|
||||||
|
description: 'Kitöltöd az irányított kérdőívet, amely alapján elkezdjük megtervezni a weboldalad.',
|
||||||
|
icon: FileText
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
title: 'Előleg fizetése',
|
||||||
|
description: 'A kérdőív után kifizeted az előleget Stripe-on keresztül, ezzel indul a fejlesztési folyamat.',
|
||||||
|
icon: CreditCard
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
title: 'Első verzió elkészítése',
|
||||||
|
description: 'Elkészítjük a bemutató weboldal első verzióját placeholder tartalmakkal.',
|
||||||
|
icon: Layout
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
title: 'Visszajelzés megadása',
|
||||||
|
description: 'Egy gyors űrlapon jelzed, hogy tetszik-e az irány, vagy milyen módosításokat szeretnél.',
|
||||||
|
icon: MessageSquare
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
title: 'Végleges rendelés & teljes fizetés',
|
||||||
|
description: 'A jóváhagyás után kifizeted a teljes árat, és elkészítjük a végleges, teljes funkcionalitású weboldalt.',
|
||||||
|
icon: CheckCircle
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
title: 'Domain csatlakoztatása',
|
||||||
|
description: 'A weboldal rákerül a megadott domainedre, és élesben is elérhetővé válik.',
|
||||||
|
icon: Globe
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
title: 'Fenntartás & támogatás',
|
||||||
|
description: 'A továbbiakban csak a fenntartási díjat kell fizetned, mi pedig gondoskodunk a stabil működésről.',
|
||||||
|
icon: ShieldCheck
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="py-24 bg-gray-50">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="text-center mb-16">
|
||||||
|
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-4">Hogyan készül el a weboldalad?</h2>
|
||||||
|
<p className="text-lg text-gray-600 max-w-3xl mx-auto">
|
||||||
|
Átlátható folyamat, gyors és professzionális kivitelezés – pontosan tudni fogod, mire számíthatsz.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||||
|
{steps.map((step) => (
|
||||||
|
<div
|
||||||
|
key={step.id}
|
||||||
|
className="bg-white rounded-[24px] p-8 shadow-sm hover:shadow-lg hover:-translate-y-1 transition-all duration-300 group border border-gray-100/50"
|
||||||
|
>
|
||||||
|
<div className="flex justify-between items-start mb-6">
|
||||||
|
<div className="text-blue-500 transition-transform duration-300 group-hover:scale-110">
|
||||||
|
<step.icon className="w-8 h-8 stroke-[1.5]" />
|
||||||
|
</div>
|
||||||
|
<div className="w-8 h-8 rounded-full border-2 border-[#A78BFA] flex items-center justify-center text-sm font-bold text-[#A78BFA] group-hover:bg-[#A78BFA] group-hover:text-white transition-colors duration-300">
|
||||||
|
{step.id}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-lg font-semibold text-gray-900 mb-2">{step.title}</h3>
|
||||||
|
<p className="text-gray-500 text-sm leading-relaxed">{step.description}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
250
components/ProfileCompleter.tsx
Normal file
250
components/ProfileCompleter.tsx
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useAuth } from '../context/AuthContext';
|
||||||
|
import { supabase, isSupabaseConfigured } from '../lib/supabaseClient';
|
||||||
|
import { Button } from './Button';
|
||||||
|
import { User, Save, AlertCircle, Calendar } from 'lucide-react';
|
||||||
|
|
||||||
|
export const ProfileCompleter: React.FC = () => {
|
||||||
|
const { user, refreshDemoUser } = useAuth();
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [firstName, setFirstName] = useState('');
|
||||||
|
const [lastName, setLastName] = useState('');
|
||||||
|
const [dateOfBirth, setDateOfBirth] = useState('');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [checking, setChecking] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!user) {
|
||||||
|
setIsOpen(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkProfile = async () => {
|
||||||
|
setChecking(true);
|
||||||
|
|
||||||
|
// --- DEMO MODE ---
|
||||||
|
if (!isSupabaseConfigured) {
|
||||||
|
const meta = user.user_metadata || {};
|
||||||
|
if (!meta.first_name || !meta.last_name || !meta.date_of_birth) {
|
||||||
|
setIsOpen(true);
|
||||||
|
if (meta.first_name) setFirstName(meta.first_name);
|
||||||
|
if (meta.last_name) setLastName(meta.last_name);
|
||||||
|
if (meta.date_of_birth) setDateOfBirth(meta.date_of_birth);
|
||||||
|
}
|
||||||
|
setChecking(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// -----------------
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('profiles')
|
||||||
|
.select('first_name, last_name, date_of_birth')
|
||||||
|
.eq('id', user.id)
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('Error checking profile:', error.message || error);
|
||||||
|
// If DB check fails, fallback to metadata to see if we should block the user.
|
||||||
|
// This prevents locking the user out if the DB is temporarily unavailable or misconfigured,
|
||||||
|
// provided they have the data in their auth metadata.
|
||||||
|
const meta = user.user_metadata || {};
|
||||||
|
const hasMetadata = meta.first_name && meta.last_name && meta.date_of_birth;
|
||||||
|
|
||||||
|
if (!hasMetadata) {
|
||||||
|
setIsOpen(true);
|
||||||
|
if (meta.first_name) setFirstName(meta.first_name);
|
||||||
|
if (meta.last_name) setLastName(meta.last_name);
|
||||||
|
if (meta.date_of_birth) setDateOfBirth(meta.date_of_birth);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no profile exists, or names/dob are missing
|
||||||
|
if (!data || !data.first_name || !data.last_name || !data.date_of_birth) {
|
||||||
|
setIsOpen(true);
|
||||||
|
// Pre-fill if partial data exists
|
||||||
|
if (data?.first_name) setFirstName(data.first_name);
|
||||||
|
else if (user.user_metadata?.first_name) setFirstName(user.user_metadata.first_name);
|
||||||
|
|
||||||
|
if (data?.last_name) setLastName(data.last_name);
|
||||||
|
else if (user.user_metadata?.last_name) setLastName(user.user_metadata.last_name);
|
||||||
|
|
||||||
|
if (data?.date_of_birth) setDateOfBirth(data.date_of_birth);
|
||||||
|
else if (user.user_metadata?.date_of_birth) setDateOfBirth(user.user_metadata.date_of_birth);
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('Unexpected error in ProfileCompleter:', err);
|
||||||
|
} finally {
|
||||||
|
setChecking(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkProfile();
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setError(null);
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
if (!firstName.trim() || !lastName.trim() || !dateOfBirth) {
|
||||||
|
setError('Minden mező kitöltése kötelező.');
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// --- DEMO MODE UPDATE ---
|
||||||
|
if (!isSupabaseConfigured && user) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 800)); // Fake delay
|
||||||
|
|
||||||
|
// Update local storage session
|
||||||
|
const storedSession = localStorage.getItem('demo_user_session');
|
||||||
|
if (storedSession) {
|
||||||
|
const parsed = JSON.parse(storedSession);
|
||||||
|
parsed.user.user_metadata = {
|
||||||
|
...parsed.user.user_metadata,
|
||||||
|
first_name: firstName,
|
||||||
|
last_name: lastName,
|
||||||
|
date_of_birth: dateOfBirth
|
||||||
|
};
|
||||||
|
localStorage.setItem('demo_user_session', JSON.stringify(parsed));
|
||||||
|
refreshDemoUser(); // Refresh context
|
||||||
|
}
|
||||||
|
setIsOpen(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// ------------------------
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
// 1. Update Profile Table
|
||||||
|
const { error: dbError } = await supabase
|
||||||
|
.from('profiles')
|
||||||
|
.upsert({
|
||||||
|
id: user.id,
|
||||||
|
email: user.email,
|
||||||
|
first_name: firstName,
|
||||||
|
last_name: lastName,
|
||||||
|
date_of_birth: dateOfBirth,
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dbError) throw dbError;
|
||||||
|
|
||||||
|
// 2. Update Auth Metadata (optional, but good for consistency)
|
||||||
|
await supabase.auth.updateUser({
|
||||||
|
data: {
|
||||||
|
first_name: firstName,
|
||||||
|
last_name: lastName,
|
||||||
|
date_of_birth: dateOfBirth
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsOpen(false);
|
||||||
|
// We do not reload here to avoid infinite loops if checks fail on reload.
|
||||||
|
// The state close is enough.
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('Error updating profile:', err);
|
||||||
|
setError('Hiba történt a mentés során: ' + (err.message || 'Ismeretlen hiba'));
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-gray-900/70 backdrop-blur-sm">
|
||||||
|
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-md overflow-hidden border border-gray-100 animate-fade-in-up">
|
||||||
|
<div className="bg-gradient-to-r from-primary to-secondary p-6 text-white text-center">
|
||||||
|
<div className="w-16 h-16 bg-white/20 rounded-full flex items-center justify-center mx-auto mb-4 backdrop-blur-md border border-white/30">
|
||||||
|
<User className="w-8 h-8 text-white" />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-2xl font-bold">Hiányzó Adatok</h2>
|
||||||
|
<p className="text-blue-100 text-sm mt-2">
|
||||||
|
Kérjük, a folytatáshoz adja meg a hiányzó adatait.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-8">
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
|
{error && (
|
||||||
|
<div className="bg-red-50 text-red-700 p-3 rounded-lg text-sm flex items-start">
|
||||||
|
<AlertCircle className="w-5 h-5 mr-2 flex-shrink-0" />
|
||||||
|
<span>{error}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label htmlFor="comp_lastname" className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
Vezetéknév
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="comp_lastname"
|
||||||
|
type="text"
|
||||||
|
value={lastName}
|
||||||
|
onChange={(e) => setLastName(e.target.value)}
|
||||||
|
className="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all bg-white text-gray-900"
|
||||||
|
placeholder="Kovács"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label htmlFor="comp_firstname" className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
Keresztnév
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="comp_firstname"
|
||||||
|
type="text"
|
||||||
|
value={firstName}
|
||||||
|
onChange={(e) => setFirstName(e.target.value)}
|
||||||
|
className="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all bg-white text-gray-900"
|
||||||
|
placeholder="János"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label htmlFor="comp_dob" className="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
Születési dátum
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
id="comp_dob"
|
||||||
|
type="date"
|
||||||
|
value={dateOfBirth}
|
||||||
|
onChange={(e) => setDateOfBirth(e.target.value)}
|
||||||
|
className="w-full px-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all bg-white text-gray-900"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
|
||||||
|
<Calendar className="h-5 w-5 text-gray-400" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-2">
|
||||||
|
<Button type="submit" fullWidth disabled={loading} className="flex justify-center items-center">
|
||||||
|
{loading ? 'Mentés...' : (
|
||||||
|
<>
|
||||||
|
Adatok Mentése <Save className="ml-2 w-4 h-4" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-xs text-center text-gray-500">
|
||||||
|
Ezekre az adatokra a számlázáshoz és a kapcsolattartáshoz van szükségünk.
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
17
components/ProtectedRoute.tsx
Normal file
17
components/ProtectedRoute.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Navigate } from 'react-router-dom';
|
||||||
|
import { useAuth } from '../context/AuthContext';
|
||||||
|
|
||||||
|
export const ProtectedRoute: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||||
|
const { user, loading } = useAuth();
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <div className="min-h-screen flex items-center justify-center bg-gray-50 text-primary">Betöltés...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return <Navigate to="/auth/login" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
22
components/ScrollToTop.tsx
Normal file
22
components/ScrollToTop.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
||||||
|
export default function ScrollToTop() {
|
||||||
|
const { pathname, hash } = useLocation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (hash) {
|
||||||
|
// Use setTimeout to allow DOM to render if navigating from another page
|
||||||
|
setTimeout(() => {
|
||||||
|
const element = document.getElementById(hash.replace('#', ''));
|
||||||
|
if (element) {
|
||||||
|
element.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
} else {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}
|
||||||
|
}, [pathname, hash]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
20
components/ServiceCard.tsx
Normal file
20
components/ServiceCard.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { LucideIcon } from 'lucide-react';
|
||||||
|
|
||||||
|
interface ServiceCardProps {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
Icon: LucideIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServiceCard: React.FC<ServiceCardProps> = ({ title, description, Icon }) => {
|
||||||
|
return (
|
||||||
|
<div className="bg-white p-8 rounded-2xl shadow-lg hover:shadow-2xl transition-all duration-300 border border-gray-100 group">
|
||||||
|
<div className="w-14 h-14 bg-purple-50 rounded-xl flex items-center justify-center mb-6 group-hover:bg-primary transition-colors duration-300">
|
||||||
|
<Icon className="w-7 h-7 text-primary group-hover:text-white transition-colors duration-300" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-bold mb-3 text-gray-900 group-hover:text-primary transition-colors">{title}</h3>
|
||||||
|
<p className="text-gray-600 leading-relaxed">{description}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
188
components/SettingsModal.tsx
Normal file
188
components/SettingsModal.tsx
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { X, Save, Lock, User, CheckCircle, AlertCircle, Info } from 'lucide-react';
|
||||||
|
import { Button } from './Button';
|
||||||
|
import { supabase, isSupabaseConfigured } from '../lib/supabaseClient';
|
||||||
|
import { useAuth } from '../context/AuthContext';
|
||||||
|
|
||||||
|
interface UserProfile {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
first_name?: string;
|
||||||
|
last_name?: string;
|
||||||
|
date_of_birth: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SettingsModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
userProfile: UserProfile | null;
|
||||||
|
onUpdate: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, userProfile, onUpdate }) => {
|
||||||
|
const { user, refreshDemoUser } = useAuth();
|
||||||
|
const [activeTab, setActiveTab] = useState<'profile' | 'security'>('profile');
|
||||||
|
|
||||||
|
const [firstName, setFirstName] = useState('');
|
||||||
|
const [lastName, setLastName] = useState('');
|
||||||
|
const [dateOfBirth, setDateOfBirth] = useState('');
|
||||||
|
|
||||||
|
const [newPassword, setNewPassword] = useState('');
|
||||||
|
const [confirmPassword, setConfirmPassword] = useState('');
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [message, setMessage] = useState<{type: 'success' | 'error', text: string} | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen && userProfile) {
|
||||||
|
setFirstName(userProfile.first_name || '');
|
||||||
|
setLastName(userProfile.last_name || '');
|
||||||
|
setDateOfBirth(userProfile.date_of_birth || '');
|
||||||
|
setNewPassword('');
|
||||||
|
setConfirmPassword('');
|
||||||
|
setMessage(null);
|
||||||
|
setActiveTab('profile');
|
||||||
|
}
|
||||||
|
}, [isOpen, userProfile]);
|
||||||
|
|
||||||
|
const commonInputStyles = "w-full px-4 py-3 rounded-xl border border-gray-300 focus:ring-4 focus:ring-primary/10 focus:border-primary outline-none transition-all bg-white text-black font-medium shadow-sm";
|
||||||
|
|
||||||
|
const handleUpdateProfile = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setLoading(true);
|
||||||
|
setMessage(null);
|
||||||
|
if (!user) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!isSupabaseConfigured) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 800));
|
||||||
|
setMessage({ type: 'success', text: 'Profil frissítve (Demo Mód).' });
|
||||||
|
setTimeout(() => onUpdate(), 1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { error: dbError } = await supabase.from('profiles').update({ first_name: firstName, last_name: lastName, date_of_birth: dateOfBirth, updated_at: new Date().toISOString() }).eq('id', user.id);
|
||||||
|
if (dbError) throw dbError;
|
||||||
|
await supabase.auth.updateUser({ data: { first_name: firstName, last_name: lastName, date_of_birth: dateOfBirth } });
|
||||||
|
setMessage({ type: 'success', text: 'Adatok sikeresen mentve.' });
|
||||||
|
setTimeout(() => onUpdate(), 1000);
|
||||||
|
} catch (err: any) {
|
||||||
|
setMessage({ type: 'error', text: 'Hiba: ' + err.message });
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChangePassword = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setLoading(true);
|
||||||
|
setMessage(null);
|
||||||
|
if (newPassword.length < 6) { setMessage({ type: 'error', text: 'Túl rövid jelszó.' }); setLoading(false); return; }
|
||||||
|
if (newPassword !== confirmPassword) { setMessage({ type: 'error', text: 'A jelszavak nem egyeznek.' }); setLoading(false); return; }
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!isSupabaseConfigured) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 800));
|
||||||
|
setMessage({ type: 'success', text: 'Jelszó frissítve (Demo Mód).' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { error } = await supabase.auth.updateUser({ password: newPassword });
|
||||||
|
if (error) throw error;
|
||||||
|
setMessage({ type: 'success', text: 'Jelszó sikeresen módosítva.' });
|
||||||
|
setNewPassword(''); setConfirmPassword('');
|
||||||
|
} catch (err: any) {
|
||||||
|
setMessage({ type: 'error', text: 'Hiba: ' + err.message });
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-gray-900/60 backdrop-blur-sm animate-fade-in">
|
||||||
|
<div className="bg-white rounded-3xl shadow-2xl w-full max-w-2xl overflow-hidden flex flex-col max-h-[90vh] border border-white/20">
|
||||||
|
|
||||||
|
<div className="p-8 border-b border-gray-100 flex justify-between items-center bg-gray-50/50">
|
||||||
|
<h2 className="text-2xl font-black text-gray-900 tracking-tighter">Fiók Beállítások</h2>
|
||||||
|
<button onClick={onClose} className="p-2 hover:bg-gray-200 rounded-full transition-colors bg-white shadow-sm border border-gray-100">
|
||||||
|
<X className="w-5 h-5 text-gray-500" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex border-b border-gray-100 bg-white">
|
||||||
|
<button onClick={() => { setActiveTab('profile'); setMessage(null); }} className={`flex-1 py-4 text-xs font-black uppercase tracking-widest flex items-center justify-center gap-2 transition-all ${activeTab === 'profile' ? 'text-primary border-b-4 border-primary bg-purple-50/30' : 'text-gray-400 hover:text-gray-600 hover:bg-gray-50'}`}>
|
||||||
|
<User className="w-4 h-4" /> Profil
|
||||||
|
</button>
|
||||||
|
<button onClick={() => { setActiveTab('security'); setMessage(null); }} className={`flex-1 py-4 text-xs font-black uppercase tracking-widest flex items-center justify-center gap-2 transition-all ${activeTab === 'security' ? 'text-primary border-b-4 border-primary bg-purple-50/30' : 'text-gray-400 hover:text-gray-600 hover:bg-gray-50'}`}>
|
||||||
|
<Lock className="w-4 h-4" /> Biztonság
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-10 overflow-y-auto bg-white">
|
||||||
|
{message && (
|
||||||
|
<div className={`mb-8 p-5 rounded-2xl flex items-start gap-4 animate-fade-in ${message.type === 'success' ? 'bg-green-50 text-green-800 border-2 border-green-100' : 'bg-red-50 text-red-800 border-2 border-red-100'}`}>
|
||||||
|
{message.type === 'success' ? <CheckCircle className="w-6 h-6 mt-0.5" /> : <AlertCircle className="w-6 h-6 mt-0.5" />}
|
||||||
|
<span className="font-bold text-sm">{message.text}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'profile' && (
|
||||||
|
<form onSubmit={handleUpdateProfile} className="space-y-8">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-black text-gray-400 uppercase tracking-widest mb-3">Vezetéknév</label>
|
||||||
|
<input type="text" value={lastName} onChange={(e) => setLastName(e.target.value)} className={commonInputStyles} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-black text-gray-400 uppercase tracking-widest mb-3">Keresztnév</label>
|
||||||
|
<input type="text" value={firstName} onChange={(e) => setFirstName(e.target.value)} className={commonInputStyles} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-black text-gray-400 uppercase tracking-widest mb-3">Születési Dátum</label>
|
||||||
|
<input type="date" value={dateOfBirth} onChange={(e) => setDateOfBirth(e.target.value)} className={commonInputStyles} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-black text-gray-400 uppercase tracking-widest mb-3">E-mail cím</label>
|
||||||
|
<input type="email" value={user?.email || ''} disabled className="w-full px-4 py-3 rounded-xl border border-gray-200 bg-gray-50 text-gray-400 cursor-not-allowed font-medium" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-6 border-t border-gray-100 flex justify-end">
|
||||||
|
<Button type="submit" disabled={loading} className="font-black uppercase tracking-widest px-10 shadow-lg shadow-primary/20">
|
||||||
|
{loading ? 'Mentés...' : 'Adatok Frissítése'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'security' && (
|
||||||
|
<form onSubmit={handleChangePassword} className="space-y-8">
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-black text-gray-400 uppercase tracking-widest mb-3">Új Jelszó</label>
|
||||||
|
<input type="password" value={newPassword} onChange={(e) => setNewPassword(e.target.value)} placeholder="Minimum 6 karakter" className={commonInputStyles} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-black text-gray-400 uppercase tracking-widest mb-3">Jelszó Megerősítése</label>
|
||||||
|
<input type="password" value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} placeholder="Jelszó újra" className={commonInputStyles} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-blue-50 p-5 rounded-2xl text-sm text-blue-900 border-2 border-blue-100 flex items-start gap-4">
|
||||||
|
<Info className="w-6 h-6 flex-shrink-0 text-blue-500" />
|
||||||
|
<p className="font-bold leading-relaxed">Biztonsági okokból a jelszó megváltoztatása után javasolt az újra-bejelentkezés minden eszközön.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-6 border-t border-gray-100 flex justify-end">
|
||||||
|
<Button type="submit" disabled={loading} className="font-black uppercase tracking-widest px-10 shadow-lg shadow-primary/20">
|
||||||
|
{loading ? 'Folyamatban...' : 'Jelszó Mentése'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
150
context/AuthContext.tsx
Normal file
150
context/AuthContext.tsx
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
|
||||||
|
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||||
|
import { supabase, isSupabaseConfigured } from '../lib/supabaseClient';
|
||||||
|
import { Session, User } from '@supabase/supabase-js';
|
||||||
|
|
||||||
|
interface AuthContextType {
|
||||||
|
session: Session | null;
|
||||||
|
user: User | null;
|
||||||
|
loading: boolean;
|
||||||
|
isAdmin: boolean;
|
||||||
|
signOut: () => Promise<void>;
|
||||||
|
refreshDemoUser: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthContext = createContext<AuthContextType>({
|
||||||
|
session: null,
|
||||||
|
user: null,
|
||||||
|
loading: true,
|
||||||
|
isAdmin: false,
|
||||||
|
signOut: async () => {},
|
||||||
|
refreshDemoUser: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AuthProvider: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||||
|
const [session, setSession] = useState<Session | null>(null);
|
||||||
|
const [user, setUser] = useState<User | null>(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [isAdmin, setIsAdmin] = useState(false);
|
||||||
|
|
||||||
|
const loadDemoUser = () => {
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem('demo_user_session');
|
||||||
|
if (stored) {
|
||||||
|
const parsed = JSON.parse(stored);
|
||||||
|
setSession(parsed);
|
||||||
|
setUser(parsed.user);
|
||||||
|
setIsAdmin(parsed.user.email === 'motionstudiohq@gmail.com');
|
||||||
|
} else {
|
||||||
|
setSession(null);
|
||||||
|
setUser(null);
|
||||||
|
setIsAdmin(false);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading demo user', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkAdminStatus = async (currentUser: User | null) => {
|
||||||
|
if (!currentUser) {
|
||||||
|
setIsAdmin(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Hardcoded Super Admin Check (Ez mindig működik, RLS-től függetlenül)
|
||||||
|
if (currentUser.email === 'motionstudiohq@gmail.com') {
|
||||||
|
setIsAdmin(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Database Role Check (Hibatűrő módon)
|
||||||
|
if (isSupabaseConfigured) {
|
||||||
|
try {
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('roles')
|
||||||
|
.select('role')
|
||||||
|
.eq('id', currentUser.id)
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.warn('RLS Policy Error in AuthContext (recursion?):', error.message);
|
||||||
|
return; // Ha hiba van, hagyatkozunk az email alapú ellenőrzésre
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data?.role === 'admin') {
|
||||||
|
setIsAdmin(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error checking admin role:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsAdmin(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const initAuth = async () => {
|
||||||
|
if (!isSupabaseConfigured) {
|
||||||
|
loadDemoUser();
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data, error } = await supabase.auth.getSession();
|
||||||
|
if (error) throw error;
|
||||||
|
|
||||||
|
const currentSession = data.session;
|
||||||
|
setSession(currentSession);
|
||||||
|
setUser(currentSession?.user ?? null);
|
||||||
|
if (currentSession?.user) await checkAdminStatus(currentSession.user);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Auth initialization error:', err);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: listener } = supabase.auth.onAuthStateChange(async (_event, session) => {
|
||||||
|
setSession(session);
|
||||||
|
setUser(session?.user ?? null);
|
||||||
|
if (session?.user) await checkAdminStatus(session.user);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
listener.subscription.unsubscribe();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
initAuth();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const signOut = async () => {
|
||||||
|
if (!isSupabaseConfigured) {
|
||||||
|
localStorage.removeItem('demo_user_session');
|
||||||
|
setSession(null);
|
||||||
|
setUser(null);
|
||||||
|
setIsAdmin(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await supabase.auth.signOut();
|
||||||
|
setIsAdmin(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error signing out:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshDemoUser = () => {
|
||||||
|
if (!isSupabaseConfigured) loadDemoUser();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthContext.Provider value={{ session, user, loading, isAdmin, signOut, refreshDemoUser }}>
|
||||||
|
{!loading && children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAuth = () => useContext(AuthContext);
|
||||||
2
db_fix.sql
Normal file
2
db_fix.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<EFBFBD>
|
||||||
|
窔垫<EFBFBD>Z^<EFBFBD><EFBFBD>亘炡呯$i佗炈Z峨,y双z\绝
|
||||||
BIN
db_schema.sql
Normal file
BIN
db_schema.sql
Normal file
Binary file not shown.
77
index.html
Normal file
77
index.html
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="hu">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="https://picsum.photos/id/48/32/32" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Motion Web Stúdió - Professzionális Webfejlesztés</title>
|
||||||
|
|
||||||
|
<!-- SEO Meta Tags -->
|
||||||
|
<meta name="description" content="A Motion Web Stúdió egyedi weboldalak, webáruházak és komplex digitális megoldások fejlesztésével foglalkozik. Növelje cége forgalmát velünk." />
|
||||||
|
<meta name="keywords" content="webfejlesztés, nFix, weboldal készítés, webshop, egyedi fejlesztés, SEO, Motion Web" />
|
||||||
|
<meta name="author" content="Motion Web Stúdió" />
|
||||||
|
|
||||||
|
<!-- Tailwind CSS -->
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<script>
|
||||||
|
tailwind.config = {
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
primary: {
|
||||||
|
DEFAULT: '#7c3aed', // lila (violet-600)
|
||||||
|
dark: '#6d28d9', // violet-700
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
DEFAULT: '#3b82f6', // kék (blue-500)
|
||||||
|
dark: '#2563eb', // blue-600
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
sans: ['Inter', 'sans-serif'],
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
fadeInUp: {
|
||||||
|
'0%': { opacity: '0', transform: 'translateY(20px)' },
|
||||||
|
'100%': { opacity: '1', transform: 'translateY(0)' },
|
||||||
|
},
|
||||||
|
fadeIn: {
|
||||||
|
'0%': { opacity: '0' },
|
||||||
|
'100%': { opacity: '1' },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
'fade-in-up': 'fadeInUp 0.8s ease-out both',
|
||||||
|
'fade-in': 'fadeIn 1.2s ease-out both',
|
||||||
|
'bounce-slow': 'bounce 3s infinite',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700;800&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body { font-family: 'Inter', sans-serif; }
|
||||||
|
</style>
|
||||||
|
<script type="importmap">
|
||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"react": "https://esm.sh/react@18.2.0",
|
||||||
|
"react/jsx-runtime": "https://esm.sh/react@18.2.0/jsx-runtime",
|
||||||
|
"react-dom/client": "https://esm.sh/react-dom@18.2.0/client",
|
||||||
|
"react-dom": "https://esm.sh/react-dom@18.2.0",
|
||||||
|
"react-router-dom": "https://esm.sh/react-router-dom@6.22.3?deps=react@18.2.0,react-dom@18.2.0",
|
||||||
|
"lucide-react": "https://esm.sh/lucide-react@0.344.0?deps=react@18.2.0",
|
||||||
|
"@supabase/supabase-js": "https://esm.sh/@supabase/supabase-js@2.39.7",
|
||||||
|
"react-dom/": "https://esm.sh/react-dom@^19.2.3/",
|
||||||
|
"react/": "https://esm.sh/react@^19.2.3/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<link rel="stylesheet" href="/index.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/index.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
15
index.tsx
Normal file
15
index.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
const rootElement = document.getElementById('root');
|
||||||
|
if (!rootElement) {
|
||||||
|
throw new Error("Could not find root element to mount to");
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(rootElement);
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
54
lib/defaultPlans.ts
Normal file
54
lib/defaultPlans.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { ProductPackage } from '../types';
|
||||||
|
|
||||||
|
export const defaultPlans: ProductPackage[] = [
|
||||||
|
{
|
||||||
|
id: 'start',
|
||||||
|
name: "Landing Page",
|
||||||
|
price: "190.000 Ft-tól",
|
||||||
|
desc: "Ideális induló vállalkozásoknak és személyes brandeknek.",
|
||||||
|
features: [
|
||||||
|
"Egyoldalas Landing Page",
|
||||||
|
"Reszponzív (mobilbarát) design",
|
||||||
|
"Kapcsolati űrlap",
|
||||||
|
"Alapvető SEO beállítások",
|
||||||
|
"Social Media integráció",
|
||||||
|
"2 hét ingyenes támogatás"
|
||||||
|
],
|
||||||
|
cta: "Megrendelem",
|
||||||
|
isPopular: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'pro',
|
||||||
|
name: "Pro Web",
|
||||||
|
price: "350.000 Ft-tól",
|
||||||
|
desc: "A legnépszerűbb választás kis- és középvállalkozások számára.",
|
||||||
|
features: [
|
||||||
|
"Max 5 aloldal (Rólunk, Szolgáltatások...)",
|
||||||
|
"Blog funkció",
|
||||||
|
"Google Térkép integráció",
|
||||||
|
"Bővített SEO optimalizálás",
|
||||||
|
"Gyorsított betöltés",
|
||||||
|
"Google Analytics bekötés",
|
||||||
|
"1 hónap ingyenes támogatás"
|
||||||
|
],
|
||||||
|
isPopular: true,
|
||||||
|
cta: "Ezt választom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'enterprise',
|
||||||
|
name: "Enterprise",
|
||||||
|
price: "Egyedi árazás",
|
||||||
|
desc: "Komplex rendszerek és webáruházak nagyvállalati igényekre.",
|
||||||
|
features: [
|
||||||
|
"Korlátlan aloldalak",
|
||||||
|
"Webshop funkcionalitás",
|
||||||
|
"Bankkártyás fizetés",
|
||||||
|
"CRM/ERP rendszer integráció",
|
||||||
|
"Egyedi fejlesztésű funkciók",
|
||||||
|
"Prémium support",
|
||||||
|
"Karbantartási szerződés"
|
||||||
|
],
|
||||||
|
cta: "Ajánlatot kérek",
|
||||||
|
isPopular: false
|
||||||
|
}
|
||||||
|
];
|
||||||
296
lib/promptGenerator.ts
Normal file
296
lib/promptGenerator.ts
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
import { ProductPackage } from '../types';
|
||||||
|
|
||||||
|
export interface PromptResult {
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
tags: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const generateOrderPrompts = (order: any): PromptResult[] => {
|
||||||
|
const pkg = order.package || 'Pro Web';
|
||||||
|
const details = order.details || {};
|
||||||
|
|
||||||
|
// Helper to safely get data
|
||||||
|
const get = (val: any, fallback = 'Not specified') => val || fallback;
|
||||||
|
const getArr = (val: any[]) => (val && val.length > 0 ? val.join(', ') : 'None selected');
|
||||||
|
|
||||||
|
// Common Context for all prompts
|
||||||
|
const commonContext = `
|
||||||
|
PROJECT CONTEXT:
|
||||||
|
- Client Name: ${get(details.name)}
|
||||||
|
- Company: ${get(details.company)}
|
||||||
|
- Business Description: ${get(details.description)}
|
||||||
|
- Target Audience: ${get(details.targetAudience)}
|
||||||
|
- Primary Color: ${get(details.primaryColor)}
|
||||||
|
- Secondary Color: ${get(details.secondaryColor)}
|
||||||
|
- Desired Style: ${getArr(details.style)}
|
||||||
|
- Goals: ${getArr(details.goals)}
|
||||||
|
- Domain: ${get(details.domainName, 'TBD')}
|
||||||
|
|
||||||
|
TECHNICAL STANDARDS:
|
||||||
|
- Language: Hungarian (Magyar)
|
||||||
|
- Framework: React with Next.js (App Router)
|
||||||
|
- Styling: Tailwind CSS
|
||||||
|
- Icons: Lucide React
|
||||||
|
- Code Style: Clean, component-based, exportable code.
|
||||||
|
- Backend: Supabase (if needed for features).
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 1. SIMPLE LANDING PAGE STRATEGY
|
||||||
|
if (pkg === 'Landing Page' || pkg === 'Start') {
|
||||||
|
return [{
|
||||||
|
title: 'Landing Page Website Generator Prompt',
|
||||||
|
tags: ['Single Page', 'Conversion', 'UI/UX'],
|
||||||
|
content: `
|
||||||
|
Act as a world-class Frontend Engineer and UI/UX Designer.
|
||||||
|
Your task is to create a high-conversion, single-page Landing Website in Hungarian.
|
||||||
|
|
||||||
|
${commonContext}
|
||||||
|
|
||||||
|
INSTRUCTIONS:
|
||||||
|
1. Create a single-file React component (or a clean component structure) for a Landing Page.
|
||||||
|
2. Structure:
|
||||||
|
- Hero Section: High impact, using the business description.
|
||||||
|
- About/Introduction: Based on the client's description.
|
||||||
|
- Services/Features: Highlight the features requested.
|
||||||
|
- Call to Action (CTA): Strong focus on the client's goals (e.g., ${getArr(details.goals)}).
|
||||||
|
- Contact Section: Include a functional looking form and placeholders for Phone/Email (${get(details.email)}).
|
||||||
|
3. Design:
|
||||||
|
- Use the primary color (${get(details.primaryColor)}) for main actions and accents.
|
||||||
|
- Use the requested style (${getArr(details.style)}) to determine font choices and spacing.
|
||||||
|
4. Content:
|
||||||
|
- Write all visible text in Hungarian.
|
||||||
|
- Use persuasive, marketing-oriented copy.
|
||||||
|
|
||||||
|
OUTPUT:
|
||||||
|
Provide the full React code using Tailwind CSS.
|
||||||
|
`
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. COMPLEX SITE STRATEGY (Pro Business / Enterprise)
|
||||||
|
const prompts: PromptResult[] = [];
|
||||||
|
|
||||||
|
// Prompt 1: Main Structure
|
||||||
|
prompts.push({
|
||||||
|
title: 'Phase 1: Core Structure & Homepage',
|
||||||
|
tags: ['Architecture', 'Layout', 'Homepage'],
|
||||||
|
content: `
|
||||||
|
Act as a Senior Full-Stack Developer.
|
||||||
|
We are building a Multi-Page Website in Hungarian using Next.js App Router and Tailwind CSS.
|
||||||
|
|
||||||
|
${commonContext}
|
||||||
|
|
||||||
|
TASK:
|
||||||
|
Generate the core application structure and the Homepage.
|
||||||
|
|
||||||
|
REQUIREMENTS:
|
||||||
|
1. **Layout & Navigation**:
|
||||||
|
- Create a responsive Navbar including links for requested pages: ${getArr(details.content)}.
|
||||||
|
- Create a professional Footer with company info and links.
|
||||||
|
- Define \`app/layout.tsx\` with font setup (Inter/Roboto).
|
||||||
|
2. **Homepage Design**:
|
||||||
|
- Create a modern, section-based homepage reflecting the brand identity.
|
||||||
|
- Hero Section: Engaging headline based on "${get(details.description)}".
|
||||||
|
- Key Value Proposition sections.
|
||||||
|
- Use the primary color (${get(details.primaryColor)}) effectively.
|
||||||
|
3. **Routing**:
|
||||||
|
- Setup the directory structure for Next.js App Router.
|
||||||
|
|
||||||
|
OUTPUT:
|
||||||
|
- \`app/layout.tsx\`
|
||||||
|
- \`components/Navbar.tsx\`
|
||||||
|
- \`components/Footer.tsx\`
|
||||||
|
- \`app/page.tsx\` (Homepage content)
|
||||||
|
`
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prompt 2: Subpages
|
||||||
|
if (details.content && details.content.length > 0) {
|
||||||
|
prompts.push({
|
||||||
|
title: 'Phase 2: Standard Subpages',
|
||||||
|
tags: ['Content', 'Static Pages'],
|
||||||
|
content: `
|
||||||
|
Act as a Frontend Specialist.
|
||||||
|
Continuing from the previous website structure, generate the specific subpages requested by the client.
|
||||||
|
|
||||||
|
${commonContext}
|
||||||
|
|
||||||
|
PAGES TO GENERATE:
|
||||||
|
${getArr(details.content)}
|
||||||
|
|
||||||
|
INSTRUCTIONS:
|
||||||
|
1. **Content Generation**: For each page, generate realistic Hungarian content based on the business description: "${get(details.description)}".
|
||||||
|
2. **Design**: Ensure consistent styling with the Homepage (Tailwind CSS).
|
||||||
|
3. **Specifics**:
|
||||||
|
- If "Rólunk" (About): Tell the company story.
|
||||||
|
- If "Szolgáltatások" (Services): Grid layout of offerings.
|
||||||
|
- If "Kapcsolat" (Contact): Contact details and map placeholder.
|
||||||
|
|
||||||
|
OUTPUT:
|
||||||
|
Provide the code for the page components (e.g., \`app/rolunk/page.tsx\`, \`app/szolgaltatasok/page.tsx\`).
|
||||||
|
`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prompts 3+: Individual Features (One prompt per feature)
|
||||||
|
if (details.features && details.features.length > 0) {
|
||||||
|
details.features.forEach((feature: string, index: number) => {
|
||||||
|
let featurePromptContent = '';
|
||||||
|
let featureTitle = `Phase 3.${index + 1}: Feature - ${feature}`;
|
||||||
|
let featureTags = ['Feature', 'Logic'];
|
||||||
|
|
||||||
|
switch (feature) {
|
||||||
|
case 'Kapcsolatfelvételi űrlap':
|
||||||
|
featurePromptContent = `
|
||||||
|
TASK: Implement a fully functional **Contact Form**.
|
||||||
|
REQUIREMENTS:
|
||||||
|
1. Create a \`ContactForm\` component with Name, Email, Phone, Message fields.
|
||||||
|
2. Form Validation (Zod or simple HTML5).
|
||||||
|
3. Backend: Define a Supabase table \`messages\` to store submissions.
|
||||||
|
4. Logic: Create a server action or API route to handle the submission to Supabase.
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Időpontfoglalás rendszer':
|
||||||
|
featurePromptContent = `
|
||||||
|
TASK: Implement an **Appointment Booking System**.
|
||||||
|
REQUIREMENTS:
|
||||||
|
1. Database: Define a Supabase schema for \`appointments\` and \`available_slots\`.
|
||||||
|
2. UI: Create a calendar or slot picker component.
|
||||||
|
3. Logic: Allow users to select a time and book it. Handle availability checks.
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Hírlevél-feliratkozás':
|
||||||
|
featurePromptContent = `
|
||||||
|
TASK: Implement a **Newsletter Subscription** feature.
|
||||||
|
REQUIREMENTS:
|
||||||
|
1. UI: A clean input field and subscribe button (Footer or dedicated section).
|
||||||
|
2. Backend: Define a Supabase table \`subscribers\`.
|
||||||
|
3. Logic: Handle the submission and prevent duplicate emails.
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Webshop / fizetési lehetőség':
|
||||||
|
featurePromptContent = `
|
||||||
|
TASK: Implement core **E-commerce / Webshop** functionality.
|
||||||
|
REQUIREMENTS:
|
||||||
|
1. Database: Define tables for \`products\` and \`orders\`.
|
||||||
|
2. UI: Create a Product Listing component and a Product Detail page.
|
||||||
|
3. State: Implement a simple Cart context.
|
||||||
|
4. Checkout: Create a basic checkout form structure (mock payment integration is fine for now, or setup Stripe logic).
|
||||||
|
`;
|
||||||
|
featureTags.push('E-commerce');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Blog / hírek':
|
||||||
|
featurePromptContent = `
|
||||||
|
TASK: Implement a **Blog / News** section.
|
||||||
|
REQUIREMENTS:
|
||||||
|
1. Database: Define a \`posts\` table (title, slug, content, image, date).
|
||||||
|
2. UI: Create a Blog Index page (grid of cards) and a Blog Post layout.
|
||||||
|
3. Logic: Fetch data from Supabase to render posts dynamically.
|
||||||
|
`;
|
||||||
|
featureTags.push('CMS');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Többnyelvűség':
|
||||||
|
featurePromptContent = `
|
||||||
|
TASK: Implement **Multi-language Support (i18n)**.
|
||||||
|
REQUIREMENTS:
|
||||||
|
1. Setup a localization strategy (e.g., using URL path prefixes /en, /hu or a context provider).
|
||||||
|
2. Create a dictionary file for translations.
|
||||||
|
3. Add a language switcher component to the Navbar.
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Képgaléria / videógaléria':
|
||||||
|
featurePromptContent = `
|
||||||
|
TASK: Implement a **Media Gallery**.
|
||||||
|
REQUIREMENTS:
|
||||||
|
1. UI: Create a responsive masonry or grid layout for images/videos.
|
||||||
|
2. Functionality: Implement a Lightbox (modal) view when clicking an image.
|
||||||
|
3. Data: Define a data structure (array) for the gallery items.
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Google Térkép integráció':
|
||||||
|
featurePromptContent = `
|
||||||
|
TASK: Implement **Google Maps Integration**.
|
||||||
|
REQUIREMENTS:
|
||||||
|
1. Create a \`MapComponent\`.
|
||||||
|
2. Integrate an embed iframe or use a React Google Maps library.
|
||||||
|
3. Place a marker at the company's location.
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Chat gomb (Messenger / WhatsApp)':
|
||||||
|
featurePromptContent = `
|
||||||
|
TASK: Implement a **Floating Chat Widget**.
|
||||||
|
REQUIREMENTS:
|
||||||
|
1. UI: A fixed button in the bottom-right corner.
|
||||||
|
2. Interaction: On click, either open a modal or redirect to WhatsApp/Messenger API links.
|
||||||
|
3. Styling: Make it unobtrusive but visible.
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
featurePromptContent = `
|
||||||
|
TASK: Implement the requested feature: **${feature}**.
|
||||||
|
REQUIREMENTS:
|
||||||
|
1. Analyze the requirement based on the project context.
|
||||||
|
2. Create necessary UI components.
|
||||||
|
3. Define backend logic or database schema if data persistence is needed.
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
prompts.push({
|
||||||
|
title: featureTitle,
|
||||||
|
tags: featureTags,
|
||||||
|
content: `
|
||||||
|
Act as a Full-Stack Developer.
|
||||||
|
You are adding a specific feature to the existing Next.js + Supabase project.
|
||||||
|
|
||||||
|
${commonContext}
|
||||||
|
|
||||||
|
${featurePromptContent}
|
||||||
|
|
||||||
|
OUTPUT:
|
||||||
|
- React Components
|
||||||
|
- Supabase SQL Definitions (if applicable)
|
||||||
|
- Integration instructions
|
||||||
|
`
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return prompts;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateDownloadContent = (order: any, prompts: PromptResult[]): string => {
|
||||||
|
const header = `
|
||||||
|
==============================================================================
|
||||||
|
MOTIONWEB - AI PROMPT PACKAGE
|
||||||
|
==============================================================================
|
||||||
|
Order ID: ${order.displayId}
|
||||||
|
Client: ${order.customer}
|
||||||
|
Date: ${new Date().toLocaleDateString()}
|
||||||
|
Package: ${order.package}
|
||||||
|
==============================================================================
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
const body = prompts.map((p, i) => `
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
PROMPT ${i + 1}: ${p.title.toUpperCase()}
|
||||||
|
Tags: [${p.tags.join(', ')}]
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
${p.content.trim()}
|
||||||
|
|
||||||
|
`).join('\n\n');
|
||||||
|
|
||||||
|
return header + body;
|
||||||
|
};
|
||||||
27
lib/supabaseClient.ts
Normal file
27
lib/supabaseClient.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { createClient } from '@supabase/supabase-js';
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
// You can switch these to import.meta.env.VITE_SUPABASE_URL if you use a .env file later.
|
||||||
|
const supabaseUrl = 'https://fhgxhqagvjnjamsyilmh.supabase.co';
|
||||||
|
const supabaseAnonKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImZoZ3hocWFndmpuamFtc3lpbG1oIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjQ3NDg2OTMsImV4cCI6MjA4MDMyNDY5M30.t4RhJlqM5f_N7PK3Qx58KhvTl9zZB6KYxLGr_gc_3Ok';
|
||||||
|
|
||||||
|
// Validation to ensure keys are not empty or default placeholders
|
||||||
|
const isPlaceholder =
|
||||||
|
!supabaseUrl ||
|
||||||
|
!supabaseAnonKey ||
|
||||||
|
supabaseUrl.includes('placeholder') ||
|
||||||
|
supabaseAnonKey.includes('placeholder');
|
||||||
|
|
||||||
|
if (isPlaceholder) {
|
||||||
|
console.warn(
|
||||||
|
"Missing or invalid Supabase environment variables. Auth features will run in DEMO MODE."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the Supabase client
|
||||||
|
export const supabase = createClient(
|
||||||
|
supabaseUrl || 'https://placeholder.supabase.co',
|
||||||
|
supabaseAnonKey || 'placeholder-key'
|
||||||
|
);
|
||||||
|
|
||||||
|
export const isSupabaseConfigured = !isPlaceholder;
|
||||||
5
metadata.json
Normal file
5
metadata.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "Motion Web Stúdió",
|
||||||
|
"description": "Professzionális webfejlesztés, webshop készítés és digitális megoldások cégek számára.",
|
||||||
|
"requestFramePermissions": []
|
||||||
|
}
|
||||||
BIN
motion-web-studio-0.1-login-order-status.zip
Normal file
BIN
motion-web-studio-0.1-login-order-status.zip
Normal file
Binary file not shown.
24
package.json
Normal file
24
package.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "motion-web-stúdió",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^19.2.3",
|
||||||
|
"react-dom": "^19.2.3",
|
||||||
|
"react-router-dom": "6.22.3",
|
||||||
|
"lucide-react": "0.344.0",
|
||||||
|
"@supabase/supabase-js": "2.39.7"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.14.0",
|
||||||
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
|
"typescript": "~5.8.2",
|
||||||
|
"vite": "^6.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
72
pages/About.tsx
Normal file
72
pages/About.tsx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Target, Users, Zap } from 'lucide-react';
|
||||||
|
|
||||||
|
export const About: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<div className="pt-20">
|
||||||
|
{/* Hero */}
|
||||||
|
<div className="relative bg-gray-900 py-24 px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="absolute inset-0 overflow-hidden">
|
||||||
|
<img
|
||||||
|
src="https://images.unsplash.com/photo-1522071820081-009f0129c71c?ixlib=rb-4.0.3&auto=format&fit=crop&w=1170&q=80"
|
||||||
|
alt="Office team"
|
||||||
|
className="w-full h-full object-cover opacity-20"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="relative max-w-7xl mx-auto text-center">
|
||||||
|
<h1 className="text-4xl font-extrabold tracking-tight text-white sm:text-5xl lg:text-6xl">
|
||||||
|
Rólunk
|
||||||
|
</h1>
|
||||||
|
<p className="mt-6 text-xl text-gray-300 max-w-3xl mx-auto">
|
||||||
|
A Motion Web Stúdió (Balogh Bence Benedek) egy fiatalos, dinamikus csapat, akik elkötelezettek a minőségi webfejlesztés mellett.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className="bg-white py-16 px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="max-w-7xl mx-auto">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 items-center mb-20">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-3xl font-bold text-gray-900 mb-6">A küldetésünk</h2>
|
||||||
|
<p className="text-lg text-gray-600 leading-relaxed mb-4">
|
||||||
|
Hiszünk abban, hogy egy weboldal több, mint digitális névjegykártya. Egy jól felépített oldal értékesítő gépezet, márkaépítő eszköz és ügyfélszolgálati csatorna egyben.
|
||||||
|
</p>
|
||||||
|
<p className="text-lg text-gray-600 leading-relaxed">
|
||||||
|
Célunk, hogy a magyar kis- és középvállalkozásokat olyan digitális eszközökkel lássuk el, amelyekkel versenyképesek maradhatnak a nemzetközi piacon is. Minden projektbe szívünket-lelkünket beletesszük.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<img className="rounded-xl shadow-lg w-full h-48 object-cover" src="https://images.unsplash.com/photo-1531482615713-2afd69097998?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80" alt="Working" />
|
||||||
|
<img className="rounded-xl shadow-lg w-full h-48 object-cover mt-8" src="https://images.unsplash.com/photo-1504384308090-c54be3852f33?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80" alt="Coding" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
|
<div className="bg-gray-50 p-8 rounded-2xl text-center">
|
||||||
|
<div className="w-12 h-12 bg-purple-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<Target className="text-primary w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-bold mb-2">Eredményorientáltság</h3>
|
||||||
|
<p className="text-gray-600">Nem csak szépet alkotunk, hanem olyat, ami működik. A konverzió és a ROI a legfontosabb mérőszámunk.</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-gray-50 p-8 rounded-2xl text-center">
|
||||||
|
<div className="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<Users className="text-secondary w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-bold mb-2">Ügyfélközpontúság</h3>
|
||||||
|
<p className="text-gray-600">Folyamatos kommunikáció és átlátható folyamatok. Ön mindig tudja, hol tart a projektje.</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-gray-50 p-8 rounded-2xl text-center">
|
||||||
|
<div className="w-12 h-12 bg-purple-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<Zap className="text-primary w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-bold mb-2">Modern Technológia</h3>
|
||||||
|
<p className="text-gray-600">React, TypeScript, Tailwind. A legmodernebb eszközöket használjuk a gyors és biztonságos működésért.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
588
pages/Admin.tsx
Normal file
588
pages/Admin.tsx
Normal file
@@ -0,0 +1,588 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useAuth } from '../context/AuthContext';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
ChevronLeft, RefreshCw, BarChart2, Users, ShoppingCart, Package,
|
||||||
|
FileText, Globe, Sparkles, MessageSquare, AlertCircle, CheckCircle,
|
||||||
|
XCircle, Clock, Activity, Save, Mail, Rocket, Edit3, Bell,
|
||||||
|
AlertTriangle, Archive, Send, Layout, History
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { Button } from '../components/Button';
|
||||||
|
import { supabase, isSupabaseConfigured } from '../lib/supabaseClient';
|
||||||
|
import { ProductPackage } from '../types';
|
||||||
|
import { defaultPlans } from '../lib/defaultPlans';
|
||||||
|
import { generateOrderPrompts, PromptResult } from '../lib/promptGenerator';
|
||||||
|
|
||||||
|
interface AdminUser {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
first_name?: string;
|
||||||
|
last_name?: string;
|
||||||
|
created_at: string;
|
||||||
|
role: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OrderHistoryEntry {
|
||||||
|
id: string;
|
||||||
|
status: string;
|
||||||
|
changed_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EmailLogEntry {
|
||||||
|
id: string;
|
||||||
|
email_type: string;
|
||||||
|
sent_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AdminOrder {
|
||||||
|
id: string;
|
||||||
|
user_id: string;
|
||||||
|
displayId: string;
|
||||||
|
customer: string;
|
||||||
|
email: string;
|
||||||
|
package: string;
|
||||||
|
status: string;
|
||||||
|
date: string;
|
||||||
|
amount: string;
|
||||||
|
details?: any;
|
||||||
|
history?: OrderHistoryEntry[];
|
||||||
|
emailLogs?: EmailLogEntry[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Admin: React.FC = () => {
|
||||||
|
const { user, isAdmin, loading } = useAuth();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [activeTab, setActiveTab] = useState<'overview' | 'users' | 'orders' | 'plans'>('overview');
|
||||||
|
const [users, setUsers] = useState<AdminUser[]>([]);
|
||||||
|
const [orders, setOrders] = useState<AdminOrder[]>([]);
|
||||||
|
const [plans, setPlans] = useState<ProductPackage[]>([]);
|
||||||
|
const [loadingData, setLoadingData] = useState(false);
|
||||||
|
const [visitorStats, setVisitorStats] = useState({ month: 0, week: 0 });
|
||||||
|
const [statusUpdating, setStatusUpdating] = useState<string | null>(null);
|
||||||
|
const [planSaving, setPlanSaving] = useState<string | null>(null);
|
||||||
|
const [errorMsg, setErrorMsg] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const [viewOrder, setViewOrder] = useState<AdminOrder | null>(null);
|
||||||
|
const [generatedPrompts, setGeneratedPrompts] = useState<PromptResult[]>([]);
|
||||||
|
const [copiedIndex, setCopiedIndex] = useState<number | null>(null);
|
||||||
|
const [demoUrlInput, setDemoUrlInput] = useState('');
|
||||||
|
const [savingDemoUrl, setSavingDemoUrl] = useState(false);
|
||||||
|
const [emailSending, setEmailSending] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && !isAdmin) {
|
||||||
|
navigate('/dashboard');
|
||||||
|
}
|
||||||
|
}, [isAdmin, loading, navigate]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isAdmin) fetchAdminData();
|
||||||
|
}, [isAdmin]);
|
||||||
|
|
||||||
|
const fetchAdminData = async () => {
|
||||||
|
setLoadingData(true);
|
||||||
|
setErrorMsg(null);
|
||||||
|
|
||||||
|
if (!isSupabaseConfigured) {
|
||||||
|
setPlans(defaultPlans);
|
||||||
|
setLoadingData(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data: profiles, error: pErr } = await supabase.from('profiles').select('*');
|
||||||
|
if (pErr) throw pErr;
|
||||||
|
|
||||||
|
let rolesData: any[] = [];
|
||||||
|
try {
|
||||||
|
const { data: roles } = await supabase.from('roles').select('*');
|
||||||
|
rolesData = roles || [];
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Could not fetch roles table.");
|
||||||
|
}
|
||||||
|
|
||||||
|
setUsers(profiles.map(p => ({
|
||||||
|
id: p.id,
|
||||||
|
email: p.email || 'N/A',
|
||||||
|
first_name: p.first_name,
|
||||||
|
last_name: p.last_name,
|
||||||
|
created_at: p.created_at,
|
||||||
|
role: rolesData.find(r => r.id === p.id)?.role || (p.email === 'motionstudiohq@gmail.com' ? 'admin' : 'user')
|
||||||
|
})));
|
||||||
|
|
||||||
|
const { data: oData, error: oErr } = await supabase.from('orders').select('*').order('created_at', { ascending: false });
|
||||||
|
if (oErr) throw oErr;
|
||||||
|
setOrders(oData.map(o => ({
|
||||||
|
...o,
|
||||||
|
displayId: o.id.substring(0, 8).toUpperCase(),
|
||||||
|
customer: o.customer_name,
|
||||||
|
email: o.customer_email
|
||||||
|
})));
|
||||||
|
|
||||||
|
const { data: plData } = await supabase.from('plans').select('*').order('price', { ascending: true });
|
||||||
|
setPlans(plData && plData.length > 0 ? plData : defaultPlans);
|
||||||
|
|
||||||
|
const { count: mCount } = await supabase.from('page_visits').select('*', { count: 'exact', head: true });
|
||||||
|
setVisitorStats({ month: mCount || 0, week: Math.floor((mCount || 0) / 4) });
|
||||||
|
|
||||||
|
} catch (err: any) {
|
||||||
|
setErrorMsg(err.message || "Hiba történt az adatok lekérésekor.");
|
||||||
|
} finally {
|
||||||
|
setLoadingData(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchOrderHistory = async (orderId: string) => {
|
||||||
|
if (!isSupabaseConfigured) return [];
|
||||||
|
try {
|
||||||
|
const { data } = await supabase
|
||||||
|
.from('order_status_history')
|
||||||
|
.select('*')
|
||||||
|
.eq('order_id', orderId)
|
||||||
|
.order('changed_at', { ascending: false });
|
||||||
|
return data || [];
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchEmailLogs = async (orderId: string) => {
|
||||||
|
if (!isSupabaseConfigured) return [];
|
||||||
|
try {
|
||||||
|
const { data } = await supabase
|
||||||
|
.from('email_log')
|
||||||
|
.select('*')
|
||||||
|
.eq('order_id', orderId)
|
||||||
|
.order('sent_at', { ascending: false });
|
||||||
|
return data || [];
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Could not fetch email_log table.");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStatusChange = async (orderId: string, newStatus: string) => {
|
||||||
|
if (statusUpdating) return;
|
||||||
|
setStatusUpdating(orderId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (isSupabaseConfigured) {
|
||||||
|
const { error } = await supabase.from('orders').update({ status: newStatus }).eq('id', orderId);
|
||||||
|
if (error) throw error;
|
||||||
|
|
||||||
|
await supabase.from('order_status_history').insert({ order_id: orderId, status: newStatus });
|
||||||
|
const newHist = await fetchOrderHistory(orderId);
|
||||||
|
|
||||||
|
setOrders(prev => prev.map(o => o.id === orderId ? { ...o, status: newStatus } : o));
|
||||||
|
if (viewOrder && viewOrder.id === orderId) {
|
||||||
|
setViewOrder({ ...viewOrder, status: newStatus, history: newHist });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setOrders(prev => prev.map(o => o.id === orderId ? { ...o, status: newStatus } : o));
|
||||||
|
if (viewOrder && viewOrder.id === orderId) {
|
||||||
|
setViewOrder({ ...viewOrder, status: newStatus });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
alert("Hiba: " + e.message);
|
||||||
|
} finally {
|
||||||
|
setStatusUpdating(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdateDemoUrl = async () => {
|
||||||
|
if (!viewOrder || savingDemoUrl) return;
|
||||||
|
setSavingDemoUrl(true);
|
||||||
|
|
||||||
|
const orderId = viewOrder.id;
|
||||||
|
const targetStatus = 'pending_feedback';
|
||||||
|
const updatedDetails = { ...(viewOrder.details || {}), demoUrl: demoUrlInput };
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (isSupabaseConfigured) {
|
||||||
|
const { error } = await supabase.from('orders').update({
|
||||||
|
details: updatedDetails,
|
||||||
|
status: targetStatus
|
||||||
|
}).eq('id', orderId);
|
||||||
|
|
||||||
|
if (error) throw error;
|
||||||
|
|
||||||
|
await supabase.from('order_status_history').insert({ order_id: orderId, status: targetStatus });
|
||||||
|
const newHist = await fetchOrderHistory(orderId);
|
||||||
|
|
||||||
|
setOrders(prev => prev.map(o => o.id === orderId ? { ...o, status: targetStatus, details: updatedDetails } : o));
|
||||||
|
setViewOrder({ ...viewOrder, status: targetStatus, details: updatedDetails, history: newHist });
|
||||||
|
|
||||||
|
alert(`Sikeres mentés és visszajelzés kérése!`);
|
||||||
|
} else {
|
||||||
|
setViewOrder({ ...viewOrder, details: updatedDetails });
|
||||||
|
alert("Demo URL mentve.");
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
alert("Hiba: " + e.message);
|
||||||
|
} finally {
|
||||||
|
setSavingDemoUrl(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleViewDetails = async (order: AdminOrder) => {
|
||||||
|
setLoadingData(true);
|
||||||
|
const history = await fetchOrderHistory(order.id);
|
||||||
|
const emailLogs = await fetchEmailLogs(order.id);
|
||||||
|
setViewOrder({ ...order, history, emailLogs });
|
||||||
|
setDemoUrlInput(order.details?.demoUrl || '');
|
||||||
|
setGeneratedPrompts([]);
|
||||||
|
setLoadingData(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSendEmail = async (emailType: string) => {
|
||||||
|
if (!viewOrder || emailSending) return;
|
||||||
|
setEmailSending(emailType);
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log(`Email notification trigger: ${emailType}`);
|
||||||
|
|
||||||
|
if (isSupabaseConfigured) {
|
||||||
|
const { error } = await supabase.from('email_log').insert({
|
||||||
|
order_id: viewOrder.id,
|
||||||
|
email_type: emailType
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) throw error;
|
||||||
|
|
||||||
|
const newLogs = await fetchEmailLogs(viewOrder.id);
|
||||||
|
setViewOrder({ ...viewOrder, emailLogs: newLogs });
|
||||||
|
|
||||||
|
alert(`E-mail ("${emailType}") naplózva és kiküldve (szimulált).`);
|
||||||
|
} else {
|
||||||
|
alert(`E-mail értesítő előkészítve: "${emailType}"\n\nJelenleg ez a funkció csak placeholder.`);
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
alert("Hiba az e-mail naplózásakor: " + e.message);
|
||||||
|
} finally {
|
||||||
|
setEmailSending(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const StatusBadge = ({ status }: { status: string }) => {
|
||||||
|
const configs: any = {
|
||||||
|
new: { label: 'Új', color: 'bg-blue-100 text-blue-800' },
|
||||||
|
in_progress: { label: 'Folyamatban', color: 'bg-yellow-100 text-yellow-800' },
|
||||||
|
pending_feedback: { label: 'Visszajelzés', color: 'bg-purple-100 text-purple-800' },
|
||||||
|
completed: { label: 'Kész', color: 'bg-green-100 text-green-800' },
|
||||||
|
cancelled: { label: 'Törölve', color: 'bg-gray-100 text-gray-800' }
|
||||||
|
};
|
||||||
|
const c = configs[status] || configs.new;
|
||||||
|
return <span className={`text-[10px] uppercase font-black px-2 py-0.5 rounded-full ${c.color}`}>{c.label}</span>;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) return <div className="min-h-screen flex items-center justify-center font-bold text-primary">Admin felület...</div>;
|
||||||
|
if (!isAdmin) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pt-24 bg-gray-50 min-h-screen pb-20 font-sans text-gray-900">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex flex-col md:flex-row justify-between items-start md:items-end mb-8 gap-4">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<button onClick={() => navigate('/dashboard')} className="p-2 hover:bg-gray-200 rounded-full transition-colors bg-white shadow-sm border border-gray-100">
|
||||||
|
<ChevronLeft className="w-5 h-5 text-gray-500" />
|
||||||
|
</button>
|
||||||
|
<span className="text-[10px] font-black text-red-600 bg-red-50 px-3 py-1 rounded-full border border-red-100 uppercase tracking-widest">Admin Access</span>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-4xl font-black tracking-tighter text-gray-900">Vezérlőpult</h1>
|
||||||
|
</div>
|
||||||
|
<Button variant="white" size="sm" onClick={fetchAdminData} disabled={loadingData} className="border-gray-200 shadow-sm font-bold uppercase text-[10px] tracking-widest">
|
||||||
|
<RefreshCw className={`w-4 h-4 mr-2 ${loadingData ? 'animate-spin' : ''}`} /> Adatok frissítése
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-2 mb-10 bg-white p-2 rounded-[24px] border border-gray-100 shadow-sm w-fit">
|
||||||
|
{[
|
||||||
|
{ id: 'overview', label: 'Statisztika', icon: BarChart2 },
|
||||||
|
{ id: 'users', label: 'Felhasználók', icon: Users },
|
||||||
|
{ id: 'orders', label: 'Rendelések', icon: ShoppingCart },
|
||||||
|
{ id: 'plans', label: 'Csomagok', icon: Package }
|
||||||
|
].map((tab) => (
|
||||||
|
<button
|
||||||
|
key={tab.id}
|
||||||
|
onClick={() => setActiveTab(tab.id as any)}
|
||||||
|
className={`flex items-center gap-2 px-6 py-3 rounded-2xl text-sm font-bold transition-all ${
|
||||||
|
activeTab === tab.id ? 'bg-primary text-white shadow-lg' : 'text-gray-500 hover:bg-gray-50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<tab.icon className="w-4 h-4" />
|
||||||
|
{tab.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="animate-fade-in">
|
||||||
|
{activeTab === 'overview' && (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
|
<div className="bg-white p-10 rounded-[32px] border border-gray-100 shadow-sm">
|
||||||
|
<p className="text-xs font-bold text-gray-400 uppercase mb-3 tracking-widest">Látogatók</p>
|
||||||
|
<h3 className="text-5xl font-black text-gray-900">{visitorStats.month}</h3>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white p-10 rounded-[32px] border border-gray-100 shadow-sm">
|
||||||
|
<p className="text-xs font-bold text-gray-400 uppercase mb-3 tracking-widest">Rendelések</p>
|
||||||
|
<h3 className="text-5xl font-black text-gray-900">{orders.length}</h3>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white p-10 rounded-[32px] border border-gray-100 shadow-sm">
|
||||||
|
<p className="text-xs font-bold text-gray-400 uppercase mb-3 tracking-widest">Regisztrációk</p>
|
||||||
|
<h3 className="text-5xl font-black text-gray-900">{users.length}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'users' && (
|
||||||
|
<div className="bg-white rounded-[32px] border border-gray-100 overflow-hidden shadow-sm">
|
||||||
|
<table className="w-full text-left">
|
||||||
|
<thead className="bg-gray-50/50 border-b border-gray-100">
|
||||||
|
<tr className="text-xs font-bold text-gray-400 uppercase tracking-widest">
|
||||||
|
<th className="px-10 py-6">Felhasználó Neve</th>
|
||||||
|
<th className="px-10 py-6">E-mail</th>
|
||||||
|
<th className="px-10 py-6">Szint</th>
|
||||||
|
<th className="px-10 py-6 text-right">Regisztráció</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-gray-50 font-medium">
|
||||||
|
{users.map(u => (
|
||||||
|
<tr key={u.id} className="hover:bg-gray-50/50 transition-colors">
|
||||||
|
<td className="px-10 py-6">
|
||||||
|
<span className="font-black text-gray-900">{u.last_name || ''} {u.first_name || ''}</span>
|
||||||
|
{(!u.last_name && !u.first_name) && <span className="text-gray-400 italic">Nincs megadva</span>}
|
||||||
|
</td>
|
||||||
|
<td className="px-10 py-6">
|
||||||
|
<span className="text-sm font-bold text-gray-900">{u.email}</span>
|
||||||
|
</td>
|
||||||
|
<td className="px-10 py-6">
|
||||||
|
<span className={`px-3 py-1 rounded-full text-[10px] font-black uppercase tracking-widest ${u.role === 'admin' ? 'bg-red-100 text-red-700' : 'bg-gray-100 text-gray-600'}`}>{u.role}</span>
|
||||||
|
</td>
|
||||||
|
<td className="px-10 py-6 text-right text-sm text-gray-500">{new Date(u.created_at).toLocaleDateString('hu-HU')}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'orders' && (
|
||||||
|
<div className="bg-white rounded-[32px] border border-gray-100 overflow-hidden shadow-sm">
|
||||||
|
<table className="w-full text-left">
|
||||||
|
<thead className="bg-gray-50/50 border-b border-gray-100 text-xs font-bold text-gray-400 uppercase tracking-widest">
|
||||||
|
<tr>
|
||||||
|
<th className="px-10 py-6">Ügyfél</th>
|
||||||
|
<th className="px-10 py-6">Csomag</th>
|
||||||
|
<th className="px-10 py-6">Státusz</th>
|
||||||
|
<th className="px-10 py-6 text-right">Művelet</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-gray-50">
|
||||||
|
{orders.map(o => (
|
||||||
|
<tr key={o.id} className="hover:bg-gray-50/30 transition-colors">
|
||||||
|
<td className="px-10 py-6">
|
||||||
|
<p className="font-black leading-tight text-gray-900">{o.customer}</p>
|
||||||
|
<p className="text-[11px] text-gray-500 font-bold">{o.email}</p>
|
||||||
|
</td>
|
||||||
|
<td className="px-10 py-6 text-sm font-bold text-primary">{o.package}</td>
|
||||||
|
<td className="px-10 py-6"><StatusBadge status={o.status} /></td>
|
||||||
|
<td className="px-10 py-6 text-right">
|
||||||
|
<Button size="sm" variant="outline" onClick={() => handleViewDetails(o)} className="rounded-2xl border-gray-200 px-6 font-black uppercase text-[10px] tracking-widest">Kezelés</Button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === 'plans' && (
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-10">
|
||||||
|
{plans.map((p, i) => (
|
||||||
|
<div key={p.id} className="bg-white p-10 rounded-[32px] border border-gray-100 shadow-sm flex flex-col">
|
||||||
|
<input type="text" value={p.name} onChange={e => { const n = [...plans]; n[i].name = e.target.value; setPlans(n); }} className="text-xl font-black border-b border-gray-200 focus:border-primary outline-none w-full pb-1 text-black bg-white mb-6" />
|
||||||
|
<textarea rows={8} value={p.features.join('\n')} onChange={e => { const n = [...plans]; n[i].features = e.target.value.split('\n'); setPlans(n); }} className="text-sm p-4 bg-white rounded-2xl border border-gray-200 w-full resize-none outline-none focus:ring-4 focus:ring-primary/5 focus:border-primary font-medium text-black mb-8" />
|
||||||
|
<Button fullWidth onClick={() => { setPlanSaving(p.id); supabase.from('plans').upsert(p).then(() => { setPlanSaving(null); alert("Mentve!"); }); }} disabled={planSaving === p.id}>
|
||||||
|
{planSaving === p.id ? <RefreshCw className="w-5 h-5 animate-spin" /> : <span className="flex items-center gap-2"><Save className="w-5 h-5" /> Mentés</span>}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{viewOrder && (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-gray-900/80 backdrop-blur-sm overflow-y-auto">
|
||||||
|
<div className="bg-white rounded-[40px] shadow-2xl w-full max-w-6xl max-h-[95vh] overflow-hidden flex flex-col animate-fade-in-up border border-white/10">
|
||||||
|
<div className="px-10 py-8 border-b border-gray-100 flex justify-between items-center bg-gray-50/50">
|
||||||
|
<div className="flex items-center gap-6">
|
||||||
|
<div className="w-14 h-14 bg-white rounded-3xl shadow-md flex items-center justify-center text-primary border border-gray-100">
|
||||||
|
<ShoppingCart className="w-7 h-7" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 className="text-3xl font-black tracking-tighter text-gray-900">{viewOrder.customer}</h2>
|
||||||
|
<p className="text-sm text-gray-400 font-bold uppercase tracking-widest">{viewOrder.email} • ID: {viewOrder.displayId}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button onClick={() => setViewOrder(null)} className="p-3 hover:bg-gray-200 rounded-full transition-all bg-white border border-gray-100">
|
||||||
|
<XCircle className="w-7 h-7 text-gray-400" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-grow overflow-y-auto p-10 lg:p-14 space-y-16 bg-gray-50/30">
|
||||||
|
<div className="grid md:grid-cols-12 gap-16">
|
||||||
|
<div className="md:col-span-8 space-y-12">
|
||||||
|
<section>
|
||||||
|
<h3 className="text-[11px] font-black text-gray-400 uppercase tracking-[0.3em] mb-6 flex items-center gap-2"><FileText className="w-4 h-4" /> Projekt részletei</h3>
|
||||||
|
<div className="bg-white p-8 rounded-[32px] border border-gray-100 shadow-sm text-black space-y-6">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div><p className="text-[10px] font-black text-gray-400 uppercase tracking-widest mb-1">Csomag</p><p className="font-bold text-primary">{viewOrder.package}</p></div>
|
||||||
|
<div><p className="text-[10px] font-black text-gray-400 uppercase tracking-widest mb-1">Összeg</p><p className="font-bold text-gray-900">{viewOrder.amount}</p></div>
|
||||||
|
</div>
|
||||||
|
<div><p className="text-[10px] font-black text-gray-400 uppercase tracking-widest mb-1">Domain</p><p className="font-bold bg-gray-50 px-3 py-1.5 rounded-lg border border-gray-100 inline-block text-gray-900">{viewOrder.details?.domainName || 'Nincs'}</p></div>
|
||||||
|
<div className="pt-6 border-t border-gray-200"><p className="text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2">Leírás:</p><p className="italic leading-relaxed font-medium text-gray-800">"{viewOrder.details?.description || 'Nincs'}"</p></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3 className="text-[11px] font-black text-gray-400 uppercase tracking-[0.3em] mb-6">Státusz módosítása</h3>
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3">
|
||||||
|
{[
|
||||||
|
{ id: 'new', label: 'Új' },
|
||||||
|
{ id: 'in_progress', label: 'Folyamatban' },
|
||||||
|
{ id: 'pending_feedback', label: 'Visszajelzés' },
|
||||||
|
{ id: 'completed', label: 'Kész' },
|
||||||
|
{ id: 'cancelled', label: 'Törölve' }
|
||||||
|
].map(s => (
|
||||||
|
<button
|
||||||
|
key={s.id}
|
||||||
|
onClick={() => handleStatusChange(viewOrder.id, s.id)}
|
||||||
|
disabled={statusUpdating === viewOrder.id}
|
||||||
|
className={`px-4 py-3 rounded-2xl text-[10px] font-black uppercase tracking-widest transition-all border-2 flex items-center justify-between gap-2 ${viewOrder.status === s.id ? 'bg-gray-900 text-white border-gray-900 shadow-xl' : 'bg-white text-gray-500 border-gray-100 hover:border-primary/40'}`}
|
||||||
|
>
|
||||||
|
<span>{s.label}</span>
|
||||||
|
{viewOrder.status === s.id && <CheckCircle className="w-3 h-3 text-green-400" />}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* EMAIL NOTIFICATIONS & LOGS COMBINED */}
|
||||||
|
<section className="space-y-8">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-[11px] font-black text-gray-400 uppercase tracking-[0.3em] mb-6 flex items-center gap-2"><Mail className="w-4 h-4" /> E-mail értesítők</h3>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
|
{[
|
||||||
|
{ label: 'Fejlesztés megkezdése', icon: Rocket },
|
||||||
|
{ label: 'Demó oldal elkészült', icon: Layout },
|
||||||
|
{ label: 'Módosítások fejlesztése', icon: Edit3 },
|
||||||
|
{ label: '1 hete nincs visszajelzés', icon: Bell },
|
||||||
|
{ label: '2 hete nincs visszajelzés', icon: AlertTriangle },
|
||||||
|
{ label: 'Projekt lezárva (Nincs válasz)', icon: Archive },
|
||||||
|
{ label: 'Végleges oldal elérhető', icon: CheckCircle }
|
||||||
|
].map((email, idx) => (
|
||||||
|
<button
|
||||||
|
key={idx}
|
||||||
|
onClick={() => handleSendEmail(email.label)}
|
||||||
|
disabled={!!emailSending}
|
||||||
|
className="flex items-center gap-4 p-4 bg-white border border-gray-100 rounded-[20px] hover:border-primary hover:shadow-md transition-all text-left group disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<div className="w-10 h-10 bg-gray-50 rounded-xl flex items-center justify-center text-gray-400 group-hover:bg-primary/10 group-hover:text-primary transition-colors">
|
||||||
|
{emailSending === email.label ? <RefreshCw className="w-5 h-5 animate-spin" /> : <email.icon className="w-5 h-5" />}
|
||||||
|
</div>
|
||||||
|
<div className="flex-grow">
|
||||||
|
<p className="text-xs font-bold text-gray-900">{email.label}</p>
|
||||||
|
<p className="text-[9px] text-gray-400 uppercase font-black tracking-widest mt-0.5">Értesítő küldése</p>
|
||||||
|
</div>
|
||||||
|
<Send className="w-3 h-3 text-gray-300 group-hover:text-primary group-hover:translate-x-1 transition-all" />
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* IN-PLACE EMAIL LOG */}
|
||||||
|
<div className="bg-purple-50/50 p-8 rounded-[32px] border border-purple-100 shadow-sm">
|
||||||
|
<h4 className="text-[11px] font-black text-purple-600 uppercase tracking-[0.3em] mb-6 flex items-center gap-2"><History className="w-4 h-4" /> Korábban kiküldött e-mailek</h4>
|
||||||
|
<div className="space-y-6 relative before:absolute before:left-[11px] before:top-2 before:bottom-2 before:w-0.5 before:bg-purple-200">
|
||||||
|
{viewOrder.emailLogs && viewOrder.emailLogs.length > 0 ? (
|
||||||
|
viewOrder.emailLogs.map((log) => (
|
||||||
|
<div key={log.id} className="relative pl-8">
|
||||||
|
<div className={`absolute left-0 top-1.5 w-6 h-6 rounded-full border-4 border-white shadow-sm z-10 bg-purple-500`} />
|
||||||
|
<div className="text-black">
|
||||||
|
<p className="text-xs font-bold text-gray-900">{log.email_type}</p>
|
||||||
|
<p className="text-[10px] font-black text-purple-400 uppercase tracking-widest mt-0.5">{new Date(log.sent_at).toLocaleString('hu-HU')}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p className="text-xs text-gray-400 italic pl-8">Még nem küldtünk e-mailt ehhez a rendeléshez.</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="pt-8 border-t border-gray-100">
|
||||||
|
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-10 gap-4">
|
||||||
|
<h3 className="text-2xl font-black text-gray-900 flex items-center gap-3 uppercase tracking-tighter"><Sparkles className="w-7 h-7 text-purple-600" /> AI Fejlesztői Promptok</h3>
|
||||||
|
<Button size="sm" onClick={() => setGeneratedPrompts(generateOrderPrompts(viewOrder))}>Promptok Generálása</Button>
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-6">
|
||||||
|
{generatedPrompts.map((p, i) => (
|
||||||
|
<div key={i} className="border border-gray-100 rounded-[32px] overflow-hidden bg-white shadow-sm">
|
||||||
|
<div className="bg-gray-50/50 px-8 py-4 flex justify-between items-center border-b border-gray-100">
|
||||||
|
<span className="text-[11px] font-black text-gray-600 uppercase tracking-widest">{p.title}</span>
|
||||||
|
<button onClick={() => { navigator.clipboard.writeText(p.content); setCopiedIndex(i); setTimeout(() => setCopiedIndex(null), 2000); }} className={`text-[10px] font-black px-4 py-1.5 rounded-full uppercase tracking-widest ${copiedIndex === i ? 'bg-green-500 text-white' : 'bg-primary/10 text-primary'}`}>{copiedIndex === i ? 'MÁSOLVA!' : 'MÁSOLÁS'}</button>
|
||||||
|
</div>
|
||||||
|
<div className="p-8 bg-gray-900 overflow-x-auto"><pre className="text-gray-300 text-[11px] font-mono leading-relaxed"><code>{p.content}</code></pre></div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="md:col-span-4 space-y-10 border-l border-gray-100 pl-10">
|
||||||
|
<section className="bg-blue-50/50 p-8 rounded-[40px] border border-blue-100 shadow-sm">
|
||||||
|
<h3 className="text-blue-900 font-black mb-6 flex items-center gap-3 text-lg uppercase tracking-tighter"><Globe className="w-5 h-5" /> Demo URL közzététele</h3>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={demoUrlInput}
|
||||||
|
onChange={e => setDemoUrlInput(e.target.value)}
|
||||||
|
placeholder="https://..."
|
||||||
|
className="w-full px-5 py-4 rounded-2xl border border-gray-200 bg-white text-black text-sm font-bold outline-none focus:border-blue-500 transition-all shadow-sm"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleUpdateDemoUrl}
|
||||||
|
disabled={savingDemoUrl}
|
||||||
|
className="bg-blue-600 hover:bg-blue-700 text-white font-black px-6 py-4 rounded-2xl text-xs uppercase tracking-widest transition-all disabled:opacity-50 shadow-lg shadow-blue-200"
|
||||||
|
>
|
||||||
|
{savingDemoUrl ? 'MENTÉS...' : 'PUBLIKÁLÁS ÉS VISSZAJELZÉS KÉRÉSE'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h3 className="text-gray-900 font-black mb-6 flex items-center gap-3 text-lg uppercase tracking-tighter"><Clock className="w-5 h-5 text-primary" /> Státusz Történet</h3>
|
||||||
|
<div className="space-y-6 relative before:absolute before:left-[11px] before:top-2 before:bottom-2 before:w-0.5 before:bg-gray-100">
|
||||||
|
{viewOrder.history?.map((h, i) => (
|
||||||
|
<div key={h.id} className="relative pl-8">
|
||||||
|
<div className={`absolute left-0 top-1.5 w-6 h-6 rounded-full border-4 border-white shadow-sm z-10 ${i === 0 ? 'bg-primary' : 'bg-gray-300'}`} />
|
||||||
|
<div className="text-black">
|
||||||
|
<StatusBadge status={h.status} />
|
||||||
|
<p className="text-[11px] font-bold text-gray-500 mt-1 uppercase">{new Date(h.changed_at).toLocaleString('hu-HU')}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
58
pages/Blog.tsx
Normal file
58
pages/Blog.tsx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const Blog: React.FC = () => {
|
||||||
|
const posts = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: "Miért fontos a gyors weboldal 2024-ben?",
|
||||||
|
excerpt: "A betöltési sebesség nemcsak a felhasználói élményt, hanem a Google helyezést is drasztikusan befolyásolja.",
|
||||||
|
date: "2024. Március 15.",
|
||||||
|
image: "https://images.unsplash.com/photo-1460925895917-afdab827c52f?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: "Webshop trendek: Mire figyeljünk idén?",
|
||||||
|
excerpt: "Az AI alapú ajánlók és a mobilfizetési megoldások térnyerése az e-kereskedelemben.",
|
||||||
|
date: "2024. Február 28.",
|
||||||
|
image: "https://images.unsplash.com/photo-1556742049-0cfed4f7a07d?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
title: "SEO alapok kezdőknek",
|
||||||
|
excerpt: "Hogyan optimalizáld weboldalad tartalmát, hogy a Google szeresse?",
|
||||||
|
date: "2024. Február 10.",
|
||||||
|
image: "https://images.unsplash.com/photo-1432888498266-38ffec3eaf0a?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pt-20 bg-gray-50 min-h-screen">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
||||||
|
<div className="text-center mb-16">
|
||||||
|
<h1 className="text-4xl font-extrabold text-gray-900 mb-4">Blog</h1>
|
||||||
|
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
|
||||||
|
Hírek, tippek és szakmai cikkek a webfejlesztés és online marketing világából.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
|
{posts.map((post) => (
|
||||||
|
<article key={post.id} className="bg-white rounded-2xl shadow-md overflow-hidden hover:shadow-xl transition-shadow duration-300">
|
||||||
|
<img src={post.image} alt={post.title} className="w-full h-48 object-cover" />
|
||||||
|
<div className="p-6">
|
||||||
|
<span className="text-sm text-primary font-semibold">{post.date}</span>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mt-2 mb-3 hover:text-primary cursor-pointer">{post.title}</h3>
|
||||||
|
<p className="text-gray-600 text-sm mb-4">{post.excerpt}</p>
|
||||||
|
<button className="text-primary font-medium hover:underline">Olvass tovább →</button>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-12 text-center p-8 border-2 border-dashed border-gray-300 rounded-xl bg-white/50">
|
||||||
|
<p className="text-gray-500 font-medium">További cikkek hamarosan feltöltésre kerülnek...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
169
pages/Contact.tsx
Normal file
169
pages/Contact.tsx
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Mail, Phone, MapPin, Send } from 'lucide-react';
|
||||||
|
import { Button } from '../components/Button';
|
||||||
|
|
||||||
|
export const Contact: React.FC = () => {
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: '',
|
||||||
|
email: '',
|
||||||
|
phone: '',
|
||||||
|
service: 'webdev',
|
||||||
|
message: ''
|
||||||
|
});
|
||||||
|
const [submitted, setSubmitted] = useState(false);
|
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
|
||||||
|
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
// Simulate API call
|
||||||
|
console.log('Form submitted:', formData);
|
||||||
|
setSubmitted(true);
|
||||||
|
setTimeout(() => setSubmitted(false), 5000);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pt-20 bg-white min-h-screen">
|
||||||
|
<div className="bg-gray-900 py-16">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||||
|
<h1 className="text-4xl font-extrabold text-white mb-4">Lépjen velünk kapcsolatba</h1>
|
||||||
|
<p className="text-xl text-gray-300 max-w-2xl mx-auto">
|
||||||
|
Készen áll arra, hogy megvalósítsa elképzeléseit? Töltse ki az űrlapot, és hamarosan felvesszük Önnel a kapcsolatot!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 -mt-10">
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
|
{/* Contact Info Card */}
|
||||||
|
<div className="bg-gradient-to-br from-primary to-secondary rounded-2xl shadow-xl p-8 text-white h-fit">
|
||||||
|
<h3 className="text-2xl font-bold mb-6">Elérhetőségeink</h3>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-start space-x-4">
|
||||||
|
<div className="p-3 bg-white/20 rounded-lg">
|
||||||
|
<Phone className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-blue-100 text-sm">Telefon</p>
|
||||||
|
<p className="font-semibold">+36 30 123 4567</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-start space-x-4">
|
||||||
|
<div className="p-3 bg-white/20 rounded-lg">
|
||||||
|
<Mail className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-blue-100 text-sm">E-mail</p>
|
||||||
|
<p className="font-semibold">info@motionweb.hu</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-start space-x-4">
|
||||||
|
<div className="p-3 bg-white/20 rounded-lg">
|
||||||
|
<MapPin className="w-6 h-6" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-blue-100 text-sm">Iroda</p>
|
||||||
|
<p className="font-semibold">Budapest, Magyarország</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Form */}
|
||||||
|
<div className="lg:col-span-2 bg-white rounded-2xl shadow-xl border border-gray-100 p-8">
|
||||||
|
{submitted ? (
|
||||||
|
<div className="h-full flex flex-col items-center justify-center text-center py-10">
|
||||||
|
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mb-4">
|
||||||
|
<Send className="w-8 h-8 text-green-600" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900 mb-2">Köszönjük megkeresését!</h3>
|
||||||
|
<p className="text-gray-600">Munkatársunk hamarosan felveszi Önnel a kapcsolatot a megadott elérhetőségeken.</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<label htmlFor="name" className="block text-sm font-medium text-gray-700 mb-1">Név</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="name"
|
||||||
|
id="name"
|
||||||
|
required
|
||||||
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all bg-white text-gray-900"
|
||||||
|
placeholder="Teljes név"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">E-mail cím</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
id="email"
|
||||||
|
required
|
||||||
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all bg-white text-gray-900"
|
||||||
|
placeholder="pelda@email.com"
|
||||||
|
value={formData.email}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<label htmlFor="phone" className="block text-sm font-medium text-gray-700 mb-1">Telefonszám</label>
|
||||||
|
<input
|
||||||
|
type="tel"
|
||||||
|
name="phone"
|
||||||
|
id="phone"
|
||||||
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all bg-white text-gray-900"
|
||||||
|
placeholder="+36 30 123 4567"
|
||||||
|
value={formData.phone}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="service" className="block text-sm font-medium text-gray-700 mb-1">Érdeklődés tárgya</label>
|
||||||
|
<select
|
||||||
|
name="service"
|
||||||
|
id="service"
|
||||||
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all bg-white text-gray-900"
|
||||||
|
value={formData.service}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
<option value="webdev">Egyedi Weboldal</option>
|
||||||
|
<option value="webshop">Webshop</option>
|
||||||
|
<option value="marketing">SEO & Marketing</option>
|
||||||
|
<option value="other">Egyéb</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label htmlFor="message" className="block text-sm font-medium text-gray-700 mb-1">Üzenet</label>
|
||||||
|
<textarea
|
||||||
|
name="message"
|
||||||
|
id="message"
|
||||||
|
rows={4}
|
||||||
|
required
|
||||||
|
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent outline-none transition-all bg-white text-gray-900"
|
||||||
|
placeholder="Írja le röviden elképzeléseit..."
|
||||||
|
value={formData.message}
|
||||||
|
onChange={handleChange}
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-2">
|
||||||
|
<Button type="submit" size="lg" fullWidth>Ajánlatkérés küldése</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
313
pages/Dashboard.tsx
Normal file
313
pages/Dashboard.tsx
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useAuth } from '../context/AuthContext';
|
||||||
|
import { Button } from '../components/Button';
|
||||||
|
import { LogOut, User, Settings as SettingsIcon, CreditCard, Layout, Cake, ShieldAlert, Clock, Activity, CheckCircle, XCircle, MessageSquare, ArrowRight, Edit2, Download, FileText, ExternalLink, History, RefreshCw } from 'lucide-react';
|
||||||
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
|
import { supabase, isSupabaseConfigured } from '../lib/supabaseClient';
|
||||||
|
import { SettingsModal } from '../components/SettingsModal';
|
||||||
|
import { FeedbackModal } from '../components/FeedbackModal';
|
||||||
|
import { Invoice } from '../types';
|
||||||
|
|
||||||
|
interface UserProfile {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
first_name?: string;
|
||||||
|
last_name?: string;
|
||||||
|
date_of_birth: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OrderHistoryEntry {
|
||||||
|
id: string;
|
||||||
|
status: string;
|
||||||
|
changed_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserOrder {
|
||||||
|
id: string;
|
||||||
|
created_at: string;
|
||||||
|
package: string;
|
||||||
|
status: string;
|
||||||
|
amount: string;
|
||||||
|
displayId?: string;
|
||||||
|
details?: any;
|
||||||
|
history?: OrderHistoryEntry[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Dashboard: React.FC = () => {
|
||||||
|
const { user, signOut, isAdmin } = useAuth();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [profile, setProfile] = useState<UserProfile | null>(null);
|
||||||
|
const [orders, setOrders] = useState<UserOrder[]>([]);
|
||||||
|
const [invoices, setInvoices] = useState<Invoice[]>([]);
|
||||||
|
const [loadingOrders, setLoadingOrders] = useState(true);
|
||||||
|
const [showSettings, setShowSettings] = useState(false);
|
||||||
|
|
||||||
|
const [feedbackModalOpen, setFeedbackModalOpen] = useState(false);
|
||||||
|
const [selectedOrderId, setSelectedOrderId] = useState<string | null>(null);
|
||||||
|
const [feedbackLoading, setFeedbackLoading] = useState(false);
|
||||||
|
|
||||||
|
const fetchOrderHistory = async (orderId: string) => {
|
||||||
|
if (!isSupabaseConfigured) return [];
|
||||||
|
try {
|
||||||
|
const { data } = await supabase
|
||||||
|
.from('order_status_history')
|
||||||
|
.select('*')
|
||||||
|
.eq('order_id', orderId)
|
||||||
|
.order('changed_at', { ascending: false });
|
||||||
|
return data || [];
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
if (!user) return;
|
||||||
|
setLoadingOrders(true);
|
||||||
|
|
||||||
|
if (!isSupabaseConfigured) {
|
||||||
|
const meta = user.user_metadata || {};
|
||||||
|
setProfile({ id: user.id, email: user.email || '', first_name: meta.first_name || '', last_name: meta.last_name || '', date_of_birth: meta.date_of_birth || '1990-01-01' });
|
||||||
|
setOrders([{ id: 'demo-order-1', created_at: new Date().toISOString(), package: 'Pro Web', status: 'pending_feedback', amount: '350.000 Ft', displayId: 'DEMO-123', details: { demoUrl: 'https://example.com' }, history: [{ id: 'h1', status: 'new', changed_at: new Date().toISOString() }] }]);
|
||||||
|
setLoadingOrders(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data: profileData } = await supabase.from('profiles').select('*').eq('id', user.id).maybeSingle();
|
||||||
|
if (profileData) setProfile(profileData);
|
||||||
|
|
||||||
|
const { data: orderData } = await supabase.from('orders').select('*').eq('user_id', user.id).order('created_at', { ascending: false });
|
||||||
|
|
||||||
|
if (orderData) {
|
||||||
|
const enrichedOrders = await Promise.all(orderData.map(async (o) => {
|
||||||
|
const history = await fetchOrderHistory(o.id);
|
||||||
|
return {
|
||||||
|
...o,
|
||||||
|
displayId: o.id.substring(0, 8).toUpperCase(),
|
||||||
|
history: history
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
setOrders(enrichedOrders as UserOrder[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: invoiceData } = await supabase.from('invoices').select('*').eq('user_id', user.id).order('created_at', { ascending: false });
|
||||||
|
if (invoiceData) setInvoices(invoiceData as Invoice[]);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Unexpected error fetching data:", err);
|
||||||
|
} finally {
|
||||||
|
setLoadingOrders(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
await signOut();
|
||||||
|
navigate('/');
|
||||||
|
};
|
||||||
|
|
||||||
|
const openFeedbackModal = (orderId: string) => {
|
||||||
|
setSelectedOrderId(orderId);
|
||||||
|
setFeedbackModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmitFeedback = async (feedbackData: any) => {
|
||||||
|
if (!selectedOrderId) return;
|
||||||
|
setFeedbackLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!isSupabaseConfigured) {
|
||||||
|
await new Promise(r => setTimeout(r, 1000));
|
||||||
|
setOrders(prev => prev.map(o => o.id === selectedOrderId ? { ...o, status: 'in_progress' } : o));
|
||||||
|
setFeedbackModalOpen(false);
|
||||||
|
setFeedbackLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: currentOrder } = await supabase.from('orders').select('details').eq('id', selectedOrderId).single();
|
||||||
|
const updatedDetails = { ...(currentOrder?.details || {}), latestFeedback: feedbackData, feedbackDate: new Date().toISOString() };
|
||||||
|
|
||||||
|
// Approved status also goes to in_progress now, admin will set to completed later
|
||||||
|
const { error: updateError } = await supabase.from('orders').update({
|
||||||
|
status: 'in_progress',
|
||||||
|
details: updatedDetails
|
||||||
|
}).eq('id', selectedOrderId);
|
||||||
|
|
||||||
|
if (updateError) throw updateError;
|
||||||
|
|
||||||
|
// Log history
|
||||||
|
await supabase.from('order_status_history').insert({ order_id: selectedOrderId, status: 'in_progress' });
|
||||||
|
|
||||||
|
await fetchData();
|
||||||
|
setFeedbackModalOpen(false);
|
||||||
|
} catch (err: any) {
|
||||||
|
alert("Hiba: " + err.message);
|
||||||
|
} finally {
|
||||||
|
setFeedbackLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFullName = () => profile?.last_name && profile?.first_name ? `${profile.last_name} ${profile.first_name}` : user?.email?.split('@')[0] || 'Felhasználó';
|
||||||
|
|
||||||
|
const getStatusConfig = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'new': return { label: 'Beérkezett', color: 'bg-blue-100 text-blue-800', icon: Clock };
|
||||||
|
case 'in_progress':
|
||||||
|
case 'progress': return { label: 'Fejlesztés', color: 'bg-yellow-100 text-yellow-800', icon: Activity };
|
||||||
|
case 'pending_feedback':
|
||||||
|
case 'waiting_feedback':
|
||||||
|
case 'feedback': return { label: 'Visszajelzés', color: 'bg-purple-100 text-purple-800', icon: MessageSquare };
|
||||||
|
case 'completed': return { label: 'Kész', color: 'bg-green-100 text-green-800', icon: CheckCircle };
|
||||||
|
case 'cancelled': return { label: 'Törölve', color: 'bg-gray-100 text-gray-500', icon: XCircle };
|
||||||
|
default: return { label: 'Beérkezett', color: 'bg-gray-100 text-gray-800', icon: Clock };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pt-24 bg-gray-50 min-h-screen">
|
||||||
|
<SettingsModal isOpen={showSettings} onClose={() => setShowSettings(false)} userProfile={profile} onUpdate={fetchData} />
|
||||||
|
<FeedbackModal isOpen={feedbackModalOpen} onClose={() => setFeedbackModalOpen(false)} onSubmit={handleSubmitFeedback} loading={feedbackLoading} />
|
||||||
|
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="bg-white rounded-[24px] shadow-lg border border-gray-100 overflow-hidden mb-8">
|
||||||
|
<div className="bg-gradient-to-r from-gray-900 to-gray-800 p-8 text-white flex flex-col md:flex-row justify-between items-center gap-4">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold mb-2">Fiók Áttekintése</h1>
|
||||||
|
<p className="text-gray-300">Üdvözöljük, {getFullName()}!</p>
|
||||||
|
</div>
|
||||||
|
<div className="h-12 w-12 bg-white/10 rounded-full flex items-center justify-center border border-white/20">
|
||||||
|
<User className="h-6 w-6 text-white" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-8">
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-8">
|
||||||
|
{/* ORDERS COLUMN */}
|
||||||
|
<div className="lg:col-span-2 space-y-6">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<h3 className="font-bold text-gray-900 flex items-center gap-2 text-xl">
|
||||||
|
<Layout className="w-5 h-5 text-primary" /> Aktív Projektjeim
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{loadingOrders ? (
|
||||||
|
<div className="flex justify-center py-10"><RefreshCw className="animate-spin text-primary" /></div>
|
||||||
|
) : orders.length > 0 ? (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{orders.map(order => {
|
||||||
|
const statusConfig = getStatusConfig(order.status);
|
||||||
|
const needsFeedback = ['pending_feedback', 'waiting_feedback', 'feedback'].includes(order.status);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={order.id} className="bg-white rounded-2xl border border-gray-100 shadow-sm overflow-hidden hover:shadow-md transition-shadow">
|
||||||
|
<div className="p-6 flex flex-col sm:flex-row justify-between gap-6 border-b border-gray-50 bg-gray-50/30">
|
||||||
|
<div>
|
||||||
|
<p className="text-xs font-black text-primary uppercase tracking-widest mb-1">{order.package}</p>
|
||||||
|
<h4 className="text-xl font-bold text-gray-900">ID: {order.displayId}</h4>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
{needsFeedback && (
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<a href={order.details?.demoUrl || '#'} target="_blank" rel="noreferrer" className="text-xs px-4 py-2 rounded-xl font-black bg-blue-600 text-white flex items-center gap-2 shadow-lg shadow-blue-200">
|
||||||
|
<ExternalLink className="w-3 h-3" /> Demó oldal megnyitása
|
||||||
|
</a>
|
||||||
|
<button onClick={() => openFeedbackModal(order.id)} className="text-xs px-4 py-2 rounded-xl font-black bg-purple-100 text-purple-700 hover:bg-purple-200">
|
||||||
|
VISSZAJELZÉS
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!needsFeedback && (
|
||||||
|
<span className={`text-xs px-4 py-2 rounded-xl font-black flex items-center gap-2 ${statusConfig.color}`}>
|
||||||
|
<statusConfig.icon className="w-4 h-4" /> {statusConfig.label.toUpperCase()}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* PROJECT TIMELINE */}
|
||||||
|
<div className="p-6 bg-white">
|
||||||
|
<div className="flex items-center gap-2 mb-4 text-xs font-black text-gray-400 uppercase tracking-[0.2em]">
|
||||||
|
<History className="w-3 h-3" /> Projekt Történet
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-4 relative before:absolute before:left-[7px] before:top-2 before:bottom-2 before:w-0.5 before:bg-gray-50">
|
||||||
|
{order.history && order.history.length > 0 ? (
|
||||||
|
order.history.map((h, idx) => (
|
||||||
|
<div key={h.id} className="relative pl-6 flex justify-between items-center group">
|
||||||
|
<div className={`absolute left-0 top-1.5 w-3.5 h-3.5 rounded-full border-2 border-white shadow-sm z-10 ${idx === 0 ? 'bg-primary' : 'bg-gray-200'}`}></div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className={`text-xs font-bold ${idx === 0 ? 'text-gray-900' : 'text-gray-500'}`}>
|
||||||
|
{getStatusConfig(h.status).label}
|
||||||
|
</span>
|
||||||
|
<span className="text-[10px] text-gray-400 font-medium">
|
||||||
|
{new Date(h.changed_at).toLocaleString('hu-HU')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{idx === 0 && <span className="text-[9px] font-black text-primary bg-primary/5 px-2 py-0.5 rounded uppercase tracking-tighter">Aktuális</span>}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p className="text-xs text-gray-400 italic pl-6">Nincs korábbi bejegyzés.</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="bg-white p-10 rounded-3xl border-2 border-dashed border-gray-100 text-center">
|
||||||
|
<p className="text-gray-400 mb-6 italic">Még nincsenek leadott rendelései.</p>
|
||||||
|
<Link to="/#rendeles"><Button>Új projekt indítása <ArrowRight className="w-4 h-4 ml-2" /></Button></Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* SETTINGS & INVOICES COLUMN */}
|
||||||
|
<div className="space-y-8">
|
||||||
|
<div className="bg-white p-8 rounded-[32px] border border-gray-100 shadow-sm">
|
||||||
|
<div className="flex justify-between items-center mb-6">
|
||||||
|
<h3 className="font-bold text-gray-900">Beállítások</h3>
|
||||||
|
<button onClick={() => setShowSettings(true)} className="p-2 hover:bg-gray-50 rounded-xl transition-colors"><SettingsIcon className="w-5 h-5 text-gray-400" /></button>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="bg-gray-50 p-4 rounded-2xl">
|
||||||
|
<p className="text-[10px] font-black text-gray-400 uppercase tracking-widest mb-1">E-mail</p>
|
||||||
|
<p className="text-sm font-bold text-gray-900">{user?.email}</p>
|
||||||
|
</div>
|
||||||
|
<Button fullWidth variant="outline" size="sm" onClick={() => setShowSettings(true)}>Profil szerkesztése</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white p-8 rounded-[32px] border border-gray-100 shadow-sm">
|
||||||
|
<h3 className="font-bold text-gray-900 mb-6 flex items-center gap-2"><CreditCard className="w-5 h-5 text-primary" /> Számlák</h3>
|
||||||
|
{invoices.length > 0 ? (
|
||||||
|
<div className="space-y-3">
|
||||||
|
{invoices.map(inv => (
|
||||||
|
<div key={inv.id} className="flex items-center justify-between p-3 bg-gray-50 rounded-xl group hover:bg-gray-100 transition-colors">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<FileText className="w-5 h-5 text-gray-300" />
|
||||||
|
<span className="text-xs font-bold text-gray-700">{inv.invoice_number}</span>
|
||||||
|
</div>
|
||||||
|
<button className="p-2 text-gray-400 hover:text-primary"><Download className="w-4 h-4" /></button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : <p className="text-xs text-gray-400 italic text-center">Nincs számlázott tétel.</p>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="border-t border-gray-100 pt-6 flex justify-end">
|
||||||
|
<Button variant="outline" onClick={handleLogout} className="border-red-200 text-red-600 hover:bg-red-50 hover:text-red-700 hover:border-red-300">
|
||||||
|
<LogOut className="w-4 h-4 mr-2" /> Kijelentkezés
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
84
pages/FAQ.tsx
Normal file
84
pages/FAQ.tsx
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { ArrowLeft, HelpCircle, ChevronDown, ChevronUp } from 'lucide-react';
|
||||||
|
import { Button } from '../components/Button';
|
||||||
|
|
||||||
|
interface FaqItemProps {
|
||||||
|
question: string;
|
||||||
|
answer: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FaqItem: React.FC<FaqItemProps> = ({ question, answer }) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
return (
|
||||||
|
<div className="border border-gray-200 rounded-xl overflow-hidden transition-all duration-300 hover:border-primary/30">
|
||||||
|
<button
|
||||||
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
|
className="w-full flex items-center justify-between p-5 bg-white text-left focus:outline-none"
|
||||||
|
>
|
||||||
|
<span className="font-semibold text-gray-900">{question}</span>
|
||||||
|
{isOpen ? <ChevronUp className="w-5 h-5 text-primary" /> : <ChevronDown className="w-5 h-5 text-gray-400" />}
|
||||||
|
</button>
|
||||||
|
<div className={`bg-gray-50 px-5 transition-all duration-300 ${isOpen ? 'py-5 max-h-96 opacity-100' : 'max-h-0 py-0 opacity-0 overflow-hidden'}`}>
|
||||||
|
<p className="text-gray-600 leading-relaxed">{answer}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FAQ: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const faqs = [
|
||||||
|
{
|
||||||
|
question: "Mennyi idő alatt készül el egy weboldal?",
|
||||||
|
answer: "Ez a projekt összetettségétől függ. Egy egyszerűbb bemutatkozó oldal (Landing Page) általában 1-2 hét alatt elkészül, míg egy komplexebb webshop vagy egyedi fejlesztés 4-8 hetet is igénybe vehet. A pontos határidőt mindig az árajánlatban rögzítjük."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Kell saját domain névvel és tárhellyel rendelkeznem?",
|
||||||
|
answer: "Nem feltétlenül. Ha még nincs, szívesen segítünk a beszerzésében és a beállításokban. Ha már van, akkor azokat használjuk. A 'Domain ügyintézés' és 'Tárhely ügyintézés' opciókat a megrendelésnél is kiválaszthatja."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Vannak rejtett költségek vagy havidíjak?",
|
||||||
|
answer: "Nincsenek rejtett költségek. A fejlesztési díj egyszeri. Éves költségként a domain és tárhely díj merülhet fel (ha rajtunk keresztül intézi, akkor mi számlázzuk évente), illetve opcionálisan választható karbantartási csomagunk a folyamatos frissítésekért."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Kapok számlát a szolgáltatásról?",
|
||||||
|
answer: "Természetesen. Minden szolgáltatásunkról hivatalos számlát állítunk ki, amelyet e-mailben küldünk el Önnek. Mivel alanyi adómentes (AAM) vállalkozás vagyunk, a számlán nem szerepel felszámított ÁFA, így a feltüntetett árak a teljes fizetendő összeget jelentik."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Tudom majd egyedül szerkeszteni az oldalt?",
|
||||||
|
answer: "Igen! Olyan rendszereket építünk, amelyekhez kérés esetén adminisztrációs felületet biztosítunk (pl. blogbejegyzések írásához, termékek feltöltéséhez), és az átadáskor megmutatjuk a használatát."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Mi történik, ha elakadok vagy hibát találok?",
|
||||||
|
answer: "Elakadás vagy technikai probléma esetén örömmel segítünk, kérjük, jelezze ezt felénk e-mailben. Amennyiben a hiba a mi oldalunkon merül fel, azt természetesen javítjuk. Ha azonban a probléma tőlünk független, vagy utólagos módosítást (pl. design változtatást) szeretne, abban is szívesen állunk rendelkezésére, de ezek a fejlesztések már nem ingyenesek."
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pt-24 pb-16 min-h-screen bg-gray-50">
|
||||||
|
<div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<Button variant="white" onClick={() => navigate(-1)} className="mb-8 shadow-sm border border-gray-200">
|
||||||
|
<ArrowLeft className="w-4 h-4 mr-2" /> Vissza
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<div className="w-16 h-16 bg-blue-100 rounded-2xl flex items-center justify-center mx-auto mb-6 text-primary">
|
||||||
|
<HelpCircle className="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<h1 className="text-4xl font-extrabold text-gray-900 mb-4">Gyakori Kérdések</h1>
|
||||||
|
<p className="text-xl text-gray-600">
|
||||||
|
Összeszedtük a leggyakrabban felmerülő kérdéseket, hogy segítsünk a döntésben.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{faqs.map((faq, index) => (
|
||||||
|
<FaqItem key={index} question={faq.question} answer={faq.answer} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
316
pages/Home.tsx
Normal file
316
pages/Home.tsx
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { ArrowRight, Monitor, Search, ExternalLink, Check, Star, Smartphone, PenTool } from 'lucide-react';
|
||||||
|
import { Button } from '../components/Button';
|
||||||
|
import { ServiceCard } from '../components/ServiceCard';
|
||||||
|
import { OrderForm } from '../components/OrderForm';
|
||||||
|
import { ProcessSection } from '../components/ProcessSection';
|
||||||
|
import { supabase } from '../lib/supabaseClient';
|
||||||
|
import { ProductPackage } from '../types';
|
||||||
|
import { defaultPlans } from '../lib/defaultPlans';
|
||||||
|
|
||||||
|
export const Home: React.FC = () => {
|
||||||
|
const [packages, setPackages] = useState<ProductPackage[]>(defaultPlans);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchPlans = async () => {
|
||||||
|
try {
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('plans')
|
||||||
|
.select('*')
|
||||||
|
.order('price', { ascending: true }); // Simple text ordering, ideally numerical but works for default prices somewhat
|
||||||
|
|
||||||
|
if (!error && data && data.length > 0) {
|
||||||
|
setPackages(data);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error fetching plans:", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchPlans();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="overflow-x-hidden">
|
||||||
|
{/* Hero Section */}
|
||||||
|
<section className="relative min-h-screen flex items-center justify-center pt-20 bg-gray-900 overflow-hidden">
|
||||||
|
{/* Background Elements */}
|
||||||
|
<div className="absolute inset-0 z-0">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-br from-gray-900 via-[#0f172a] to-purple-900"></div>
|
||||||
|
{/* Subtle texture overlay */}
|
||||||
|
<div className="absolute inset-0 opacity-10 bg-[url('https://www.transparenttextures.com/patterns/cubes.png')]"></div>
|
||||||
|
{/* Animated glow effects */}
|
||||||
|
<div className="absolute top-[-20%] left-[-10%] w-[500px] h-[500px] bg-purple-600/20 rounded-full blur-[100px] animate-pulse"></div>
|
||||||
|
<div className="absolute bottom-[-20%] right-[-10%] w-[500px] h-[500px] bg-blue-600/20 rounded-full blur-[100px] animate-pulse"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10 py-12 text-center">
|
||||||
|
<div
|
||||||
|
className="inline-block px-4 py-2 bg-white/5 backdrop-blur-sm rounded-full border border-white/10 animate-fade-in-up mb-8"
|
||||||
|
style={{ animationDelay: '0.1s' }}
|
||||||
|
>
|
||||||
|
<span className="text-blue-300 font-medium text-sm tracking-wide uppercase">Innovatív Megoldások Neked</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1
|
||||||
|
className="text-5xl md:text-7xl lg:text-8xl font-extrabold text-white leading-tight animate-fade-in-up mb-6 tracking-tight"
|
||||||
|
style={{ animationDelay: '0.2s' }}
|
||||||
|
>
|
||||||
|
Motion Web Stúdió
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<h2
|
||||||
|
className="text-2xl md:text-4xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-purple-400 animate-fade-in-up mb-8"
|
||||||
|
style={{ animationDelay: '0.3s' }}
|
||||||
|
>
|
||||||
|
Vigye vállalkozását a következő szintre
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p
|
||||||
|
className="text-lg text-gray-400 max-w-2xl mx-auto leading-relaxed animate-fade-in-up mb-12"
|
||||||
|
style={{ animationDelay: '0.5s' }}
|
||||||
|
>
|
||||||
|
A MotionWeb csapata prémium minőségű weboldalakat és webáruházakat fejleszt, amelyek nemcsak szépek, de vevőket is hoznak.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="flex flex-col sm:flex-row gap-5 justify-center animate-fade-in-up"
|
||||||
|
style={{ animationDelay: '0.7s' }}
|
||||||
|
>
|
||||||
|
<Link to="/#rendeles">
|
||||||
|
<Button size="lg" className="w-full sm:w-auto shadow-lg shadow-purple-900/20">
|
||||||
|
Kezdjük el <ArrowRight className="ml-2 w-5 h-5" />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<Link to="/#references">
|
||||||
|
<Button variant="outline" size="lg" className="border-white/20 text-white hover:bg-white/10 hover:border-white w-full sm:w-auto backdrop-blur-sm">
|
||||||
|
Referenciák
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Services Section */}
|
||||||
|
<section id="services" className="py-24 bg-gray-50 scroll-mt-20">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="text-center max-w-3xl mx-auto mb-16">
|
||||||
|
<h2 className="text-base font-semibold text-primary tracking-wide uppercase">Szolgáltatásaink</h2>
|
||||||
|
<p className="mt-2 text-3xl font-extrabold text-gray-900 sm:text-4xl">
|
||||||
|
Minden, ami a sikeres online jelenléthez kell
|
||||||
|
</p>
|
||||||
|
<p className="mt-4 text-xl text-gray-500">
|
||||||
|
Teljeskörű digitális kivitelezés a tervezéstől az üzemeltetésig.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||||
|
<ServiceCard
|
||||||
|
title="Egyedi Weboldal"
|
||||||
|
description="Modern, gyors és reszponzív weboldalak, amelyek tükrözik márkája értékeit."
|
||||||
|
Icon={Monitor}
|
||||||
|
/>
|
||||||
|
<ServiceCard
|
||||||
|
title="Reszponzív Design"
|
||||||
|
description="Mobile-first szemléletünk garantálja, hogy oldala minden eszközön tökéletesen jelenjen meg."
|
||||||
|
Icon={Smartphone}
|
||||||
|
/>
|
||||||
|
<ServiceCard
|
||||||
|
title="SEO Optimalizálás"
|
||||||
|
description="Keresőoptimalizált tartalom és stratégia, hogy ügyfelei könnyen megtalálják."
|
||||||
|
Icon={Search}
|
||||||
|
/>
|
||||||
|
<ServiceCard
|
||||||
|
title="UI/UX Design"
|
||||||
|
description="Felhasználóbarát felületek tervezése a maximális látogatói élményért és konverzióért."
|
||||||
|
Icon={PenTool}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* References Section */}
|
||||||
|
<section id="references" className="py-24 bg-white scroll-mt-20">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="text-center mb-16">
|
||||||
|
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-4">Referenciák</h2>
|
||||||
|
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
|
||||||
|
Tekintsd meg, milyen modern és reszponzív weboldalakat készítünk ügyfeleink számára.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
|
{/* Card 1: SweetCraving */}
|
||||||
|
<div className="bg-white rounded-2xl overflow-hidden shadow-lg border border-gray-100 hover:shadow-xl transition-all duration-300 flex flex-col">
|
||||||
|
<div className="h-64 overflow-hidden">
|
||||||
|
<img
|
||||||
|
src="https://images.unsplash.com/photo-1563729784474-d77dbb933a9e?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80"
|
||||||
|
alt="SweetCraving Desszertműhely"
|
||||||
|
className="w-full h-full object-cover transform hover:scale-105 transition-transform duration-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="p-8 flex flex-col flex-grow">
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-2">SweetCraving Desszertműhely</h3>
|
||||||
|
<p className="text-sm text-gray-500 font-medium mb-4">Kézműves desszertek – Landing Page csomag</p>
|
||||||
|
<p className="text-gray-600 mb-6 flex-grow leading-relaxed">
|
||||||
|
A SweetCraving számára egy fiatalos, barátságos hangulatú bemutatkozó oldalt hoztunk létre.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-3 mb-8">
|
||||||
|
<span className="text-sm text-gray-500 mr-2">Színek:</span>
|
||||||
|
<div className="w-6 h-6 rounded-full bg-pink-300 border border-gray-200"></div>
|
||||||
|
<div className="w-6 h-6 rounded-full bg-yellow-200 border border-gray-200"></div>
|
||||||
|
<div className="w-6 h-6 rounded-full bg-white border border-gray-200"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full">
|
||||||
|
<Link to="/demos/sweetcraving">
|
||||||
|
<Button variant="outline" fullWidth className="group justify-between text-gray-700 border-gray-300 hover:border-gray-400 hover:bg-gray-50">
|
||||||
|
Oldal megtekintése
|
||||||
|
<ExternalLink className="w-4 h-4 ml-2" />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Card 2: BlueWave */}
|
||||||
|
<div className="bg-white rounded-2xl overflow-hidden shadow-lg border border-gray-100 hover:shadow-xl transition-all duration-300 flex flex-col">
|
||||||
|
<div className="h-64 overflow-hidden">
|
||||||
|
<img
|
||||||
|
src="https://images.unsplash.com/photo-1509391366360-2e959784a276?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80"
|
||||||
|
alt="BlueWave Solar Kft."
|
||||||
|
className="w-full h-full object-cover transform hover:scale-105 transition-transform duration-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="p-8 flex flex-col flex-grow">
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-2">BlueWave Solar Kft.</h3>
|
||||||
|
<p className="text-sm text-gray-500 font-medium mb-4">Zöld energia megoldások – Pro Web csomag</p>
|
||||||
|
<p className="text-gray-600 mb-6 flex-grow leading-relaxed">
|
||||||
|
A BlueWave Solar számára modern, üzleti stílusú, reszponzív weboldalt készítettünk.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-3 mb-8">
|
||||||
|
<span className="text-sm text-gray-500 mr-2">Színek:</span>
|
||||||
|
<div className="w-6 h-6 rounded-full bg-sky-600 border border-gray-200"></div>
|
||||||
|
<div className="w-6 h-6 rounded-full bg-green-500 border border-gray-200"></div>
|
||||||
|
<div className="w-6 h-6 rounded-full bg-white border border-gray-200"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full">
|
||||||
|
<Link to="/demos/bluewave">
|
||||||
|
<Button variant="outline" fullWidth className="group justify-between text-gray-700 border-gray-300 hover:border-gray-400 hover:bg-gray-50">
|
||||||
|
Oldal megtekintése
|
||||||
|
<ExternalLink className="w-4 h-4 ml-2" />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Card 3: Steelguard */}
|
||||||
|
<div className="bg-white rounded-2xl overflow-hidden shadow-lg border border-gray-100 hover:shadow-xl transition-all duration-300 flex flex-col">
|
||||||
|
<div className="h-64 overflow-hidden">
|
||||||
|
<img
|
||||||
|
src="https://images.unsplash.com/photo-1518770660439-4636190af475?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80"
|
||||||
|
alt="Steelguard Biztonságtechnika"
|
||||||
|
className="w-full h-full object-cover transform hover:scale-105 transition-transform duration-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="p-8 flex flex-col flex-grow">
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-2">Steelguard Biztonságtechnika</h3>
|
||||||
|
<p className="text-sm text-gray-500 font-medium mb-4">Biztonságtechnika és IT rendszerek – Enterprise csomag</p>
|
||||||
|
<p className="text-gray-600 mb-6 flex-grow leading-relaxed">
|
||||||
|
A Steelguard számára technológiai, sötét tónusú weboldalt készítettünk.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-3 mb-8">
|
||||||
|
<span className="text-sm text-gray-500 mr-2">Színek:</span>
|
||||||
|
<div className="w-6 h-6 rounded-full bg-black border border-gray-200"></div>
|
||||||
|
<div className="w-6 h-6 rounded-full bg-blue-600 border border-gray-200"></div>
|
||||||
|
<div className="w-6 h-6 rounded-full bg-gray-300 border border-gray-200"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full">
|
||||||
|
<Link to="/demos/steelguard">
|
||||||
|
<Button variant="outline" fullWidth className="group justify-between text-gray-700 border-gray-300 hover:border-gray-400 hover:bg-gray-50">
|
||||||
|
Oldal megtekintése
|
||||||
|
<ExternalLink className="w-4 h-4 ml-2" />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Products/Packages Section */}
|
||||||
|
<section id="products" className="py-24 bg-gray-50 scroll-mt-20">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="text-center mb-16">
|
||||||
|
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-4">Csomagajánlataink</h2>
|
||||||
|
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
|
||||||
|
Átlátható árazás, rejtett költségek nélkül. Válassza az Ön céljaihoz leginkább illeszkedő csomagot.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
|
{packages.map((pkg, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`relative bg-white rounded-2xl shadow-xl flex flex-col p-8 transition-transform hover:-translate-y-2 duration-300 ${pkg.isPopular ? 'border-2 border-primary ring-4 ring-purple-100' : 'border border-gray-100'}`}
|
||||||
|
>
|
||||||
|
{pkg.isPopular && (
|
||||||
|
<div className="absolute top-0 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-gradient-to-r from-primary to-secondary text-white px-4 py-1 rounded-full text-sm font-bold shadow-lg flex items-center gap-1">
|
||||||
|
<Star className="w-4 h-4 fill-current" /> Legnépszerűbb
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="mb-6">
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900">{pkg.name}</h3>
|
||||||
|
<p className="text-gray-500 mt-2 text-sm min-h-[40px]">{pkg.desc}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-8">
|
||||||
|
<span className="text-3xl font-extrabold text-gray-900">{pkg.price}</span>
|
||||||
|
{pkg.price.includes('Ft') && <span className="text-gray-500 text-sm font-normal"> + ÁFA</span>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul className="space-y-4 mb-8 flex-grow">
|
||||||
|
{pkg.features.map((feature, i) => (
|
||||||
|
<li key={i} className="flex items-start text-gray-600">
|
||||||
|
<Check className="w-5 h-5 text-green-500 mr-3 flex-shrink-0 mt-0.5" />
|
||||||
|
<span className="text-sm">{feature}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div className="mt-auto">
|
||||||
|
<Link to="/#rendeles">
|
||||||
|
<Button
|
||||||
|
variant={pkg.isPopular ? 'primary' : 'outline'}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
{pkg.cta}
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Process Section */}
|
||||||
|
<ProcessSection />
|
||||||
|
|
||||||
|
{/* Order Form Section */}
|
||||||
|
<section id="rendeles" className="py-20 bg-white scroll-mt-20">
|
||||||
|
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<OrderForm />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
342
pages/Privacy.tsx
Normal file
342
pages/Privacy.tsx
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { ArrowLeft, Shield, Mail, Globe, Database, Cookie, UserCheck, Scale, Lock } from 'lucide-react';
|
||||||
|
import { Button } from '../components/Button';
|
||||||
|
|
||||||
|
export const Privacy: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pt-24 pb-20 min-h-screen bg-gray-50/50">
|
||||||
|
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<Button
|
||||||
|
variant="white"
|
||||||
|
onClick={() => navigate(-1)}
|
||||||
|
className="mb-10 shadow-sm border border-gray-200 group"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="w-4 h-4 mr-2 group-hover:-translate-x-1 transition-transform" /> Vissza
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-[32px] shadow-sm border border-gray-100 overflow-hidden">
|
||||||
|
{/* Header Section */}
|
||||||
|
<div className="bg-gradient-to-br from-gray-900 to-gray-800 p-8 md:p-12 text-white">
|
||||||
|
<div className="flex flex-col md:flex-row md:items-center gap-6">
|
||||||
|
<div className="p-4 bg-white/10 backdrop-blur-md rounded-2xl border border-white/20 w-fit">
|
||||||
|
<Shield className="w-10 h-10 text-blue-300" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl md:text-4xl font-extrabold tracking-tight uppercase">Adatkezelési Tájékoztató</h1>
|
||||||
|
<div className="flex flex-wrap items-center gap-4 mt-3">
|
||||||
|
<span className="px-3 py-1 bg-secondary/20 border border-secondary/30 rounded-full text-xs font-bold uppercase tracking-widest text-blue-200">
|
||||||
|
MotionWeb
|
||||||
|
</span>
|
||||||
|
<span className="text-gray-400 text-sm">
|
||||||
|
Hatályos: 2025. január 1-től
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content Sections */}
|
||||||
|
<div className="p-8 md:p-16">
|
||||||
|
<div className="space-y-16 text-gray-600 leading-relaxed">
|
||||||
|
|
||||||
|
{/* Section 1 */}
|
||||||
|
<section className="relative">
|
||||||
|
<div className="absolute -left-6 top-0 bottom-0 w-1 bg-secondary/10 rounded-full hidden md:block" />
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-6 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-secondary/10 text-secondary text-sm">1</span>
|
||||||
|
Az adatkezelő adatai
|
||||||
|
</h3>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 bg-gray-50 p-6 rounded-2xl border border-gray-100">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest mb-1">Adatkezelő neve</p>
|
||||||
|
<p className="font-semibold text-gray-900">Balogh Bence Benedek egyéni vállalkozó</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest mb-1">Projekt / márkanév</p>
|
||||||
|
<p className="font-semibold text-gray-900">MotionWeb</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<Globe className="w-4 h-4 text-secondary mt-1 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest mb-1">Weboldal</p>
|
||||||
|
<a href="https://motionweb.hu" className="font-semibold text-secondary hover:underline">https://motionweb.hu</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<Mail className="w-4 h-4 text-secondary mt-1 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest mb-1">Kapcsolattartási e-mail</p>
|
||||||
|
<a href="mailto:motionstudiohq@gmail.com" className="font-semibold text-secondary hover:underline">motionstudiohq@gmail.com</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="md:col-span-2 pt-4 border-t border-gray-200">
|
||||||
|
<p className="text-sm italic text-gray-500">
|
||||||
|
A MotionWeb elnevezés projektnév, nem külön jogi személy. Az adatkezelést Balogh Bence Benedek egyéni vállalkozó végzi.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 2 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-secondary/10 text-secondary text-sm">2</span>
|
||||||
|
Az adatkezelés jogalapja
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p>Az adatkezelés a GDPR 6. cikk (1) bekezdése alapján történik, az alábbi jogalapokon:</p>
|
||||||
|
<ul className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||||
|
{[
|
||||||
|
'szerződés teljesítése, valamint',
|
||||||
|
'szerződés megkötését megelőző lépések,',
|
||||||
|
'jogi kötelezettség teljesítése (számlázás),',
|
||||||
|
'jogos érdek (a szolgáltatás működtetése és biztonsága),',
|
||||||
|
'érintetti hozzájárulás (cookie-k alkalmazása).'
|
||||||
|
].map((item, i) => (
|
||||||
|
<li key={i} className="flex items-center gap-3 text-sm bg-gray-50 p-3 rounded-lg border border-gray-100">
|
||||||
|
<UserCheck className="w-4 h-4 text-secondary flex-shrink-0" />
|
||||||
|
{item}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<p className="bg-blue-50 p-4 rounded-xl border border-blue-100 text-gray-700 mt-4">
|
||||||
|
A MotionWeb szolgáltatásai fix csomagárakon érhetők el. A megrendelés leadásával és az előleg megfizetésével a felek között szerződés jön létre.
|
||||||
|
</p>
|
||||||
|
<p className="font-bold text-gray-800">A MotionWeb nem végez marketing célú adatkezelést.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 3 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-secondary/10 text-secondary text-sm">3</span>
|
||||||
|
Kezelt személyes adatok köre
|
||||||
|
</h3>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||||
|
<div className="bg-gray-50 p-6 rounded-2xl border border-gray-100">
|
||||||
|
<h4 className="font-bold text-gray-900 mb-4 flex items-center gap-2">
|
||||||
|
<Mail className="w-4 h-4 text-secondary" /> Kapcsolatfelvétel és rendelés
|
||||||
|
</h4>
|
||||||
|
<ul className="space-y-2 text-sm">
|
||||||
|
{['név', 'cégnév (amennyiben megadásra kerül)', 'e-mail cím', 'telefonszám', 'számlázási adatok', 'projektleírás, megjegyzések'].map((item, i) => (
|
||||||
|
<li key={i} className="flex items-center gap-2">
|
||||||
|
<div className="w-1.5 h-1.5 rounded-full bg-secondary" /> {item}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="bg-gray-50 p-6 rounded-2xl border border-gray-100">
|
||||||
|
<h4 className="font-bold text-gray-900 mb-4 flex items-center gap-2">
|
||||||
|
<Database className="w-4 h-4 text-secondary" /> Felhasználói fiók esetén
|
||||||
|
</h4>
|
||||||
|
<ul className="space-y-2 text-sm">
|
||||||
|
{['e-mail cím', 'jelszó (titkosított formában)', 'fiók státusz', 'rendelésekhez kapcsolódó adatok', 'technikai aktivitási adatok'].map((item, i) => (
|
||||||
|
<li key={i} className="flex items-center gap-2">
|
||||||
|
<div className="w-1.5 h-1.5 rounded-full bg-secondary" /> {item}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="md:col-span-2 bg-blue-50/50 p-6 rounded-2xl border border-blue-100">
|
||||||
|
<h4 className="font-bold text-gray-900 mb-2 flex items-center gap-2">
|
||||||
|
<Shield className="w-4 h-4 text-secondary" /> Fizetés
|
||||||
|
</h4>
|
||||||
|
<p className="text-sm">
|
||||||
|
A MotionWeb nem kezel bankkártya-adatokat. A fizetés a <span className="font-bold">Stripe</span> rendszerén keresztül történik, amelyhez az érintett átirányításra kerül.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 4 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-secondary/10 text-secondary text-sm">4</span>
|
||||||
|
Adattárolás és adatfeldolgozók
|
||||||
|
</h3>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h4 className="text-xs font-bold text-gray-400 uppercase tracking-widest">Adattárolás helye</h4>
|
||||||
|
<ul className="text-sm space-y-2">
|
||||||
|
<li><strong>Supabase</strong> – adatbázis és felhasználói adatok (EU)</li>
|
||||||
|
<li><strong>VPS szerver</strong> – weboldalak kiszolgálása (EU)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h4 className="text-xs font-bold text-gray-400 uppercase tracking-widest">E-mail küldés</h4>
|
||||||
|
<ul className="text-sm space-y-2">
|
||||||
|
<li><strong>Resend</strong> – tranzakciós e-mailek</li>
|
||||||
|
<li><strong>Supabase</strong> – fiókregisztráció és jelszó-visszaállítás</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h4 className="text-xs font-bold text-gray-400 uppercase tracking-widest">Fizetési szolgáltató</h4>
|
||||||
|
<ul className="text-sm space-y-2">
|
||||||
|
<li><strong>Stripe Payments Europe Ltd.</strong></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="mt-8 text-sm italic border-t border-gray-100 pt-4">
|
||||||
|
Az adatfeldolgozók kiválasztása során az adatkezelő kizárólag a GDPR-nak megfelelő szolgáltatókat veszi igénybe.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 5 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-secondary/10 text-secondary text-sm">5</span>
|
||||||
|
Analitikai adatok
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p>A MotionWeb kizárólag alapvető statisztikai adatokat gyűjt Supabase segítségével, így különösen:</p>
|
||||||
|
<ul className="flex flex-wrap gap-3">
|
||||||
|
{['weboldal-látogatások száma', 'regisztrált felhasználók száma', 'leadott rendelések száma'].map((item, i) => (
|
||||||
|
<li key={i} className="bg-gray-50 px-4 py-2 rounded-full border border-gray-200 text-sm font-medium">
|
||||||
|
{item}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<p className="text-sm text-gray-500">Az analitikai adatok nem alkalmasak az érintettek azonosítására, és nem szolgálnak marketing vagy profilalkotási célokat.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 6 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-secondary/10 text-secondary text-sm">6</span>
|
||||||
|
Cookie-k (sütik)
|
||||||
|
</h3>
|
||||||
|
<div className="flex flex-col md:flex-row gap-8 items-start">
|
||||||
|
<div className="bg-gray-50 p-4 rounded-2xl text-secondary">
|
||||||
|
<Cookie className="w-12 h-12" />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p>A weboldal cookie-kat alkalmaz a megfelelő működés, a felhasználói élmény és a biztonság érdekében.</p>
|
||||||
|
<p>A cookie-k használatához az érintett hozzájárulása szükséges, amelyet a weboldalon megjelenő cookie banner biztosít.</p>
|
||||||
|
<p className="font-semibold text-gray-900">Marketing célú cookie-k alkalmazására nem kerül sor.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 7 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-secondary/10 text-secondary text-sm">7</span>
|
||||||
|
Adatmegőrzés időtartama
|
||||||
|
</h3>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
{[
|
||||||
|
{ label: 'aktív ügyféladatok', val: 'a szolgáltatás fennállásának ideje alatt' },
|
||||||
|
{ label: 'szerződés megszűnését követően', val: '1 évig' },
|
||||||
|
{ label: 'számlázási adatok', val: 'jogszabályi kötelezettség szerint' },
|
||||||
|
{ label: 'felhasználói fiókadatok', val: 'határozatlan ideig, vagy az érintett törlési kérelméig' }
|
||||||
|
].map((item, i) => (
|
||||||
|
<div key={i} className="p-4 bg-gray-50 rounded-xl border border-gray-100">
|
||||||
|
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest mb-1">{item.label}</p>
|
||||||
|
<p className="font-semibold text-gray-900 text-sm">{item.val}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 8 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-secondary/10 text-secondary text-sm">8</span>
|
||||||
|
Az érintettek jogai
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p>Az érintett jogosult:</p>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-12 gap-y-2">
|
||||||
|
{[
|
||||||
|
'tájékoztatást kérni személyes adatai kezeléséről',
|
||||||
|
'hozzáférést kérni a kezelt adatokhoz',
|
||||||
|
'adatainak helyesbítését kérni',
|
||||||
|
'adatainak törlését kérni',
|
||||||
|
'az adatkezelés korlátozását kérni',
|
||||||
|
'tiltakozni az adatkezelés ellen',
|
||||||
|
'adathordozhatóságot kérni'
|
||||||
|
].map((item, i) => (
|
||||||
|
<div key={i} className="flex items-center gap-3 text-sm">
|
||||||
|
<div className="w-1.5 h-1.5 rounded-full bg-secondary" />
|
||||||
|
{item}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<p className="mt-8 p-6 bg-gray-900 text-white rounded-2xl text-center">
|
||||||
|
A kérelmek benyújthatók az alábbi e-mail címen:<br />
|
||||||
|
<a href="mailto:motionstudiohq@gmail.com" className="text-blue-300 font-bold hover:underline">motionstudiohq@gmail.com</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 9 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-secondary/10 text-secondary text-sm">9</span>
|
||||||
|
Jogorvoslati lehetőségek
|
||||||
|
</h3>
|
||||||
|
<div className="bg-gray-50 p-6 rounded-2xl border border-gray-100 flex items-start gap-4">
|
||||||
|
<div className="p-3 bg-white rounded-xl shadow-sm">
|
||||||
|
<Scale className="w-6 h-6 text-secondary" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="mb-4">Amennyiben az érintett megítélése szerint személyes adatainak kezelése jogsértő, jogosult panaszt tenni:</p>
|
||||||
|
<p className="font-bold text-gray-900">Nemzeti Adatvédelmi és Információszabadság Hatóság (NAIH)</p>
|
||||||
|
<p className="text-sm">Weboldal: <a href="https://www.naih.hu" className="text-secondary hover:underline">https://www.naih.hu</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 10 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-secondary/10 text-secondary text-sm">10</span>
|
||||||
|
Adatbiztonság
|
||||||
|
</h3>
|
||||||
|
<div className="flex gap-6 items-center">
|
||||||
|
<div className="hidden md:block p-8 bg-gray-900 rounded-3xl text-white">
|
||||||
|
<Lock className="w-12 h-12 text-secondary" />
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Az adatkezelő megfelelő technikai és szervezési intézkedéseket alkalmaz a személyes adatok biztonságának megőrzése érdekében, különös tekintettel a jogosulatlan hozzáférés, módosítás vagy törlés megelőzésére.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 11 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-secondary/10 text-secondary text-sm">11</span>
|
||||||
|
Záró rendelkezések
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p>Az adatkezelő fenntartja a jogot jelen adatkezelési tájékoztató módosítására.</p>
|
||||||
|
<p className="text-secondary font-bold">A módosítás a weboldalon történő közzététellel lép hatályba.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer Info */}
|
||||||
|
<div className="bg-gray-50 p-8 border-t border-gray-100 text-center">
|
||||||
|
<p className="text-gray-400 text-xs italic">
|
||||||
|
Utolsó frissítés: 2025. január
|
||||||
|
</p>
|
||||||
|
<p className="text-gray-400 text-xs mt-2">
|
||||||
|
MotionWeb © {new Date().getFullYear()} - Minden jog fenntartva.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
99
pages/Products.tsx
Normal file
99
pages/Products.tsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Check, Star } from 'lucide-react';
|
||||||
|
import { Button } from '../components/Button';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { supabase } from '../lib/supabaseClient';
|
||||||
|
import { ProductPackage } from '../types';
|
||||||
|
import { defaultPlans } from '../lib/defaultPlans';
|
||||||
|
|
||||||
|
export const Products: React.FC = () => {
|
||||||
|
const [packages, setPackages] = useState<ProductPackage[]>(defaultPlans);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchPlans = async () => {
|
||||||
|
try {
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('plans')
|
||||||
|
.select('*')
|
||||||
|
.order('price', { ascending: true });
|
||||||
|
|
||||||
|
if (!error && data && data.length > 0) {
|
||||||
|
setPackages(data);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error fetching plans:", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchPlans();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pt-20 bg-gray-50 min-h-screen">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
||||||
|
<div className="text-center mb-16">
|
||||||
|
<h1 className="text-4xl font-extrabold text-gray-900 mb-4">Csomagajánlataink</h1>
|
||||||
|
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
|
||||||
|
Átlátható árazás, rejtett költségek nélkül. Válassza az Ön céljaihoz leginkább illeszkedő csomagot.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
|
{packages.map((pkg, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`relative bg-white rounded-2xl shadow-xl flex flex-col p-8 transition-transform hover:-translate-y-2 duration-300 ${pkg.isPopular ? 'border-2 border-primary ring-4 ring-purple-100' : 'border border-gray-100'}`}
|
||||||
|
>
|
||||||
|
{pkg.isPopular && (
|
||||||
|
<div className="absolute top-0 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-gradient-to-r from-primary to-secondary text-white px-4 py-1 rounded-full text-sm font-bold shadow-lg flex items-center gap-1">
|
||||||
|
<Star className="w-4 h-4 fill-current" /> Legnépszerűbb
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="mb-6">
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900">{pkg.name}</h3>
|
||||||
|
<p className="text-gray-500 mt-2 text-sm min-h-[40px]">{pkg.desc}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-8">
|
||||||
|
<span className="text-3xl font-extrabold text-gray-900">{pkg.price}</span>
|
||||||
|
{pkg.price.includes('Ft') && <span className="text-gray-500 text-sm font-normal"> + ÁFA</span>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul className="space-y-4 mb-8 flex-grow">
|
||||||
|
{pkg.features.map((feature, i) => (
|
||||||
|
<li key={i} className="flex items-start text-gray-600">
|
||||||
|
<Check className="w-5 h-5 text-green-500 mr-3 flex-shrink-0 mt-0.5" />
|
||||||
|
<span className="text-sm">{feature}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div className="mt-auto">
|
||||||
|
<Link to="/contact">
|
||||||
|
<Button
|
||||||
|
variant={pkg.isPopular ? 'primary' : 'outline'}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
{pkg.cta}
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-20 bg-white rounded-2xl p-8 md:p-12 shadow-sm border border-gray-100 flex flex-col md:flex-row items-center justify-between gap-8">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900 mb-2">Egyedi igényei vannak?</h3>
|
||||||
|
<p className="text-gray-600">
|
||||||
|
Nem találja a megfelelő csomagot? Készítünk Önnek egy teljesen személyre szabott ajánlatot.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Link to="/contact">
|
||||||
|
<Button size="lg">Kapcsolatfelvétel</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
102
pages/References.tsx
Normal file
102
pages/References.tsx
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ExternalLink } from 'lucide-react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
export const References: React.FC = () => {
|
||||||
|
// Demo projects that now have real pages
|
||||||
|
const projects = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: "SweetCraving Desszertműhely",
|
||||||
|
category: "Landing Page",
|
||||||
|
image: "https://images.unsplash.com/photo-1563729784474-d77dbb933a9e?ixlib=rb-4.0.3&auto=format&fit=crop&w=1073&q=80",
|
||||||
|
description: "Fiatalos, kézműves desszertműhely bemutatkozó oldala.",
|
||||||
|
link: "/demos/sweetcraving"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: "BlueWave Solar Kft.",
|
||||||
|
category: "Pro Web + Aloldalak",
|
||||||
|
image: "https://images.unsplash.com/photo-1509391366360-2e959784a276?ixlib=rb-4.0.3&auto=format&fit=crop&w=1074&q=80",
|
||||||
|
description: "Megújuló energiaforrások vállalati weboldala kalkulátorral.",
|
||||||
|
link: "/demos/bluewave"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
title: "Steelguard Biztonságtechnika",
|
||||||
|
category: "Enterprise Rendszer",
|
||||||
|
image: "https://images.unsplash.com/photo-1518770660439-4636190af475?ixlib=rb-4.0.3&auto=format&fit=crop&w=1170&q=80",
|
||||||
|
description: "High-tech biztonságtechnikai portál és ügyfélkapu.",
|
||||||
|
link: "/demos/steelguard"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
title: "EcoHome Ingatlan",
|
||||||
|
category: "Ingatlan weboldal",
|
||||||
|
image: "https://images.unsplash.com/photo-1560518883-ce09059eeffa?ixlib=rb-4.0.3&auto=format&fit=crop&w=1073&q=80",
|
||||||
|
description: "Modern ingatlanértékesítő platform kereső funkcióval.",
|
||||||
|
link: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
title: "Glamour Beauty",
|
||||||
|
category: "Webshop",
|
||||||
|
image: "https://images.unsplash.com/photo-1483985988355-763728e1935b?ixlib=rb-4.0.3&auto=format&fit=crop&w=1170&q=80",
|
||||||
|
description: "Kozmetikai webáruház bankkártyás fizetéssel.",
|
||||||
|
link: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
title: "Law Firm",
|
||||||
|
category: "Bemutatkozó oldal",
|
||||||
|
image: "https://images.unsplash.com/photo-1589829085413-56de8ae18c73?ixlib=rb-4.0.3&auto=format&fit=crop&w=1212&q=80",
|
||||||
|
description: "Ügyvédi iroda letisztult, bizalomépítő oldala.",
|
||||||
|
link: null
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pt-20 bg-white min-h-screen">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
||||||
|
<div className="text-center mb-16">
|
||||||
|
<h1 className="text-4xl font-extrabold text-gray-900 mb-4">Referenciáink</h1>
|
||||||
|
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
|
||||||
|
Büszkék vagyunk rá, hogy ügyfeleink álmait digitális valósággá formálhatjuk. Nézze meg korábbi munkáinkat!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
|
{projects.map((project) => (
|
||||||
|
<div key={project.id} className="group relative overflow-hidden rounded-2xl shadow-lg cursor-pointer bg-gray-900">
|
||||||
|
<img
|
||||||
|
src={project.image}
|
||||||
|
alt={project.title}
|
||||||
|
className="w-full h-72 object-cover transform group-hover:scale-110 transition-transform duration-500 opacity-90 group-hover:opacity-60"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="absolute inset-0 flex flex-col justify-end p-6 bg-gradient-to-t from-gray-900 via-gray-900/40 to-transparent">
|
||||||
|
<span className="text-primary font-medium text-sm mb-1">{project.category}</span>
|
||||||
|
<h3 className="text-white text-2xl font-bold mb-2">{project.title}</h3>
|
||||||
|
<p className="text-gray-300 text-sm mb-4 line-clamp-2">{project.description}</p>
|
||||||
|
|
||||||
|
{project.link ? (
|
||||||
|
<Link to={project.link} className="flex items-center text-white font-semibold hover:text-primary transition-colors">
|
||||||
|
Megtekintés <ExternalLink className="ml-2 w-4 h-4" />
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<span className="text-gray-500 text-sm italic">Hamarosan elérhető</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-12 p-4 bg-purple-50 rounded-lg text-center text-gray-600 border border-purple-100">
|
||||||
|
<p className="italic">
|
||||||
|
"A képek illusztrációk. A valódi referenciamunkákat az ügyfél kérésére e-mailben küldött portfólió tartalmazza."
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
1
pages/SUPABASE_SETUP.sql
Normal file
1
pages/SUPABASE_SETUP.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
鏸n槨bア$掛ィコエョ・ヲレア苣,Gbカ<EFBFBD>
|
||||||
96
pages/Services.tsx
Normal file
96
pages/Services.tsx
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Monitor, Smartphone, Search, PenTool, Database, Server } from 'lucide-react';
|
||||||
|
import { Button } from '../components/Button';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
export const Services: React.FC = () => {
|
||||||
|
const services = [
|
||||||
|
{
|
||||||
|
icon: Monitor,
|
||||||
|
title: "Egyedi Webfejlesztés",
|
||||||
|
desc: "Nem sablonmegoldásokat kínálunk. Minden weboldalt a nulláról, az Ön igényeire szabva fejlesztünk React és modern technológiák használatával. Gyors, biztonságos és skálázható."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Smartphone,
|
||||||
|
title: "Reszponzív Design",
|
||||||
|
desc: "Mobile-first szemléletünk garantálja, hogy oldala minden eszközön – telefonon, tableten és asztali gépen – tökéletesen jelenjen meg és működjön."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Search,
|
||||||
|
title: "SEO Optimalizálás",
|
||||||
|
desc: "A Google találati lista élére segítjük. Technikai SEO, kulcsszókutatás és tartalomoptimalizálás, hogy a potenciális ügyfelek Önre találjanak."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: PenTool,
|
||||||
|
title: "UI/UX Design",
|
||||||
|
desc: "Felhasználóbarát felületek tervezése. A látogatói élmény (UX) maximalizálása növeli a konverziót és a vásárlási hajlandóságot."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Database,
|
||||||
|
title: "Webshop Rendszerek",
|
||||||
|
desc: "Teljeskörű e-kereskedelmi megoldások. Termékkezelés, raktárkészlet, automatikus számlázás és bankkártyás fizetés integrációja."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Server,
|
||||||
|
title: "Hoszting & Domain",
|
||||||
|
desc: "Segítünk a megfelelő domain név kiválasztásában és a gyors, megbízható tárhely beállításában. Teljeskörű üzemeltetést is vállalunk."
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pt-20">
|
||||||
|
<div className="bg-gray-50 py-16">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||||
|
<h1 className="text-4xl font-extrabold text-gray-900 mb-4">Szolgáltatásaink</h1>
|
||||||
|
<p className="text-xl text-gray-600 max-w-3xl mx-auto">
|
||||||
|
A Motion Web Stúdió a digitális megoldások széles spektrumát kínálja, hogy vállalkozása sikeres legyen az online térben.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-20">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10">
|
||||||
|
{services.map((service, index) => (
|
||||||
|
<div key={index} className="flex flex-col h-full bg-white p-8 rounded-2xl shadow-sm hover:shadow-xl transition-all duration-300 border border-gray-100">
|
||||||
|
<div className="w-14 h-14 bg-purple-100 rounded-xl flex items-center justify-center mb-6">
|
||||||
|
<service.icon className="w-7 h-7 text-primary" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold text-gray-900 mb-4">{service.title}</h3>
|
||||||
|
<p className="text-gray-600 leading-relaxed flex-grow">
|
||||||
|
{service.desc}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Process Section */}
|
||||||
|
<section className="bg-gray-900 py-20 text-white">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<h2 className="text-3xl font-bold text-center mb-16">A Munkafolyamatunk</h2>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
|
||||||
|
{[
|
||||||
|
{ step: "01", title: "Konzultáció", desc: "Felmérjük igényeit és céljait." },
|
||||||
|
{ step: "02", title: "Tervezés", desc: "Drótvázak és design tervek készítése." },
|
||||||
|
{ step: "03", title: "Fejlesztés", desc: "Programozás és tartalomfeltöltés." },
|
||||||
|
{ step: "04", title: "Átadás", desc: "Tesztelés, élesítés és oktatás." }
|
||||||
|
].map((item, i) => (
|
||||||
|
<div key={i} className="relative">
|
||||||
|
<div className="text-6xl font-black text-gray-800 absolute -top-8 -left-4 z-0 opacity-50">{item.step}</div>
|
||||||
|
<div className="relative z-10">
|
||||||
|
<h3 className="text-xl font-bold text-primary mb-2">{item.title}</h3>
|
||||||
|
<p className="text-gray-400">{item.desc}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="mt-16 text-center">
|
||||||
|
<Link to="/contact">
|
||||||
|
<Button size="lg">Kezdjük el a közös munkát</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
320
pages/Terms.tsx
Normal file
320
pages/Terms.tsx
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
// Add XCircle to the import list
|
||||||
|
import { ArrowLeft, FileText, Globe, Mail, Shield, XCircle } from 'lucide-react';
|
||||||
|
import { Button } from '../components/Button';
|
||||||
|
|
||||||
|
export const Terms: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pt-24 pb-20 min-h-screen bg-gray-50/50">
|
||||||
|
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<Button
|
||||||
|
variant="white"
|
||||||
|
onClick={() => navigate(-1)}
|
||||||
|
className="mb-10 shadow-sm border border-gray-200 group"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="w-4 h-4 mr-2 group-hover:-translate-x-1 transition-transform" /> Vissza
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-[32px] shadow-sm border border-gray-100 overflow-hidden">
|
||||||
|
{/* Header Section */}
|
||||||
|
<div className="bg-gradient-to-br from-gray-900 to-gray-800 p-8 md:p-12 text-white">
|
||||||
|
<div className="flex flex-col md:flex-row md:items-center gap-6">
|
||||||
|
<div className="p-4 bg-white/10 backdrop-blur-md rounded-2xl border border-white/20 w-fit">
|
||||||
|
<FileText className="w-10 h-10 text-purple-300" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl md:text-4xl font-extrabold tracking-tight uppercase">Általános Szerződési Feltételek</h1>
|
||||||
|
<div className="flex flex-wrap items-center gap-4 mt-3">
|
||||||
|
<span className="px-3 py-1 bg-primary/20 border border-primary/30 rounded-full text-xs font-bold uppercase tracking-widest text-purple-200">
|
||||||
|
MotionWeb
|
||||||
|
</span>
|
||||||
|
<span className="text-gray-400 text-sm">
|
||||||
|
Hatályos: 2025. január 1-től
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content Sections */}
|
||||||
|
<div className="p-8 md:p-16">
|
||||||
|
<div className="space-y-16 text-gray-600 leading-relaxed">
|
||||||
|
|
||||||
|
{/* Section 1 */}
|
||||||
|
<section className="relative">
|
||||||
|
<div className="absolute -left-6 top-0 bottom-0 w-1 bg-primary/10 rounded-full hidden md:block" />
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-6 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10 text-primary text-sm">1</span>
|
||||||
|
A szolgáltató adatai
|
||||||
|
</h3>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 bg-gray-50 p-6 rounded-2xl border border-gray-100">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest mb-1">Szolgáltató neve</p>
|
||||||
|
<p className="font-semibold text-gray-900">Balogh Bence Benedek egyéni vállalkozó</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest mb-1">Projekt / márkanév</p>
|
||||||
|
<p className="font-semibold text-gray-900">MotionWeb</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<Globe className="w-4 h-4 text-primary mt-1 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest mb-1">Weboldal</p>
|
||||||
|
<a href="https://motionweb.hu" className="font-semibold text-primary hover:underline">https://motionweb.hu</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<Mail className="w-4 h-4 text-primary mt-1 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest mb-1">Kapcsolattartási e-mail</p>
|
||||||
|
<a href="mailto:motionstudiohq@gmail.com" className="font-semibold text-primary hover:underline">motionstudiohq@gmail.com</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="md:col-span-2 pt-4 border-t border-gray-200">
|
||||||
|
<p className="text-sm italic text-gray-500">
|
||||||
|
A MotionWeb elnevezés projektnév, nem önálló jogi személy. A szolgáltatásokat Balogh Bence Benedek egyéni vállalkozó nyújtja.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 2 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10 text-primary text-sm">2</span>
|
||||||
|
Az ÁSZF hatálya
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p>Jelen Általános Szerződési Feltételek (ÁSZF) a MotionWeb weboldalon keresztül megrendelt valamennyi szolgáltatásra vonatkoznak.</p>
|
||||||
|
<p className="bg-purple-50 p-4 rounded-xl border border-purple-100 text-gray-700">
|
||||||
|
A megrendelő a rendelés leadásával és az előleg megfizetésével jelen ÁSZF rendelkezéseit elfogadja, és a felek között szerződés jön létre.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 3 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10 text-primary text-sm">3</span>
|
||||||
|
A szolgáltatás jellege
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p>A MotionWeb fix csomagárakon kínál weboldalkészítési szolgáltatásokat.</p>
|
||||||
|
<p>A szolgáltatás digitális szolgáltatásnak minősül, amely egy bemutató (demó) weboldal elkészítésével kezdődik, majd a megrendelő jóváhagyása esetén a végleges weboldal fejlesztésével folytatódik.</p>
|
||||||
|
<p className="font-bold text-gray-800">A MotionWeb nem nyújt egyedi ajánlattételt.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 4 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10 text-primary text-sm">4</span>
|
||||||
|
Szerződés létrejötte
|
||||||
|
</h3>
|
||||||
|
<p className="mb-4">A szerződés az alábbi lépések teljesülésével jön létre:</p>
|
||||||
|
<ul className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
{['a megrendelő kiválasztja a szolgáltatási csomagot,', 'kitölti a rendelési űrlapot,', 'megfizeti az előleget.'].map((step, i) => (
|
||||||
|
<li key={i} className="bg-gray-50 p-4 rounded-xl border border-gray-100 text-sm font-medium flex items-center gap-3">
|
||||||
|
<span className="w-5 h-5 rounded-full bg-white border border-gray-200 flex items-center justify-center text-[10px] text-gray-400">{i+1}</span>
|
||||||
|
{step}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<p className="mt-4 text-primary font-semibold">A szerződés létrejöttével a MotionWeb megkezdi a demó weboldal elkészítését.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 5 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10 text-primary text-sm">5</span>
|
||||||
|
Előleg
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p>Az előleg a szolgáltatás megkezdésének feltétele.</p>
|
||||||
|
<p className="font-bold text-red-600 underline decoration-red-200 underline-offset-4">Az előleg nem visszatérítendő, mivel az a demó weboldal elkészítéséhez kapcsolódó munkavégzést és ráfordítást fedezi.</p>
|
||||||
|
<p>Amennyiben a megrendelő a demó weboldalt nem kívánja jóváhagyni, a fennmaradó díjat nem köteles megfizetni, azonban az előleg visszatérítésére ilyen esetben sem jogosult.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 6 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10 text-primary text-sm">6</span>
|
||||||
|
Demó weboldal és visszajelzés
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p>A MotionWeb a demó weboldal elkészültéről a megrendelőt e-mailben értesíti.</p>
|
||||||
|
<p>A megrendelő a demó weboldalt a motionweb.hu oldalon, bejelentkezést követően, a „Rendeléseim” menüpont alatt tekintheti meg és adhat visszajelzést.</p>
|
||||||
|
<p className="font-bold text-gray-800">A demó weboldal visszajelzésére összesen 1 hónap áll rendelkezésre.</p>
|
||||||
|
<p>A MotionWeb a visszajelzés hiánya esetén:</p>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{['az elkészülést követően,', '1 hét elteltével,', '2 hét elteltével'].map((time, i) => (
|
||||||
|
<span key={i} className="px-3 py-1 bg-gray-100 rounded-full text-xs font-medium text-gray-600">{time}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<p>emlékeztető e-mailt küld.</p>
|
||||||
|
<p className="bg-gray-900 text-gray-300 p-6 rounded-2xl text-sm italic">
|
||||||
|
Amennyiben a visszajelzési határidő eredménytelenül telik el, a MotionWeb a projektet kommunikáció hiányában lezártnak tekinti, és a projektfájlokat e-mailben megküldi.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 7 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10 text-primary text-sm">7</span>
|
||||||
|
Módosítási körök
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p>A csomagár legfeljebb <span className="font-bold text-gray-900">két (2) módosítási kört</span> tartalmaz.</p>
|
||||||
|
<p>Amennyiben a két módosítási kör után a demó weboldal nem kerül jóváhagyásra, a MotionWeb jogosult a projektet lezárni.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 8 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10 text-primary text-sm">8</span>
|
||||||
|
Jóváhagyás és végfizetés
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p>A demó weboldal jóváhagyását követően a megrendelő köteles megfizetni a fennmaradó díjat.</p>
|
||||||
|
<p className="font-semibold text-gray-800">A végfizetés beérkezését követően a MotionWeb megkezdi a weboldal végleges, funkciókkal ellátott verziójának fejlesztését és élesítését.</p>
|
||||||
|
<p>A végfizetés elmaradása esetén a végleges weboldal fejlesztése és élesítése nem történik meg.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 9 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10 text-primary text-sm">9</span>
|
||||||
|
Határidők
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p>A MotionWeb nem vállal konkrét teljesítési határidőt.</p>
|
||||||
|
<p>A weboldalon vagy kommunikáció során feltüntetett időtartamok kizárólag tájékoztató jellegű becslések.</p>
|
||||||
|
<p className="text-sm border-l-4 border-amber-400 pl-4 py-1 text-gray-500">
|
||||||
|
A megrendelő tudomásul veszi, hogy határidő túllépésből eredő igényt nem érvényesíthet.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 10 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10 text-primary text-sm">10</span>
|
||||||
|
Admin felület és utólagos módosítások
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p>Az admin felület kizárólag azokat a funkciókat tartalmazza, amelyek a megrendelés során előzetesen egyeztetésre kerültek.</p>
|
||||||
|
<p>A megrendelő kizárólag az admin felületen elérhető funkciók körében jogosult önálló módosításokra.</p>
|
||||||
|
<p>Egyszerű, technikailag nem összetett kérések (például szövegcsere) esetén a megrendelő e-mailben jelezheti igényét, amelynek teljesítésében a MotionWeb eseti alapon segítséget nyújthat.</p>
|
||||||
|
<p className="font-bold text-gray-800 border-t border-gray-100 pt-4">Dizájnmódosítás, új funkció, új aloldal vagy admin bővítés külön megrendelésnek minősül.</p>
|
||||||
|
<p>A külön megrendelések egyedi árazás alapján, előzetes befizetést követően valósulnak meg.</p>
|
||||||
|
<p className="italic">A MotionWeb nem nyújt folyamatos technikai támogatási vagy karbantartási szolgáltatást.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 11 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10 text-primary text-sm">11</span>
|
||||||
|
Tulajdonjog és referenciahasználat
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p>A weboldal forráskódja és tartalma a teljes díj megfizetéséig a MotionWeb tulajdonában marad.</p>
|
||||||
|
<p>A végfizetést követően a megrendelő jogosult a weboldalt használni.</p>
|
||||||
|
<p>A MotionWeb jogosult az elkészült weboldalt referenciaként, anonim módon, a megrendelő nevének feltüntetése nélkül bemutatni.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 12 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10 text-primary text-sm">12</span>
|
||||||
|
Felelősségkorlátozás
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p>A MotionWeb nem vállal felelősséget:</p>
|
||||||
|
<ul className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
{['elmaradt haszonért,', 'közvetett károkért,', 'üzleti veszteségekért.'].map((item, i) => (
|
||||||
|
<li key={i} className="bg-red-50/50 p-4 rounded-xl border border-red-100 text-sm font-semibold text-red-700 flex items-center gap-2">
|
||||||
|
<XCircle className="w-4 h-4" /> {item}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<p className="font-bold text-gray-800">A szolgáltatás nem garantál üzleti eredményt.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 13 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10 text-primary text-sm">13</span>
|
||||||
|
Elállási jog kizárása
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p>A szolgáltatás digitális szolgáltatásnek minősül.</p>
|
||||||
|
<p className="font-bold text-gray-800">A megrendelő a rendelés leadásával és az előleg megfizetésével kifejezetten tudomásul veszi, hogy a teljesítés megkezdésével elállási joga megszűnik.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 14 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10 text-primary text-sm">14</span>
|
||||||
|
Ügyfélkör
|
||||||
|
</h3>
|
||||||
|
<p>A MotionWeb szolgáltatásait magánszemélyek és vállalkozások egyaránt igénybe vehetik.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 15 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10 text-primary text-sm">15</span>
|
||||||
|
Jogvita és irányadó jog
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p>Jelen ÁSZF-re a magyar jog az irányadó.</p>
|
||||||
|
<p>Jogvita esetén a szolgáltató székhelye szerinti bíróság rendelkezik illetékességgel.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Section 16 */}
|
||||||
|
<section>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-4 flex items-center gap-3">
|
||||||
|
<span className="flex items-center justify-center w-8 h-8 rounded-lg bg-primary/10 text-primary text-sm">16</span>
|
||||||
|
Záró rendelkezések
|
||||||
|
</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p>A MotionWeb fenntartja a jogot jelen ÁSZF módosítására.</p>
|
||||||
|
<p className="text-primary font-bold">A módosítás a weboldalon történő közzététellel lép hatályba.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer Info */}
|
||||||
|
<div className="bg-gray-50 p-8 border-t border-gray-100 text-center">
|
||||||
|
<div className="flex items-center justify-center gap-2 text-gray-400 text-sm font-medium mb-4">
|
||||||
|
<Shield className="w-4 h-4" />
|
||||||
|
<span>Biztonságos Adatkezelés & Jogi Megfelelőség</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-400 text-xs italic">
|
||||||
|
MotionWeb © {new Date().getFullYear()} - Minden jog fenntartva.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
112
pages/auth/ForgotPassword.tsx
Normal file
112
pages/auth/ForgotPassword.tsx
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { supabase, isSupabaseConfigured } from '../../lib/supabaseClient';
|
||||||
|
import { Button } from '../../components/Button';
|
||||||
|
import { Mail, ArrowLeft, AlertCircle, CheckCircle, RefreshCw, Send } from 'lucide-react';
|
||||||
|
|
||||||
|
export const ForgotPassword: React.FC = () => {
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [success, setSuccess] = useState(false);
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setSuccess(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!isSupabaseConfigured) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
setSuccess(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const redirectUrl = `${window.location.origin}/#/auth/reset-password`;
|
||||||
|
|
||||||
|
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
||||||
|
redirectTo: redirectUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
setError(error.message);
|
||||||
|
} else {
|
||||||
|
setSuccess(true);
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
setError('Váratlan hiba történt.');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen pt-20 pb-12 bg-gray-50 flex flex-col items-center justify-center px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="w-full max-w-md">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<h2 className="text-3xl font-extrabold text-gray-900 tracking-tight">Jelszó visszaállítása</h2>
|
||||||
|
<p className="mt-2 text-sm text-gray-600 font-medium">Küldünk egy linket a jelszava megváltoztatásához.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white py-10 px-6 md:px-10 shadow-xl rounded-[32px] border border-gray-100">
|
||||||
|
{success ? (
|
||||||
|
<div className="text-center animate-fade-in">
|
||||||
|
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||||
|
<CheckCircle className="w-8 h-8 text-green-600" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-2">E-mail elküldve!</h3>
|
||||||
|
<p className="text-gray-600 mb-8 text-sm leading-relaxed">
|
||||||
|
Ellenőrizze a(z) <strong>{email}</strong> postaládáját a visszaállításhoz szükséges linkért.
|
||||||
|
</p>
|
||||||
|
<Link to="/auth/login" className="block">
|
||||||
|
<Button fullWidth>Vissza a bejelentkezéshez</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<form className="space-y-6" onSubmit={handleSubmit}>
|
||||||
|
{error && (
|
||||||
|
<div className="rounded-xl bg-red-50 p-4 border border-red-100 flex items-start animate-fade-in">
|
||||||
|
<AlertCircle className="h-5 w-5 text-red-400 shrink-0 mt-0.5" />
|
||||||
|
<p className="ml-3 text-sm font-medium text-red-800">{error}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label htmlFor="email" className="block text-xs font-black text-gray-400 uppercase tracking-widest mb-2">E-mail cím</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Mail className="absolute left-3 top-3.5 w-5 h-5 text-gray-300" />
|
||||||
|
<input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
required
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
className="appearance-none block w-full pl-10 pr-4 py-3 border border-gray-200 rounded-xl shadow-sm focus:outline-none focus:ring-4 focus:ring-primary/10 focus:border-primary sm:text-sm bg-white text-gray-900 font-medium"
|
||||||
|
placeholder="pelda@email.hu"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Button type="submit" fullWidth disabled={loading} size="lg" className="shadow-lg shadow-primary/20">
|
||||||
|
{loading ? (
|
||||||
|
<RefreshCw className="w-5 h-5 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<>Link küldése <Send className="ml-2 w-4 h-4" /></>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-center pt-2">
|
||||||
|
<Link to="/auth/login" className="text-sm font-bold text-gray-400 hover:text-primary transition-colors flex items-center justify-center group">
|
||||||
|
<ArrowLeft className="w-4 h-4 mr-2 group-hover:-translate-x-1 transition-transform" /> Vissza a bejelentkezéshez
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
171
pages/auth/Login.tsx
Normal file
171
pages/auth/Login.tsx
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
|
import { supabase, isSupabaseConfigured } from '../../lib/supabaseClient';
|
||||||
|
import { Button } from '../../components/Button';
|
||||||
|
import { useAuth } from '../../context/AuthContext';
|
||||||
|
import { LogIn, AlertCircle, ArrowLeft, RefreshCw, Mail, ShieldCheck } from 'lucide-react';
|
||||||
|
|
||||||
|
export const Login: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { refreshDemoUser } = useAuth();
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [needsConfirmation, setNeedsConfirmation] = useState(false);
|
||||||
|
const [resendLoading, setResendLoading] = useState(false);
|
||||||
|
const [resendSuccess, setResendSuccess] = useState(false);
|
||||||
|
|
||||||
|
const handleLogin = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setNeedsConfirmation(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!isSupabaseConfigured) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
const demoUser = {
|
||||||
|
user: {
|
||||||
|
id: 'demo-user-123',
|
||||||
|
email: email,
|
||||||
|
user_metadata: { date_of_birth: '1990-01-01' }
|
||||||
|
},
|
||||||
|
access_token: 'demo-token',
|
||||||
|
};
|
||||||
|
localStorage.setItem('demo_user_session', JSON.stringify(demoUser));
|
||||||
|
refreshDemoUser();
|
||||||
|
navigate('/dashboard');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { error } = await supabase.auth.signInWithPassword({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('Login error:', error);
|
||||||
|
if (error.message.includes('Invalid login credentials')) {
|
||||||
|
setError('Helytelen e-mail cím vagy jelszó. Kérjük, győződjön meg róla, hogy megerősítette az e-mail címét!');
|
||||||
|
setNeedsConfirmation(true);
|
||||||
|
} else if (error.message.includes('Email not confirmed')) {
|
||||||
|
setError('Az e-mail cím még nincs megerősítve.');
|
||||||
|
setNeedsConfirmation(true);
|
||||||
|
} else {
|
||||||
|
setError('Hiba történt a bejelentkezés során: ' + error.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
navigate('/dashboard');
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
setError('Váratlan hiba történt.');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResendConfirmation = async () => {
|
||||||
|
if (!email) {
|
||||||
|
setError('Kérjük, először adja meg az e-mail címét!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setResendLoading(true);
|
||||||
|
setResendSuccess(false);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const redirectUrl = `${window.location.origin}/#/auth/login`;
|
||||||
|
const { error } = await supabase.auth.resend({
|
||||||
|
type: 'signup',
|
||||||
|
email: email,
|
||||||
|
options: { emailRedirectTo: redirectUrl }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
setError('Hiba az újraküldéskor: ' + error.message);
|
||||||
|
} else {
|
||||||
|
setResendSuccess(true);
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
setError('Hiba történt.');
|
||||||
|
} finally {
|
||||||
|
setResendLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen pt-20 pb-12 bg-gray-50 flex flex-col items-center justify-center px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="w-full max-w-md">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<h2 className="text-3xl font-extrabold text-gray-900">Bejelentkezés</h2>
|
||||||
|
<p className="mt-2 text-sm text-gray-600 font-medium">Jelentkezzen be fiókjába a folytatáshoz.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white py-8 px-4 shadow-xl rounded-[24px] sm:px-10 border border-gray-100">
|
||||||
|
<form className="space-y-6" onSubmit={handleLogin}>
|
||||||
|
{error && (
|
||||||
|
<div className="rounded-lg bg-red-50 p-4 border border-red-100 flex flex-col items-start">
|
||||||
|
<div className="flex items-start">
|
||||||
|
<div className="flex-shrink-0"><AlertCircle className="h-5 w-5 text-red-400" /></div>
|
||||||
|
<div className="ml-3"><h3 className="text-sm font-medium text-red-800">{error}</h3></div>
|
||||||
|
</div>
|
||||||
|
{needsConfirmation && (
|
||||||
|
<div className="mt-4 w-full p-4 bg-white/80 rounded-xl border border-red-200">
|
||||||
|
<button type="button" onClick={handleResendConfirmation} disabled={resendLoading} className="text-xs font-bold text-primary hover:text-primary-dark underline flex items-center">
|
||||||
|
{resendLoading ? <RefreshCw className="w-3 h-3 animate-spin mr-1" /> : <Mail className="w-3 h-3 mr-1" />}
|
||||||
|
Megerősítő e-mail újraküldése
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{resendSuccess && (
|
||||||
|
<div className="rounded-lg bg-green-50 p-4 border border-green-100 flex items-start animate-fade-in">
|
||||||
|
<div className="flex-shrink-0"><Mail className="h-5 w-5 text-green-400" /></div>
|
||||||
|
<div className="ml-3"><p className="text-sm font-medium text-green-800">A megerősítő e-mailt újraküldtük!</p></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label htmlFor="email" className="block text-sm font-medium text-gray-700">E-mail cím</label>
|
||||||
|
<div className="mt-1">
|
||||||
|
<input id="email" type="email" required value={email} onChange={(e) => setEmail(e.target.value)} className="appearance-none block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm bg-white text-gray-900" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between items-center mb-1">
|
||||||
|
<label htmlFor="password" className="block text-sm font-medium text-gray-700">Jelszó</label>
|
||||||
|
<Link to="/auth/forgot-password" size="sm" className="text-xs font-bold text-primary hover:underline">Elfelejtette?</Link>
|
||||||
|
</div>
|
||||||
|
<div className="mt-1">
|
||||||
|
<input id="password" type="password" required value={password} onChange={(e) => setPassword(e.target.value)} className="appearance-none block w-full px-4 py-3 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-primary focus:border-primary sm:text-sm bg-white text-gray-900" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Button type="submit" fullWidth disabled={loading} className="flex justify-center items-center">
|
||||||
|
{loading ? <RefreshCw className="w-5 h-5 animate-spin" /> : <>Bejelentkezés <LogIn className="ml-2 w-4 h-4" /></>}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<div className="relative"><div className="absolute inset-0 flex items-center"><div className="w-full border-t border-gray-300" /></div><div className="relative flex justify-center text-sm"><span className="px-2 bg-white text-gray-500">Nincs még fiókod?</span></div></div>
|
||||||
|
<div className="mt-6 text-center">
|
||||||
|
<Link to="/auth/register" className="font-medium text-primary hover:text-secondary transition-colors underline-offset-4 hover:underline">Regisztrálj ingyenesen</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 text-center">
|
||||||
|
<Link to="/" className="text-sm text-gray-500 hover:text-gray-900 flex items-center justify-center group">
|
||||||
|
<ArrowLeft className="w-4 h-4 mr-1 group-hover:-translate-x-1 transition-transform" /> Vissza a főoldalra
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
249
pages/auth/Register.tsx
Normal file
249
pages/auth/Register.tsx
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
|
import { supabase, isSupabaseConfigured } from '../../lib/supabaseClient';
|
||||||
|
import { Button } from '../../components/Button';
|
||||||
|
import { useAuth } from '../../context/AuthContext';
|
||||||
|
import { UserPlus, AlertCircle, ArrowLeft, CheckCircle, Mail, Info, Send, ShieldCheck } from 'lucide-react';
|
||||||
|
|
||||||
|
export const Register: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { refreshDemoUser } = useAuth();
|
||||||
|
const [lastName, setLastName] = useState('');
|
||||||
|
const [firstName, setFirstName] = useState('');
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [confirmPassword, setConfirmPassword] = useState('');
|
||||||
|
const [dateOfBirth, setDateOfBirth] = useState('');
|
||||||
|
const [privacyAccepted, setPrivacyAccepted] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [success, setSuccess] = useState(false);
|
||||||
|
|
||||||
|
const handleRegister = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
// Alapvető validációk
|
||||||
|
if (!lastName || !firstName) {
|
||||||
|
setError('A név megadása kötelező.');
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password !== confirmPassword) {
|
||||||
|
setError('A jelszavak nem egyeznek.');
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.length < 6) {
|
||||||
|
setError('A jelszónak legalább 6 karakter hosszúnak kell lennie.');
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dateOfBirth) {
|
||||||
|
setError('A születési dátum megadása kötelező.');
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!privacyAccepted) {
|
||||||
|
setError('A regisztrációhoz el kell fogadnia az Adatkezelési tájékoztatót.');
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!isSupabaseConfigured) {
|
||||||
|
// Demo mód szimuláció
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
const demoUser = {
|
||||||
|
user: {
|
||||||
|
id: 'demo-user-123',
|
||||||
|
email: email,
|
||||||
|
user_metadata: {
|
||||||
|
first_name: firstName,
|
||||||
|
last_name: lastName,
|
||||||
|
date_of_birth: dateOfBirth
|
||||||
|
}
|
||||||
|
},
|
||||||
|
access_token: 'demo-token',
|
||||||
|
};
|
||||||
|
localStorage.setItem('demo_user_session', JSON.stringify(demoUser));
|
||||||
|
refreshDemoUser();
|
||||||
|
navigate('/dashboard');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashRouter esetén a redirect URL-nek pontosnak kell lennie.
|
||||||
|
// A window.location.origin a base URL (pl. http://localhost:5173)
|
||||||
|
const redirectUrl = `${window.location.origin}/#/auth/login`;
|
||||||
|
|
||||||
|
console.log('Attempting signup with redirect:', redirectUrl);
|
||||||
|
|
||||||
|
const { data, error: signUpError } = await supabase.auth.signUp({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
options: {
|
||||||
|
emailRedirectTo: redirectUrl,
|
||||||
|
data: {
|
||||||
|
first_name: firstName,
|
||||||
|
last_name: lastName,
|
||||||
|
date_of_birth: dateOfBirth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (signUpError) {
|
||||||
|
console.error('Detailed registration error:', signUpError);
|
||||||
|
|
||||||
|
// Specifikus hibaüzenetek kezelése
|
||||||
|
if (signUpError.message.includes('Error sending confirmation email')) {
|
||||||
|
setError('A rendszer nem tudta kiküldeni a megerősítő e-mailt a Resend szerverén keresztül. Kérjük, ellenőrizze, hogy a Supabase Dashboard-on a "Custom SMTP" beállításoknál a Sender Email megegyezik-e a Resend-ben hitelesített domainnel!');
|
||||||
|
} else if (signUpError.message.includes('User already registered') || signUpError.status === 422) {
|
||||||
|
setError('Ezzel az e-mail címmel már regisztráltak. Kérjük, próbáljon meg bejelentkezni.');
|
||||||
|
} else if (signUpError.status === 429) {
|
||||||
|
setError('Túl sok regisztrációs kísérlet. Kérjük, várjon néhány percet!');
|
||||||
|
} else {
|
||||||
|
setError(`Hiba történt a regisztráció során: ${signUpError.message}`);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.user) {
|
||||||
|
// Ha azonnal van session, akkor nincs e-mail megerősítés (ritka egyedi SMTP-nél)
|
||||||
|
if (data.session) {
|
||||||
|
navigate('/dashboard');
|
||||||
|
} else {
|
||||||
|
setSuccess(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
setError('Váratlan hiba történt a regisztráció során. Kérjük, próbálja meg később.');
|
||||||
|
console.error('Unexpected catch error:', err);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen pt-20 pb-12 bg-gray-50 flex flex-col items-center justify-center px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="w-full max-w-lg bg-white p-8 md:p-12 shadow-2xl rounded-[32px] border border-gray-100 text-center animate-fade-in-up">
|
||||||
|
<div className="w-20 h-20 bg-primary/10 rounded-3xl flex items-center justify-center mx-auto mb-8 text-primary shadow-inner">
|
||||||
|
<Mail className="w-10 h-10" />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-3xl font-black text-gray-900 mb-4 tracking-tight">E-mail kiküldve!</h2>
|
||||||
|
<p className="text-gray-600 mb-8 leading-relaxed">
|
||||||
|
Küldtünk egy megerősítő linket a(z) <strong className="text-gray-900">{email}</strong> e-mail címre a <strong>Resend</strong> szolgáltatón keresztül. A belépéshez kérjük, kattintson a levélben található linkre.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="bg-blue-50/50 p-6 rounded-2xl text-left mb-8 border border-blue-100 flex gap-4">
|
||||||
|
<div className="mt-1"><Info className="w-5 h-5 text-blue-500" /></div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-bold text-blue-900 mb-1">Nem találja a levelet?</p>
|
||||||
|
<p className="text-xs text-blue-800 leading-relaxed">
|
||||||
|
Nézze meg a <strong>Spam</strong> vagy a <strong>Promóciók</strong> mappát is. Ha 5 percen belül nem érkezik meg, próbálja meg újra a bejelentkezési oldalon az újraküldést.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Link to="/auth/login" className="block">
|
||||||
|
<Button fullWidth size="lg">Tovább a bejelentkezéshez</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen pt-20 pb-12 bg-gray-50 flex flex-col items-center justify-center px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="w-full max-w-md">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<h2 className="text-4xl font-black text-gray-900 tracking-tighter">Regisztráció</h2>
|
||||||
|
<p className="mt-2 text-sm text-gray-600 font-medium">
|
||||||
|
Csatlakozzon a MotionWeb közösségéhez.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white py-10 px-6 md:px-10 shadow-xl rounded-[32px] border border-gray-100">
|
||||||
|
<form className="space-y-6" onSubmit={handleRegister}>
|
||||||
|
{error && (
|
||||||
|
<div className="rounded-2xl bg-red-50 p-4 border border-red-100 flex items-start animate-fade-in shadow-sm">
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<AlertCircle className="h-5 w-5 text-red-400" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
<div className="ml-3 text-xs font-bold text-red-800 uppercase tracking-wide leading-relaxed">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label htmlFor="lastName" className="block text-xs font-black text-gray-400 uppercase tracking-widest mb-1">Vezetéknév</label>
|
||||||
|
<input id="lastName" type="text" required value={lastName} onChange={(e) => setLastName(e.target.value)} className="appearance-none block w-full px-4 py-3 border border-gray-200 rounded-xl shadow-sm focus:outline-none focus:ring-4 focus:ring-primary/10 focus:border-primary sm:text-sm bg-white text-gray-900 font-medium" placeholder="Kovács" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="firstName" className="block text-xs font-black text-gray-400 uppercase tracking-widest mb-1">Keresztnév</label>
|
||||||
|
<input id="firstName" type="text" required value={firstName} onChange={(e) => setFirstName(e.target.value)} className="appearance-none block w-full px-4 py-3 border border-gray-200 rounded-xl shadow-sm focus:outline-none focus:ring-4 focus:ring-primary/10 focus:border-primary sm:text-sm bg-white text-gray-900 font-medium" placeholder="János" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label htmlFor="email" className="block text-xs font-black text-gray-400 uppercase tracking-widest mb-1">E-mail cím</label>
|
||||||
|
<input id="email" type="email" required value={email} onChange={(e) => setEmail(e.target.value)} className="appearance-none block w-full px-4 py-3 border border-gray-200 rounded-xl shadow-sm focus:outline-none focus:ring-4 focus:ring-primary/10 focus:border-primary sm:text-sm bg-white text-gray-900 font-medium" placeholder="pelda@email.hu" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label htmlFor="dateOfBirth" className="block text-xs font-black text-gray-400 uppercase tracking-widest mb-1">Születési dátum</label>
|
||||||
|
<input id="dateOfBirth" type="date" required value={dateOfBirth} onChange={(e) => setDateOfBirth(e.target.value)} className="appearance-none block w-full px-4 py-3 border border-gray-200 rounded-xl shadow-sm focus:outline-none focus:ring-4 focus:ring-primary/10 focus:border-primary sm:text-sm bg-white text-gray-900 font-medium" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label htmlFor="password" className="block text-xs font-black text-gray-400 uppercase tracking-widest mb-1">Jelszó</label>
|
||||||
|
<input id="password" type="password" required value={password} onChange={(e) => setPassword(e.target.value)} className="appearance-none block w-full px-4 py-3 border border-gray-200 rounded-xl shadow-sm focus:outline-none focus:ring-4 focus:ring-primary/10 focus:border-primary sm:text-sm bg-white text-gray-900 font-medium" placeholder="••••••••" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label htmlFor="confirmPassword" className="block text-xs font-black text-gray-400 uppercase tracking-widest mb-1">Megerősítés</label>
|
||||||
|
<input id="confirmPassword" type="password" required value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} className="appearance-none block w-full px-4 py-3 border border-gray-200 rounded-xl shadow-sm focus:outline-none focus:ring-4 focus:ring-primary/10 focus:border-primary sm:text-sm bg-white text-gray-900 font-medium" placeholder="••••••••" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-start bg-gray-50 p-4 rounded-2xl border border-gray-100">
|
||||||
|
<input id="privacy" type="checkbox" required checked={privacyAccepted} onChange={(e) => setPrivacyAccepted(e.target.checked)} className="h-5 w-5 text-primary focus:ring-primary border-gray-300 rounded-lg cursor-pointer mt-0.5" />
|
||||||
|
<div className="ml-3 text-sm">
|
||||||
|
<label htmlFor="privacy" className="font-semibold text-gray-700 leading-tight">
|
||||||
|
Elfogadom az <Link to="/privacy" className="text-primary hover:underline">Adatkezelési tájékoztatót</Link>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-2">
|
||||||
|
<Button type="submit" fullWidth disabled={loading} size="lg" className="shadow-lg shadow-primary/20">
|
||||||
|
{loading ? 'Fiók létrehozása...' : <>Regisztráció <UserPlus className="ml-2 w-5 h-5" /></>}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="mt-8 text-center border-t border-gray-100 pt-6">
|
||||||
|
<span className="text-sm text-gray-500 font-medium">Már van fiókja? </span>
|
||||||
|
<Link to="/auth/login" className="text-sm font-bold text-primary hover:text-secondary transition-colors underline-offset-4 hover:underline">Jelentkezzen be</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8 text-center">
|
||||||
|
<Link to="/" className="text-sm font-bold text-gray-400 hover:text-gray-900 flex items-center justify-center transition-all group">
|
||||||
|
<ArrowLeft className="w-4 h-4 mr-2 group-hover:-translate-x-1 transition-transform" /> Vissza a főoldalra
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
128
pages/auth/ResetPassword.tsx
Normal file
128
pages/auth/ResetPassword.tsx
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { supabase, isSupabaseConfigured } from '../../lib/supabaseClient';
|
||||||
|
import { Button } from '../../components/Button';
|
||||||
|
import { Lock, AlertCircle, CheckCircle, RefreshCw, Save } from 'lucide-react';
|
||||||
|
|
||||||
|
export const ResetPassword: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [confirmPassword, setConfirmPassword] = useState('');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [success, setSuccess] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Supabase sets the session automatically when clicking the recovery link
|
||||||
|
const checkSession = async () => {
|
||||||
|
if (isSupabaseConfigured) {
|
||||||
|
const { data } = await supabase.auth.getSession();
|
||||||
|
if (!data.session) {
|
||||||
|
setError("A link lejárt vagy érvénytelen. Kérjük, igényeljen újat!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
checkSession();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
if (password.length < 6) {
|
||||||
|
setError('A jelszónak legalább 6 karakternek kell lennie.');
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password !== confirmPassword) {
|
||||||
|
setError('A jelszavak nem egyeznek.');
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!isSupabaseConfigured) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
setSuccess(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { error } = await supabase.auth.updateUser({
|
||||||
|
password: password
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
setError(error.message);
|
||||||
|
} else {
|
||||||
|
setSuccess(true);
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
setError('Váratlan hiba történt.');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen pt-20 pb-12 bg-gray-50 flex flex-col items-center justify-center px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="w-full max-w-md">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<h2 className="text-3xl font-extrabold text-gray-900 tracking-tight">Új jelszó megadása</h2>
|
||||||
|
<p className="mt-2 text-sm text-gray-600 font-medium">Kérjük, adja meg az új biztonságos jelszavát.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white py-10 px-6 md:px-10 shadow-xl rounded-[32px] border border-gray-100">
|
||||||
|
{success ? (
|
||||||
|
<div className="text-center animate-fade-in">
|
||||||
|
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||||
|
<CheckCircle className="w-8 h-8 text-green-600" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-2">Jelszó sikeresen módosítva!</h3>
|
||||||
|
<p className="text-gray-600 mb-8 text-sm leading-relaxed">Most már bejelentkezhet az új jelszavával.</p>
|
||||||
|
<Button fullWidth onClick={() => navigate('/auth/login')}>Bejelentkezés</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<form className="space-y-6" onSubmit={handleSubmit}>
|
||||||
|
{error && (
|
||||||
|
<div className="rounded-xl bg-red-50 p-4 border border-red-100 flex items-start animate-fade-in">
|
||||||
|
<AlertCircle className="h-5 w-5 text-red-400 shrink-0 mt-0.5" />
|
||||||
|
<p className="ml-3 text-sm font-medium text-red-800">{error}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-black text-gray-400 uppercase tracking-widest mb-2">Új jelszó</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Lock className="absolute left-3 top-3.5 w-5 h-5 text-gray-300" />
|
||||||
|
<input type="password" required value={password} onChange={(e) => setPassword(e.target.value)} className="appearance-none block w-full pl-10 pr-4 py-3 border border-gray-200 rounded-xl shadow-sm focus:outline-none focus:ring-4 focus:ring-primary/10 focus:border-primary sm:text-sm bg-white text-gray-900 font-medium" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-black text-gray-400 uppercase tracking-widest mb-2">Megerősítés</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Lock className="absolute left-3 top-3.5 w-5 h-5 text-gray-300" />
|
||||||
|
<input type="password" required value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} className="appearance-none block w-full pl-10 pr-4 py-3 border border-gray-200 rounded-xl shadow-sm focus:outline-none focus:ring-4 focus:ring-primary/10 focus:border-primary sm:text-sm bg-white text-gray-900 font-medium" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Button type="submit" fullWidth disabled={loading} size="lg" className="shadow-lg shadow-primary/20">
|
||||||
|
{loading ? (
|
||||||
|
<RefreshCw className="w-5 h-5 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<>Jelszó mentése <Save className="ml-2 w-4 h-4" /></>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
474
pages/demos/BlueWave.tsx
Normal file
474
pages/demos/BlueWave.tsx
Normal file
@@ -0,0 +1,474 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { ArrowLeft, Sun, Battery, Zap, ChevronRight, Menu, X, Phone, Mail, CheckCircle, Home, Info, Briefcase, Calculator, ArrowRight } from 'lucide-react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
export const BlueWave: React.FC = () => {
|
||||||
|
const [activePage, setActivePage] = useState<'home' | 'services' | 'about' | 'contact'>('home');
|
||||||
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||||
|
const [scrolled, setScrolled] = useState(false);
|
||||||
|
|
||||||
|
// Smooth scroll to top when "changing pages" in this SPA simulation
|
||||||
|
useEffect(() => {
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}, [activePage]);
|
||||||
|
|
||||||
|
// Navbar scroll effect
|
||||||
|
useEffect(() => {
|
||||||
|
const handleScroll = () => setScrolled(window.scrollY > 20);
|
||||||
|
window.addEventListener('scroll', handleScroll);
|
||||||
|
return () => window.removeEventListener('scroll', handleScroll);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{ id: 'home', label: 'Főoldal', icon: Home },
|
||||||
|
{ id: 'services', label: 'Megoldások', icon: Zap },
|
||||||
|
{ id: 'about', label: 'Cégünkről', icon: Info },
|
||||||
|
{ id: 'contact', label: 'Kapcsolat', icon: Phone },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="font-sans text-gray-800 bg-white min-h-screen flex flex-col selection:bg-[#0284c7] selection:text-white">
|
||||||
|
{/* Return to Main Site */}
|
||||||
|
<div className="fixed bottom-6 right-6 z-[60]">
|
||||||
|
<Link to="/#references">
|
||||||
|
<button className="bg-gray-900 text-white shadow-xl px-5 py-3 rounded-xl text-sm font-sans font-medium flex items-center hover:bg-gray-800 hover:-translate-y-1 transition-all duration-300">
|
||||||
|
<ArrowLeft className="w-4 h-4 mr-2" /> Vissza a referenciákhoz
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Top Bar */}
|
||||||
|
<div className="bg-[#0f172a] text-gray-300 py-2.5 px-4 text-xs font-medium hidden md:block border-b border-gray-800">
|
||||||
|
<div className="max-w-7xl mx-auto flex justify-between items-center">
|
||||||
|
<div className="flex space-x-8">
|
||||||
|
<span className="flex items-center hover:text-white transition-colors cursor-pointer"><Phone className="w-3.5 h-3.5 mr-2 text-[#38bdf8]" /> +36 30 999 8888</span>
|
||||||
|
<span className="flex items-center hover:text-white transition-colors cursor-pointer"><Mail className="w-3.5 h-3.5 mr-2 text-[#38bdf8]" /> info@bluewave-solar.demo</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex space-x-6">
|
||||||
|
<a href="#" className="hover:text-white transition-colors">Lakossági</a>
|
||||||
|
<a href="#" className="hover:text-white transition-colors">Ipari</a>
|
||||||
|
<a href="#" className="hover:text-white transition-colors text-[#38bdf8]">Partnerprogram</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Navbar */}
|
||||||
|
<nav className={`sticky top-0 z-50 transition-all duration-300 border-b ${scrolled ? 'bg-white/95 backdrop-blur-md shadow-md py-0 border-gray-200' : 'bg-white py-4 border-transparent'}`}>
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex justify-between h-20 items-center">
|
||||||
|
<div className="flex items-center cursor-pointer group" onClick={() => setActivePage('home')}>
|
||||||
|
<div className="bg-gradient-to-tr from-[#0284c7] to-[#0ea5e9] p-2.5 rounded-xl mr-3 shadow-lg shadow-sky-200 group-hover:scale-105 transition-transform duration-300">
|
||||||
|
<Sun className="w-7 h-7 text-white" />
|
||||||
|
</div>
|
||||||
|
<div className="leading-none">
|
||||||
|
<span className="block text-2xl font-bold text-gray-900 tracking-tight">BlueWave</span>
|
||||||
|
<span className="block text-xs font-bold text-[#0284c7] uppercase tracking-widest ml-0.5">Solar Systems</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="hidden md:flex items-center space-x-1">
|
||||||
|
{navItems.map((item) => (
|
||||||
|
<button
|
||||||
|
key={item.id}
|
||||||
|
onClick={() => setActivePage(item.id as any)}
|
||||||
|
className={`relative px-5 py-2.5 rounded-lg text-sm font-semibold transition-all duration-300 ${
|
||||||
|
activePage === item.id
|
||||||
|
? 'text-[#0284c7] bg-sky-50'
|
||||||
|
: 'text-gray-600 hover:text-[#0284c7] hover:bg-gray-50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
{activePage === item.id && (
|
||||||
|
<span className="absolute bottom-0 left-1/2 transform -translate-x-1/2 w-1/2 h-0.5 bg-[#0284c7] rounded-full"></span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
<div className="pl-4 ml-4 border-l border-gray-200">
|
||||||
|
<button
|
||||||
|
onClick={() => setActivePage('contact')}
|
||||||
|
className="bg-[#0284c7] hover:bg-[#0369a1] text-white px-6 py-3 rounded-lg font-bold text-sm transition-all shadow-lg shadow-sky-200 hover:-translate-y-0.5"
|
||||||
|
>
|
||||||
|
Ingyenes felmérés
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-6 md:hidden">
|
||||||
|
<button onClick={() => setMobileMenuOpen(!mobileMenuOpen)} className="p-2 text-gray-600 hover:bg-gray-100 rounded-lg">
|
||||||
|
{mobileMenuOpen ? <X /> : <Menu />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Menu */}
|
||||||
|
{mobileMenuOpen && (
|
||||||
|
<div className="md:hidden bg-white border-t border-gray-100 p-4 space-y-2 shadow-xl absolute w-full animate-fade-in">
|
||||||
|
{navItems.map((item) => (
|
||||||
|
<button
|
||||||
|
key={item.id}
|
||||||
|
onClick={() => { setActivePage(item.id as any); setMobileMenuOpen(false); }}
|
||||||
|
className={`w-full flex items-center px-4 py-4 rounded-xl text-base font-medium ${activePage === item.id ? 'bg-sky-50 text-[#0284c7]' : 'text-gray-700 hover:bg-gray-50'}`}
|
||||||
|
>
|
||||||
|
<item.icon className="w-5 h-5 mr-3 opacity-70" />
|
||||||
|
{item.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
<button
|
||||||
|
onClick={() => { setActivePage('contact'); setMobileMenuOpen(false); }}
|
||||||
|
className="w-full mt-4 bg-[#0284c7] text-white px-4 py-4 rounded-xl font-bold shadow-md"
|
||||||
|
>
|
||||||
|
Ingyenes felmérés
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Main Content Area - Simulating Routing with smooth fade */}
|
||||||
|
<main className="flex-grow relative">
|
||||||
|
<div key={activePage} className="animate-fade-in">
|
||||||
|
|
||||||
|
{/* HOME PAGE */}
|
||||||
|
{activePage === 'home' && (
|
||||||
|
<>
|
||||||
|
<section className="relative bg-[#0f172a] py-32 lg:py-48 overflow-hidden">
|
||||||
|
<div className="absolute inset-0 z-0">
|
||||||
|
<img src="https://images.unsplash.com/photo-1509391366360-2e959784a276?ixlib=rb-4.0.3&auto=format&fit=crop&w=2000&q=80" alt="Solar Panels" className="w-full h-full object-cover opacity-30 scale-105 animate-pulse-slow" />
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-[#0f172a] via-[#0f172a]/90 to-transparent"></div>
|
||||||
|
</div>
|
||||||
|
<div className="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="max-w-3xl">
|
||||||
|
<div className="inline-flex items-center px-4 py-2 rounded-full bg-[#0ea5e9]/10 text-[#38bdf8] text-sm font-bold mb-8 border border-[#0ea5e9]/20 backdrop-blur-sm animate-fade-in-up">
|
||||||
|
<CheckCircle className="w-4 h-4 mr-2" /> Hivatalos állami kivitelező partner
|
||||||
|
</div>
|
||||||
|
<h1 className="text-5xl md:text-7xl font-extrabold text-white mb-8 leading-tight animate-fade-in-up" style={{ animationDelay: '0.1s' }}>
|
||||||
|
Váltson tiszta energiára <br/>
|
||||||
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-[#38bdf8] to-[#0ea5e9]">a jövőjéért</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-xl text-gray-300 mb-10 leading-relaxed max-w-2xl animate-fade-in-up" style={{ animationDelay: '0.2s' }}>
|
||||||
|
Csökkentse rezsijét akár 100%-kal prémium napelem rendszereinkkel. Teljeskörű ügyintézés a tervezéstől a kulcsrakész átadásig, 25 év garanciával.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-col sm:flex-row gap-5 animate-fade-in-up" style={{ animationDelay: '0.3s' }}>
|
||||||
|
<button onClick={() => setActivePage('contact')} className="bg-[#0284c7] hover:bg-[#0369a1] text-white px-10 py-4 rounded-xl font-bold text-lg transition-all flex items-center justify-center shadow-lg shadow-sky-900/50 hover:scale-105">
|
||||||
|
Kalkuláció kérése <ChevronRight className="w-5 h-5 ml-2" />
|
||||||
|
</button>
|
||||||
|
<button onClick={() => setActivePage('services')} className="bg-white/5 hover:bg-white/10 text-white backdrop-blur-sm px-10 py-4 rounded-xl font-bold text-lg transition-all border border-white/10 hover:border-white/30">
|
||||||
|
Rendszereink
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-16 flex items-center gap-8 text-gray-400 text-sm font-medium animate-fade-in-up" style={{ animationDelay: '0.5s' }}>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-2 h-2 rounded-full bg-green-500"></div> 1000+ elégedett ügyfél
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-2 h-2 rounded-full bg-green-500"></div> Országos lefedettség
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="py-24 bg-gray-50 relative">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 -mt-32 relative z-20">
|
||||||
|
<div className="grid md:grid-cols-3 gap-8">
|
||||||
|
{[
|
||||||
|
{ icon: Sun, color: 'text-[#0284c7]', bg: 'bg-sky-50', title: 'Prémium Panelek', desc: 'Tier-1 kategóriás napelemek 25 év teljesítménygaranciával és kiemelkedő hatásfokkal.' },
|
||||||
|
{ icon: Battery, color: 'text-emerald-600', bg: 'bg-emerald-50', title: 'Energiatárolás', desc: 'Hibrid rendszerek és akkumulátoros megoldások a teljes függetlenségért.' },
|
||||||
|
{ icon: Briefcase, color: 'text-orange-600', bg: 'bg-orange-50', title: 'Kulcsrakész', desc: 'Minden papírmunkát, pályázatot és engedélyeztetést elintézünk Ön helyett.' }
|
||||||
|
].map((item, i) => (
|
||||||
|
<div key={i} className="bg-white p-10 rounded-2xl shadow-xl border border-gray-100 hover:-translate-y-2 transition-transform duration-300">
|
||||||
|
<div className={`w-16 h-16 ${item.bg} rounded-2xl flex items-center justify-center mb-8 ${item.color}`}>
|
||||||
|
<item.icon className="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold mb-4 text-gray-900">{item.title}</h3>
|
||||||
|
<p className="text-gray-600 leading-relaxed">{item.desc}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mt-24">
|
||||||
|
<div className="text-center mb-16">
|
||||||
|
<h2 className="text-3xl font-bold text-gray-900 mb-4">Miért válassza a BlueWave-et?</h2>
|
||||||
|
<p className="text-xl text-gray-600">Szakértelem, minőség és megbízhatóság egy helyen.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-2 gap-12 items-center">
|
||||||
|
<img src="https://images.unsplash.com/photo-1581092160562-40aa08e78837?ixlib=rb-4.0.3&auto=format&fit=crop&w=1000&q=80" alt="Engineer" className="rounded-3xl shadow-2xl" />
|
||||||
|
<div className="space-y-8">
|
||||||
|
{[
|
||||||
|
{ title: 'Szakértő Mérnökcsapat', text: 'Saját, magasan képzett mérnökeink tervezik meg rendszerét a maximális hatékonyság érdekében.' },
|
||||||
|
{ title: 'Gyors Kivitelezés', text: 'A szerződéskötéstől számított 30 napon belül telepítjük rendszerét.' },
|
||||||
|
{ title: 'Folyamatos Támogatás', text: 'Telepítés után sem engedjük el a kezét, távfelügyeletet és karbantartást biztosítunk.' }
|
||||||
|
].map((feat, i) => (
|
||||||
|
<div key={i} className="flex gap-4">
|
||||||
|
<div className="w-8 h-8 rounded-full bg-[#0284c7] flex-shrink-0 flex items-center justify-center text-white font-bold mt-1">
|
||||||
|
{i + 1}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 mb-2">{feat.title}</h3>
|
||||||
|
<p className="text-gray-600 leading-relaxed">{feat.text}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<button onClick={() => setActivePage('about')} className="mt-4 text-[#0284c7] font-bold flex items-center hover:underline">
|
||||||
|
Tudjon meg többet rólunk <ArrowRight className="w-5 h-5 ml-2" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* SERVICES PAGE */}
|
||||||
|
{activePage === 'services' && (
|
||||||
|
<div className="py-20 bg-gray-50 min-h-screen">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="text-center mb-20">
|
||||||
|
<span className="text-[#0284c7] font-bold uppercase tracking-wider text-sm mb-2 block">Megoldásaink</span>
|
||||||
|
<h2 className="text-4xl md:text-5xl font-extrabold text-gray-900 mb-6">Minden igényre van válaszunk</h2>
|
||||||
|
<p className="text-xl text-gray-600 max-w-3xl mx-auto">Legyen szó kis családi házról vagy hatalmas ipari létesítményről, mi megtaláljuk az optimális rendszert.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid lg:grid-cols-2 gap-16">
|
||||||
|
{/* Residential */}
|
||||||
|
<div className="group bg-white rounded-3xl overflow-hidden shadow-xl hover:shadow-2xl transition-all duration-300 border border-gray-100">
|
||||||
|
<div className="h-80 overflow-hidden relative">
|
||||||
|
<div className="absolute inset-0 bg-black/20 group-hover:bg-black/10 transition-colors z-10"></div>
|
||||||
|
<img src="https://images.unsplash.com/photo-1613665813446-82a78c468a1d?ixlib=rb-4.0.3&auto=format&fit=crop&w=1200&q=80" alt="Residential" className="w-full h-full object-cover transform group-hover:scale-110 transition-transform duration-700" />
|
||||||
|
<div className="absolute bottom-6 left-6 z-20">
|
||||||
|
<span className="bg-white px-4 py-1 rounded-full text-sm font-bold text-[#0284c7] mb-2 inline-block">Lakossági</span>
|
||||||
|
<h3 className="text-3xl font-bold text-white">Családi Házak</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-10">
|
||||||
|
<p className="text-gray-600 mb-8 text-lg leading-relaxed">
|
||||||
|
Csökkentse otthona energiafüggőségét és növelje ingatlana értékét. Rendszereink diszkrétek, esztétikusak és okosotthon-kompatibilisek.
|
||||||
|
</p>
|
||||||
|
<div className="grid sm:grid-cols-2 gap-4 mb-8">
|
||||||
|
{['Rezsi nullázása', 'Hibrid Inverterek', 'Okos monitorozás', 'Elektromos autó töltés'].map((item, i) => (
|
||||||
|
<div key={i} className="flex items-center text-gray-700 font-medium">
|
||||||
|
<CheckCircle className="w-5 h-5 text-green-500 mr-3" /> {item}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<button onClick={() => setActivePage('contact')} className="w-full py-4 border-2 border-[#0284c7] text-[#0284c7] font-bold rounded-xl hover:bg-[#0284c7] hover:text-white transition-all">
|
||||||
|
Ajánlatkérés Családi Házra
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Industrial */}
|
||||||
|
<div className="group bg-white rounded-3xl overflow-hidden shadow-xl hover:shadow-2xl transition-all duration-300 border border-gray-100">
|
||||||
|
<div className="h-80 overflow-hidden relative">
|
||||||
|
<div className="absolute inset-0 bg-black/20 group-hover:bg-black/10 transition-colors z-10"></div>
|
||||||
|
<img src="https://images.unsplash.com/photo-1624397640148-949b1732bb0a?ixlib=rb-4.0.3&auto=format&fit=crop&w=1200&q=80" alt="Commercial" className="w-full h-full object-cover transform group-hover:scale-110 transition-transform duration-700" />
|
||||||
|
<div className="absolute bottom-6 left-6 z-20">
|
||||||
|
<span className="bg-white px-4 py-1 rounded-full text-sm font-bold text-[#0284c7] mb-2 inline-block">Ipari</span>
|
||||||
|
<h3 className="text-3xl font-bold text-white">Vállalatok & Üzemek</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-10">
|
||||||
|
<p className="text-gray-600 mb-8 text-lg leading-relaxed">
|
||||||
|
Optimalizálja vállalkozása működési költségeit. Nagy teljesítményű rendszerek, amelyek fedezik a gépek és üzemcsarnokok energiaigényét.
|
||||||
|
</p>
|
||||||
|
<div className="grid sm:grid-cols-2 gap-4 mb-8">
|
||||||
|
{['Gyors megtérülés', '50kW - 5MW teljesítmény', 'ESG megfelelőség', 'Saját transzformátor'].map((item, i) => (
|
||||||
|
<div key={i} className="flex items-center text-gray-700 font-medium">
|
||||||
|
<CheckCircle className="w-5 h-5 text-green-500 mr-3" /> {item}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<button onClick={() => setActivePage('contact')} className="w-full py-4 border-2 border-[#0284c7] text-[#0284c7] font-bold rounded-xl hover:bg-[#0284c7] hover:text-white transition-all">
|
||||||
|
Ipari Konzultáció Kérése
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* ABOUT PAGE */}
|
||||||
|
{activePage === 'about' && (
|
||||||
|
<div className="py-20 bg-white">
|
||||||
|
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||||
|
<div className="inline-block p-3 rounded-full bg-sky-100 mb-6 text-[#0284c7]">
|
||||||
|
<Sun className="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-4xl font-extrabold text-gray-900 mb-8">A BlueWave Küldetése</h2>
|
||||||
|
<p className="text-xl text-gray-700 leading-relaxed mb-12">
|
||||||
|
A BlueWave Solar Kft. 2015-ben azzal a céllal alakult, hogy elérhetővé tegye a napenergiát mindenki számára. Hiszünk abban, hogy a fenntarthatóság nem luxus, hanem a jövő záloga. Csapatunk okleveles mérnökökből, pályázati szakértőkből és tapasztalt kivitelezőkből áll.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="bg-gradient-to-br from-[#0f172a] to-[#1e293b] rounded-3xl p-12 text-white shadow-2xl mb-16 relative overflow-hidden">
|
||||||
|
<div className="relative z-10 grid grid-cols-1 md:grid-cols-3 gap-12">
|
||||||
|
<div>
|
||||||
|
<div className="text-5xl font-bold text-[#38bdf8] mb-2">500+</div>
|
||||||
|
<div className="text-gray-400 font-medium uppercase tracking-wide text-sm">Telepített rendszer</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-5xl font-bold text-[#38bdf8] mb-2">15MW</div>
|
||||||
|
<div className="text-gray-400 font-medium uppercase tracking-wide text-sm">Össz teljesítmény</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-5xl font-bold text-[#38bdf8] mb-2">100%</div>
|
||||||
|
<div className="text-gray-400 font-medium uppercase tracking-wide text-sm">Ügyfél elégedettség</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Background decorations */}
|
||||||
|
<div className="absolute top-0 right-0 w-64 h-64 bg-[#38bdf8] rounded-full filter blur-[100px] opacity-20"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-3 gap-8 text-left">
|
||||||
|
<div className="bg-gray-50 p-8 rounded-2xl">
|
||||||
|
<h3 className="font-bold text-xl mb-3">Minőség</h3>
|
||||||
|
<p className="text-gray-600">Kizárólag minősített, prémium gyártók termékeit használjuk, hogy rendszere évtizedekig működjön.</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-gray-50 p-8 rounded-2xl">
|
||||||
|
<h3 className="font-bold text-xl mb-3">Innováció</h3>
|
||||||
|
<p className="text-gray-600">Folyamatos követjük a technológia fejlődését, hogy a legmodernebb megoldásokat kínálhassuk.</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-gray-50 p-8 rounded-2xl">
|
||||||
|
<h3 className="font-bold text-xl mb-3">Garancia</h3>
|
||||||
|
<p className="text-gray-600">A kivitelezésre és a termékekre is kiemelkedő garanciális feltételeket biztosítunk.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* CONTACT PAGE */}
|
||||||
|
{activePage === 'contact' && (
|
||||||
|
<div className="py-20 bg-[#f8fafc] min-h-screen">
|
||||||
|
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="grid lg:grid-cols-5 gap-10 bg-white rounded-3xl shadow-xl overflow-hidden border border-gray-100">
|
||||||
|
{/* Left Panel */}
|
||||||
|
<div className="lg:col-span-2 bg-[#0284c7] p-10 text-white flex flex-col justify-between relative overflow-hidden">
|
||||||
|
<div className="relative z-10">
|
||||||
|
<h3 className="text-2xl font-bold mb-6">Lépjen velünk kapcsolatba</h3>
|
||||||
|
<p className="text-sky-100 mb-8">
|
||||||
|
Töltse ki az űrlapot egy ingyenes, kötelezettségmentes előzetes kalkulációért. Mérnök kollégánk 24 órán belül keresni fogja.
|
||||||
|
</p>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<Phone className="w-6 h-6 text-sky-200" />
|
||||||
|
<span className="font-medium">+36 30 999 8888</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<Mail className="w-6 h-6 text-sky-200" />
|
||||||
|
<span className="font-medium">info@bluewave-solar.demo</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<Home className="w-6 h-6 text-sky-200" />
|
||||||
|
<span className="font-medium">1117 Budapest, Napelem u. 1.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="relative z-10 mt-12">
|
||||||
|
<p className="text-sm text-sky-200 opacity-80">
|
||||||
|
Nyitvatartás: H-P 8:00 - 17:00
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/* Circle decorations */}
|
||||||
|
<div className="absolute -bottom-10 -right-10 w-48 h-48 bg-sky-500 rounded-full opacity-50"></div>
|
||||||
|
<div className="absolute top-10 right-10 w-20 h-20 bg-sky-400 rounded-full opacity-30"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Panel - Form */}
|
||||||
|
<div className="lg:col-span-3 p-10">
|
||||||
|
<h2 className="text-2xl font-bold text-gray-900 mb-6">Ingyenes Ajánlatkérés</h2>
|
||||||
|
<form className="space-y-6">
|
||||||
|
<div className="grid md:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-semibold text-gray-700 mb-2">Teljes Név</label>
|
||||||
|
<input type="text" className="w-full px-4 py-3 rounded-lg border border-gray-200 focus:ring-2 focus:ring-[#0284c7] focus:border-transparent outline-none transition-all bg-gray-50 focus:bg-white text-gray-900" placeholder="Kovács János" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-semibold text-gray-700 mb-2">Telefonszám</label>
|
||||||
|
<input type="tel" className="w-full px-4 py-3 rounded-lg border border-gray-200 focus:ring-2 focus:ring-[#0284c7] focus:border-transparent outline-none transition-all bg-gray-50 focus:bg-white text-gray-900" placeholder="+36 30 123 4567" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-semibold text-gray-700 mb-2">Email Cím</label>
|
||||||
|
<input type="email" className="w-full px-4 py-3 rounded-lg border border-gray-200 focus:ring-2 focus:ring-[#0284c7] focus:border-transparent outline-none transition-all bg-gray-50 focus:bg-white text-gray-900" placeholder="janos@pelda.hu" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-6 bg-sky-50 rounded-xl border border-sky-100">
|
||||||
|
<div className="flex items-center gap-2 mb-4 text-[#0284c7] font-bold">
|
||||||
|
<Calculator className="w-5 h-5" /> Gyors Kalkulátor Adatok
|
||||||
|
</div>
|
||||||
|
<div className="grid md:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-semibold text-gray-700 mb-2">Havi villanyszámla (kb.)</label>
|
||||||
|
<input type="number" className="w-full px-4 py-3 rounded-lg border border-gray-200 focus:ring-2 focus:ring-[#0284c7] outline-none bg-white text-gray-900" placeholder="Pl: 25000" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-semibold text-gray-700 mb-2">Telepítés Helyszíne</label>
|
||||||
|
<input type="text" className="w-full px-4 py-3 rounded-lg border border-gray-200 focus:ring-2 focus:ring-[#0284c7] outline-none bg-white text-gray-900" placeholder="Város / Irányítószám" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-2">
|
||||||
|
<button type="button" className="w-full bg-[#0284c7] hover:bg-[#0369a1] text-white font-bold py-4 rounded-xl transition-all shadow-lg hover:shadow-xl transform hover:-translate-y-1">
|
||||||
|
Díjmentes Kalkuláció Kérése
|
||||||
|
</button>
|
||||||
|
<p className="text-center text-xs text-gray-500 mt-4">
|
||||||
|
Az ajánlatkérés nem jár kötelezettséggel. Adatvédelmi irányelveinket elfogadom.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<footer className="bg-[#0f172a] text-white py-16 border-t border-gray-800">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="grid md:grid-cols-4 gap-12 mb-12">
|
||||||
|
<div className="col-span-1 md:col-span-2">
|
||||||
|
<div className="flex items-center mb-6">
|
||||||
|
<div className="bg-gradient-to-tr from-[#0284c7] to-[#0ea5e9] p-2 rounded-lg mr-3">
|
||||||
|
<Sun className="w-6 h-6 text-white" />
|
||||||
|
</div>
|
||||||
|
<span className="text-2xl font-bold">BlueWave<span className="text-[#38bdf8]">Solar</span></span>
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-400 max-w-sm leading-relaxed">
|
||||||
|
Magyarország vezető napenergia szolgáltatója. Célunk, hogy ügyfeleink számára energiafüggetlenséget és fenntartható jövőt biztosítsunk.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="font-bold text-lg mb-6 text-white">Navigáció</h4>
|
||||||
|
<ul className="space-y-3 text-gray-400">
|
||||||
|
<li onClick={() => setActivePage('home')} className="cursor-pointer hover:text-[#38bdf8] transition-colors">Főoldal</li>
|
||||||
|
<li onClick={() => setActivePage('services')} className="cursor-pointer hover:text-[#38bdf8] transition-colors">Megoldások</li>
|
||||||
|
<li onClick={() => setActivePage('about')} className="cursor-pointer hover:text-[#38bdf8] transition-colors">Rólunk</li>
|
||||||
|
<li onClick={() => setActivePage('contact')} className="cursor-pointer hover:text-[#38bdf8] transition-colors">Kapcsolat</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 className="font-bold text-lg mb-6 text-white">Kapcsolat</h4>
|
||||||
|
<div className="space-y-3 text-gray-400">
|
||||||
|
<p>1117 Budapest, Napelem u. 1.</p>
|
||||||
|
<p>+36 30 999 8888</p>
|
||||||
|
<p>info@bluewave-solar.demo</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="border-t border-gray-800 pt-8 flex flex-col md:flex-row justify-between items-center text-gray-500 text-sm">
|
||||||
|
<p>© 2024 BlueWave Solar Kft. Minden jog fenntartva.</p>
|
||||||
|
<p>Demonstrációs weboldal - MotionWeb Pro csomag</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
428
pages/demos/Steelguard.tsx
Normal file
428
pages/demos/Steelguard.tsx
Normal file
@@ -0,0 +1,428 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { ArrowLeft, Shield, Lock, Eye, Server, Cpu, Globe, ChevronDown, PlayCircle, Fingerprint, Scan, Activity, Radio, Terminal, Box, Building2, Users, MapPin } from 'lucide-react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
export const Steelguard: React.FC = () => {
|
||||||
|
const [scrolled, setScrolled] = useState(false);
|
||||||
|
const [activeTab, setActiveTab] = useState<'home' | 'solutions' | 'hardware' | 'sectors' | 'company'>('home');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleScroll = () => setScrolled(window.scrollY > 20);
|
||||||
|
window.addEventListener('scroll', handleScroll);
|
||||||
|
return () => window.removeEventListener('scroll', handleScroll);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Reset scroll when changing tabs
|
||||||
|
useEffect(() => {
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}, [activeTab]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="font-sans bg-[#020205] text-gray-100 min-h-screen selection:bg-cyan-500 selection:text-black overflow-x-hidden relative">
|
||||||
|
|
||||||
|
{/* INJECTED STYLES FOR ADVANCED ANIMATIONS */}
|
||||||
|
<style>{`
|
||||||
|
@keyframes grid-move {
|
||||||
|
0% { transform: perspective(500px) rotateX(60deg) translateY(0); }
|
||||||
|
100% { transform: perspective(500px) rotateX(60deg) translateY(40px); }
|
||||||
|
}
|
||||||
|
@keyframes scanline {
|
||||||
|
0% { transform: translateY(-100%); }
|
||||||
|
100% { transform: translateY(100%); }
|
||||||
|
}
|
||||||
|
@keyframes blob {
|
||||||
|
0% { transform: translate(0px, 0px) scale(1); }
|
||||||
|
33% { transform: translate(30px, -50px) scale(1.1); }
|
||||||
|
66% { transform: translate(-20px, 20px) scale(0.9); }
|
||||||
|
100% { transform: translate(0px, 0px) scale(1); }
|
||||||
|
}
|
||||||
|
@keyframes pulse-glow {
|
||||||
|
0%, 100% { box-shadow: 0 0 10px rgba(6, 182, 212, 0.2); }
|
||||||
|
50% { box-shadow: 0 0 25px rgba(6, 182, 212, 0.6); }
|
||||||
|
}
|
||||||
|
.text-glow:hover {
|
||||||
|
text-shadow: 0 0 10px rgba(34, 211, 238, 0.8), 0 0 20px rgba(34, 211, 238, 0.4);
|
||||||
|
}
|
||||||
|
.animate-grid-move {
|
||||||
|
animation: grid-move 2s linear infinite;
|
||||||
|
}
|
||||||
|
.animate-blob {
|
||||||
|
animation: blob 10s infinite;
|
||||||
|
}
|
||||||
|
.animation-delay-2000 {
|
||||||
|
animation-delay: 2s;
|
||||||
|
}
|
||||||
|
.animation-delay-4000 {
|
||||||
|
animation-delay: 4s;
|
||||||
|
}
|
||||||
|
.glass-panel {
|
||||||
|
background: rgba(10, 15, 30, 0.6);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
border: 1px solid rgba(6, 182, 212, 0.1);
|
||||||
|
box-shadow: 0 0 15px rgba(0,0,0,0.5);
|
||||||
|
}
|
||||||
|
.cyber-button {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
.cyber-button::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
|
||||||
|
transition: 0.5s;
|
||||||
|
}
|
||||||
|
.cyber-button:hover::before {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
.clip-path-polygon {
|
||||||
|
clip-path: polygon(10% 0, 100% 0, 100% 70%, 90% 100%, 0 100%, 0 30%);
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
|
||||||
|
{/* Return to Main Site */}
|
||||||
|
<div className="fixed bottom-6 right-6 z-[60] mix-blend-difference">
|
||||||
|
<Link to="/#references">
|
||||||
|
<button className="bg-white text-black px-6 py-2 rounded-sm uppercase text-[10px] font-bold tracking-[0.3em] hover:bg-cyan-500 hover:text-white transition-all duration-300 flex items-center group">
|
||||||
|
<ArrowLeft className="w-3 h-3 mr-3 group-hover:-translate-x-1 transition-transform" /> BACK TO REALITY
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Enterprise Navbar */}
|
||||||
|
<nav className={`fixed w-full z-40 transition-all duration-500 border-b ${scrolled ? 'bg-[#050505]/80 backdrop-blur-xl border-cyan-900/30 py-2' : 'bg-transparent border-transparent py-6'}`}>
|
||||||
|
<div className="max-w-[1600px] mx-auto px-8 flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-4 group cursor-pointer" onClick={() => setActiveTab('home')}>
|
||||||
|
<div className="relative">
|
||||||
|
<div className="absolute inset-0 bg-cyan-500 blur-md opacity-20 group-hover:opacity-60 transition-opacity duration-500 animate-pulse"></div>
|
||||||
|
<div className="w-10 h-10 bg-[#0a0a0a] border border-cyan-500/50 flex items-center justify-center relative z-10 clip-path-polygon group-hover:border-cyan-400 transition-colors">
|
||||||
|
<Shield className="text-cyan-400 w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col text-left">
|
||||||
|
<span className="text-2xl font-bold tracking-tighter text-white font-mono group-hover:text-cyan-400 transition-colors">STEELGUARD</span>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-1.5 h-1.5 bg-green-500 rounded-full animate-pulse"></div>
|
||||||
|
<span className="text-[9px] text-cyan-600 tracking-[0.4em] uppercase">Systems Online</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="hidden lg:flex items-center space-x-12 text-[10px] font-bold tracking-[0.2em] text-gray-500 font-mono">
|
||||||
|
{[
|
||||||
|
{ id: 'solutions', label: 'Solutions' },
|
||||||
|
{ id: 'hardware', label: 'Hardware' },
|
||||||
|
{ id: 'sectors', label: 'Sectors' },
|
||||||
|
{ id: 'company', label: 'Company' }
|
||||||
|
].map((item) => (
|
||||||
|
<button
|
||||||
|
key={item.id}
|
||||||
|
onClick={() => setActiveTab(item.id as any)}
|
||||||
|
className={`transition-all relative group py-2 uppercase ${activeTab === item.id ? 'text-cyan-400' : 'hover:text-cyan-400'}`}
|
||||||
|
>
|
||||||
|
<span className={`text-cyan-800 transition-opacity ${activeTab === item.id ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'} mr-1`}><</span>
|
||||||
|
{item.label}
|
||||||
|
<span className={`text-cyan-800 transition-opacity ${activeTab === item.id ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'} ml-1`}>/></span>
|
||||||
|
<span className={`absolute bottom-0 left-0 h-[1px] bg-cyan-500 transition-all duration-300 shadow-[0_0_10px_#06b6d4] ${activeTab === item.id ? 'w-full' : 'w-0 group-hover:w-full'}`}></span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-6">
|
||||||
|
<button onClick={() => setActiveTab('home')} className="text-[10px] font-bold text-gray-500 hover:text-white uppercase tracking-[0.2em] hidden sm:block transition-colors border border-transparent hover:border-gray-800 px-4 py-2">
|
||||||
|
Login_Portal
|
||||||
|
</button>
|
||||||
|
<button className="cyber-button bg-cyan-600 hover:bg-cyan-500 text-black px-8 py-3 text-[10px] font-bold uppercase tracking-[0.2em] transition-all shadow-[0_0_20px_rgba(8,145,178,0.4)] hover:shadow-[0_0_30px_rgba(6,182,212,0.6)]">
|
||||||
|
Initialize
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Main Content Router Simulation */}
|
||||||
|
<main className="animate-fade-in">
|
||||||
|
|
||||||
|
{/* HOME VIEW */}
|
||||||
|
{activeTab === 'home' && (
|
||||||
|
<>
|
||||||
|
<header className="relative h-screen flex items-center justify-center overflow-hidden bg-[#020205]">
|
||||||
|
<div className="absolute inset-0 z-0 overflow-hidden pointer-events-none">
|
||||||
|
<div className="absolute top-[-10%] left-[-10%] w-[600px] h-[600px] bg-blue-900/20 rounded-full mix-blend-screen filter blur-[80px] opacity-30 animate-blob"></div>
|
||||||
|
<div className="absolute top-[20%] right-[-20%] w-[500px] h-[500px] bg-cyan-900/20 rounded-full mix-blend-screen filter blur-[80px] opacity-30 animate-blob animation-delay-2000"></div>
|
||||||
|
<div className="absolute bottom-[-20%] left-[20%] w-[600px] h-[600px] bg-purple-900/10 rounded-full mix-blend-screen filter blur-[80px] opacity-30 animate-blob animation-delay-4000"></div>
|
||||||
|
<div className="absolute inset-0 bg-[linear-gradient(rgba(255,255,255,0.02)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.02)_1px,transparent_1px)] bg-[size:50px_50px]"></div>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-[#020205] via-transparent to-transparent"></div>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-[#020205] via-transparent to-transparent"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative z-10 max-w-[1400px] mx-auto px-6 grid lg:grid-cols-2 gap-20 items-center mt-20">
|
||||||
|
<div>
|
||||||
|
<div className="inline-flex items-center gap-3 px-4 py-2 border border-cyan-500/30 bg-cyan-900/10 text-cyan-400 text-[10px] font-bold tracking-[0.3em] mb-10 animate-fade-in-up backdrop-blur-md">
|
||||||
|
<Terminal className="w-3 h-3" />
|
||||||
|
<span className="animate-pulse">ESTABLISHING SECURE CONNECTION...</span>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-6xl lg:text-8xl font-bold text-white leading-[0.9] mb-8 font-mono animate-fade-in-up" style={{animationDelay: '0.1s'}}>
|
||||||
|
TOTAL <br/>
|
||||||
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-cyan-400 via-blue-500 to-purple-600 drop-shadow-[0_0_15px_rgba(6,182,212,0.5)]">DEFENSE</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-lg text-gray-400 max-w-lg mb-12 leading-relaxed animate-fade-in-up border-l-2 border-cyan-900/50 pl-6" style={{animationDelay: '0.2s'}}>
|
||||||
|
Advanced AI-driven security architecture for enterprise infrastructure. <span className="text-cyan-400 font-bold">Zero-trust protocols</span> and predictive threat neutralization active.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-wrap gap-6 animate-fade-in-up" style={{animationDelay: '0.3s'}}>
|
||||||
|
<button onClick={() => setActiveTab('solutions')} className="cyber-button bg-white text-black hover:bg-cyan-50 px-10 py-4 text-xs font-bold uppercase tracking-[0.2em] flex items-center gap-4 transition-all hover:pr-12 group shadow-[0_0_20px_rgba(255,255,255,0.3)]">
|
||||||
|
Execute Protocol <ChevronDown className="w-4 h-4 group-hover:translate-y-1 transition-transform" />
|
||||||
|
</button>
|
||||||
|
<button className="border border-gray-800 hover:border-cyan-500 text-gray-300 hover:text-cyan-400 px-10 py-4 text-xs font-bold uppercase tracking-[0.2em] flex items-center gap-4 transition-all bg-[#0a0a0a]/50 backdrop-blur-sm group hover:shadow-[0_0_15px_rgba(6,182,212,0.3)]">
|
||||||
|
<PlayCircle className="w-4 h-4 group-hover:scale-110 transition-transform" /> Simulation
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative hidden lg:block animate-fade-in perspective-1000" style={{animationDelay: '0.5s'}}>
|
||||||
|
<div className="relative w-full aspect-square flex items-center justify-center transform-style-3d rotate-y-12">
|
||||||
|
<div className="absolute w-[80%] h-[80%] border border-cyan-900/40 rounded-full animate-[spin_20s_linear_infinite] shadow-[0_0_30px_rgba(6,182,212,0.1)]"></div>
|
||||||
|
<div className="absolute w-[60%] h-[60%] border-2 border-dashed border-cyan-500/20 rounded-full animate-[spin_15s_linear_infinite_reverse]"></div>
|
||||||
|
<div className="relative z-10 w-[70%] h-[70%] rounded-2xl overflow-hidden border border-cyan-500/30 group shadow-[0_0_50px_rgba(6,182,212,0.15)]">
|
||||||
|
<img src="https://images.unsplash.com/photo-1550751827-4bd374c3f58b?auto=format&fit=crop&w=1000&q=80" alt="Tech Visual" className="w-full h-full object-cover opacity-60 mix-blend-luminosity group-hover:scale-105 transition-transform duration-1000 grayscale" />
|
||||||
|
<div className="absolute inset-0 bg-cyan-900/20 mix-blend-overlay"></div>
|
||||||
|
<div className="absolute top-0 left-0 w-full h-[2px] bg-cyan-400 shadow-[0_0_20px_#22d3ee] animate-[scanline_3s_linear_infinite]"></div>
|
||||||
|
</div>
|
||||||
|
<div className="absolute top-20 -right-10 glass-panel p-4 flex items-center gap-4 animate-[bounce_4s_infinite] border-l-2 border-l-green-500">
|
||||||
|
<Activity className="w-6 h-6 text-green-500 animate-pulse" />
|
||||||
|
<div>
|
||||||
|
<p className="text-[9px] text-gray-400 uppercase tracking-widest font-mono">Status</p>
|
||||||
|
<p className="text-xs font-bold text-green-400 font-mono tracking-wider">OPTIMAL_FLOW</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section className="py-32 bg-[#020205]">
|
||||||
|
<div className="max-w-[1400px] mx-auto px-6">
|
||||||
|
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
{[
|
||||||
|
{ icon: Eye, title: 'Neural Surveillance', desc: 'Real-time object recognition and behavioral prediction algorithms.' },
|
||||||
|
{ icon: Scan, title: 'Retinal Access', desc: '99.999% accuracy biometric entry points with liveness detection.' },
|
||||||
|
{ icon: Server, title: 'Core Defense', desc: 'Hardened physical and logical shielding for data centers.' },
|
||||||
|
{ icon: Globe, title: 'Global Ops', desc: '24/7 localized threat monitoring from orbit-synced satellites.' },
|
||||||
|
{ icon: Cpu, title: 'IoT Mesh', desc: 'Thousands of interconnected sensors forming an unbreakable web.' },
|
||||||
|
{ icon: Shield, title: 'Hybrid Firewall', desc: 'Simultaneous protection against kinetic and digital intrusion.' },
|
||||||
|
].map((item, i) => (
|
||||||
|
<div key={i} className="group relative bg-[#05070a] border border-gray-800 p-10 overflow-hidden hover:border-cyan-500/50 transition-all duration-500">
|
||||||
|
<div className="relative z-10">
|
||||||
|
<div className="w-14 h-14 bg-[#0a0f18] border border-gray-800 group-hover:border-cyan-500 flex items-center justify-center mb-8 transition-all">
|
||||||
|
<item.icon className="w-6 h-6 text-gray-500 group-hover:text-cyan-400 transition-colors" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-bold text-white mb-4 font-mono group-hover:text-cyan-300 transition-colors">{item.title}</h3>
|
||||||
|
<p className="text-gray-500 text-xs leading-relaxed group-hover:text-gray-300 transition-colors font-mono">{item.desc}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* SOLUTIONS VIEW */}
|
||||||
|
{activeTab === 'solutions' && (
|
||||||
|
<section className="pt-40 pb-20 px-8">
|
||||||
|
<div className="max-w-6xl mx-auto">
|
||||||
|
<div className="flex items-center gap-4 mb-10">
|
||||||
|
<div className="w-12 h-1 bg-cyan-500"></div>
|
||||||
|
<h2 className="text-4xl font-mono font-black uppercase tracking-tighter">Strategic Solutions</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid lg:grid-cols-2 gap-16 items-center">
|
||||||
|
<div className="space-y-10">
|
||||||
|
<p className="text-xl text-gray-400 font-mono leading-relaxed">
|
||||||
|
We offer a multi-layered security stack designed to neutralize threats before they reach your perimeter.
|
||||||
|
</p>
|
||||||
|
<div className="space-y-6">
|
||||||
|
{[
|
||||||
|
{ title: "Zero Trust Architecture", desc: "Never trust, always verify. Every node is isolated and encrypted." },
|
||||||
|
{ title: "Predictive AI Shield", desc: "Our neural networks predict intrusion patterns with 99.8% accuracy." },
|
||||||
|
{ title: "Cloud Integration", desc: "Seamless security across AWS, Azure, and private data centers." }
|
||||||
|
].map((sol, i) => (
|
||||||
|
<div key={i} className="bg-[#05070a] border-l-4 border-l-cyan-500 p-6">
|
||||||
|
<h4 className="font-bold text-cyan-400 mb-2 font-mono uppercase text-sm">{sol.title}</h4>
|
||||||
|
<p className="text-gray-500 text-xs font-mono">{sol.desc}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="relative group">
|
||||||
|
<div className="absolute -inset-2 bg-cyan-500 blur-2xl opacity-10 group-hover:opacity-30 transition-opacity"></div>
|
||||||
|
<img src="https://images.unsplash.com/photo-1550751827-4bd374c3f58b?auto=format&fit=crop&w=1200&q=80" alt="Solution" className="rounded-2xl border border-cyan-500/20 relative z-10" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* HARDWARE VIEW */}
|
||||||
|
{activeTab === 'hardware' && (
|
||||||
|
<section className="pt-40 pb-20 px-8">
|
||||||
|
<div className="max-w-6xl mx-auto">
|
||||||
|
<div className="flex items-center gap-4 mb-10">
|
||||||
|
<div className="w-12 h-1 bg-cyan-500"></div>
|
||||||
|
<h2 className="text-4xl font-mono font-black uppercase tracking-tighter">Hardened Hardware</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-3 gap-8">
|
||||||
|
{[
|
||||||
|
{ icon: Box, name: "Node V4 Unit", img: "https://images.unsplash.com/photo-1518770660439-4636190af475?auto=format&fit=crop&w=800&q=80" },
|
||||||
|
{ icon: Cpu, name: "Neural Processor", img: "https://images.unsplash.com/photo-1591799264318-7e6ef8ddb7ea?auto=format&fit=crop&w=800&q=80" },
|
||||||
|
{ icon: Shield, name: "Quantum Enclosure", img: "https://images.unsplash.com/photo-1544197150-b99a580bb7a8?auto=format&fit=crop&w=800&q=80" }
|
||||||
|
].map((hw, i) => (
|
||||||
|
<div key={i} className="bg-[#05070a] rounded-2xl overflow-hidden border border-gray-800 hover:border-cyan-500/40 transition-all">
|
||||||
|
<img src={hw.img} alt={hw.name} className="h-48 w-full object-cover grayscale opacity-60 hover:grayscale-0 hover:opacity-100 transition-all" />
|
||||||
|
<div className="p-6">
|
||||||
|
<div className="flex items-center gap-3 mb-4">
|
||||||
|
<hw.icon className="w-5 h-5 text-cyan-500" />
|
||||||
|
<span className="font-mono font-bold uppercase text-xs">{hw.name}</span>
|
||||||
|
</div>
|
||||||
|
<button className="w-full py-2 bg-gray-900 border border-gray-800 text-[10px] font-bold uppercase tracking-widest hover:bg-cyan-600 hover:text-black transition-all">View Specs</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* SECTORS VIEW */}
|
||||||
|
{activeTab === 'sectors' && (
|
||||||
|
<section className="pt-40 pb-20 px-8 text-center">
|
||||||
|
<div className="max-w-5xl mx-auto">
|
||||||
|
<h2 className="text-5xl font-mono font-black uppercase tracking-tighter mb-6">Strategic Sectors</h2>
|
||||||
|
<p className="text-gray-400 font-mono mb-20">Steelguard protects the infrastructure that power nations.</p>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-3 gap-12">
|
||||||
|
{[
|
||||||
|
{ icon: Building2, label: "Enterprise", count: "400+ Nodes" },
|
||||||
|
{ icon: Globe, label: "Government", count: "12 Nations" },
|
||||||
|
{ icon: Shield, label: "Defense", count: "Active Mesh" }
|
||||||
|
].map((sector, i) => (
|
||||||
|
<div key={i} className="flex flex-col items-center group cursor-default">
|
||||||
|
<div className="w-24 h-24 rounded-full border-2 border-gray-800 flex items-center justify-center mb-6 group-hover:border-cyan-500 group-hover:shadow-[0_0_30px_rgba(6,182,212,0.2)] transition-all">
|
||||||
|
<sector.icon className="w-10 h-10 text-gray-500 group-hover:text-cyan-400 transition-colors" />
|
||||||
|
</div>
|
||||||
|
<h3 className="font-mono font-black uppercase text-xl mb-2">{sector.label}</h3>
|
||||||
|
<span className="text-[10px] font-mono text-cyan-600 tracking-widest">{sector.count}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* COMPANY VIEW */}
|
||||||
|
{activeTab === 'company' && (
|
||||||
|
<section className="pt-40 pb-20 px-8">
|
||||||
|
<div className="max-w-4xl mx-auto">
|
||||||
|
<div className="grid md:grid-cols-2 gap-16 items-center">
|
||||||
|
<div className="relative">
|
||||||
|
<div className="absolute -inset-4 bg-cyan-500/5 blur-3xl rounded-full"></div>
|
||||||
|
<img src="https://images.unsplash.com/photo-1497366216548-37526070297c?auto=format&fit=crop&w=1000&q=80" alt="Office" className="rounded-3xl relative z-10 opacity-70 grayscale hover:grayscale-0 transition-all" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-cyan-500 font-mono text-[10px] tracking-widest block mb-4 uppercase">Origins</span>
|
||||||
|
<h2 className="text-4xl font-mono font-black uppercase mb-8">Engineering <br/>Secured Future</h2>
|
||||||
|
<p className="text-gray-400 font-mono text-sm leading-loose mb-8">
|
||||||
|
Founded in 2042, Steelguard Defense Systems has evolved from a small hardware research lab into a global leader in AI-driven security architecture. Our mission is absolute protection through innovation.
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center gap-8">
|
||||||
|
<div>
|
||||||
|
<p className="text-3xl font-black text-white font-mono">15</p>
|
||||||
|
<p className="text-[9px] text-gray-600 uppercase font-mono tracking-widest">Global Hubs</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-3xl font-black text-white font-mono">850+</p>
|
||||||
|
<p className="text-[9px] text-gray-600 uppercase font-mono tracking-widest">Architects</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* MAP INTEGRATION */}
|
||||||
|
{activeTab === 'home' && (
|
||||||
|
<section className="py-20 bg-[#050505] relative overflow-hidden">
|
||||||
|
<div className="max-w-[1400px] mx-auto px-6">
|
||||||
|
<div className="flex items-center gap-4 mb-10">
|
||||||
|
<MapPin className="text-cyan-500 w-6 h-6" />
|
||||||
|
<h2 className="text-2xl font-mono font-black uppercase tracking-widest">Command Center HQ</h2>
|
||||||
|
</div>
|
||||||
|
<div className="relative w-full h-[500px] rounded-3xl overflow-hidden border border-cyan-900/30 group shadow-[0_0_30px_rgba(0,0,0,0.5)]">
|
||||||
|
<div className="absolute inset-0 z-10 pointer-events-none border-[10px] border-[#020205] opacity-80"></div>
|
||||||
|
<div className="absolute inset-0 z-10 pointer-events-none ring-1 ring-cyan-500/20"></div>
|
||||||
|
<iframe
|
||||||
|
title="Steelguard HQ Map"
|
||||||
|
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2695.565377508381!2d19.0354183769188!3d47.49831737118037!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x4741dc41c2c8f877%3A0x7292275f0a2072e!2sBudapest%2C%20Szent%20Gy%C3%B6rgy%20t%C3%A9r%2C%201014!5e0!3m2!1shu!2shu!4v1716300000000!5m2!1shu!2shu"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
style={{ border: 0, filter: 'grayscale(1) invert(0.9) contrast(1.2)' }}
|
||||||
|
allowFullScreen
|
||||||
|
loading="lazy"
|
||||||
|
referrerPolicy="no-referrer-when-downgrade"
|
||||||
|
></iframe>
|
||||||
|
<div className="absolute top-0 left-0 w-full h-full bg-cyan-500/5 mix-blend-color pointer-events-none"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* Footer / CTA */}
|
||||||
|
<footer className="py-32 bg-[#020205] relative overflow-hidden">
|
||||||
|
<div className="max-w-[1400px] mx-auto px-6 grid md:grid-cols-4 gap-0 divide-x divide-gray-900 text-center relative z-10 border-y border-gray-900 mb-20">
|
||||||
|
{[
|
||||||
|
{ val: '500+', label: 'NODES ACTIVE' },
|
||||||
|
{ val: '0.01ms', label: 'LATENCY' },
|
||||||
|
{ val: '100%', label: 'UPTIME' },
|
||||||
|
{ val: 'SECURE', label: 'STATUS' }
|
||||||
|
].map((stat, i) => (
|
||||||
|
<div key={i} className="relative py-10 group hover:bg-cyan-900/5 transition-colors cursor-default">
|
||||||
|
<div className="text-5xl font-bold text-gray-700 mb-2 font-mono group-hover:text-cyan-400 group-hover:scale-110 transition-all duration-300 text-glow">{stat.val}</div>
|
||||||
|
<div className="text-cyan-900 text-[10px] font-bold uppercase tracking-[0.3em] group-hover:text-cyan-600">{stat.label}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="max-w-4xl mx-auto px-6 text-center relative z-10">
|
||||||
|
<div className="inline-block mb-10 p-4 border border-cyan-500/30 rounded-full animate-pulse-glow">
|
||||||
|
<Shield className="w-12 h-12 text-cyan-500" />
|
||||||
|
</div>
|
||||||
|
<h2 className="text-4xl md:text-6xl font-bold text-white mb-8 font-mono uppercase">
|
||||||
|
SECURE YOUR <span className="text-cyan-500">FUTURE</span>
|
||||||
|
</h2>
|
||||||
|
<p className="text-gray-400 mb-12 text-lg max-w-xl mx-auto font-mono text-xs leading-loose">
|
||||||
|
Initiate contact with our security architects. Design a defense strategy leveraging next-gen technologies.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-col sm:flex-row justify-center gap-6">
|
||||||
|
<button className="cyber-button bg-cyan-600 hover:bg-cyan-500 text-black px-12 py-5 font-bold uppercase tracking-[0.2em] text-xs transition-all shadow-[0_0_20px_rgba(8,145,178,0.5)]">
|
||||||
|
Initialize Sequence
|
||||||
|
</button>
|
||||||
|
<button className="border border-gray-800 hover:border-white text-gray-400 hover:text-white px-12 py-5 font-bold uppercase tracking-[0.2em] text-xs transition-all hover:bg-white/5 hover:shadow-[0_0_15px_rgba(255,255,255,0.2)]">
|
||||||
|
Download Manifest
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="mt-32 pt-10 border-t border-gray-900 text-gray-600 text-[9px] uppercase tracking-[0.2em] flex flex-col md:flex-row justify-between items-center gap-4 font-mono">
|
||||||
|
<div>© 2024 STEELGUARD DEFENSE SYSTEMS.</div>
|
||||||
|
<div className="text-cyan-900">SYSTEM_ID: MW-ENT-8842</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
266
pages/demos/SweetCraving.tsx
Normal file
266
pages/demos/SweetCraving.tsx
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { ArrowLeft, Instagram, Facebook, MapPin, Phone, Mail, Clock, Heart, Star, ChevronDown } from 'lucide-react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
export const SweetCraving: React.FC = () => {
|
||||||
|
const [scrolled, setScrolled] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleScroll = () => {
|
||||||
|
setScrolled(window.scrollY > 50);
|
||||||
|
};
|
||||||
|
window.addEventListener('scroll', handleScroll);
|
||||||
|
return () => window.removeEventListener('scroll', handleScroll);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="font-serif bg-[#FFF5F5] text-[#5D4037] min-h-screen scroll-smooth">
|
||||||
|
{/* Return to Main Site Button */}
|
||||||
|
<div className="fixed bottom-6 right-6 z-[60]">
|
||||||
|
<Link to="/#references">
|
||||||
|
<button className="bg-white/90 backdrop-blur-md shadow-lg px-5 py-2.5 rounded-full text-sm font-sans font-medium flex items-center hover:bg-white hover:scale-105 transition-all duration-300 group text-[#E91E63]">
|
||||||
|
<ArrowLeft className="w-4 h-4 mr-2 group-hover:-translate-x-1 transition-transform" />
|
||||||
|
Vissza a referenciákhoz
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Dynamic Navigation */}
|
||||||
|
<nav
|
||||||
|
className={`fixed top-0 w-full z-40 transition-all duration-500 ${
|
||||||
|
scrolled
|
||||||
|
? 'bg-white/90 backdrop-blur-md shadow-lg py-3'
|
||||||
|
: 'bg-transparent py-6'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="max-w-6xl mx-auto px-6 flex justify-between items-center">
|
||||||
|
<div className={`text-3xl font-bold text-[#E91E63] italic transition-all duration-300 ${scrolled ? 'scale-90' : 'scale-100'}`}>
|
||||||
|
SweetCraving
|
||||||
|
</div>
|
||||||
|
<div className="hidden md:flex space-x-10 font-medium font-sans text-sm tracking-wide">
|
||||||
|
{['Kezdőlap', 'Rólunk', 'Kínálat', 'Vélemények'].map((item, i) => (
|
||||||
|
<a
|
||||||
|
key={i}
|
||||||
|
href={`#${item.toLowerCase() === 'kezdőlap' ? 'home' : item === 'Kínálat' ? 'menu' : item === 'Rólunk' ? 'about' : 'contact'}`}
|
||||||
|
className="hover:text-[#E91E63] relative group transition-colors"
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
<span className="absolute -bottom-1 left-0 w-0 h-0.5 bg-[#E91E63] transition-all group-hover:w-full"></span>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<button className="bg-[#E91E63] text-white px-8 py-2.5 rounded-full font-sans font-bold text-sm hover:bg-[#D81B60] transition-all shadow-lg hover:shadow-[#E91E63]/30 hover:-translate-y-0.5 transform">
|
||||||
|
Rendelés
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Hero Section */}
|
||||||
|
<section id="home" className="relative min-h-screen flex items-center justify-center overflow-hidden">
|
||||||
|
<div className="absolute inset-0 z-0">
|
||||||
|
<img
|
||||||
|
src="https://images.unsplash.com/photo-1558961363-fa8fdf82db35?auto=format&fit=crop&w=1920&q=80"
|
||||||
|
alt="Desszertműhely Hero"
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-[#FFF5F5] via-white/20 to-transparent"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative z-10 text-center px-4 max-w-4xl mx-auto mt-16">
|
||||||
|
<div className="animate-fade-in-up" style={{ animationDelay: '0.1s' }}>
|
||||||
|
<span className="bg-white/80 backdrop-blur-md px-6 py-2 rounded-full text-[#E91E63] text-xs font-bold font-sans tracking-[0.2em] uppercase mb-8 inline-block shadow-sm">
|
||||||
|
Kézműves Desszertműhely
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1 className="text-6xl md:text-8xl font-bold mb-8 drop-shadow-sm text-gray-900 leading-tight animate-fade-in-up" style={{ animationDelay: '0.3s' }}>
|
||||||
|
Édes Álmok <br/>
|
||||||
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-[#E91E63] to-[#FF80AB]">
|
||||||
|
Süteménybe Zárva
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="text-xl md:text-2xl text-gray-800/80 max-w-2xl mx-auto mb-12 font-medium leading-relaxed animate-fade-in-up" style={{ animationDelay: '0.5s' }}>
|
||||||
|
Természetes alapanyagokból, szívvel-lélekkel készült desszertek minden alkalomra.
|
||||||
|
<br className="hidden md:block"/> Mert minden nap megérdemel egy kis kényeztetést.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex flex-col sm:flex-row gap-5 justify-center animate-fade-in-up" style={{ animationDelay: '0.7s' }}>
|
||||||
|
<a href="#menu" className="bg-[#E91E63] text-white px-10 py-4 rounded-full font-sans font-bold hover:bg-[#D81B60] transition-all shadow-xl shadow-[#E91E63]/30 hover:-translate-y-1 hover:scale-105">
|
||||||
|
Kínálatunk Megtekintése
|
||||||
|
</a>
|
||||||
|
<a href="#about" className="bg-white text-[#5D4037] px-10 py-4 rounded-full font-sans font-bold hover:bg-gray-50 transition-all shadow-lg hover:-translate-y-1 hover:scale-105">
|
||||||
|
Ismerj meg minket
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Scroll Indicator */}
|
||||||
|
<div className="absolute bottom-10 left-1/2 transform -translate-x-1/2 animate-bounce">
|
||||||
|
<ChevronDown className="w-8 h-8 text-[#E91E63] opacity-60" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* About Section */}
|
||||||
|
<section id="about" className="py-32 px-6 bg-white overflow-hidden">
|
||||||
|
<div className="max-w-6xl mx-auto">
|
||||||
|
<div className="grid md:grid-cols-2 gap-20 items-center">
|
||||||
|
<div className="relative group">
|
||||||
|
<div className="absolute -top-6 -left-6 w-full h-full border-4 border-[#FF80AB]/30 rounded-[2rem] transition-transform duration-500 group-hover:translate-x-2 group-hover:translate-y-2"></div>
|
||||||
|
<img
|
||||||
|
src="https://images.unsplash.com/photo-1556910103-1c02745aae4d?auto=format&fit=crop&w=1000&q=80"
|
||||||
|
alt="Pék és Cukrászmunka"
|
||||||
|
className="rounded-[2rem] shadow-2xl relative z-10 transform transition-transform duration-700 hover:scale-[1.02]"
|
||||||
|
/>
|
||||||
|
{/* Floating Badge */}
|
||||||
|
<div className="absolute -bottom-10 -right-10 z-20 bg-white p-6 rounded-2xl shadow-xl animate-bounce-slow hidden lg:block">
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="text-4xl font-bold text-[#E91E63]">5+</p>
|
||||||
|
<p className="text-sm font-sans text-gray-500 uppercase tracking-wider">Év Tapasztalat</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-8">
|
||||||
|
<h2 className="text-5xl font-bold text-[#5D4037] leading-tight">
|
||||||
|
A szenvedélyünk <br/>
|
||||||
|
<span className="text-[#E91E63] italic">a minőség</span>
|
||||||
|
</h2>
|
||||||
|
<p className="text-lg leading-relaxed text-[#795548] font-sans">
|
||||||
|
2018 óta készítjük desszertjeinket Budapest szívében. Hiszünk abban, hogy egy sütemény nemcsak étel, hanem élmény. Ezért kizárólag prémium belga csokoládét, termelői tejet és friss gyümölcsöket használunk.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="grid gap-6">
|
||||||
|
{[
|
||||||
|
{ text: '100% Természetes alapanyagok', icon: Heart },
|
||||||
|
{ text: 'Glutén- és laktózmentes opciók', icon: Star },
|
||||||
|
{ text: 'Egyedi tortarendelés 48 órán belül', icon: Clock }
|
||||||
|
].map((item, i) => (
|
||||||
|
<div key={i} className="flex items-center gap-4 p-4 rounded-xl bg-[#FFF5F5] border border-[#FF80AB]/20 transition-all hover:shadow-md hover:bg-[#fff0f0]">
|
||||||
|
<div className="w-10 h-10 rounded-full bg-white flex items-center justify-center text-[#E91E63] shadow-sm">
|
||||||
|
<item.icon className="w-5 h-5" />
|
||||||
|
</div>
|
||||||
|
<span className="font-bold text-[#5D4037]">{item.text}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Menu Grid */}
|
||||||
|
<section id="menu" className="py-32 bg-[#FFF5F5]">
|
||||||
|
<div className="max-w-6xl mx-auto px-6">
|
||||||
|
<div className="text-center mb-20">
|
||||||
|
<h2 className="text-5xl font-bold mb-6 text-[#5D4037]">Legnépszerűbb Finomságok</h2>
|
||||||
|
<p className="text-[#795548] text-xl max-w-2xl mx-auto font-sans">
|
||||||
|
Válogasson kedvenc süteményeink közül, melyeket minden reggel frissen készítünk.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-10">
|
||||||
|
{[
|
||||||
|
{ title: 'Macaron Válogatás', price: '3 490 Ft', img: 'https://images.unsplash.com/photo-1569864358642-9d1684040f43?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80', tag: 'Best Seller' },
|
||||||
|
{ title: 'Epres Sajttorta', price: '1 290 Ft', img: 'https://images.unsplash.com/photo-1533134242443-d4fd215305ad?auto=format&fit=crop&w=800&q=80', tag: null },
|
||||||
|
{ title: 'Csokimousse', price: '1 490 Ft', img: 'https://images.unsplash.com/photo-1541783245831-57d6fb0926d3?auto=format&fit=crop&w=800&q=80', tag: 'Új' },
|
||||||
|
{ title: 'Gyümölcstorta', price: '8 990 Ft', img: 'https://images.unsplash.com/photo-1565958011703-44f9829ba187?auto=format&fit=crop&w=800&q=80', tag: 'Rendelésre' },
|
||||||
|
{ title: 'Cupcake Mix', price: '2 990 Ft', img: 'https://images.unsplash.com/photo-1519869325930-281384150729?auto=format&fit=crop&w=800&q=80', tag: null },
|
||||||
|
{ title: 'Vajas Croissant', price: '890 Ft', img: 'https://images.unsplash.com/photo-1509440159596-0249088772ff?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80', tag: 'Friss' }
|
||||||
|
].map((item, i) => (
|
||||||
|
<div key={i} className="group bg-white rounded-3xl p-4 shadow-md hover:shadow-2xl transition-all duration-500 hover:-translate-y-2">
|
||||||
|
<div className="overflow-hidden rounded-2xl mb-6 h-64 relative">
|
||||||
|
<img
|
||||||
|
src={item.img}
|
||||||
|
alt={item.title}
|
||||||
|
className="w-full h-full object-cover transform group-hover:scale-110 transition-transform duration-700"
|
||||||
|
/>
|
||||||
|
{item.tag && (
|
||||||
|
<div className="absolute top-4 left-4 bg-white/90 backdrop-blur px-3 py-1 rounded-full text-xs font-bold text-[#E91E63] uppercase tracking-wider shadow-sm">
|
||||||
|
{item.tag}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* Quick Add Overlay */}
|
||||||
|
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center">
|
||||||
|
<button className="bg-white text-[#E91E63] px-6 py-2 rounded-full font-bold transform translate-y-4 group-hover:translate-y-0 transition-transform duration-300">
|
||||||
|
Kosárba
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="px-2 pb-2">
|
||||||
|
<div className="flex justify-between items-start mb-2">
|
||||||
|
<h3 className="text-2xl font-bold text-[#5D4037] group-hover:text-[#E91E63] transition-colors">{item.title}</h3>
|
||||||
|
<span className="text-lg font-bold text-[#E91E63] bg-[#FFF5F5] px-3 py-1 rounded-lg">{item.price}</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-gray-500 text-sm line-clamp-2">Krémes, ízletes finomság, amely elolvad a szádban.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-16 text-center">
|
||||||
|
<button className="border-2 border-[#E91E63] text-[#E91E63] px-10 py-3 rounded-full font-bold hover:bg-[#E91E63] hover:text-white transition-all duration-300 uppercase tracking-wider text-sm">
|
||||||
|
Teljes étlap letöltése
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Testimonials (Simple) */}
|
||||||
|
<section className="py-24 bg-white">
|
||||||
|
<div className="max-w-4xl mx-auto px-6 text-center">
|
||||||
|
<Star className="w-10 h-10 text-yellow-400 fill-current mx-auto mb-6" />
|
||||||
|
<h2 className="text-4xl font-bold mb-8 text-[#5D4037]">Vendégeink mondták</h2>
|
||||||
|
<blockquote className="text-2xl italic text-gray-600 font-serif leading-relaxed mb-8">
|
||||||
|
"A legfinomabb macaron, amit valaha ettem! A pisztáciás egyszerűen mennyei. Mindenkinek csak ajánlani tudom a helyet, igazi ékszerdoboz."
|
||||||
|
</blockquote>
|
||||||
|
<cite className="font-bold text-[#E91E63] not-italic">— Kovács Anna, Budapest</cite>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Contact & Footer */}
|
||||||
|
<section id="contact" className="bg-[#5D4037] text-white py-24 px-6 relative overflow-hidden">
|
||||||
|
{/* Background Pattern */}
|
||||||
|
<div className="absolute inset-0 opacity-5" style={{backgroundImage: 'radial-gradient(circle, #ffffff 2px, transparent 2px)', backgroundSize: '30px 30px'}}></div>
|
||||||
|
|
||||||
|
<div className="max-w-4xl mx-auto text-center relative z-10">
|
||||||
|
<h2 className="text-4xl font-bold mb-16">Látogass el hozzánk</h2>
|
||||||
|
|
||||||
|
<div className="grid md:grid-cols-3 gap-12 mb-16">
|
||||||
|
<div className="flex flex-col items-center group">
|
||||||
|
<div className="w-16 h-16 bg-white/10 rounded-full flex items-center justify-center mb-6 group-hover:bg-[#E91E63] transition-colors duration-300">
|
||||||
|
<MapPin className="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<h4 className="font-bold text-xl mb-3">Címünk</h4>
|
||||||
|
<p className="text-white/70 leading-relaxed">1052 Budapest,<br/>Petőfi Sándor utca 12.</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center group">
|
||||||
|
<div className="w-16 h-16 bg-white/10 rounded-full flex items-center justify-center mb-6 group-hover:bg-[#E91E63] transition-colors duration-300">
|
||||||
|
<Clock className="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<h4 className="font-bold text-xl mb-3">Nyitvatartás</h4>
|
||||||
|
<p className="text-white/70 leading-relaxed">H-P: 08:00 - 19:00<br/>Szo-V: 09:00 - 16:00</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center group">
|
||||||
|
<div className="w-16 h-16 bg-white/10 rounded-full flex items-center justify-center mb-6 group-hover:bg-[#E91E63] transition-colors duration-300">
|
||||||
|
<Phone className="w-8 h-8" />
|
||||||
|
</div>
|
||||||
|
<h4 className="font-bold text-xl mb-3">Kapcsolat</h4>
|
||||||
|
<p className="text-white/70 leading-relaxed">+36 1 234 5678<br/>hello@sweetcraving.hu</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-center space-x-8">
|
||||||
|
<a href="#" className="opacity-60 hover:opacity-100 hover:text-[#E91E63] transition-all transform hover:scale-110"><Facebook className="w-8 h-8" /></a>
|
||||||
|
<a href="#" className="opacity-60 hover:opacity-100 hover:text-[#E91E63] transition-all transform hover:scale-110"><Instagram className="w-8 h-8" /></a>
|
||||||
|
<a href="#" className="opacity-60 hover:opacity-100 hover:text-[#E91E63] transition-all transform hover:scale-110"><Mail className="w-8 h-8" /></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-16 pt-8 border-t border-white/10 text-white/40 text-sm font-sans">
|
||||||
|
© 2024 SweetCraving. Ez egy demonstrációs weboldal a MotionWeb referenciáihoz.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
0
schema.sql
Normal file
0
schema.sql
Normal file
2
supabase_schema.sql
Normal file
2
supabase_schema.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ϋν
|
||||||
|
·<EFBFBD>µζ΅zZ^ϋ§rΨ¨<EFBFBD>Ϊ…η$
|
||||||
29
tsconfig.json
Normal file
29
tsconfig.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"useDefineForClassFields": false,
|
||||||
|
"module": "ESNext",
|
||||||
|
"lib": [
|
||||||
|
"ES2022",
|
||||||
|
"DOM",
|
||||||
|
"DOM.Iterable"
|
||||||
|
],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"types": [
|
||||||
|
"node"
|
||||||
|
],
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"allowJs": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"./*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"noEmit": true
|
||||||
|
}
|
||||||
|
}
|
||||||
44
types.ts
Normal file
44
types.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
|
||||||
|
export interface Service {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProductPackage {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
price: string;
|
||||||
|
desc: string; // Description
|
||||||
|
features: string[];
|
||||||
|
isPopular?: boolean;
|
||||||
|
cta: string; // Call to Action button text
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Reference {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
category: string;
|
||||||
|
imageUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BlogPost {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
excerpt: string;
|
||||||
|
date: string;
|
||||||
|
imageUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Invoice {
|
||||||
|
id: string;
|
||||||
|
user_id: string;
|
||||||
|
order_id?: string;
|
||||||
|
invoice_type?: 'advance' | 'final' | null;
|
||||||
|
invoice_number: string;
|
||||||
|
amount: string;
|
||||||
|
created_at: string;
|
||||||
|
status: 'paid' | 'pending' | 'overdue';
|
||||||
|
pdf_url?: string;
|
||||||
|
}
|
||||||
23
vite.config.ts
Normal file
23
vite.config.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import { defineConfig, loadEnv } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
|
export default defineConfig(({ mode }) => {
|
||||||
|
const env = loadEnv(mode, '.', '');
|
||||||
|
return {
|
||||||
|
server: {
|
||||||
|
port: 3000,
|
||||||
|
host: '0.0.0.0',
|
||||||
|
},
|
||||||
|
plugins: [react()],
|
||||||
|
define: {
|
||||||
|
'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
|
||||||
|
'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, '.'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user