From c7f669fc117602ea6267d24c9917f268bb2e7f2c Mon Sep 17 00:00:00 2001 From: htom Date: Sun, 21 Dec 2025 20:40:32 +0100 Subject: [PATCH] init --- .gitignore | 24 + App.tsx | 96 ++ README.md | 20 + SUPABASE_AUTO_PROFILES.sql | 0 SUPABASE_SETUP.sql | 1 + components/AnalyticsTracker.tsx | 57 ++ components/Button.tsx | 42 + components/CookieBanner.tsx | 132 +++ components/FeedbackModal.tsx | 289 ++++++ components/Footer.tsx | 72 ++ components/Navbar.tsx | 236 +++++ components/OrderForm.tsx | 876 +++++++++++++++++++ components/ProcessSection.tsx | 97 ++ components/ProfileCompleter.tsx | 250 ++++++ components/ProtectedRoute.tsx | 17 + components/ScrollToTop.tsx | 22 + components/ServiceCard.tsx | 20 + components/SettingsModal.tsx | 188 ++++ context/AuthContext.tsx | 150 ++++ db_fix.sql | 2 + db_schema.sql | Bin 0 -> 12 bytes index.html | 77 ++ index.tsx | 15 + lib/defaultPlans.ts | 54 ++ lib/promptGenerator.ts | 296 +++++++ lib/supabaseClient.ts | 27 + metadata.json | 5 + motion-web-studio-0.1-login-order-status.zip | Bin 0 -> 380489 bytes package.json | 24 + pages/About.tsx | 72 ++ pages/Admin.tsx | 588 +++++++++++++ pages/Blog.tsx | 58 ++ pages/Contact.tsx | 169 ++++ pages/Dashboard.tsx | 313 +++++++ pages/FAQ.tsx | 84 ++ pages/Home.tsx | 316 +++++++ pages/Privacy.tsx | 342 ++++++++ pages/Products.tsx | 99 +++ pages/References.tsx | 102 +++ pages/SUPABASE_SETUP.sql | 1 + pages/Services.tsx | 96 ++ pages/Terms.tsx | 320 +++++++ pages/auth/ForgotPassword.tsx | 112 +++ pages/auth/Login.tsx | 171 ++++ pages/auth/Register.tsx | 249 ++++++ pages/auth/ResetPassword.tsx | 128 +++ pages/demos/BlueWave.tsx | 474 ++++++++++ pages/demos/Steelguard.tsx | 428 +++++++++ pages/demos/SweetCraving.tsx | 266 ++++++ schema.sql | 0 supabase_schema.sql | 2 + tsconfig.json | 29 + types.ts | 44 + vite.config.ts | 23 + 54 files changed, 7575 insertions(+) create mode 100644 .gitignore create mode 100644 App.tsx create mode 100644 README.md create mode 100644 SUPABASE_AUTO_PROFILES.sql create mode 100644 SUPABASE_SETUP.sql create mode 100644 components/AnalyticsTracker.tsx create mode 100644 components/Button.tsx create mode 100644 components/CookieBanner.tsx create mode 100644 components/FeedbackModal.tsx create mode 100644 components/Footer.tsx create mode 100644 components/Navbar.tsx create mode 100644 components/OrderForm.tsx create mode 100644 components/ProcessSection.tsx create mode 100644 components/ProfileCompleter.tsx create mode 100644 components/ProtectedRoute.tsx create mode 100644 components/ScrollToTop.tsx create mode 100644 components/ServiceCard.tsx create mode 100644 components/SettingsModal.tsx create mode 100644 context/AuthContext.tsx create mode 100644 db_fix.sql create mode 100644 db_schema.sql create mode 100644 index.html create mode 100644 index.tsx create mode 100644 lib/defaultPlans.ts create mode 100644 lib/promptGenerator.ts create mode 100644 lib/supabaseClient.ts create mode 100644 metadata.json create mode 100644 motion-web-studio-0.1-login-order-status.zip create mode 100644 package.json create mode 100644 pages/About.tsx create mode 100644 pages/Admin.tsx create mode 100644 pages/Blog.tsx create mode 100644 pages/Contact.tsx create mode 100644 pages/Dashboard.tsx create mode 100644 pages/FAQ.tsx create mode 100644 pages/Home.tsx create mode 100644 pages/Privacy.tsx create mode 100644 pages/Products.tsx create mode 100644 pages/References.tsx create mode 100644 pages/SUPABASE_SETUP.sql create mode 100644 pages/Services.tsx create mode 100644 pages/Terms.tsx create mode 100644 pages/auth/ForgotPassword.tsx create mode 100644 pages/auth/Login.tsx create mode 100644 pages/auth/Register.tsx create mode 100644 pages/auth/ResetPassword.tsx create mode 100644 pages/demos/BlueWave.tsx create mode 100644 pages/demos/Steelguard.tsx create mode 100644 pages/demos/SweetCraving.tsx create mode 100644 schema.sql create mode 100644 supabase_schema.sql create mode 100644 tsconfig.json create mode 100644 types.ts create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -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? diff --git a/App.tsx b/App.tsx new file mode 100644 index 0000000..fd16ab8 --- /dev/null +++ b/App.tsx @@ -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 ; +}; + +const App: React.FC = () => { + return ( + + +
+ + + + + + + {/* Main Application Routes */} +
} /> +
} /> +
} /> +
} /> +
} /> + + {/* Info Pages */} +
} /> +
} /> +
} /> + + {/* Auth Routes */} +
} /> +
} /> +
} /> +
} /> + + {/* Protected Routes */} + + <>
+ + } + /> + + + <>
+ + } + /> + + {/* Demo Routes - These have their own custom layouts/navbars */} + } /> + } /> + } /> + + +
+
+
+ ); +}; + +export default App; \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2bd8010 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +
+GHBanner +
+ +# 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` diff --git a/SUPABASE_AUTO_PROFILES.sql b/SUPABASE_AUTO_PROFILES.sql new file mode 100644 index 0000000..e69de29 diff --git a/SUPABASE_SETUP.sql b/SUPABASE_SETUP.sql new file mode 100644 index 0000000..f958e34 --- /dev/null +++ b/SUPABASE_SETUP.sql @@ -0,0 +1 @@ +nb+b^JZmIv+h,z۩ʋuZm \ No newline at end of file diff --git a/components/AnalyticsTracker.tsx b/components/AnalyticsTracker.tsx new file mode 100644 index 0000000..3a37dcd --- /dev/null +++ b/components/AnalyticsTracker.tsx @@ -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; +}; \ No newline at end of file diff --git a/components/Button.tsx b/components/Button.tsx new file mode 100644 index 0000000..ce8e17e --- /dev/null +++ b/components/Button.tsx @@ -0,0 +1,42 @@ +import React from 'react'; + +interface ButtonProps extends React.ButtonHTMLAttributes { + variant?: 'primary' | 'secondary' | 'outline' | 'white'; + size?: 'sm' | 'md' | 'lg'; + fullWidth?: boolean; +} + +export const Button: React.FC = ({ + 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 ( + + ); +}; \ No newline at end of file diff --git a/components/CookieBanner.tsx b/components/CookieBanner.tsx new file mode 100644 index 0000000..fc63af1 --- /dev/null +++ b/components/CookieBanner.tsx @@ -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 ( +
+
+
+
+ +
+
+

Sütik és Adatvédelem

+

+ 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. +

+
+ +
+ +
+ + +
+ +
+ További információkért olvassa el az Adatkezelési tájékoztatónkat. +
+
+
+ ); +}; \ No newline at end of file diff --git a/components/FeedbackModal.tsx b/components/FeedbackModal.tsx new file mode 100644 index 0000000..f2f9df0 --- /dev/null +++ b/components/FeedbackModal.tsx @@ -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; + loading: boolean; +} + +export const FeedbackModal: React.FC = ({ 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([]); + const [designText, setDesignText] = useState(''); + + const [contentCheckboxes, setContentCheckboxes] = useState([]); + const [contentText, setContentText] = useState(''); + + const [structureCheckboxes, setStructureCheckboxes] = useState([]); + const [structureText, setStructureText] = useState(''); + + const [funcCheckboxes, setFuncCheckboxes] = useState([]); + 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>, + 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>, + textValue: string, + setTextValue: React.Dispatch>, + placeholder: string + ) => { + const noneOption = options[options.length - 1]; + const showTextarea = selected.length > 0 && !selected.includes(noneOption); + + return ( +
+

{title}

+
+ {options.map(opt => ( + + ))} +
+ {showTextarea && ( + + )} +
+ ); + }; + + return ( +
+
+ + + +
+
+
+ +
+
+

Visszajelzés a Weboldalról

+

A bemutató verzió alapján kérjük jelezd, ha valamit módosítanál.

+
+
+
+ +
+
+

+ Döntés a továbblépésről * +

+
+ {[ + { 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 => ( + + ))} +
+
+ + {mainDecision === 'approved' && ( +
+

+ Megerősítés +

+ +
+ )} + + {isRevision && ( +
+ + {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..." + )} + +
+

Mi a legfontosabb kérésed? *

+ +
+ +
+

Egyéb észrevételek

+ +
+ +
+ +
+ +
+ )} +
+ +
+ + + {mainDecision === 'approved' && ( + + )} + + {isRevision && ( + + )} + + {!mainDecision && ( + + )} +
+
+
+ ); +}; \ No newline at end of file diff --git a/components/Footer.tsx b/components/Footer.tsx new file mode 100644 index 0000000..4751d03 --- /dev/null +++ b/components/Footer.tsx @@ -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 ( +
+
+
+ {/* Brand Info */} +
+
+
+ +
+ MotionWeb +
+

+ Innovatív webes megoldások, amelyek üzleti eredményeket hoznak. Teljeskörű digitális partner cégek számára. +

+ +
+ + {/* Services */} +
+

Szolgáltatások

+
    +
  • Egyedi Weboldal
  • +
  • Reszponzív Design
  • +
  • SEO Optimalizálás
  • +
  • UI/UX Design
  • +
+
+ + {/* Contact Info - Modified */} +
+

Elérhetőség

+ +
+ + {/* Information (Replaced Newsletter) */} +
+

Információk

+
    +
  • Adatkezelési tájékoztató
  • +
  • Általános Szerződési Feltételek
  • +
  • Gyakori Kérdések
  • +
+
+
+ +
+

+ © {new Date().getFullYear()} Motion Web Stúdió. Minden jog fenntartva. +

+
+
+
+ ); +}; \ No newline at end of file diff --git a/components/Navbar.tsx b/components/Navbar.tsx new file mode 100644 index 0000000..21f73fc --- /dev/null +++ b/components/Navbar.tsx @@ -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(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 ( + + ); +}; \ No newline at end of file diff --git a/components/OrderForm.tsx b/components/OrderForm.tsx new file mode 100644 index 0000000..34593a5 --- /dev/null +++ b/components/OrderForm.tsx @@ -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(null); + const [errors, setErrors] = useState([]); + 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 ( +
+
Betöltés...
+
+ ); + } + + // Auth Protection - Lock Screen + if (!user) { + return ( +
+
+
+ +
+

Jelentkezzen be a rendeléshez

+

+ Weboldal rendelés leadásához kérjük, jelentkezzen be fiókjába, vagy regisztráljon egyet ingyenesen. +

+
+
+
+ + + + + + +
+

+ A regisztráció mindössze 1 percet vesz igénybe, és segít a projekt későbbi nyomon követésében. +

+
+
+ ); + } + + const handleInputChange = (e: React.ChangeEvent) => { + 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 }) => ( +
+ +
+ {colorOptions.map((color) => ( + + ))} +
+

{selected ? `Választott: ${selected}` : ''}

+
+ ); + + if (isSubmitted) { + return ( +
+
+ +
+

Rendelését sikeresen rögzítettük!

+

+ Átirányítjuk az előlegfizetési oldalra. Amennyiben ez nem történik meg, kollégáink hamarosan felveszik Önnel a kapcsolatot. +

+ +
+ ); + } + + return ( +
+ {/* Light Gradient Header & Step Navigation */} +
+
+

Rendelés Leadása

+

Töltse ki az űrlapot a pontos ajánlatadáshoz, és segítünk megvalósítani elképzeléseit.

+
+ + {/* Desktop Stepper Navigation */} +
+ {/* Background Line */} +
+ {/* Active Progress Line */} +
+ + {steps.map((step) => { + const isActive = step.id === currentStep; + const isCompleted = step.id < currentStep; + + return ( +
+
+ {isCompleted ? : } +
+
+ {step.title} +
+
+ ); + })} +
+ + {/* Mobile/Tablet Progress Bar */} +
+
+ Lépés {currentStep} / {totalSteps} + {steps[currentStep - 1].title} +
+
+
+
+
+
+ +
+ {errors.length > 0 && ( +
+ +
+

Kérjük, javítsa a következő hibákat:

+
    + {errors.map((err, idx) =>
  • {err}
  • )} +
+
+
+ )} + + {submitError && ( +
+ +
{submitError}
+
+ )} + + {/* Step 1: Kapcsolattartási adatok */} + {currentStep === 1 && ( +
+
+
+ +
+

Kapcsolattartási adatok

+
+ +
+
+ + +
+
+ + +
+
+ + +

A bejelentkezett e-mail címed.

+
+
+ + +
+
+ +
+ +
+ {['Landing Page', 'Pro Web', 'Enterprise'].map((pkg) => ( +
+
+ )} + + {currentStep === 2 && ( +
+
+
+ +
+

A vállalkozás rövid bemutatása

+
+ +
+ + +

💡 Tipp: Írjon le mindent, amit fontosnak tart a tevékenységével kapcsolatban.

+
+
+ )} + + {currentStep === 3 && ( +
+
+
+ +
+

Mi a weboldal fő célja?

+
+ +
+ {['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) => ( +
+ + {goal === 'Egyéb' && formData.goals.includes('Egyéb') && ( +
+ +
+ )} +
+ ))} +
+
+ + +
+
+ )} + + {currentStep === 4 && ( +
+
+
+ +
+

Tartalom és szerkezet

+
+ +
+ +
+ {['Rólunk', 'Szolgáltatások', 'Termékek', 'Referenciák / Vélemények', 'Blog / Hírek', 'Kapcsolat', 'Egyéb'].map((item) => ( +
+ + {item === 'Egyéb' && formData.content.includes('Egyéb') && ( + + )} +
+ ))} +
+
+ +
+ +
+ {['Igen', 'Nem', 'Részben'].map((opt) => ( + + ))} +
+ + {/* Content Upload / Link Section */} + {(formData.existingAssets === 'Igen' || formData.existingAssets === 'Részben') && ( +
+

+ Tartalom megosztása +

+

+ 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). +

+
+
+ + +
+
+ +
+ +

Kattintson a feltöltéshez vagy húzza ide a fájlt

+

(Max 1GB)

+ +
+
+
+
+ )} +
+
+ )} + + {currentStep === 5 && ( +
+
+
+ +
+

Design és stílus

+
+ +
+ setFormData(prev => ({ ...prev, primaryColor: c }))} /> + setFormData(prev => ({ ...prev, secondaryColor: c }))} /> + setFormData(prev => ({ ...prev, balanceColor: c }))} /> +
+ +
+ +
+ {styleOptions.map((style) => ( + + ))} +
+
+ +
+ + +
+
+ )} + + {currentStep === 6 && ( +
+
+
+ +
+

Funkciók és technikai igények

+
+ +
+ {['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) => ( + + ))} +
+
+ )} + + {currentStep === 7 && ( +
+
+
+ +
+

Inspirációk

+

Segítsen megérteni az ízlését!

+
+ +
+
+

Osszon meg velünk 3 weboldalt, ami tetszik, és írja le röviden, miért (pl. színek, elrendezés, hangulat).

+
+ +
+ {[0, 1, 2].map((i) => ( +
+
+ +
+ + handleInspirationChange(i, 'url', e.target.value)} + placeholder="https://pelda.hu" + className={`${inputClass} pl-9`} + /> +
+
+
+ + handleInspirationChange(i, 'comment', e.target.value)} + placeholder="Színek, elrendezés, animációk..." + className={inputClass} + /> +
+
+ ))} +
+
+ )} + + {currentStep === 8 && ( +
+
+
+ +
+

Extra szolgáltatások és Megjegyzések

+
+ +
+ +
+ {['SEO optimalizálás', 'Szövegírás', 'Domain ügyintézés', 'Tárhely ügyintézés'].map((extra) => ( + + ))} +
+ + {/* Dynamic Content */} +
+ {formData.extras.includes('Domain ügyintézés') && ( +
+ +
+ + +
+
+ )} + + {/* Domain Warning */} + {!formData.extras.includes('Domain ügyintézés') && ( +
+
+
+ Figyelem: 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. +
+
+ )} + + {!formData.extras.includes('Tárhely ügyintézés') && ( +
+
+
+ Figyelem: 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. +
+
+ )} +
+
+ +
+ + +
+
+ )} + + {/* Step 9: Összegzés */} + {currentStep === 9 && ( +
+
+
+ +
+

Rendelés összesítése

+

Ellenőrizze az adatokat a véglegesítés előtt.

+
+ +
+
+
+

Kapcsolattartó

+

{formData.name}

+

{formData.email}

+

{formData.phone}

+ {formData.company &&

{formData.company}

} +
+
+

Választott Csomag

+ {formData.package} +
+
+ +
+
+

Célok

+
+ {formData.goals.length > 0 ? formData.goals.map(g => {g}) : Nincs megadva} +
+
+
+

Színvilág

+
+ {formData.primaryColor &&
c.name === formData.primaryColor)?.value}} title="Főszín">
} + {formData.secondaryColor &&
c.name === formData.secondaryColor)?.value}} title="Mellékszín">
} + {formData.balanceColor &&
c.name === formData.balanceColor)?.value}} title="Kiegyensúlyozó">
} + {!formData.primaryColor && !formData.secondaryColor && !formData.balanceColor && Nincs színválasztás} +
+
+
+
+ +
+

Mi történik a rendelés leadása után?

+

+ 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. +

+
+ +
+
+
+ setPrivacyAccepted(e.target.checked)} + className="h-4 w-4 text-primary focus:ring-primary border-gray-300 rounded cursor-pointer" + /> +
+
+ +
+
+ +
+
+ setAszfAccepted(e.target.checked)} + className="h-4 w-4 text-primary focus:ring-primary border-gray-300 rounded cursor-pointer" + /> +
+
+ +
+
+
+
+ )} +
+ + {/* Footer Navigation */} +
+ + + {currentStep < totalSteps ? ( + + ) : ( + + )} +
+
+ ); +}; \ No newline at end of file diff --git a/components/ProcessSection.tsx b/components/ProcessSection.tsx new file mode 100644 index 0000000..d68303e --- /dev/null +++ b/components/ProcessSection.tsx @@ -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 ( +
+
+
+

Hogyan készül el a weboldalad?

+

+ Átlátható folyamat, gyors és professzionális kivitelezés – pontosan tudni fogod, mire számíthatsz. +

+
+ +
+ {steps.map((step) => ( +
+
+
+ +
+
+ {step.id} +
+
+

{step.title}

+

{step.description}

+
+ ))} +
+
+
+ ); +}; \ No newline at end of file diff --git a/components/ProfileCompleter.tsx b/components/ProfileCompleter.tsx new file mode 100644 index 0000000..18b8847 --- /dev/null +++ b/components/ProfileCompleter.tsx @@ -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(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 ( +
+
+
+
+ +
+

Hiányzó Adatok

+

+ Kérjük, a folytatáshoz adja meg a hiányzó adatait. +

+
+ +
+
+ {error && ( +
+ + {error} +
+ )} + +
+
+ + 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 + /> +
+ +
+ + 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 + /> +
+ +
+ +
+ 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 + /> +
+ +
+
+
+
+ +
+ +
+ +

+ Ezekre az adatokra a számlázáshoz és a kapcsolattartáshoz van szükségünk. +

+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/components/ProtectedRoute.tsx b/components/ProtectedRoute.tsx new file mode 100644 index 0000000..4f4ce70 --- /dev/null +++ b/components/ProtectedRoute.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { Navigate } from 'react-router-dom'; +import { useAuth } from '../context/AuthContext'; + +export const ProtectedRoute: React.FC = ({ children }) => { + const { user, loading } = useAuth(); + + if (loading) { + return
Betöltés...
; + } + + if (!user) { + return ; + } + + return <>{children}; +}; \ No newline at end of file diff --git a/components/ScrollToTop.tsx b/components/ScrollToTop.tsx new file mode 100644 index 0000000..88d7589 --- /dev/null +++ b/components/ScrollToTop.tsx @@ -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; +} \ No newline at end of file diff --git a/components/ServiceCard.tsx b/components/ServiceCard.tsx new file mode 100644 index 0000000..7c78446 --- /dev/null +++ b/components/ServiceCard.tsx @@ -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 = ({ title, description, Icon }) => { + return ( +
+
+ +
+

{title}

+

{description}

+
+ ); +}; \ No newline at end of file diff --git a/components/SettingsModal.tsx b/components/SettingsModal.tsx new file mode 100644 index 0000000..be1f1c0 --- /dev/null +++ b/components/SettingsModal.tsx @@ -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 = ({ 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 ( +
+
+ +
+

Fiók Beállítások

+ +
+ +
+ + +
+ +
+ {message && ( +
+ {message.type === 'success' ? : } + {message.text} +
+ )} + + {activeTab === 'profile' && ( +
+
+
+ + setLastName(e.target.value)} className={commonInputStyles} /> +
+
+ + setFirstName(e.target.value)} className={commonInputStyles} /> +
+
+ +
+ + setDateOfBirth(e.target.value)} className={commonInputStyles} /> +
+ +
+ + +
+ +
+ +
+
+ )} + + {activeTab === 'security' && ( +
+
+ + setNewPassword(e.target.value)} placeholder="Minimum 6 karakter" className={commonInputStyles} /> +
+ +
+ + setConfirmPassword(e.target.value)} placeholder="Jelszó újra" className={commonInputStyles} /> +
+ +
+ +

Biztonsági okokból a jelszó megváltoztatása után javasolt az újra-bejelentkezés minden eszközön.

+
+ +
+ +
+
+ )} +
+
+
+ ); +}; \ No newline at end of file diff --git a/context/AuthContext.tsx b/context/AuthContext.tsx new file mode 100644 index 0000000..067fb89 --- /dev/null +++ b/context/AuthContext.tsx @@ -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; + refreshDemoUser: () => void; +} + +const AuthContext = createContext({ + session: null, + user: null, + loading: true, + isAdmin: false, + signOut: async () => {}, + refreshDemoUser: () => {}, +}); + +export const AuthProvider: React.FC = ({ children }) => { + const [session, setSession] = useState(null); + const [user, setUser] = useState(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 ( + + {!loading && children} + + ); +}; + +export const useAuth = () => useContext(AuthContext); diff --git a/db_fix.sql b/db_fix.sql new file mode 100644 index 0000000..624e0cd --- /dev/null +++ b/db_fix.sql @@ -0,0 +1,2 @@ + +zZ^rب$i٢Z,y˫z\ \ No newline at end of file diff --git a/db_schema.sql b/db_schema.sql new file mode 100644 index 0000000000000000000000000000000000000000..a7ff1e43941806f19c99bb864291386f6640a0c3 GIT binary patch literal 12 TcmezU)>gPxVWW@_gSP+xBx(dm literal 0 HcmV?d00001 diff --git a/index.html b/index.html new file mode 100644 index 0000000..9d38f93 --- /dev/null +++ b/index.html @@ -0,0 +1,77 @@ + + + + + + + Motion Web Stúdió - Professzionális Webfejlesztés + + + + + + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/index.tsx b/index.tsx new file mode 100644 index 0000000..6ca5361 --- /dev/null +++ b/index.tsx @@ -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( + + + +); \ No newline at end of file diff --git a/lib/defaultPlans.ts b/lib/defaultPlans.ts new file mode 100644 index 0000000..95a0d44 --- /dev/null +++ b/lib/defaultPlans.ts @@ -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 + } +]; \ No newline at end of file diff --git a/lib/promptGenerator.ts b/lib/promptGenerator.ts new file mode 100644 index 0000000..639e793 --- /dev/null +++ b/lib/promptGenerator.ts @@ -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; +}; diff --git a/lib/supabaseClient.ts b/lib/supabaseClient.ts new file mode 100644 index 0000000..e93b85e --- /dev/null +++ b/lib/supabaseClient.ts @@ -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; \ No newline at end of file diff --git a/metadata.json b/metadata.json new file mode 100644 index 0000000..9b94a7d --- /dev/null +++ b/metadata.json @@ -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": [] +} \ No newline at end of file diff --git a/motion-web-studio-0.1-login-order-status.zip b/motion-web-studio-0.1-login-order-status.zip new file mode 100644 index 0000000000000000000000000000000000000000..4dea16da6063fc27c9ca43254553fc0374ddcc9f GIT binary patch literal 380489 zcmeFa+izW0nkOctJDI@2bTX64AQ>RRHeFq$x_J)o5=E1A)3Q|dSh6&t?CL2&vG+M= zlicFD)!B!nNS1=ByiI@!;2CrWJ!l|s<{|Kd9||5AL8aaZAxIwb59A@i`~kT<Ld-WP5z}>rQ_5_0%jIbh4s7 z%7^@w?H-N>J6Tbj;Op5J-MmPSver)ateX`lT5ntstc`(l(f)u^NF=LcCazB|J79%iFnUKALyzzm)| zedEPLZ;^95|Ht3{gWtP$jeq}LEOLB2%!=l?cq4m09E`?E-XCY9owS`L+u7(a$G=>8 z-dRhE@hI=_zKw6=eB9042hOtH$9a3uFTcNdqdEov)H!I6AExd7bT>8>AzpToB*PJ= zrQ?H9R+uJFo^k(9P05AGgP;Y45I!Dt%V)7+ z-t7){9}bG~MK&gT-p)qDaoJ�HZwsUTn}0`iFx!G8`1y=u;m*2BVHV19VHI$yE=Z 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 literal 0 HcmV?d00001 diff --git a/package.json b/package.json new file mode 100644 index 0000000..d30e329 --- /dev/null +++ b/package.json @@ -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" + } +} diff --git a/pages/About.tsx b/pages/About.tsx new file mode 100644 index 0000000..245394c --- /dev/null +++ b/pages/About.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { Target, Users, Zap } from 'lucide-react'; + +export const About: React.FC = () => { + return ( +
+ {/* Hero */} +
+
+ Office team +
+
+

+ Rólunk +

+

+ A Motion Web Stúdió (Balogh Bence Benedek) egy fiatalos, dinamikus csapat, akik elkötelezettek a minőségi webfejlesztés mellett. +

+
+
+ + {/* Content */} +
+
+
+
+

A küldetésünk

+

+ 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. +

+

+ 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. +

+
+
+ Working + Coding +
+
+ +
+
+
+ +
+

Eredményorientáltság

+

Nem csak szépet alkotunk, hanem olyat, ami működik. A konverzió és a ROI a legfontosabb mérőszámunk.

+
+
+
+ +
+

Ügyfélközpontúság

+

Folyamatos kommunikáció és átlátható folyamatok. Ön mindig tudja, hol tart a projektje.

+
+
+
+ +
+

Modern Technológia

+

React, TypeScript, Tailwind. A legmodernebb eszközöket használjuk a gyors és biztonságos működésért.

+
+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/Admin.tsx b/pages/Admin.tsx new file mode 100644 index 0000000..390c57c --- /dev/null +++ b/pages/Admin.tsx @@ -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([]); + const [orders, setOrders] = useState([]); + const [plans, setPlans] = useState([]); + const [loadingData, setLoadingData] = useState(false); + const [visitorStats, setVisitorStats] = useState({ month: 0, week: 0 }); + const [statusUpdating, setStatusUpdating] = useState(null); + const [planSaving, setPlanSaving] = useState(null); + const [errorMsg, setErrorMsg] = useState(null); + + const [viewOrder, setViewOrder] = useState(null); + const [generatedPrompts, setGeneratedPrompts] = useState([]); + const [copiedIndex, setCopiedIndex] = useState(null); + const [demoUrlInput, setDemoUrlInput] = useState(''); + const [savingDemoUrl, setSavingDemoUrl] = useState(false); + const [emailSending, setEmailSending] = useState(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 {c.label}; + }; + + if (loading) return
Admin felület...
; + if (!isAdmin) return null; + + return ( +
+
+
+
+
+ + Admin Access +
+

Vezérlőpult

+
+ +
+ +
+ {[ + { 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) => ( + + ))} +
+ +
+ {activeTab === 'overview' && ( +
+
+

Látogatók

+

{visitorStats.month}

+
+
+

Rendelések

+

{orders.length}

+
+
+

Regisztrációk

+

{users.length}

+
+
+ )} + + {activeTab === 'users' && ( +
+ + + + + + + + + + + {users.map(u => ( + + + + + + + ))} + +
Felhasználó NeveE-mailSzintRegisztráció
+ {u.last_name || ''} {u.first_name || ''} + {(!u.last_name && !u.first_name) && Nincs megadva} + + {u.email} + + {u.role} + {new Date(u.created_at).toLocaleDateString('hu-HU')}
+
+ )} + + {activeTab === 'orders' && ( +
+ + + + + + + + + + + {orders.map(o => ( + + + + + + + ))} + +
ÜgyfélCsomagStátuszMűvelet
+

{o.customer}

+

{o.email}

+
{o.package} + +
+
+ )} + + {activeTab === 'plans' && ( +
+ {plans.map((p, i) => ( +
+ { 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" /> + +
+ +
+ +
+ + )} +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/Dashboard.tsx b/pages/Dashboard.tsx new file mode 100644 index 0000000..178c878 --- /dev/null +++ b/pages/Dashboard.tsx @@ -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(null); + const [orders, setOrders] = useState([]); + const [invoices, setInvoices] = useState([]); + const [loadingOrders, setLoadingOrders] = useState(true); + const [showSettings, setShowSettings] = useState(false); + + const [feedbackModalOpen, setFeedbackModalOpen] = useState(false); + const [selectedOrderId, setSelectedOrderId] = useState(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 ( +
+ ); +}; \ No newline at end of file diff --git a/pages/FAQ.tsx b/pages/FAQ.tsx new file mode 100644 index 0000000..1d97b63 --- /dev/null +++ b/pages/FAQ.tsx @@ -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 = ({ question, answer }) => { + const [isOpen, setIsOpen] = useState(false); + return ( +
+ +
+

{answer}

+
+
+ ); +}; + +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 ( +
+
+ + +
+
+ +
+

Gyakori Kérdések

+

+ Összeszedtük a leggyakrabban felmerülő kérdéseket, hogy segítsünk a döntésben. +

+
+ +
+ {faqs.map((faq, index) => ( + + ))} +
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/Home.tsx b/pages/Home.tsx new file mode 100644 index 0000000..cf1c4bd --- /dev/null +++ b/pages/Home.tsx @@ -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(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 ( +
+ {/* Hero Section */} +
+ {/* Background Elements */} +
+
+ {/* Subtle texture overlay */} +
+ {/* Animated glow effects */} +
+
+
+ +
+
+ Innovatív Megoldások Neked +
+ +

+ Motion Web Stúdió +

+ +

+ Vigye vállalkozását a következő szintre +

+ +

+ A MotionWeb csapata prémium minőségű weboldalakat és webáruházakat fejleszt, amelyek nemcsak szépek, de vevőket is hoznak. +

+ +
+ + + + + + +
+
+
+ + {/* Services Section */} +
+
+
+

Szolgáltatásaink

+

+ Minden, ami a sikeres online jelenléthez kell +

+

+ Teljeskörű digitális kivitelezés a tervezéstől az üzemeltetésig. +

+
+ +
+ + + + +
+
+
+ + {/* References Section */} +
+
+
+

Referenciák

+

+ Tekintsd meg, milyen modern és reszponzív weboldalakat készítünk ügyfeleink számára. +

+
+ +
+ {/* Card 1: SweetCraving */} +
+
+ SweetCraving Desszertműhely +
+
+

SweetCraving Desszertműhely

+

Kézműves desszertek – Landing Page csomag

+

+ A SweetCraving számára egy fiatalos, barátságos hangulatú bemutatkozó oldalt hoztunk létre. +

+ +
+ Színek: +
+
+
+
+ +
+ + + +
+
+
+ + {/* Card 2: BlueWave */} +
+
+ BlueWave Solar Kft. +
+
+

BlueWave Solar Kft.

+

Zöld energia megoldások – Pro Web csomag

+

+ A BlueWave Solar számára modern, üzleti stílusú, reszponzív weboldalt készítettünk. +

+ +
+ Színek: +
+
+
+
+ +
+ + + +
+
+
+ + {/* Card 3: Steelguard */} +
+
+ Steelguard Biztonságtechnika +
+
+

Steelguard Biztonságtechnika

+

Biztonságtechnika és IT rendszerek – Enterprise csomag

+

+ A Steelguard számára technológiai, sötét tónusú weboldalt készítettünk. +

+ +
+ Színek: +
+
+
+
+ +
+ + + +
+
+
+
+
+
+ + {/* Products/Packages Section */} +
+
+
+

Csomagajánlataink

+

+ Átlátható árazás, rejtett költségek nélkül. Válassza az Ön céljaihoz leginkább illeszkedő csomagot. +

+
+ +
+ {packages.map((pkg, index) => ( +
+ {pkg.isPopular && ( +
+ Legnépszerűbb +
+ )} + +
+

{pkg.name}

+

{pkg.desc}

+
+ +
+ {pkg.price} + {pkg.price.includes('Ft') && + ÁFA} +
+ +
    + {pkg.features.map((feature, i) => ( +
  • + + {feature} +
  • + ))} +
+ +
+ + + +
+
+ ))} +
+
+
+ + {/* Process Section */} + + + {/* Order Form Section */} +
+
+ +
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/Privacy.tsx b/pages/Privacy.tsx new file mode 100644 index 0000000..fbb5a9f --- /dev/null +++ b/pages/Privacy.tsx @@ -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 ( +
+
+ + +
+ {/* Header Section */} +
+
+
+ +
+
+

Adatkezelési Tájékoztató

+
+ + MotionWeb + + + Hatályos: 2025. január 1-től + +
+
+
+
+ + {/* Content Sections */} +
+
+ + {/* Section 1 */} +
+
+ + {/* Section 2 */} +
+

+ 2 + Az adatkezelés jogalapja +

+
+

Az adatkezelés a GDPR 6. cikk (1) bekezdése alapján történik, az alábbi jogalapokon:

+
    + {[ + '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) => ( +
  • + + {item} +
  • + ))} +
+

+ 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. +

+

A MotionWeb nem végez marketing célú adatkezelést.

+
+
+ + {/* Section 3 */} +
+

+ 3 + Kezelt személyes adatok köre +

+
+
+

+ Kapcsolatfelvétel és rendelés +

+
    + {['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) => ( +
  • +
    {item} +
  • + ))} +
+
+
+

+ Felhasználói fiók esetén +

+
    + {['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) => ( +
  • +
    {item} +
  • + ))} +
+
+
+

+ Fizetés +

+

+ A MotionWeb nem kezel bankkártya-adatokat. A fizetés a Stripe rendszerén keresztül történik, amelyhez az érintett átirányításra kerül. +

+
+
+
+ + {/* Section 4 */} +
+

+ 4 + Adattárolás és adatfeldolgozók +

+
+
+

Adattárolás helye

+
    +
  • Supabase – adatbázis és felhasználói adatok (EU)
  • +
  • VPS szerver – weboldalak kiszolgálása (EU)
  • +
+
+
+

E-mail küldés

+
    +
  • Resend – tranzakciós e-mailek
  • +
  • Supabase – fiókregisztráció és jelszó-visszaállítás
  • +
+
+
+

Fizetési szolgáltató

+
    +
  • Stripe Payments Europe Ltd.
  • +
+
+
+

+ Az adatfeldolgozók kiválasztása során az adatkezelő kizárólag a GDPR-nak megfelelő szolgáltatókat veszi igénybe. +

+
+ + {/* Section 5 */} +
+

+ 5 + Analitikai adatok +

+
+

A MotionWeb kizárólag alapvető statisztikai adatokat gyűjt Supabase segítségével, így különösen:

+
    + {['weboldal-látogatások száma', 'regisztrált felhasználók száma', 'leadott rendelések száma'].map((item, i) => ( +
  • + {item} +
  • + ))} +
+

Az analitikai adatok nem alkalmasak az érintettek azonosítására, és nem szolgálnak marketing vagy profilalkotási célokat.

+
+
+ + {/* Section 6 */} +
+

+ 6 + Cookie-k (sütik) +

+
+
+ +
+
+

A weboldal cookie-kat alkalmaz a megfelelő működés, a felhasználói élmény és a biztonság érdekében.

+

A cookie-k használatához az érintett hozzájárulása szükséges, amelyet a weboldalon megjelenő cookie banner biztosít.

+

Marketing célú cookie-k alkalmazására nem kerül sor.

+
+
+
+ + {/* Section 7 */} +
+

+ 7 + Adatmegőrzés időtartama +

+
+ {[ + { 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) => ( +
+

{item.label}

+

{item.val}

+
+ ))} +
+
+ + {/* Section 8 */} +
+

+ 8 + Az érintettek jogai +

+
+

Az érintett jogosult:

+
+ {[ + '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) => ( +
+
+ {item} +
+ ))} +
+

+ A kérelmek benyújthatók az alábbi e-mail címen:
+ motionstudiohq@gmail.com +

+
+
+ + {/* Section 9 */} +
+

+ 9 + Jogorvoslati lehetőségek +

+
+
+ +
+
+

Amennyiben az érintett megítélése szerint személyes adatainak kezelése jogsértő, jogosult panaszt tenni:

+

Nemzeti Adatvédelmi és Információszabadság Hatóság (NAIH)

+

Weboldal: https://www.naih.hu

+
+
+
+ + {/* Section 10 */} +
+

+ 10 + Adatbiztonság +

+
+
+ +
+

+ 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. +

+
+
+ + {/* Section 11 */} +
+

+ 11 + Záró rendelkezések +

+
+

Az adatkezelő fenntartja a jogot jelen adatkezelési tájékoztató módosítására.

+

A módosítás a weboldalon történő közzététellel lép hatályba.

+
+
+ +
+
+ + {/* Footer Info */} +
+

+ Utolsó frissítés: 2025. január +

+

+ MotionWeb © {new Date().getFullYear()} - Minden jog fenntartva. +

+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/Products.tsx b/pages/Products.tsx new file mode 100644 index 0000000..e44cccc --- /dev/null +++ b/pages/Products.tsx @@ -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(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 ( +
+
+
+

Csomagajánlataink

+

+ Átlátható árazás, rejtett költségek nélkül. Válassza az Ön céljaihoz leginkább illeszkedő csomagot. +

+
+ +
+ {packages.map((pkg, index) => ( +
+ {pkg.isPopular && ( +
+ Legnépszerűbb +
+ )} + +
+

{pkg.name}

+

{pkg.desc}

+
+ +
+ {pkg.price} + {pkg.price.includes('Ft') && + ÁFA} +
+ +
    + {pkg.features.map((feature, i) => ( +
  • + + {feature} +
  • + ))} +
+ +
+ + + +
+
+ ))} +
+ +
+
+

Egyedi igényei vannak?

+

+ Nem találja a megfelelő csomagot? Készítünk Önnek egy teljesen személyre szabott ajánlatot. +

+
+ + + +
+
+
+ ); +}; diff --git a/pages/References.tsx b/pages/References.tsx new file mode 100644 index 0000000..9429387 --- /dev/null +++ b/pages/References.tsx @@ -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 ( +
+
+
+

Referenciáink

+

+ Büszkék vagyunk rá, hogy ügyfeleink álmait digitális valósággá formálhatjuk. Nézze meg korábbi munkáinkat! +

+
+ +
+ {projects.map((project) => ( +
+ {project.title} + +
+ {project.category} +

{project.title}

+

{project.description}

+ + {project.link ? ( + + Megtekintés + + ) : ( + Hamarosan elérhető + )} +
+
+ ))} +
+ +
+

+ "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." +

+
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/SUPABASE_SETUP.sql b/pages/SUPABASE_SETUP.sql new file mode 100644 index 0000000..928e689 --- /dev/null +++ b/pages/SUPABASE_SETUP.sql @@ -0,0 +1 @@ +nb$ |ڱ,Gb \ No newline at end of file diff --git a/pages/Services.tsx b/pages/Services.tsx new file mode 100644 index 0000000..f92f4a3 --- /dev/null +++ b/pages/Services.tsx @@ -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 ( +
+
+
+

Szolgáltatásaink

+

+ 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. +

+
+
+ +
+
+ {services.map((service, index) => ( +
+
+ +
+

{service.title}

+

+ {service.desc} +

+
+ ))} +
+
+ + {/* Process Section */} +
+
+

A Munkafolyamatunk

+
+ {[ + { 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) => ( +
+
{item.step}
+
+

{item.title}

+

{item.desc}

+
+
+ ))} +
+
+ + + +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/Terms.tsx b/pages/Terms.tsx new file mode 100644 index 0000000..2f3351b --- /dev/null +++ b/pages/Terms.tsx @@ -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 ( +
+
+ + +
+ {/* Header Section */} +
+
+
+ +
+
+

Általános Szerződési Feltételek

+
+ + MotionWeb + + + Hatályos: 2025. január 1-től + +
+
+
+
+ + {/* Content Sections */} +
+
+ + {/* Section 1 */} +
+
+

+ 1 + A szolgáltató adatai +

+
+
+
+

Szolgáltató neve

+

Balogh Bence Benedek egyéni vállalkozó

+
+
+

Projekt / márkanév

+

MotionWeb

+
+
+
+
+ +
+

Weboldal

+ https://motionweb.hu +
+
+
+ +
+

Kapcsolattartási e-mail

+ motionstudiohq@gmail.com +
+
+
+
+

+ 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. +

+
+
+
+ + {/* Section 2 */} +
+

+ 2 + Az ÁSZF hatálya +

+
+

Jelen Általános Szerződési Feltételek (ÁSZF) a MotionWeb weboldalon keresztül megrendelt valamennyi szolgáltatásra vonatkoznak.

+

+ 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. +

+
+
+ + {/* Section 3 */} +
+

+ 3 + A szolgáltatás jellege +

+
+

A MotionWeb fix csomagárakon kínál weboldalkészítési szolgáltatásokat.

+

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.

+

A MotionWeb nem nyújt egyedi ajánlattételt.

+
+
+ + {/* Section 4 */} +
+

+ 4 + Szerződés létrejötte +

+

A szerződés az alábbi lépések teljesülésével jön létre:

+
    + {['a megrendelő kiválasztja a szolgáltatási csomagot,', 'kitölti a rendelési űrlapot,', 'megfizeti az előleget.'].map((step, i) => ( +
  • + {i+1} + {step} +
  • + ))} +
+

A szerződés létrejöttével a MotionWeb megkezdi a demó weboldal elkészítését.

+
+ + {/* Section 5 */} +
+

+ 5 + Előleg +

+
+

Az előleg a szolgáltatás megkezdésének feltétele.

+

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.

+

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.

+
+
+ + {/* Section 6 */} +
+

+ 6 + Demó weboldal és visszajelzés +

+
+

A MotionWeb a demó weboldal elkészültéről a megrendelőt e-mailben értesíti.

+

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.

+

A demó weboldal visszajelzésére összesen 1 hónap áll rendelkezésre.

+

A MotionWeb a visszajelzés hiánya esetén:

+
+ {['az elkészülést követően,', '1 hét elteltével,', '2 hét elteltével'].map((time, i) => ( + {time} + ))} +
+

emlékeztető e-mailt küld.

+

+ 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. +

+
+
+ + {/* Section 7 */} +
+

+ 7 + Módosítási körök +

+
+

A csomagár legfeljebb két (2) módosítási kört tartalmaz.

+

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.

+
+
+ + {/* Section 8 */} +
+

+ 8 + Jóváhagyás és végfizetés +

+
+

A demó weboldal jóváhagyását követően a megrendelő köteles megfizetni a fennmaradó díjat.

+

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.

+

A végfizetés elmaradása esetén a végleges weboldal fejlesztése és élesítése nem történik meg.

+
+
+ + {/* Section 9 */} +
+

+ 9 + Határidők +

+
+

A MotionWeb nem vállal konkrét teljesítési határidőt.

+

A weboldalon vagy kommunikáció során feltüntetett időtartamok kizárólag tájékoztató jellegű becslések.

+

+ A megrendelő tudomásul veszi, hogy határidő túllépésből eredő igényt nem érvényesíthet. +

+
+
+ + {/* Section 10 */} +
+

+ 10 + Admin felület és utólagos módosítások +

+
+

Az admin felület kizárólag azokat a funkciókat tartalmazza, amelyek a megrendelés során előzetesen egyeztetésre kerültek.

+

A megrendelő kizárólag az admin felületen elérhető funkciók körében jogosult önálló módosításokra.

+

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.

+

Dizájnmódosítás, új funkció, új aloldal vagy admin bővítés külön megrendelésnek minősül.

+

A külön megrendelések egyedi árazás alapján, előzetes befizetést követően valósulnak meg.

+

A MotionWeb nem nyújt folyamatos technikai támogatási vagy karbantartási szolgáltatást.

+
+
+ + {/* Section 11 */} +
+

+ 11 + Tulajdonjog és referenciahasználat +

+
+

A weboldal forráskódja és tartalma a teljes díj megfizetéséig a MotionWeb tulajdonában marad.

+

A végfizetést követően a megrendelő jogosult a weboldalt használni.

+

A MotionWeb jogosult az elkészült weboldalt referenciaként, anonim módon, a megrendelő nevének feltüntetése nélkül bemutatni.

+
+
+ + {/* Section 12 */} +
+

+ 12 + Felelősségkorlátozás +

+
+

A MotionWeb nem vállal felelősséget:

+
    + {['elmaradt haszonért,', 'közvetett károkért,', 'üzleti veszteségekért.'].map((item, i) => ( +
  • + {item} +
  • + ))} +
+

A szolgáltatás nem garantál üzleti eredményt.

+
+
+ + {/* Section 13 */} +
+

+ 13 + Elállási jog kizárása +

+
+

A szolgáltatás digitális szolgáltatásnek minősül.

+

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.

+
+
+ + {/* Section 14 */} +
+

+ 14 + Ügyfélkör +

+

A MotionWeb szolgáltatásait magánszemélyek és vállalkozások egyaránt igénybe vehetik.

+
+ + {/* Section 15 */} +
+

+ 15 + Jogvita és irányadó jog +

+
+

Jelen ÁSZF-re a magyar jog az irányadó.

+

Jogvita esetén a szolgáltató székhelye szerinti bíróság rendelkezik illetékességgel.

+
+
+ + {/* Section 16 */} +
+

+ 16 + Záró rendelkezések +

+
+

A MotionWeb fenntartja a jogot jelen ÁSZF módosítására.

+

A módosítás a weboldalon történő közzététellel lép hatályba.

+
+
+ +
+
+ + {/* Footer Info */} +
+
+ + Biztonságos Adatkezelés & Jogi Megfelelőség +
+

+ MotionWeb © {new Date().getFullYear()} - Minden jog fenntartva. +

+
+
+
+
+ ); +}; diff --git a/pages/auth/ForgotPassword.tsx b/pages/auth/ForgotPassword.tsx new file mode 100644 index 0000000..3b94a41 --- /dev/null +++ b/pages/auth/ForgotPassword.tsx @@ -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(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 ( +
+
+
+

Jelszó visszaállítása

+

Küldünk egy linket a jelszava megváltoztatásához.

+
+ +
+ {success ? ( +
+
+ +
+

E-mail elküldve!

+

+ Ellenőrizze a(z) {email} postaládáját a visszaállításhoz szükséges linkért. +

+ + + +
+ ) : ( +
+ {error && ( +
+ +

{error}

+
+ )} + +
+ +
+ + 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" + /> +
+
+ +
+ +
+ +
+ + Vissza a bejelentkezéshez + +
+
+ )} +
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/auth/Login.tsx b/pages/auth/Login.tsx new file mode 100644 index 0000000..6a37641 --- /dev/null +++ b/pages/auth/Login.tsx @@ -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(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 ( +
+
+
+

Bejelentkezés

+

Jelentkezzen be fiókjába a folytatáshoz.

+
+ +
+
+ {error && ( +
+
+
+

{error}

+
+ {needsConfirmation && ( +
+ +
+ )} +
+ )} + + {resendSuccess && ( +
+
+

A megerősítő e-mailt újraküldtük!

+
+ )} + +
+ +
+ 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" /> +
+
+ +
+
+ + Elfelejtette? +
+
+ 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" /> +
+
+ +
+ +
+
+ +
+
Nincs még fiókod?
+
+ Regisztrálj ingyenesen +
+
+
+ +
+ + Vissza a főoldalra + +
+
+
+ ); +} \ No newline at end of file diff --git a/pages/auth/Register.tsx b/pages/auth/Register.tsx new file mode 100644 index 0000000..fa8a315 --- /dev/null +++ b/pages/auth/Register.tsx @@ -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(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 ( +
+
+
+ +
+

E-mail kiküldve!

+

+ Küldtünk egy megerősítő linket a(z) {email} e-mail címre a Resend szolgáltatón keresztül. A belépéshez kérjük, kattintson a levélben található linkre. +

+ +
+
+
+

Nem találja a levelet?

+

+ Nézze meg a Spam vagy a Promóciók mappát is. Ha 5 percen belül nem érkezik meg, próbálja meg újra a bejelentkezési oldalon az újraküldést. +

+
+
+ +
+ + + +
+
+
+ ); + } + + return ( +
+
+
+

Regisztráció

+

+ Csatlakozzon a MotionWeb közösségéhez. +

+
+ +
+
+ {error && ( +
+
+
+
+ {error} +
+
+ )} + +
+
+ + 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" /> +
+
+ + 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" /> +
+
+ +
+ + 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" /> +
+ +
+ + 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" /> +
+ +
+
+ + 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="••••••••" /> +
+
+ + 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="••••••••" /> +
+
+ +
+ setPrivacyAccepted(e.target.checked)} className="h-5 w-5 text-primary focus:ring-primary border-gray-300 rounded-lg cursor-pointer mt-0.5" /> +
+ +
+
+ +
+ +
+
+ +
+ Már van fiókja? + Jelentkezzen be +
+
+ +
+ + Vissza a főoldalra + +
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/auth/ResetPassword.tsx b/pages/auth/ResetPassword.tsx new file mode 100644 index 0000000..d0b953c --- /dev/null +++ b/pages/auth/ResetPassword.tsx @@ -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(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 ( +
+
+
+

Új jelszó megadása

+

Kérjük, adja meg az új biztonságos jelszavát.

+
+ +
+ {success ? ( +
+
+ +
+

Jelszó sikeresen módosítva!

+

Most már bejelentkezhet az új jelszavával.

+ +
+ ) : ( +
+ {error && ( +
+ +

{error}

+
+ )} + +
+
+ +
+ + 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" /> +
+
+ +
+ +
+ + 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" /> +
+
+
+ +
+ +
+
+ )} +
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/demos/BlueWave.tsx b/pages/demos/BlueWave.tsx new file mode 100644 index 0000000..82e40e7 --- /dev/null +++ b/pages/demos/BlueWave.tsx @@ -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 ( +
+ {/* Return to Main Site */} +
+ + + +
+ + {/* Top Bar */} +
+
+
+ +36 30 999 8888 + info@bluewave-solar.demo +
+ +
+
+ + {/* Navbar */} + + + {/* Main Content Area - Simulating Routing with smooth fade */} +
+
+ + {/* HOME PAGE */} + {activePage === 'home' && ( + <> +
+
+ Solar Panels +
+
+
+
+
+ Hivatalos állami kivitelező partner +
+

+ Váltson tiszta energiára
+ a jövőjéért +

+

+ 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. +

+
+ + +
+ +
+
+
1000+ elégedett ügyfél +
+
+
Országos lefedettség +
+
+
+
+
+ +
+
+
+ {[ + { 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) => ( +
+
+ +
+

{item.title}

+

{item.desc}

+
+ ))} +
+
+ +
+
+

Miért válassza a BlueWave-et?

+

Szakértelem, minőség és megbízhatóság egy helyen.

+
+ +
+ Engineer +
+ {[ + { 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) => ( +
+
+ {i + 1} +
+
+

{feat.title}

+

{feat.text}

+
+
+ ))} + +
+
+
+
+ + )} + + {/* SERVICES PAGE */} + {activePage === 'services' && ( +
+
+
+ Megoldásaink +

Minden igényre van válaszunk

+

Legyen szó kis családi házról vagy hatalmas ipari létesítményről, mi megtaláljuk az optimális rendszert.

+
+ +
+ {/* Residential */} +
+
+
+ Residential +
+ Lakossági +

Családi Házak

+
+
+
+

+ Csökkentse otthona energiafüggőségét és növelje ingatlana értékét. Rendszereink diszkrétek, esztétikusak és okosotthon-kompatibilisek. +

+
+ {['Rezsi nullázása', 'Hibrid Inverterek', 'Okos monitorozás', 'Elektromos autó töltés'].map((item, i) => ( +
+ {item} +
+ ))} +
+ +
+
+ + {/* Industrial */} +
+
+
+ Commercial +
+ Ipari +

Vállalatok & Üzemek

+
+
+
+

+ 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. +

+
+ {['Gyors megtérülés', '50kW - 5MW teljesítmény', 'ESG megfelelőség', 'Saját transzformátor'].map((item, i) => ( +
+ {item} +
+ ))} +
+ +
+
+
+
+
+ )} + + {/* ABOUT PAGE */} + {activePage === 'about' && ( +
+
+
+ +
+

A BlueWave Küldetése

+

+ 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. +

+ +
+
+
+
500+
+
Telepített rendszer
+
+
+
15MW
+
Össz teljesítmény
+
+
+
100%
+
Ügyfél elégedettség
+
+
+ {/* Background decorations */} +
+
+ +
+
+

Minőség

+

Kizárólag minősített, prémium gyártók termékeit használjuk, hogy rendszere évtizedekig működjön.

+
+
+

Innováció

+

Folyamatos követjük a technológia fejlődését, hogy a legmodernebb megoldásokat kínálhassuk.

+
+
+

Garancia

+

A kivitelezésre és a termékekre is kiemelkedő garanciális feltételeket biztosítunk.

+
+
+
+
+ )} + + {/* CONTACT PAGE */} + {activePage === 'contact' && ( +
+
+
+ {/* Left Panel */} +
+
+

Lépjen velünk kapcsolatba

+

+ 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. +

+
+
+ + +36 30 999 8888 +
+
+ + info@bluewave-solar.demo +
+
+ + 1117 Budapest, Napelem u. 1. +
+
+
+
+

+ Nyitvatartás: H-P 8:00 - 17:00 +

+
+ {/* Circle decorations */} +
+
+
+ + {/* Right Panel - Form */} +
+

Ingyenes Ajánlatkérés

+
+
+
+ + +
+
+ + +
+
+
+ + +
+ +
+
+ Gyors Kalkulátor Adatok +
+
+
+ + +
+
+ + +
+
+
+ +
+ +

+ Az ajánlatkérés nem jár kötelezettséggel. Adatvédelmi irányelveinket elfogadom. +

+
+
+
+
+
+
+ )} + +
+
+ + {/* Footer */} +
+
+
+
+
+
+ +
+ BlueWaveSolar +
+

+ Magyarország vezető napenergia szolgáltatója. Célunk, hogy ügyfeleink számára energiafüggetlenséget és fenntartható jövőt biztosítsunk. +

+
+
+

Navigáció

+
    +
  • setActivePage('home')} className="cursor-pointer hover:text-[#38bdf8] transition-colors">Főoldal
  • +
  • setActivePage('services')} className="cursor-pointer hover:text-[#38bdf8] transition-colors">Megoldások
  • +
  • setActivePage('about')} className="cursor-pointer hover:text-[#38bdf8] transition-colors">Rólunk
  • +
  • setActivePage('contact')} className="cursor-pointer hover:text-[#38bdf8] transition-colors">Kapcsolat
  • +
+
+
+

Kapcsolat

+
+

1117 Budapest, Napelem u. 1.

+

+36 30 999 8888

+

info@bluewave-solar.demo

+
+
+
+
+

© 2024 BlueWave Solar Kft. Minden jog fenntartva.

+

Demonstrációs weboldal - MotionWeb Pro csomag

+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/demos/Steelguard.tsx b/pages/demos/Steelguard.tsx new file mode 100644 index 0000000..829160a --- /dev/null +++ b/pages/demos/Steelguard.tsx @@ -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 ( +
+ + {/* INJECTED STYLES FOR ADVANCED ANIMATIONS */} + + + {/* Return to Main Site */} +
+ + + +
+ + {/* Enterprise Navbar */} + + + {/* Main Content Router Simulation */} +
+ + {/* HOME VIEW */} + {activeTab === 'home' && ( + <> +
+
+
+
+
+
+
+
+
+ +
+
+
+ + ESTABLISHING SECURE CONNECTION... +
+

+ TOTAL
+ DEFENSE +

+

+ Advanced AI-driven security architecture for enterprise infrastructure. Zero-trust protocols and predictive threat neutralization active. +

+
+ + +
+
+ +
+
+
+
+
+ Tech Visual +
+
+
+
+ +
+

Status

+

OPTIMAL_FLOW

+
+
+
+
+
+
+ +
+
+
+ {[ + { 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) => ( +
+
+
+ +
+

{item.title}

+

{item.desc}

+
+
+ ))} +
+
+
+ + )} + + {/* SOLUTIONS VIEW */} + {activeTab === 'solutions' && ( +
+
+
+
+

Strategic Solutions

+
+ +
+
+

+ We offer a multi-layered security stack designed to neutralize threats before they reach your perimeter. +

+
+ {[ + { 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) => ( +
+

{sol.title}

+

{sol.desc}

+
+ ))} +
+
+
+
+ Solution +
+
+
+
+ )} + + {/* HARDWARE VIEW */} + {activeTab === 'hardware' && ( +
+
+
+
+

Hardened Hardware

+
+ +
+ {[ + { 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) => ( +
+ {hw.name} +
+
+ + {hw.name} +
+ +
+
+ ))} +
+
+
+ )} + + {/* SECTORS VIEW */} + {activeTab === 'sectors' && ( +
+
+

Strategic Sectors

+

Steelguard protects the infrastructure that power nations.

+ +
+ {[ + { icon: Building2, label: "Enterprise", count: "400+ Nodes" }, + { icon: Globe, label: "Government", count: "12 Nations" }, + { icon: Shield, label: "Defense", count: "Active Mesh" } + ].map((sector, i) => ( +
+
+ +
+

{sector.label}

+ {sector.count} +
+ ))} +
+
+
+ )} + + {/* COMPANY VIEW */} + {activeTab === 'company' && ( +
+
+
+
+
+ Office +
+
+ Origins +

Engineering
Secured Future

+

+ 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. +

+
+
+

15

+

Global Hubs

+
+
+

850+

+

Architects

+
+
+
+
+
+
+ )} + + {/* MAP INTEGRATION */} + {activeTab === 'home' && ( +
+
+
+ +

Command Center HQ

+
+
+
+
+ +
+
+
+
+ )} + +
+ + {/* Footer / CTA */} +
+
+ {[ + { val: '500+', label: 'NODES ACTIVE' }, + { val: '0.01ms', label: 'LATENCY' }, + { val: '100%', label: 'UPTIME' }, + { val: 'SECURE', label: 'STATUS' } + ].map((stat, i) => ( +
+
{stat.val}
+
{stat.label}
+
+ ))} +
+ +
+
+ +
+

+ SECURE YOUR FUTURE +

+

+ Initiate contact with our security architects. Design a defense strategy leveraging next-gen technologies. +

+
+ + +
+
+
© 2024 STEELGUARD DEFENSE SYSTEMS.
+
SYSTEM_ID: MW-ENT-8842
+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/pages/demos/SweetCraving.tsx b/pages/demos/SweetCraving.tsx new file mode 100644 index 0000000..a551e48 --- /dev/null +++ b/pages/demos/SweetCraving.tsx @@ -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 ( +
+ {/* Return to Main Site Button */} +
+ + + +
+ + {/* Dynamic Navigation */} + + + {/* Hero Section */} +
+
+ Desszertműhely Hero +
+
+ +
+
+ + Kézműves Desszertműhely + +
+ +

+ Édes Álmok
+ + Süteménybe Zárva + +

+ +

+ Természetes alapanyagokból, szívvel-lélekkel készült desszertek minden alkalomra. +
Mert minden nap megérdemel egy kis kényeztetést. +

+ + +
+ + {/* Scroll Indicator */} +
+ +
+
+ + {/* About Section */} +
+
+
+
+
+ Pék és Cukrászmunka + {/* Floating Badge */} +
+
+

5+

+

Év Tapasztalat

+
+
+
+ +
+

+ A szenvedélyünk
+ a minőség +

+

+ 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. +

+ +
+ {[ + { 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) => ( +
+
+ +
+ {item.text} +
+ ))} +
+
+
+
+
+ + {/* Menu Grid */} + + + {/* Testimonials (Simple) */} +
+
+ +

Vendégeink mondták

+
+ "A legfinomabb macaron, amit valaha ettem! A pisztáciás egyszerűen mennyei. Mindenkinek csak ajánlani tudom a helyet, igazi ékszerdoboz." +
+ — Kovács Anna, Budapest +
+
+ + {/* Contact & Footer */} +
+ {/* Background Pattern */} +
+ +
+

Látogass el hozzánk

+ +
+
+
+ +
+

Címünk

+

1052 Budapest,
Petőfi Sándor utca 12.

+
+
+
+ +
+

Nyitvatartás

+

H-P: 08:00 - 19:00
Szo-V: 09:00 - 16:00

+
+
+
+ +
+

Kapcsolat

+

+36 1 234 5678
hello@sweetcraving.hu

+
+
+ +
+ + + +
+ +
+ © 2024 SweetCraving. Ez egy demonstrációs weboldal a MotionWeb referenciáihoz. +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/schema.sql b/schema.sql new file mode 100644 index 0000000..e69de29 diff --git a/supabase_schema.sql b/supabase_schema.sql new file mode 100644 index 0000000..faae59a --- /dev/null +++ b/supabase_schema.sql @@ -0,0 +1,2 @@ + +zZ^rب$ \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2c6eed5 --- /dev/null +++ b/tsconfig.json @@ -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 + } +} \ No newline at end of file diff --git a/types.ts b/types.ts new file mode 100644 index 0000000..2b52392 --- /dev/null +++ b/types.ts @@ -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; +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..ee5fb8d --- /dev/null +++ b/vite.config.ts @@ -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, '.'), + } + } + }; +});
+ setShowSettings(false)} userProfile={profile} onUpdate={fetchData} /> + setFeedbackModalOpen(false)} onSubmit={handleSubmitFeedback} loading={feedbackLoading} /> + +
+
+
+
+

Fiók Áttekintése

+

Üdvözöljük, {getFullName()}!

+
+
+ +
+
+ +
+
+ {/* ORDERS COLUMN */} +
+
+

+ Aktív Projektjeim +

+
+ + {loadingOrders ? ( +
+ ) : orders.length > 0 ? ( +
+ {orders.map(order => { + const statusConfig = getStatusConfig(order.status); + const needsFeedback = ['pending_feedback', 'waiting_feedback', 'feedback'].includes(order.status); + + return ( + + ); + })} +
+ ) : ( +
+

Még nincsenek leadott rendelései.

+ +
+ )} +
+ + {/* SETTINGS & INVOICES COLUMN */} +
+
+
+

Beállítások

+ +
+
+
+

E-mail

+

{user?.email}

+
+ +
+
+ +
+

Számlák

+ {invoices.length > 0 ? ( +
+ {invoices.map(inv => ( +
+
+ + {inv.invoice_number} +
+ +
+ ))} +
+ ) :

Nincs számlázott tétel.

} +
+
+
+ +
+ +
+
+
+
+