Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1aa85540e6 | |||
| 64ee8f8603 | |||
| 64770ddbcd |
Generated
+25
@@ -39,6 +39,7 @@
|
|||||||
"@tanstack/react-query": "^5.96.1",
|
"@tanstack/react-query": "^5.96.1",
|
||||||
"@tanstack/react-query-devtools": "^5.100.9",
|
"@tanstack/react-query-devtools": "^5.100.9",
|
||||||
"axios": "^1.14.0",
|
"axios": "^1.14.0",
|
||||||
|
"axios-retry": "^4.5.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
@@ -3769,6 +3770,18 @@
|
|||||||
"proxy-from-env": "^2.1.0"
|
"proxy-from-env": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/axios-retry": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"is-retry-allowed": "^2.2.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"axios": "0.x || 1.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bail": {
|
"node_modules/bail": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
|
||||||
@@ -5695,6 +5708,18 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/is-retry-allowed": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/isexe": {
|
"node_modules/isexe": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
"@tanstack/react-query": "^5.96.1",
|
"@tanstack/react-query": "^5.96.1",
|
||||||
"@tanstack/react-query-devtools": "^5.100.9",
|
"@tanstack/react-query-devtools": "^5.100.9",
|
||||||
"axios": "^1.14.0",
|
"axios": "^1.14.0",
|
||||||
|
"axios-retry": "^4.5.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,5 @@
|
|||||||
import { Toaster as Sonner } from "@/components/ui/sonner";
|
import { Toaster as Sonner } from "@/components/ui/sonner";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import { ThemeProvider } from "@/contexts/ThemeContext";
|
|
||||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||||
import { AnimatePresence } from "framer-motion";
|
import { AnimatePresence } from "framer-motion";
|
||||||
import { BrowserRouter, Route, Routes, useLocation } from "react-router-dom";
|
import { BrowserRouter, Route, Routes, useLocation } from "react-router-dom";
|
||||||
@@ -17,6 +16,7 @@ import Projects from "./pages/Projects";
|
|||||||
import Technologies from "./pages/Technologies";
|
import Technologies from "./pages/Technologies";
|
||||||
import { QueryProvider } from "./provider/QueryProvider";
|
import { QueryProvider } from "./provider/QueryProvider";
|
||||||
import OverviewPage from "./components/admin/dashboards/OverviewPage";
|
import OverviewPage from "./components/admin/dashboards/OverviewPage";
|
||||||
|
import { ThemeProvider } from "./provider/ThemeProvider";
|
||||||
|
|
||||||
function AnimatedRoutes() {
|
function AnimatedRoutes() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|||||||
@@ -1,11 +1,42 @@
|
|||||||
|
// src/api/axiosInstance.ts
|
||||||
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import axiosRetry from "axios-retry";
|
||||||
|
|
||||||
|
import { API_URL } from "./config";
|
||||||
|
|
||||||
|
import {
|
||||||
|
requestInterceptor,
|
||||||
|
responseSuccessInterceptor,
|
||||||
|
responseErrorInterceptor,
|
||||||
|
} from "./interceptors";
|
||||||
|
|
||||||
const axiosInstance = axios.create({
|
const axiosInstance = axios.create({
|
||||||
baseURL: import.meta.env.VITE_API_URL,
|
baseURL: API_URL,
|
||||||
timeout: 5000,
|
timeout: 10000,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
axiosInstance.interceptors.request.use(
|
||||||
|
requestInterceptor
|
||||||
|
);
|
||||||
|
|
||||||
|
axiosInstance.interceptors.response.use(
|
||||||
|
responseSuccessInterceptor,
|
||||||
|
responseErrorInterceptor
|
||||||
|
);
|
||||||
|
|
||||||
|
axiosRetry(axiosInstance, {
|
||||||
|
retries: 3,
|
||||||
|
retryDelay: axiosRetry.exponentialDelay,
|
||||||
|
|
||||||
|
retryCondition: (error) =>
|
||||||
|
axiosRetry.isNetworkOrIdempotentRequestError(
|
||||||
|
error
|
||||||
|
) ||
|
||||||
|
error.response?.status === 429,
|
||||||
|
});
|
||||||
|
|
||||||
export default axiosInstance;
|
export default axiosInstance;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
const apiUrl = import.meta.env.VITE_API_URL;
|
||||||
|
|
||||||
|
if (!apiUrl) {
|
||||||
|
throw new Error(
|
||||||
|
"Missing VITE_API_URL environment variable"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const API_URL = apiUrl;
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// src/api/errors.ts
|
||||||
|
|
||||||
|
import axios from "axios";
|
||||||
|
import { ApiError } from "./types";
|
||||||
|
|
||||||
|
export function normalizeApiError(error: unknown): ApiError {
|
||||||
|
if (!axios.isAxiosError(error)) {
|
||||||
|
return {
|
||||||
|
status: 0,
|
||||||
|
message: "Unexpected error occurred",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.code === "ECONNABORTED") {
|
||||||
|
return {
|
||||||
|
status: 408,
|
||||||
|
message: "Request timeout",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!error.response) {
|
||||||
|
return {
|
||||||
|
status: 0,
|
||||||
|
message: "Network error",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { status, data } = error.response;
|
||||||
|
|
||||||
|
return {
|
||||||
|
status,
|
||||||
|
message:
|
||||||
|
data?.message ||
|
||||||
|
data?.error ||
|
||||||
|
"Something went wrong",
|
||||||
|
code: data?.code,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { getAccessToken } from "@/lib/auth";
|
||||||
|
import { AxiosResponse, InternalAxiosRequestConfig } from "axios";
|
||||||
|
import { normalizeApiError } from "./errors";
|
||||||
|
|
||||||
|
export function requestInterceptor(config: InternalAxiosRequestConfig) {
|
||||||
|
const token = getAccessToken();
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const responseSuccessInterceptor = (response: AxiosResponse) => response;
|
||||||
|
|
||||||
|
export function responseErrorInterceptor(error: unknown) {
|
||||||
|
const normalizedError = normalizeApiError(error);
|
||||||
|
|
||||||
|
return Promise.reject(normalizedError);
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export interface ApiError {
|
||||||
|
status: number;
|
||||||
|
message: string;
|
||||||
|
code?: string;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useTheme } from "@/contexts/ThemeContext";
|
import { useTheme } from "@/hooks/useTheme";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
import { Moon, PanelLeftClose, PanelLeftOpen, Sun } from "lucide-react";
|
import { Moon, PanelLeftClose, PanelLeftOpen, Sun } from "lucide-react";
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
useDeleteProject,
|
useDeleteProject,
|
||||||
useProjects,
|
useProjects,
|
||||||
useUpdateProject,
|
useUpdateProject,
|
||||||
} from "@/hooks/queires/useProjects";
|
} from "@/hooks/queries/useProjects";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { T_projects } from "@/types/projects.type";
|
import { T_projects } from "@/types/projects.type";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useBlogs } from "@/hooks/queires/useBlogs";
|
import { useBlogs } from "@/hooks/queries/useBlogs";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { ArrowRight, Calendar, Clock, User } from "lucide-react";
|
import { ArrowRight, Calendar, Clock, User } from "lucide-react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import logo from "@/assets/logo.webp";
|
import logo from "@/assets/logo.webp";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useTheme } from "@/contexts/ThemeContext";
|
import { useTheme } from "@/hooks/useTheme";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
import { Menu, Moon, Sun, X } from "lucide-react";
|
import { Menu, Moon, Sun, X } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
@@ -28,7 +28,6 @@ export default function Navbar() {
|
|||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const { mode, accent, setAccent, toggleMode } = useTheme();
|
const { mode, accent, setAccent, toggleMode } = useTheme();
|
||||||
|
|
||||||
// Handle scroll effect
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
setIsScrolled(window.scrollY > 20);
|
setIsScrolled(window.scrollY > 20);
|
||||||
@@ -37,7 +36,6 @@ export default function Navbar() {
|
|||||||
return () => window.removeEventListener("scroll", handleScroll);
|
return () => window.removeEventListener("scroll", handleScroll);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Close mobile menu on resize
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
if (window.innerWidth >= 1024) {
|
if (window.innerWidth >= 1024) {
|
||||||
@@ -59,14 +57,11 @@ export default function Navbar() {
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="container mx-auto px-4 flex items-center justify-between">
|
<div className="container mx-auto px-4 flex items-center justify-between">
|
||||||
{/* Logo */}
|
|
||||||
<Link to="/" className="flex items-center gap-2">
|
<Link to="/" className="flex items-center gap-2">
|
||||||
<img src={logo} alt="TechZaa" className="h-16 w-auto" />
|
<img src={logo} alt="TechZaa" className="h-16 w-auto" />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{/* Desktop Navigation */}
|
|
||||||
<div className="hidden lg:flex items-center gap-8">
|
<div className="hidden lg:flex items-center gap-8">
|
||||||
{/* Nav Links */}
|
|
||||||
<ul className="flex items-center gap-6">
|
<ul className="flex items-center gap-6">
|
||||||
{navItems.map((item, index) => (
|
{navItems.map((item, index) => (
|
||||||
<motion.li
|
<motion.li
|
||||||
@@ -86,9 +81,7 @@ export default function Navbar() {
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{/* Theme Controls */}
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{/* Accent Color Picker */}
|
|
||||||
<div className="flex items-center gap-1.5 rounded-full glass p-1">
|
<div className="flex items-center gap-1.5 rounded-full glass p-1">
|
||||||
{accentColors.map((color) => (
|
{accentColors.map((color) => (
|
||||||
<motion.button
|
<motion.button
|
||||||
@@ -106,7 +99,6 @@ export default function Navbar() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Dark/Light Mode Toggle */}
|
|
||||||
<motion.button
|
<motion.button
|
||||||
onClick={toggleMode}
|
onClick={toggleMode}
|
||||||
className="p-1.5 rounded-full neon-glow transition-all"
|
className="p-1.5 rounded-full neon-glow transition-all"
|
||||||
@@ -140,7 +132,6 @@ export default function Navbar() {
|
|||||||
</motion.button>
|
</motion.button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* CTA Button */}
|
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, scale: 0.8 }}
|
initial={{ opacity: 0, scale: 0.8 }}
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
@@ -155,7 +146,6 @@ export default function Navbar() {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile Menu Button */}
|
|
||||||
<motion.button
|
<motion.button
|
||||||
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
||||||
className="lg:hidden p-2 rounded-lg glass"
|
className="lg:hidden p-2 rounded-lg glass"
|
||||||
@@ -187,7 +177,6 @@ export default function Navbar() {
|
|||||||
</motion.button>
|
</motion.button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile Menu */}
|
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{isMobileMenuOpen && (
|
{isMobileMenuOpen && (
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -198,7 +187,6 @@ export default function Navbar() {
|
|||||||
className="lg:hidden glass-strong mt-2 mx-4 rounded-2xl overflow-hidden"
|
className="lg:hidden glass-strong mt-2 mx-4 rounded-2xl overflow-hidden"
|
||||||
>
|
>
|
||||||
<div className="p-6 space-y-6">
|
<div className="p-6 space-y-6">
|
||||||
{/* Nav Links */}
|
|
||||||
<ul className="space-y-4">
|
<ul className="space-y-4">
|
||||||
{navItems.map((item, index) => (
|
{navItems.map((item, index) => (
|
||||||
<motion.li
|
<motion.li
|
||||||
@@ -218,9 +206,7 @@ export default function Navbar() {
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{/* Theme Controls */}
|
|
||||||
<div className="flex items-center justify-between pt-4 border-t border-border">
|
<div className="flex items-center justify-between pt-4 border-t border-border">
|
||||||
{/* Accent Colors */}
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{accentColors.map((color) => (
|
{accentColors.map((color) => (
|
||||||
<button
|
<button
|
||||||
@@ -236,7 +222,6 @@ export default function Navbar() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mode Toggle */}
|
|
||||||
<button
|
<button
|
||||||
onClick={toggleMode}
|
onClick={toggleMode}
|
||||||
className="p-2 rounded-full glass"
|
className="p-2 rounded-full glass"
|
||||||
@@ -250,7 +235,6 @@ export default function Navbar() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* CTA Button */}
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsMobileMenuOpen(false);
|
setIsMobileMenuOpen(false);
|
||||||
@@ -266,7 +250,6 @@ export default function Navbar() {
|
|||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</motion.nav>
|
</motion.nav>
|
||||||
|
|
||||||
{/* Contact Modal */}
|
|
||||||
<ContactModal
|
<ContactModal
|
||||||
isOpen={isModalOpen}
|
isOpen={isModalOpen}
|
||||||
onClose={() => setIsModalOpen(false)}
|
onClose={() => setIsModalOpen(false)}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useProjects } from "@/hooks/queires/useProjects";
|
import { useProjects } from "@/hooks/queries/useProjects";
|
||||||
import { motion, useInView } from "framer-motion";
|
import { motion, useInView } from "framer-motion";
|
||||||
import { ArrowRight } from "lucide-react";
|
import { ArrowRight } from "lucide-react";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
@@ -7,7 +7,6 @@ import { Link } from "react-router-dom";
|
|||||||
import PremiumBadge from "../shared/PremiumBadge";
|
import PremiumBadge from "../shared/PremiumBadge";
|
||||||
import { ProjectCard } from "./ProjectCard";
|
import { ProjectCard } from "./ProjectCard";
|
||||||
|
|
||||||
|
|
||||||
export default function ProjectsSection() {
|
export default function ProjectsSection() {
|
||||||
const { data: projectsData } = useProjects({
|
const { data: projectsData } = useProjects({
|
||||||
fields: "category, title, image, liveUrl",
|
fields: "category, title, image, liveUrl",
|
||||||
@@ -25,7 +24,6 @@ export default function ProjectsSection() {
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className="py-24 relative overflow-hidden bg-secondary/30"
|
className="py-24 relative overflow-hidden bg-secondary/30"
|
||||||
>
|
>
|
||||||
|
|
||||||
<div className="absolute inset-0 opacity-5">
|
<div className="absolute inset-0 opacity-5">
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0"
|
className="absolute inset-0"
|
||||||
@@ -37,7 +35,6 @@ export default function ProjectsSection() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="container mx-auto px-4 relative z-10">
|
<div className="container mx-auto px-4 relative z-10">
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||||
@@ -56,7 +53,6 @@ export default function ProjectsSection() {
|
|||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
|
|
||||||
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8 mb-16">
|
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8 mb-16">
|
||||||
{projects.map((project, index) => {
|
{projects.map((project, index) => {
|
||||||
return (
|
return (
|
||||||
@@ -70,7 +66,6 @@ export default function ProjectsSection() {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 30 }}
|
initial={{ opacity: 0, y: 30 }}
|
||||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useRef } from "react";
|
|||||||
import { Autoplay, Pagination } from "swiper/modules";
|
import { Autoplay, Pagination } from "swiper/modules";
|
||||||
import { Swiper, SwiperSlide } from "swiper/react";
|
import { Swiper, SwiperSlide } from "swiper/react";
|
||||||
|
|
||||||
import { useTeam } from "@/hooks/queires/useTeam";
|
import { useTeam } from "@/hooks/queries/useTeam";
|
||||||
import PremiumBadge from "../shared/PremiumBadge";
|
import PremiumBadge from "../shared/PremiumBadge";
|
||||||
|
|
||||||
export default function TeamSection() {
|
export default function TeamSection() {
|
||||||
@@ -17,23 +17,24 @@ export default function TeamSection() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="team" ref={ref} className="pt-20 1relative overflow-hidden">
|
<section id="team" ref={ref} className="pt-20 1relative overflow-hidden">
|
||||||
|
|
||||||
<div className="absolute inset-0 opacity-30">
|
<div className="absolute inset-0 opacity-30">
|
||||||
<div className="absolute top-0 right-1/4 w-72 h-72 bg-neon-purple/10 rounded-full blur-[120px]" />
|
<div className="absolute top-0 right-1/4 w-72 h-72 bg-neon-purple/10 rounded-full blur-[120px]" />
|
||||||
<div className="absolute bottom-0 left-1/4 w-64 h-64 bg-primary/10 rounded-full blur-xl" />
|
<div className="absolute bottom-0 left-1/4 w-64 h-64 bg-primary/10 rounded-full blur-xl" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="container mx-auto px-4 relative z-10">
|
<div className="container mx-auto px-4 relative z-10">
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
animate={isInView ? { opacity: 1, y: 0 } : {}}
|
||||||
transition={{ duration: 0.6 }}
|
transition={{ duration: 0.6 }}
|
||||||
className="text-center mb-16"
|
className="text-center mb-16"
|
||||||
>
|
>
|
||||||
<PremiumBadge text="Our experts"/>
|
<PremiumBadge text="Our experts" />
|
||||||
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold mb-4">
|
<h2 className="text-3xl md:text-4xl lg:text-5xl font-bold mb-4">
|
||||||
Meet Our <span className="text-transparent bg-clip-text bg-gradient-to-r from-primary to-accent-foreground">Team</span>
|
Meet Our{" "}
|
||||||
|
<span className="text-transparent bg-clip-text bg-gradient-to-r from-primary to-accent-foreground">
|
||||||
|
Team
|
||||||
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-muted-foreground max-w-2xl mx-auto text-lg">
|
<p className="text-muted-foreground max-w-2xl mx-auto text-lg">
|
||||||
A passionate team of innovators, designers, and developers.
|
A passionate team of innovators, designers, and developers.
|
||||||
@@ -81,7 +82,6 @@ export default function TeamSection() {
|
|||||||
className="absolute inset-0 w-full h-full object-cover object-top transition-transform duration-500 group-hover:scale-110"
|
className="absolute inset-0 w-full h-full object-cover object-top transition-transform duration-500 group-hover:scale-110"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 className="text-xl font-bold mb-1 group-hover:text-primary transition-colors">
|
<h3 className="text-xl font-bold mb-1 group-hover:text-primary transition-colors">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useReviews } from "@/hooks/queires/useReviews";
|
import { useReviews } from "@/hooks/queries/useReviews";
|
||||||
import Autoplay from "embla-carousel-autoplay";
|
import Autoplay from "embla-carousel-autoplay";
|
||||||
import useEmblaCarousel from "embla-carousel-react";
|
import useEmblaCarousel from "embla-carousel-react";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function getAccessToken() {
|
||||||
|
return localStorage.getItem("accessToken");
|
||||||
|
}
|
||||||
+1
-1
@@ -5,7 +5,7 @@ import { motion } from "framer-motion";
|
|||||||
import { ArrowLeft, Calendar, Clock, Search, User } from "lucide-react";
|
import { ArrowLeft, Calendar, Clock, Search, User } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { useBlogs } from "./../hooks/queires/useBlogs";
|
import { useBlogs } from "../hooks/queries/useBlogs";
|
||||||
|
|
||||||
const categories = [
|
const categories = [
|
||||||
"All",
|
"All",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import PageTransition from "@/components/home/PageTransition";
|
import PageTransition from "@/components/home/PageTransition";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { getRelatedPosts } from "@/data/blogData";
|
import { getRelatedPosts } from "@/data/blogData";
|
||||||
import { useBlogById } from "@/hooks/queires/useBlogs";
|
import { useBlogById } from "@/hooks/queries/useBlogs";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import PageTransition from "@/components/home/PageTransition";
|
import PageTransition from "@/components/home/PageTransition";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useProjectById } from "@/hooks/queires/useProjects";
|
import { useProjectById } from "@/hooks/queries/useProjects";
|
||||||
import { motion, useReducedMotion } from "framer-motion";
|
import { motion, useReducedMotion } from "framer-motion";
|
||||||
import {
|
import {
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import PageTransition from "@/components/home/PageTransition";
|
import PageTransition from "@/components/home/PageTransition";
|
||||||
import { ProjectCard } from "@/components/home/ProjectCard";
|
import { ProjectCard } from "@/components/home/ProjectCard";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useProjects } from "@/hooks/queires/useProjects";
|
import { useProjects } from "@/hooks/queries/useProjects";
|
||||||
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
|
||||||
import {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
|
|||||||
Reference in New Issue
Block a user