Initial push from server

This commit is contained in:
Manesh 2026-02-21 19:04:54 +00:00
parent 5371f76855
commit 89170d3eb3
632 changed files with 41089 additions and 0 deletions

13
.editorconfig Normal file
View File

@ -0,0 +1,13 @@
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

6
.eslintrc.json Normal file
View File

@ -0,0 +1,6 @@
{
"extends": "next/core-web-vitals",
"rules": {
"@next/next/no-img-element": "off"
}
}

2
.gitignore vendored
View File

@ -43,6 +43,8 @@ build/Release
node_modules/
jspm_packages/
.next/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

36
.gitignore copy Normal file
View File

@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

5
.prettierrc Normal file
View File

@ -0,0 +1,5 @@
{
"semi": true,
"singleQuote": true,
"printWidth": 200
}

41
App.tsx Normal file
View File

@ -0,0 +1,41 @@
'use client';
import { PropsWithChildren, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { IRootState } from '@/store';
import { toggleRTL, toggleTheme, toggleMenu, toggleLayout, toggleAnimation, toggleNavbar, toggleSemidark } from '@/store/themeConfigSlice';
import Loading from '@/components/layouts/loading';
import { getTranslation } from '@/i18n';
function App({ children }: PropsWithChildren) {
const themeConfig = useSelector((state: IRootState) => state.themeConfig);
const dispatch = useDispatch();
const { initLocale } = getTranslation();
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
dispatch(toggleTheme(localStorage.getItem('theme') || themeConfig.theme));
dispatch(toggleMenu(localStorage.getItem('menu') || themeConfig.menu));
dispatch(toggleLayout(localStorage.getItem('layout') || themeConfig.layout));
dispatch(toggleRTL(localStorage.getItem('rtlClass') || themeConfig.rtlClass));
dispatch(toggleAnimation(localStorage.getItem('animation') || themeConfig.animation));
dispatch(toggleNavbar(localStorage.getItem('navbar') || themeConfig.navbar));
dispatch(toggleSemidark(localStorage.getItem('semidark') || themeConfig.semidark));
// locale
initLocale(themeConfig.locale);
setIsLoading(false);
}, [dispatch, initLocale, themeConfig.theme, themeConfig.menu, themeConfig.layout, themeConfig.rtlClass, themeConfig.animation, themeConfig.navbar, themeConfig.locale, themeConfig.semidark]);
return (
<div
className={`${(themeConfig.sidebar && 'toggle-sidebar') || ''} ${themeConfig.menu} ${themeConfig.layout} ${
themeConfig.rtlClass
} main-section relative font-nunito text-sm font-normal antialiased`}
>
{isLoading ? <Loading /> : children}
</div>
);
}
export default App;

38
README copy.md Normal file
View File

@ -0,0 +1,38 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

View File

@ -0,0 +1,46 @@
import ComponentsAuthUnlockForm from '@/components/auth/components-auth-unlock-form';
// import LanguageDropdown from '@/components/language-dropdown';
import { Metadata } from 'next';
import React from 'react';
export const metadata: Metadata = {
title: 'Unlock Box',
};
const BoxedLockScreen = () => {
return (
<div>
<div className="absolute inset-0">
<img src="/assets/images/auth/bg-gradient.png" alt="image" className="h-full w-full object-cover" />
</div>
<div className="relative flex min-h-screen items-center justify-center bg-[url(/assets/images/auth/map.png)] bg-cover bg-center bg-no-repeat px-6 py-10 dark:bg-[#060818] sm:px-16">
<img src="/assets/images/auth/coming-soon-object1.png" alt="image" className="absolute left-0 top-1/2 h-full max-h-[893px] -translate-y-1/2" />
<img src="/assets/images/auth/coming-soon-object2.png" alt="image" className="absolute left-24 top-0 h-40 md:left-[30%]" />
<img src="/assets/images/auth/coming-soon-object3.png" alt="image" className="absolute right-0 top-0 h-[300px]" />
<img src="/assets/images/auth/polygon-object.svg" alt="image" className="absolute bottom-0 end-[28%]" />
<div className="relative w-full max-w-[870px] rounded-md bg-[linear-gradient(45deg,#fff9f9_0%,rgba(255,255,255,0)_25%,rgba(255,255,255,0)_75%,_#fff9f9_100%)] p-2 dark:bg-[linear-gradient(52.22deg,#0E1726_0%,rgba(14,23,38,0)_18.66%,rgba(14,23,38,0)_51.04%,rgba(14,23,38,0)_80.07%,#0E1726_100%)]">
<div className="relative flex flex-col justify-center rounded-md bg-white/60 px-6 py-20 backdrop-blur-lg dark:bg-black/50 lg:min-h-[758px]">
{/* <div className="absolute end-6 top-6">
<LanguageDropdown />
</div> */}
<div className="mx-auto w-full max-w-[440px]">
<div className="mb-10 flex items-center">
<div className="flex h-16 w-16 items-end justify-center overflow-hidden rounded-full bg-[#00AB55] ltr:mr-4 rtl:ml-4">
<img src="/assets/images/auth/user.png" className="w-full object-cover" alt="images" />
</div>
<div className="flex-1">
<h4 className="text-2xl dark:text-white">Shaun Park</h4>
<p className="text-white-dark">Enter your password to unlock your ID</p>
</div>
</div>
<ComponentsAuthUnlockForm />
</div>
</div>
</div>
</div>
</div>
);
};
export default BoxedLockScreen;

View File

@ -0,0 +1,41 @@
import ComponentsAuthResetPasswordForm from '@/components/auth/components-auth-reset-password-form';
// import LanguageDropdown from '@/components/language-dropdown';
import { Metadata } from 'next';
import React from 'react';
export const metadata: Metadata = {
title: 'Recover Id Box',
};
const BoxedPasswordReset = () => {
return (
<div>
<div className="absolute inset-0">
<img src="/assets/images/auth/bg-gradient.png" alt="image" className="h-full w-full object-cover" />
</div>
<div className="relative flex min-h-screen items-center justify-center bg-[url(/assets/images/auth/map.png)] bg-cover bg-center bg-no-repeat px-6 py-10 dark:bg-[#060818] sm:px-16">
<img src="/assets/images/auth/coming-soon-object1.png" alt="image" className="absolute left-0 top-1/2 h-full max-h-[893px] -translate-y-1/2" />
<img src="/assets/images/auth/coming-soon-object2.png" alt="image" className="absolute left-24 top-0 h-40 md:left-[30%]" />
<img src="/assets/images/auth/coming-soon-object3.png" alt="image" className="absolute right-0 top-0 h-[300px]" />
<img src="/assets/images/auth/polygon-object.svg" alt="image" className="absolute bottom-0 end-[28%]" />
<div className="relative w-full max-w-[870px] rounded-md bg-[linear-gradient(45deg,#fff9f9_0%,rgba(255,255,255,0)_25%,rgba(255,255,255,0)_75%,_#fff9f9_100%)] p-2 dark:bg-[linear-gradient(52.22deg,#0E1726_0%,rgba(14,23,38,0)_18.66%,rgba(14,23,38,0)_51.04%,rgba(14,23,38,0)_80.07%,#0E1726_100%)]">
<div className="relative flex flex-col justify-center rounded-md bg-white/60 px-6 py-20 backdrop-blur-lg dark:bg-black/50 lg:min-h-[758px]">
{/* <div className="absolute end-6 top-6">
<LanguageDropdown />
</div> */}
<div className="mx-auto w-full max-w-[440px]">
<div className="mb-7">
<h1 className="mb-3 text-2xl font-bold !leading-snug dark:text-white">Password Reset</h1>
<p>Enter your email to recover your ID</p>
</div>
<ComponentsAuthResetPasswordForm />
</div>
</div>
</div>
</div>
</div>
);
};
export default BoxedPasswordReset;

View File

@ -0,0 +1,28 @@
import React from 'react';
import { Metadata } from 'next';
import ComponentsAuthChangePasswordForm from '@/components/auth/components-auth-change-password-form';
export const metadata: Metadata = {
title: 'Change Password',
};
export default function ChangePasswordPage() {
return (
<div className="min-h-screen bg-[#0a0b17] flex items-center justify-center p-4 relative overflow-hidden">
{/* Background glows (same as login page) */}
<div className="absolute top-[180px] left-52 w-[100px] h-[100px] bg-[#1d8be0] rounded-full blur-2xl opacity-[1.5]" />
<div className="absolute top-10 left-0 w-[60px] h-[60px] bg-[#6cb655] rounded-full blur-2xl opacity-[1.5]" />
<div className="absolute -left-[80px] bottom-[140px] w-[100px] h-[200px] bg-[#db21d9] blur-3xl opacity-100" />
<div className="absolute bottom-20 left-[440px] w-[60px] h-[60px] bg-[#db21d9] rounded-full blur-xl opacity-80" />
<div className="absolute top-[100px] right-[260px] w-[100px] h-[100px] bg-[#f28f50] rounded-full blur-2xl opacity-80" />
<div className="absolute top-10 right-0 w-[60px] h-[60px] bg-[#6cb655] rounded-full blur-2xl opacity-[1.5]" />
<div className="absolute bottom-20 right-[180px] w-[80px] h-[80px] bg-[#783e8d] rounded-full blur-2xl opacity-80" />
<div className="absolute top-[280px] -right-[20px] w-[160px] h-[160px] bg-[#f1b74d] rounded-full blur-2xl opacity-100" />
{/* Card */}
<ComponentsAuthChangePasswordForm />
</div>
);
}

View File

@ -0,0 +1,25 @@
import { Metadata } from 'next';
import ForgotPasswordForm from '@/components/auth/components-auth-forgot-password-form';
export const metadata: Metadata = {
title: 'Forgot Password',
};
export default function ForgotPasswordPage() {
return (
<div className="min-h-screen bg-[#0a0b17] flex items-center justify-center p-4 relative overflow-hidden">
{/* Background glows */}
<div className="absolute top-[180px] left-52 w-[100px] h-[100px] bg-[#1d8be0] rounded-full blur-2xl opacity-[1.5]" />
<div className="absolute top-10 left-0 w-[60px] h-[60px] bg-[#6cb655] rounded-full blur-2xl opacity-[1.5]" />
<div className="absolute -left-[80px] bottom-[140px] w-[100px] h-[200px] bg-[#db21d9] blur-3xl opacity-100" />
<div className="absolute bottom-20 left-[440px] w-[60px] h-[60px] bg-[#db21d9] rounded-full blur-xl opacity-80" />
<div className="absolute top-[100px] right-[260px] w-[100px] h-[100px] bg-[#f28f50] rounded-full blur-2xl opacity-80" />
<div className="absolute top-10 right-0 w-[60px] h-[60px] bg-[#6cb655] rounded-full blur-2xl opacity-[1.5]" />
<div className="absolute bottom-20 right-[180px] w-[80px] h-[80px] bg-[#783e8d] rounded-full blur-2xl opacity-80" />
<div className="absolute top-[280px] -right-[20px] w-[160px] h-[160px] bg-[#f1b74d] rounded-full blur-2xl opacity-100" />
<ForgotPasswordForm />
</div>
);
}

7
app/(auth)/layout.tsx Normal file
View File

@ -0,0 +1,7 @@
import React from 'react';
const AuthLayout = ({ children }: { children: React.ReactNode }) => {
return <div className="min-h-screen text-black dark:text-white-dark">{children} </div>;
};
export default AuthLayout;

51
app/(auth)/login/page.tsx Normal file
View File

@ -0,0 +1,51 @@
import React from 'react';
import ComponentsAuthLoginForm from '@/components/auth/components-auth-login-form';
import { Metadata } from 'next';
import Link from 'next/link';
export const metadata: Metadata = {
title: 'Register Boxed',
};
export default function SocialBuddyLogin() {
return (
<div className="min-h-screen bg-[#0a0b17] flex items-center justify-center p-4 relative overflow-hidden">
{/* Background glows */}
{/* blue left */}
<div className="absolute top-[180px] left-52 w-[100px] h-[100px] bg-[#1d8be0] rounded-full blur-2xl opacity-[1.5] pointer-events-none"></div>
{/* green left */}
<div className="absolute top-10 left-0 w-[60px] h-[60px] bg-[#6cb655] rounded-full blur-2xl opacity-[1.5] pointer-events-none"></div>
{/* pink left */}
<div className="absolute -left-[80px] bottom-[140px] w-[100px] h-[200px] bg-[#db21d9] blur-3xl opacity-1 translate-x-1/2 translate-y-1/2"></div>
{/* pink - on card-small pink color bottom*/}
<div className="absolute bottom-20 left-[440px] w-[60px] h-[60px] bg-[#db21d9] rounded-full filter blur-xl opacity-80 -translate-x-1/2 translate-y-1/2"></div>
{/* orange*/}
<div className="absolute top-[100px] right-[260px] w-[100px] h-[100px] bg-[#f28f50] rounded-full filter blur-2xl opacity-80 -translate-x-1/2 translate-y-1/2"></div>
{/* green right */}
<div className="absolute top-10 right-0 w-[60px] h-[60px] bg-[#6cb655] rounded-full blur-2xl opacity-[1.5] pointer-events-none"></div>
{/* purple - right*/}
<div className="absolute bottom-20 right-[180px] w-[80px] h-[80px] bg-[#783e8d] rounded-full filter blur-2xl opacity-80 -translate-x-1/2 translate-y-1/2"></div>
{/* yellow bottom right */}
<div className="absolute top-[280px] -right-[20px] w-[160px] h-[160px] bg-[#f1b74d] rounded-full filter blur-2xl opacity-1 translate-x-1/2"></div>
{/* Login Form */}
<ComponentsAuthLoginForm />
</div>
);
}

View File

@ -0,0 +1,180 @@
'use client';
import { useState } from 'react';
import axios from 'axios';
import { useSearchParams, useRouter } from 'next/navigation';
import { ApiServerBaseUrl } from '@/utils/baseurl.utils';
// Import lucide icons directly
import { Eye, EyeOff } from 'lucide-react';
export default function ResetPasswordPage() {
const router = useRouter();
const searchParams = useSearchParams();
const token = searchParams.get('token');
const email_user = searchParams.get('email');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
// 👁️ Toggles
const [showPassword, setShowPassword] = useState(false);
const [showConfirm, setShowConfirm] = useState(false);
const [msg, setMsg] = useState('');
const [err, setErr] = useState('');
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setErr('');
setMsg('');
if (!password || !confirmPassword) {
setErr('Please fill all fields');
return;
}
if (password !== confirmPassword) {
setErr('Passwords do not match');
return;
}
if (!token) {
setErr('Invalid or expired token');
return;
}
setLoading(true);
try {
await axios.post(`${ApiServerBaseUrl}/auth/reset-password`, {
email: email_user,
token,
newPassword: password,
});
setMsg('Password reset successfully! Redirecting to login...');
setTimeout(() => router.push('/login'), 2000);
} catch (error: any) {
setErr(error.response?.data?.error || 'Something went wrong');
} finally {
setLoading(false);
}
};
return (
<div className="min-h-screen bg-[#0a0b17] flex items-center justify-center p-4 relative overflow-hidden">
{/* Background glows */}
<div className="absolute top-[180px] left-52 w-[100px] h-[100px] bg-[#1d8be0] rounded-full blur-2xl opacity-[1.5]" />
<div className="absolute top-10 left-0 w-[60px] h-[60px] bg-[#6cb655] rounded-full blur-2xl opacity-[1.5]" />
<div className="absolute -left-[80px] bottom-[140px] w-[100px] h-[200px] bg-[#db21d9] blur-3xl opacity-100" />
<div className="absolute bottom-20 right-[180px] w-[80px] h-[80px] bg-[#783e8d] rounded-full blur-2xl opacity-80" />
{/* Card */}
<div className="relative z-10 w-full max-w-md bg-[#0f1329] border border-white/10 rounded-3xl p-8 shadow-xl">
{/* Logo */}
<div className="flex flex-col items-center mb-4">
<img
src="/assets/images/logo_sb.png"
alt="Logo"
className="h-[120px] w-[120px]
drop-shadow-[0_0_6px_rgba(0,111,171,0.65),0_0_8px_rgba(243,195,56,0.55),
0_0_10px_rgba(226,50,118,0.50),0_0_10px_rgba(20,71,209,0.40)]"
/>
<h1
className="
text-2xl font-extrabold mt-4 text-center
bg-gradient-to-r from-blue-500 via-cyan-400 to-pink-500
bg-clip-text text-transparent
"
>
Reset Your Password
</h1>
<p className="text-sm text-gray-400 mt-2 text-center">
Enter your new password below
</p>
</div>
{err && <p className="text-red-500 text-sm mb-3 text-center">{err}</p>}
{msg && <p className="text-green-500 text-sm mb-3 text-center">{msg}</p>}
<form onSubmit={handleSubmit} className="space-y-4">
{/* 🔥 NEW PASSWORD FIELD */}
<div className="relative">
<input
type={showPassword ? "text" : "password"}
placeholder="New Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full bg-[#0a0b17] border border-gray-700 text-white rounded-lg px-4 py-3 pr-12 focus:outline-none focus:border-blue-500"
/>
{/* Eye Toggle */}
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-4 top-1/2 -translate-y-1/2 text-gray-300"
>
<span className="w-5 h-5 flex items-center justify-center">
{showPassword ? (
<EyeOff size={20} strokeWidth={1.5} />
) : (
<Eye size={20} strokeWidth={1.5} />
)}
</span>
</button>
</div>
{/* 🔥 CONFIRM PASSWORD FIELD */}
<div className="relative">
<input
type={showConfirm ? "text" : "password"}
placeholder="Confirm Password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="w-full bg-[#0a0b17] border border-gray-700 text-white rounded-lg px-4 py-3 pr-12 focus:outline-none focus:border-blue-500"
/>
{/* Eye Toggle */}
<button
type="button"
onClick={() => setShowConfirm(!showConfirm)}
className="absolute right-4 top-1/2 -translate-y-1/2 text-gray-300"
>
<span className="w-5 h-5 flex items-center justify-center">
{showConfirm ? (
<EyeOff size={20} strokeWidth={1.5} />
) : (
<Eye size={20} strokeWidth={1.5} />
)}
</span>
</button>
</div>
{/* Button */}
<button
type="submit"
disabled={loading}
className="
w-full text-white text-lg font-semibold py-3 rounded-xl
bg-gradient-to-r from-blue-600 to-pink-500
shadow-lg transition-all
hover:opacity-90 hover:scale-[1.02]
disabled:opacity-60
"
>
{loading ? 'Resetting...' : 'Reset Password'}
</button>
</form>
</div>
</div>
);
}

View File

@ -0,0 +1,59 @@
import ComponentsAuthRegisterForm from '@/components/auth/components-auth-register-form';
import { Metadata } from 'next';
import Link from 'next/link';
import React from 'react';
export const metadata: Metadata = {
title: 'Register Boxed',
};
export default function BoxedSignUp() {
return (
<div className="min-h-screen bg-[#0a0b17] flex items-center justify-center p-4 relative overflow-hidden">
{/* Background glows */}
<div className="absolute top-[180px] left-52 w-[100px] h-[100px] bg-[#1d8be0] rounded-full blur-2xl opacity-[1.5]" />
<div className="absolute top-10 left-0 w-[60px] h-[60px] bg-[#6cb655] rounded-full blur-2xl opacity-[1.5]" />
<div className="absolute -left-[80px] bottom-[140px] w-[100px] h-[200px] bg-[#db21d9] blur-3xl opacity-1" />
<div className="absolute bottom-20 left-[440px] w-[60px] h-[60px] bg-[#db21d9] rounded-full blur-xl opacity-80" />
<div className="absolute top-[100px] right-[260px] w-[100px] h-[100px] bg-[#f28f50] rounded-full blur-2xl opacity-80" />
<div className="absolute top-10 right-0 w-[60px] h-[60px] bg-[#6cb655] rounded-full blur-2xl opacity-[1.5]" />
<div className="absolute bottom-20 right-[180px] w-[80px] h-[80px] bg-[#783e8d] rounded-full blur-2xl opacity-80" />
<div className="absolute top-[280px] -right-[20px] w-[160px] h-[160px] bg-[#f1b74d] rounded-full blur-2xl opacity-1" />
{/* SIGNUP CARD — SLIGHTLY WIDER */}
<div className="relative z-10 w-full max-w-[540px] p-8 glass-card rounded-3xl">
{/* Logo */}
<div className="flex flex-col items-center justify-center mb-4">
<img
src="/assets/images/logo_sb.png"
alt="Logo"
className="h-[120px] w-[120px]
drop-shadow-[0_0_6px_rgba(0,111,171,0.65),0_0_8px_rgba(243,195,56,0.55),0_0_10px_rgba(226,50,118,0.50),0_0_10px_rgba(243,195,56,0.45),0_0_10px_rgba(20,71,209,0.40)]
"
/>
<h1 className="text-xl text-white font-medium text-center mt-3">
Create Your SocialBuddy Account
</h1>
</div>
{/* Form */}
<div className="mb-4">
<ComponentsAuthRegisterForm />
</div>
{/* Footer */}
<div className="text-center text-gray-400 text-sm mt-4">
Already have an account?{' '}
<Link href="/login" className="text-blue-400 hover:underline">
SIGN IN
</Link>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,313 @@
"use client";
import React, { useEffect, useState } from "react";
import { useParams, useRouter } from "next/navigation";
import axios from "axios";
import { ApiServerBaseUrl } from "@/utils/baseurl.utils";
const InvoiceDetail: React.FC = () => {
const params = useParams();
const router = useRouter();
const invoiceId = params?.id;
const [paymentData, setPaymentData] = useState<any>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchPaymentData = async () => {
try {
// Check if it's a trial invoice
if (invoiceId === "trial-inv") {
setPaymentData({
createdAt: new Date().toISOString(),
endDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
status: "active",
email: "user@example.com", // You could fetch meaningful user email if stored in localStorage
planId: "Free Trial",
amount: 0
});
setLoading(false);
return;
}
const sessionId = localStorage.getItem("payment_session");
if (!sessionId) {
setError("No payment session found");
setLoading(false);
return;
}
const res: any = await axios.get(`${ApiServerBaseUrl}/payment/details`, {
params: { session_id: sessionId },
});
setPaymentData(res.data.data);
} catch (err: any) {
setError(err.response?.data?.error || "Failed to fetch payment details");
} finally {
setLoading(false);
}
};
fetchPaymentData();
}, [invoiceId]);
const invoice = paymentData
? {
number: `#${invoiceId}`,
issueDate: paymentData.createdAt
? new Date(paymentData.createdAt).toLocaleDateString()
: "N/A",
validTill: paymentData.endDate
? new Date(paymentData.endDate).toLocaleDateString()
: "N/A",
status: paymentData.status || "pending",
issuedFor: paymentData.email || "",
customerId: "",
plan: paymentData.planId || "N/A",
bankName: "Bank of Canada",
accountNo: "1234567890",
country: "Canada",
items: [
{
sno: 1,
plan: paymentData.planId || "N/A",
qty: 1,
price: paymentData.amount,
amount: paymentData.amount,
},
],
subtotal: paymentData.amount,
tax: 0,
grandTotal: paymentData.amount,
createdAt: paymentData.createdAt,
}
: null;
const handlePrint = () => window.print();
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen text-gray-400">
Loading invoice details...
</div>
);
}
if (error || !invoice) {
return (
<div className="flex flex-col items-center justify-center min-h-screen text-center px-4">
<p className="text-red-500 mb-4">{error || "Invoice not found"}</p>
<button
onClick={() => router.back()}
className="mt-4 px-6 py-3 rounded-xl font-bold bg-gradient-to-r from-[#4361ee] to-[#c026d3] text-white"
>
Go Back
</button>
</div>
);
}
return (
<div className="relative min-h-screen px-4 py-10 overflow-hidden bg-[#111111]">
{/* ===================== PRICING-PAGE BACKGROUND GLOWS ===================== */}
<div className="absolute top-[180px] left-52 w-[100px] h-[100px]
bg-[#1d8be0] rounded-full blur-2xl opacity-[1.5] animate-zoomslow"></div>
<div className="absolute top-10 left-0 w-[60px] h-[60px]
bg-[#6cb655] rounded-full blur-2xl opacity-[1.5] animate-zoomslower"></div>
<div className="absolute -left-[80px] bottom-[140px] w-[100px] h-[200px]
bg-[#db21d9] blur-3xl opacity-1 animate-zoomslow"></div>
<div className="absolute bottom-20 left-[440px] w-[60px] h-[60px]
bg-[#db21d9] rounded-full blur-xl opacity-80 -translate-x-1/2 translate-y-1/2"></div>
<div className="absolute top-[100px] right-[260px] w-[100px] h-[100px]
bg-[#f28f50] rounded-full blur-2xl opacity-80 animate-zoomfast"></div>
<div className="absolute top-10 right-0 w-[60px] h-[60px]
bg-[#6cb655] rounded-full blur-2xl opacity-[1.5] animate-zoomslower"></div>
<div className="absolute bottom-20 right-[180px] w-[80px] h-[80px]
bg-[#783e8d] rounded-full blur-2xl opacity-80 animate-zoomslow"></div>
<div className="absolute top-[280px] -right-[20px] w-[160px] h-[160px]
bg-[#f1b74d] rounded-full blur-2xl opacity-1 animate-zoomslower"></div>
{/* ===================== PAGE CONTENT ===================== */}
<div className="relative z-10 max-w-5xl mx-auto">
{/* Back Button */}
<button
onClick={() => router.back()}
className="mb-6 flex items-center gap-2 text-gray-400 hover:text-white transition-colors"
>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
Back to Account Settings
</button>
{/* Invoice Card */}
<div className="glass-card rounded-2xl p-8 lg:p-12">
<div className="flex justify-between items-start mb-12">
<div>
<h1 className="text-3xl font-extrabold text-white mb-2">INVOICE</h1>
<div className="flex items-center gap-3 mb-2">
<h3 className="text-sm font-semibold text-gray-400">Invoice No:</h3>
<p className="text-white font-bold text-lg">{invoice.number}</p>
</div>
</div>
<div className="text-right">
<div className="flex items-center gap-2 mb-4">
<div className="w-12 h-12">
<img src="/assets/images/logo_sb.png" alt="logo" className="inline w-12" />
</div>
<div>
<h2 className="text-xl font-bold text-white">SOCIAL BUDDY</h2>
<p className="text-xs text-gray-400 uppercase tracking-wider">Social Media Manager</p>
</div>
</div>
<p className="text-sm text-gray-400">Social Buddy</p>
<p className="text-sm text-gray-400">support@socialbuddy.com</p>
<p className="text-sm text-gray-400">+1-555-SOCIAL-1</p>
</div>
</div>
{/* Invoice info */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 mb-12">
<div className="space-y-4">
<div>
<div className="flex items-center gap-3 mb-2">
<h3 className="text-sm font-semibold text-gray-400">Issue For:</h3>
<p className="text-white">{invoice.createdAt}</p>
</div>
<p className="text-gray-400 text-sm">Customer ID: N/A</p>
<p className="text-gray-400 text-sm">Plan: {invoice.plan}</p>
</div>
</div>
<div className="space-y-4 md:text-right">
<div className="flex items-center gap-3">
<h3 className="text-sm font-semibold text-gray-400">Issue Date:</h3>
<p className="text-white">{invoice.issueDate}</p>
</div>
<div className="flex items-center gap-3">
<h3 className="text-sm font-semibold text-gray-400">Valid Till:</h3>
<p className="text-white">{invoice.validTill}</p>
</div>
<div className="flex items-center gap-3">
<h3 className="text-sm font-semibold text-gray-400">Status:</h3>
<span className="px-4 py-1 rounded-full bg-yellow-500/20 text-yellow-400 text-sm font-semibold">
{invoice.status}
</span>
</div>
</div>
</div>
{/* Table */}
<div className="mb-12 overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-white/10">
<th className="py-4 px-4 text-gray-400 text-sm">S.NO</th>
<th className="py-4 px-4 text-gray-400 text-sm">PLAN</th>
<th className="py-4 px-4 text-gray-400 text-sm text-center">QTY</th>
<th className="py-4 px-4 text-gray-400 text-sm text-right">PRICE</th>
<th className="py-4 px-4 text-gray-400 text-sm text-right">AMOUNT</th>
</tr>
</thead>
<tbody>
{invoice.items.map((item) => (
<tr key={item.sno} className="border-b border-white/5">
<td className="py-4 px-4 text-white">{item.sno}</td>
<td className="py-4 px-4 text-white">{item.plan}</td>
<td className="py-4 px-4 text-center text-white">{item.qty}</td>
<td className="py-4 px-4 text-right text-white">${item.price.toFixed(2)}</td>
<td className="py-4 px-4 text-right text-white font-semibold">
${item.amount.toFixed(2)}
</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Totals */}
<div className="flex justify-end mb-12">
<div className="w-full md:w-1/2 lg:w-1/3 space-y-3">
<div className="flex justify-between py-2 border-b border-white/10">
<span className="text-gray-400">Subtotal:</span>
<span className="text-white font-semibold">${invoice.subtotal.toFixed(2)}</span>
</div>
<div className="flex justify-between py-2 border-b border-white/10">
<span className="text-gray-400">Tax:</span>
<span className="text-white font-semibold">${invoice.tax.toFixed(2)}</span>
</div>
<div className="flex justify-between py-3 border-t border-white/20">
<span className="text-white font-bold text-lg">Grand Total:</span>
<span className="text-white font-bold text-xl">
${invoice.grandTotal.toFixed(2)}
</span>
</div>
</div>
</div>
{/* Print Button */}
<div className="flex justify-end">
<button
onClick={handlePrint}
className="px-6 py-3 rounded-xl font-bold bg-gradient-to-r from-[#4361ee] to-[#c026d3] text-white flex items-center gap-2"
>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"
/>
</svg>
Print
</button>
</div>
</div>
</div>
{/* Print Styles */}
<style jsx global>{`
@media print {
body {
background: white !important;
}
.glass-card {
background: white !important;
border: 1px solid #e5e7eb !important;
box-shadow: none !important;
}
button {
display: none !important;
}
.text-white {
color: black !important;
}
.text-gray-400 {
color: #6b7280 !important;
}
}
`}</style>
</div>
);
};
export default InvoiceDetail;

View File

@ -0,0 +1,556 @@
"use client";
import React, { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import axios from "axios";
import { ApiBaseUrl, ApiServerBaseUrl } from "@/utils/baseurl.utils";
import Swal from 'sweetalert2';
interface Invoice {
id: string;
number: string;
date: string;
status: "Paid" | "Pending" | "Overdue";
amount: number;
}
interface UserDetails {
id?: string;
name?: string;
email?: string;
mobileNumber?: string;
role?: string;
_id?: string; // Add this as MongoDB uses _id
}
const AccountSettings: React.FC = () => {
const router = useRouter();
const [activeTab, setActiveTab] = useState<"subscription" | "billing" | "profile">("subscription");
const [paymentData, setPaymentData] = useState<any>(null);
const [subStatus, setSubStatus] = useState<any>(null);
const [billingInfo, setBillingInfo] = useState<any>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [userDetails, setUserDetails] = useState<UserDetails | null>(null)
useEffect(() => {
const fetchData = async () => {
try {
const sessionId = localStorage.getItem('payment_session');
const storedUser = localStorage.getItem("userDetails");
let currentUser: UserDetails | null = null;
if (storedUser) {
const parsedUser = JSON.parse(storedUser);
setUserDetails(parsedUser);
currentUser = parsedUser;
}
// Fetch Subscription Status (Trial / Active Plan)
if (currentUser && (currentUser.id || currentUser._id)) {
try {
const userId = currentUser.id || currentUser._id;
// 1. Status
const statusRes = await axios.get(`${ApiServerBaseUrl}/payment/status`, {
params: { userId }
});
setSubStatus(statusRes.data);
// 2. Billing Info
const billingRes = await axios.get(`${ApiServerBaseUrl}/payment/billing-info`, {
params: { userId }
});
setBillingInfo(billingRes.data);
} catch (statusErr) {
console.error("Error fetching status/billing:", statusErr);
}
}
// Fetch Payment Details (if session exists)
if (sessionId) {
try {
const res: any = await axios.get(
`${ApiServerBaseUrl}/payment/details`,
{ params: { session_id: sessionId } }
);
setPaymentData(res.data.data);
} catch (paymentErr: any) {
console.error("Error fetching payment data:", paymentErr);
}
}
} catch (err: any) {
console.error("Error in fetchData:", err);
setError("Failed to load account details");
} finally {
setLoading(false);
}
};
fetchData();
}, []);
const handleManageBilling = async () => {
if (!userDetails || (!userDetails.id && !userDetails._id)) return;
try {
const res = await axios.post(`${ApiServerBaseUrl}/payment/create-portal-session`, {
userId: userDetails.id || userDetails._id
});
if (res.data.url) {
window.location.href = res.data.url;
}
} catch (err: any) {
Swal.fire({
icon: 'error',
title: 'Error',
text: err.response?.data?.error || "Could not redirect to billing portal",
background: '#111',
color: '#fff',
confirmButtonColor: '#d33'
});
}
};
const handleActivateTrial = async () => {
if (!userDetails || (!userDetails.id && !userDetails._id)) return;
try {
const res = await axios.post(`${ApiServerBaseUrl}/payment/activate-trial`, {
userId: userDetails.id || userDetails._id,
email: userDetails.email
});
if (res.data.success) {
if (res.data.payment && res.data.payment.sessionId) {
localStorage.setItem('payment_session', res.data.payment.sessionId);
localStorage.setItem('paymentDetails', JSON.stringify(res.data.payment));
}
Swal.fire({
icon: 'success',
title: 'Trial Activated!',
text: 'Your 7-day free trial has started.',
background: '#111',
color: '#fff',
confirmButtonColor: '#4361ee'
});
// Refresh status
const statusRes = await axios.get(`${ApiServerBaseUrl}/payment/status`, {
params: { userId: userDetails.id || userDetails._id }
});
setSubStatus(statusRes.data);
// Redirect for immediate access to dashboard/feature pages
router.push('/');
}
} catch (err: any) {
Swal.fire({
icon: 'error',
title: 'Activation Failed',
text: err.response?.data?.error || "Could not activate trial",
background: '#111',
color: '#fff',
confirmButtonColor: '#d33'
});
}
};
const handleCancelTrial = async () => {
if (!userDetails || (!userDetails.id && !userDetails._id)) return;
const result = await Swal.fire({
title: 'Are you sure?',
text: "Your free trial will end immediately and you will lose access to premium features.",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#4361ee',
confirmButtonText: 'Yes, cancel it!',
background: '#111',
color: '#fff',
});
if (result.isConfirmed) {
try {
const res = await axios.post(`${ApiServerBaseUrl}/payment/cancel`, {
session_id: subStatus?.stripeSessionId
});
if (res.data) {
localStorage.removeItem('payment_session');
localStorage.removeItem('paymentDetails');
Swal.fire({
title: 'Cancelled!',
text: 'Your trial has been cancelled.',
icon: 'success',
background: '#111',
color: '#fff',
confirmButtonColor: '#4361ee'
});
// Refresh status
const userId = userDetails.id || userDetails._id;
const statusRes = await axios.get(`${ApiServerBaseUrl}/payment/status`, {
params: { userId }
});
setSubStatus(statusRes.data);
}
} catch (err: any) {
Swal.fire({
icon: 'error',
title: 'Cancellation Failed',
text: err.response?.data?.error || "Could not cancel trial",
background: '#111',
color: '#fff',
confirmButtonColor: '#d33'
});
}
}
};
// Construct invoices from either Payment Data or Subscription Status
let invoices: Invoice[] = [];
if (paymentData) {
invoices.push({
id: paymentData.id || "1",
number: paymentData.stripeSessionId?.substring(0, 12) || "INV-001",
date: paymentData.subscriptionStartDate ? new Date(paymentData.subscriptionStartDate).toLocaleDateString() : "N/A",
status: paymentData.status === "active" ? "Paid" : "Pending",
amount: paymentData.amount || 0,
});
} else if (subStatus && subStatus.hasUsedTrial) {
// If no payment data but Trial was used/active
invoices.push({
id: "trial-inv",
number: subStatus.stripeSessionId ? subStatus.stripeSessionId.substring(0, 12) : "TRIAL-START",
date: subStatus.trialStartDate ? new Date(subStatus.trialStartDate).toLocaleDateString() : "N/A",
status: "Paid", // Free trial is considered 'paid' for access
amount: 0,
});
}
const handleViewInvoice = (invoiceId: string) => {
router.push(`/account-settings/invoice/${invoiceId}`);
};
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen text-gray-400">
Loading account details...
</div>
);
}
// Removed the blocking error page so users can still see trial options even if they haven't paid yet.
return (
<div className="min-h-screen bg-[#111111] flex items-center justify-center p-4 relative overflow-hidden">
{/* Background Glows by User Request */}
<div className="absolute top-[180px] left-52 w-[100px] h-[100px] bg-[#1d8be0] rounded-full blur-2xl opacity-[1.5] animate-zoomslow"></div>
<div className="absolute top-10 left-0 w-[60px] h-[60px] bg-[#6cb655] rounded-full blur-2xl opacity-[1.5] animate-zoomslower"></div>
<div className="absolute -left-[80px] bottom-[140px] w-[100px] h-[200px] bg-[#db21d9] blur-3xl opacity-1 animate-zoomslow"></div>
<div className="absolute bottom-20 left-[440px] w-[60px] h-[60px] bg-[#db21d9] rounded-full blur-xl opacity-80 -translate-x-1/2 translate-y-1/2"></div>
<div className="absolute top-[100px] right-[260px] w-[100px] h-[100px] bg-[#f28f50] rounded-full blur-2xl opacity-80 animate-zoomfast"></div>
<div className="absolute top-10 right-0 w-[60px] h-[60px] bg-[#6cb655] rounded-full blur-2xl opacity-[1.5] animate-zoomslower"></div>
<div className="absolute bottom-20 right-[180px] w-[80px] h-[80px] bg-[#783e8d] rounded-full blur-2xl opacity-80 animate-zoomslow"></div>
<div className="absolute top-[280px] -right-[20px] w-[160px] h-[160px] bg-[#f1b74d] rounded-full blur-2xl opacity-1 animate-zoomslower"></div>
<div className="max-w-7xl mx-auto w-full">
{/* Header */}
<div className="mb-10">
<h1 className="text-4xl font-extrabold text-white mb-2">
Account <span className="text-gradient">Settings</span>
</h1>
<p className="text-gray-400">Manage your account preferences and billing</p>
</div>
{/* Tabs */}
<div className="flex gap-4 mb-8 border-b border-white/10">
<button
onClick={() => setActiveTab("subscription")}
className={`px-6 py-3 font-semibold transition-all relative ${activeTab === "subscription"
? "text-white"
: "text-gray-400 hover:text-gray-300"
}`}
>
Subscription
{activeTab === "subscription" && (
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-gradient-to-r from-[#4361ee] to-[#c026d3]" />
)}
</button>
<button
onClick={() => setActiveTab("billing")}
className={`px-6 py-3 font-semibold transition-all relative ${activeTab === "billing"
? "text-white"
: "text-gray-400 hover:text-gray-300"
}`}
>
Billing
{activeTab === "billing" && (
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-gradient-to-r from-[#4361ee] to-[#c026d3]" />
)}
</button>
<button
onClick={() => setActiveTab("profile")}
className={`px-6 py-3 font-semibold transition-all relative ${activeTab === "profile"
? "text-white"
: "text-gray-400 hover:text-gray-300"
}`}
>
Profile
{activeTab === "profile" && (
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-gradient-to-r from-[#4361ee] to-[#c026d3]" />
)}
</button>
</div>
{/* Content */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Subscription Section */}
{activeTab === "subscription" && (
<>
<div className="glass-card rounded-2xl p-6 col-span-1 lg:col-span-2">
<h3 className="text-xl font-bold text-white mb-4">Subscription Details</h3>
{/* 1. Active Subscription (Paid or Trial) */}
{subStatus?.isTrialActive ? (
// CASE A: ACTIVE FREE TRIAL
<div className="space-y-4">
<div className="p-4 rounded-xl bg-gradient-to-r from-[#4361ee]/20 to-[#c026d3]/20 border border-white/10">
<div className="flex items-center justify-between mb-2">
<h4 className="text-lg font-bold text-white">Free Trial Active</h4>
<span className="px-3 py-1 bg-green-500/20 text-green-400 text-xs font-bold rounded-full">
ACTIVE
</span>
</div>
<p className="text-gray-300 text-sm">
You are currently enjoying the 7-day free trial.
</p>
</div>
<div className="glass-card bg-white/5 p-4 rounded-xl space-y-3">
<div className="flex justify-between text-sm">
<span className="text-gray-400">Plan Type</span>
<span className="text-white font-semibold capitalize">Free Trial</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Ends On</span>
<span className="text-white">
{subStatus?.trialEndsAt
? new Date(subStatus.trialEndsAt).toLocaleDateString()
: "N/A"}
</span>
</div>
</div>
<button
onClick={handleCancelTrial}
className="w-full py-3 rounded-xl font-bold text-sm transition-all border border-red-500/50 text-red-400 hover:bg-red-500/10 mt-4"
>
Cancel Free Trial
</button>
</div>
) : (paymentData && paymentData.planId !== 'free_trial') ? (
// CASE B: ACTIVE PAID SUBSCRIPTION
<div className="space-y-4">
<div className="p-4 rounded-xl bg-gradient-to-r from-[#4361ee]/20 to-[#c026d3]/20 border border-white/10">
<div className="flex items-center justify-between mb-2">
<h4 className="text-lg font-bold text-white">
{paymentData?.planId ? paymentData.planId.replace(/_/g, " ").toUpperCase() : "CURRENT PLAN"}
</h4>
<span className="px-3 py-1 bg-green-500/20 text-green-400 text-xs font-bold rounded-full">
ACTIVE
</span>
</div>
<p className="text-gray-300 text-sm">
Your subscription is active and running.
</p>
</div>
<div className="glass-card bg-white/5 p-4 rounded-xl space-y-3">
<div className="flex justify-between text-sm">
<span className="text-gray-400">Plan Type</span>
<span className="text-white font-semibold capitalize">
{paymentData?.planId?.replace(/_/g, " ") || "N/A"}
</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Renews On</span>
<span className="text-white">
{paymentData?.subscriptionEndDate
? new Date(paymentData.subscriptionEndDate).toLocaleDateString()
: "N/A"}
</span>
</div>
</div>
</div>
) : (
// CASE C: NO ACTIVE SUBSCRIPTION
<div className="text-center py-8">
<p className="text-gray-400 mb-6">You don't have an active subscription.</p>
{/* Show Activate Trial if not used yet */}
{!subStatus?.hasUsedTrial ? (
<div className="bg-gradient-to-br from-[#4361ee]/10 to-[#c026d3]/10 border border-white/10 p-6 rounded-2xl">
<h4 className="text-xl font-bold text-white mb-2">Start Your 7-Day Free Trial</h4>
<p className="text-gray-400 text-sm mb-6">
Experience all premium features for free. No credit card required (optional).
</p>
<button
onClick={handleActivateTrial}
className="px-8 py-3 rounded-xl font-bold bg-white text-black hover:bg-gray-200 transition-all"
>
Activate Free Trial
</button>
</div>
) : (
<div className="p-4 bg-red-500/10 border border-red-500/20 rounded-xl text-red-400">
Your free trial has expired. Please upgrade to continue.
</div>
)}
<button
onClick={() => router.push('/pricing')}
className="mt-6 px-6 py-3 rounded-xl font-bold bg-gradient-to-r from-[#4361ee] to-[#c026d3] text-white hover:opacity-90 transition-all"
>
View Pricing Plans
</button>
</div>
)}
</div>
</>
)}
{/* Billing Tab Content */}
{activeTab === "billing" && (
<div className="lg:col-span-3">
<div className="glass-card rounded-2xl p-6">
<h3 className="text-xl font-bold text-white mb-4">Billing Methods</h3>
{billingInfo?.hasPaymentMethod ? (
<div className="space-y-4 mb-6">
<div className="flex items-center justify-between p-4 bg-white/5 rounded-xl border border-white/10">
<div className="flex items-center gap-4">
<div className="w-12 h-8 bg-white/10 rounded flex items-center justify-center text-xs font-bold font-mono">
{billingInfo.brand?.toUpperCase()}
</div>
<div>
<p className="text-white font-medium">
{billingInfo.brand?.charAt(0).toUpperCase() + billingInfo.brand?.slice(1)} ending in {billingInfo.last4}
</p>
<p className="text-gray-400 text-sm">
Expires {billingInfo.exp_month}/{billingInfo.exp_year}
</p>
</div>
</div>
<span className="text-xs bg-green-500/20 text-green-400 px-2 py-1 rounded">Default</span>
</div>
</div>
) : (
<div className="text-center py-6 bg-white/5 rounded-xl border border-dashed border-white/20 mb-6">
<p className="text-gray-400 mb-2">No payment method saved.</p>
</div>
)}
<div className="flex flex-col sm:flex-row gap-3">
<button
onClick={handleManageBilling}
className="py-3 px-6 rounded-xl font-bold bg-gradient-to-r from-[#4361ee] to-[#c026d3] text-white hover:opacity-90 transition-all"
>
{billingInfo?.hasPaymentMethod ? "Update Payment Method" : "Add Payment Method"}
</button>
<p className="text-xs text-gray-500 mt-2 sm:mt-0 sm:self-center">
Redirects to secure Stripe Billing Portal
</p>
</div>
</div>
</div>
)}
{/* Profile Tab Content */}
{activeTab === "profile" && (
<div className="lg:col-span-3">
<div className="glass-card rounded-2xl p-6">
<h3 className="text-xl font-bold text-white mb-4">Profile</h3>
<div className="space-y-3 mb-6">
<div className="flex justify-between text-sm">
<span className="text-gray-400">Full Name</span>
<span className="text-white">{userDetails?.name || "N/A"}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Email</span>
<span className="text-white">{userDetails?.email || "N/A"}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-gray-400">Phone</span>
<span className="text-white">{userDetails?.mobileNumber || "N/A"}</span>
</div>
</div>
</div>
</div>
)}
</div>
{/* Billing History - Always visible */}
<div className="mt-10">
<div className="glass-card rounded-2xl p-6">
<h3 className="text-xl font-bold text-white mb-6">Billing History</h3>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-white/10">
<th className="text-left py-4 px-4 text-sm font-semibold text-[#4361ee] uppercase tracking-wider">
Invoice Number
</th>
<th className="text-left py-4 px-4 text-sm font-semibold text-[#4361ee] uppercase tracking-wider">
Date
</th>
<th className="text-left py-4 px-4 text-sm font-semibold text-[#4361ee] uppercase tracking-wider">
Status
</th>
<th className="text-left py-4 px-4 text-sm font-semibold text-[#4361ee] uppercase tracking-wider">
Amount
</th>
</tr>
</thead>
<tbody>
{invoices.length > 0 ? invoices.map((invoice) => (
<tr key={invoice.id} className="border-b border-white/5 hover:bg-white/5 transition-colors">
<td className="py-4 px-4 text-white">{invoice.number}</td>
<td className="py-4 px-4 text-gray-300">{invoice.date}</td>
<td className="py-4 px-4">
<span
className={`px-3 py-1 rounded-full text-xs font-semibold ${invoice.status === "Paid"
? "bg-green-500/20 text-green-400"
: invoice.status === "Pending"
? "bg-yellow-500/20 text-yellow-400"
: "bg-red-500/20 text-red-400"
}`}
>
{invoice?.status}
</span>
</td>
<td className="py-4 px-4 text-white font-semibold">
${invoice?.amount}
</td>
</tr>
)) : (
<tr>
<td colSpan={4} className="py-8 text-center text-gray-500">
No billing history found.
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
);
};
export default AccountSettings;

View File

@ -0,0 +1,194 @@
"use client";
import { useEffect, useState, useCallback } from "react";
import axios from "axios";
import { ApiServerBaseUrl } from "@/utils/baseurl.utils";
import { getSocialAuthStatus } from "@/utils/commonFunction.utils";
import { useRouter } from "next/navigation";
const ONE_HOUR = 3600;
const AutomationPage = () => {
const router = useRouter();
const [limit, setLimit] = useState<number>(20);
const [loading, setLoading] = useState(false);
const [lastRun, setLastRun] = useState<string>("");
const [nextRunIn, setNextRunIn] = useState<number>(ONE_HOUR);
/* ------------------------------------------------------------
Validate payment + social login
------------------------------------------------------------ */
useEffect(() => {
async function validate() {
const userEmail = localStorage.getItem("user_email");
if (!userEmail) {
router.push("/social-media-connect");
return;
}
const storedUser = localStorage.getItem("userDetails");
if (!storedUser) {
router.push("/social-media-connect");
return;
}
const user = JSON.parse(storedUser);
const role = user?.role;
// ✅ CUSTOMER → pricing check FIRST
if (role === "customer") {
const session = localStorage.getItem("payment_session");
if (!session) {
router.push("/pricing");
return;
}
}
// ✅ ALL ROLES → social media connect check
const res = await getSocialAuthStatus(userEmail);
if (!res?.connected) {
router.push("/social-media-connect");
return;
}
}
validate();
}, [router]);
/* ------------------------------------------------------------
Auto Reply Function (wrapped in useCallback)
------------------------------------------------------------ */
const triggerAutoReply = useCallback(async () => {
if (!limit || limit <= 0) {
alert("Please enter a valid limit number");
return;
}
try {
setLoading(true);
await axios.post(
`${ApiServerBaseUrl}/social/automation/auto-reply?userId=${localStorage.getItem("user_email")}`,
{ limit }
);
const now = new Date().toLocaleString();
setLastRun(now);
const nextTime = Date.now() + ONE_HOUR * 1000;
localStorage.setItem("nextRunTimestamp", nextTime.toString());
setNextRunIn(ONE_HOUR);
} catch (error: any) {
alert(error.response?.data?.message || "Auto reply failed");
} finally {
setLoading(false);
}
}, [limit]);
/* ------------------------------------------------------------
Load saved next run or trigger if expired
------------------------------------------------------------ */
useEffect(() => {
const savedTime = localStorage.getItem("nextRunTimestamp");
if (savedTime) {
const diff = Math.floor((Number(savedTime) - Date.now()) / 1000);
if (diff > 0) {
setNextRunIn(diff);
} else {
triggerAutoReply();
}
} else {
const nextTime = Date.now() + ONE_HOUR * 1000;
localStorage.setItem("nextRunTimestamp", nextTime.toString());
}
}, [triggerAutoReply]);
/* ------------------------------------------------------------
Countdown timer
------------------------------------------------------------ */
useEffect(() => {
const timer = setInterval(() => {
setNextRunIn((prev) => {
if (prev <= 1) {
triggerAutoReply();
return ONE_HOUR;
}
return prev - 1;
});
}, 1000);
return () => clearInterval(timer);
}, [triggerAutoReply]);
const formatTime = (sec: number) => {
const m = Math.floor(sec / 60);
const s = sec % 60;
return `${m}m ${s < 10 ? "0" + s : s}s`;
};
return (
<div className="relative min-h-[88vh] flex items-center justify-center overflow-hidden bg-[#111111] px-4">
{/* Floating background bubbles */}
<div className="absolute bottom-[-100px] left-[10%] w-[70px] h-[70px] rounded-full bg-pink-500/40 blur-xl animate-floatUp"></div>
<div className="absolute bottom-[-120px] left-[30%] w-[90px] h-[90px] rounded-full bg-blue-500/40 blur-xl animate-floatUpSlow"></div>
<div className="absolute bottom-[-110px] left-[60%] w-[60px] h-[60px] rounded-full bg-green-500/40 blur-xl animate-floatUpFast"></div>
<div className="w-full max-w-md bg-white/10 shadow-2xl rounded-2xl p-8 border border-[#000]/20">
<h2 className="text-3xl font-bold bg-gradient-to-r from-blue-400 to-pink-400 bg-clip-text text-transparent text-center mb-6">
Instagram Auto Reply
</h2>
<div className="mb-5">
<label className="block text-left text-gray-300 font-medium mb-2">
Auto Reply Limit
</label>
<input
type="number"
className="w-full px-4 py-3 border rounded-xl
text-lg focus:ring-2 focus:ring-blue-500
focus:outline-none bg-[#111111] border-[#111111]/60 text-white"
value={limit}
onChange={(e) => setLimit(Number(e.target.value))}
/>
</div>
<button
onClick={triggerAutoReply}
disabled={loading}
title="Click to manually trigger the automation script to scan for new comments and reply."
className="w-full bg-gradient-to-r from-blue-600 to-pink-500
text-white py-3 rounded-xl font-semibold shadow-lg
transition-all hover:opacity-90"
>
{loading ? "Processing..." : "Start Auto Reply"}
</button>
<p className="mt-4 text-[10px] text-gray-500 text-center leading-tight">
* This tool uses the `instagram_manage_comments` permission to fetch your recent posts' comments and apply pre-configured automated responses.
</p>
{lastRun && (
<p className="text-center mt-4 text-gray-300">
Last executed: <b className="text-white">{lastRun}</b>
</p>
)}
<p className="text-center mt-4 text-blue-300 font-semibold text-lg">
Next auto-run in:{" "}
<span className="text-pink-400">{formatTime(nextRunIn)}</span>
</p>
<p className="text-center mt-6 text-xs text-gray-500">
Auto reply runs automatically every 1 hour.
</p>
</div>
</div>
);
};
export default AutomationPage;

48
app/(defaults)/layout.tsx Normal file
View File

@ -0,0 +1,48 @@
import ContentAnimation from '@/components/layouts/content-animation';
import Footer from '@/components/layouts/footer';
import Header from '@/components/layouts/header';
import MainContainer from '@/components/layouts/main-container';
import Overlay from '@/components/layouts/overlay';
import ScrollToTop from '@/components/layouts/scroll-to-top';
import Setting from '@/components/layouts/setting';
import Sidebar from '@/components/layouts/sidebar';
import Portals from '@/components/portals';
import ProtectedRoute from "@/components/protectedRouteLayout";
export default function DefaultLayout({ children }: { children: React.ReactNode }) {
return (
<>
<ProtectedRoute>
{/* BEGIN MAIN CONTAINER */}
<div className="relative">
<Overlay />
<ScrollToTop />
{/* BEGIN APP SETTING LAUNCHER */}
{/* <Setting /> */}
{/* END APP SETTING LAUNCHER */}
<MainContainer>
{/* BEGIN SIDEBAR */}
<Sidebar />
{/* END SIDEBAR */}
<div className="main-content flex min-h-screen flex-col">
{/* BEGIN TOP NAVBAR */}
<Header />
{/* END TOP NAVBAR */}
{/* BEGIN CONTENT AREA */}
<ContentAnimation>{children}</ContentAnimation>
{/* END CONTENT AREA */}
{/* BEGIN FOOTER */}
<Footer />
{/* END FOOTER */}
<Portals />
</div>
</MainContainer>
</div>
</ProtectedRoute>
</>
);
}

519
app/(defaults)/page.tsx Normal file
View File

@ -0,0 +1,519 @@
"use client";
import React, { useState, useEffect } from "react";
import Link from "next/link";
import axios from "axios";
import { ApiServerBaseUrl } from "@/utils/baseurl.utils";
import {
Instagram,
UserCircle2,
TrendingUp,
BarChart3,
MessageSquare,
Image as ImageIcon,
Shield,
CheckCircle,
AlertCircle,
Sparkles,
Zap,
Users,
Clock,
Globe,
Lock
} from "lucide-react";
const DashboardPage = () => {
const [connectionStatus, setConnectionStatus] = useState<"connected" | "not_connected" | "loading" | "error">("loading");
const [instagramUsername, setInstagramUsername] = useState<string | null>(null);
// Check Instagram connection status
useEffect(() => {
async function checkConnection() {
try {
const userId = localStorage.getItem("user_email");
const res = await axios.get(
`${ApiServerBaseUrl}/social/auth/status`,
{ params: { userId }, withCredentials: true }
);
if (res.data.connected) {
setConnectionStatus("connected");
if (res.data.username) {
setInstagramUsername(res.data.username);
}
} else {
setConnectionStatus("not_connected");
}
} catch (err) {
console.error(err);
setConnectionStatus("error");
}
}
checkConnection();
}, []);
const cards = [
{
name: "Instagram Media",
icon: <ImageIcon size={32} className="text-pink-500" />,
description: "Browse and manage your Instagram posts, stories, and media library.",
link: "/social-media-posts",
title: "View and organize your Instagram media content",
status: connectionStatus === "connected" ? "active" : "requires-connection"
},
{
name: "Social Media Channels",
icon: <Globe size={32} className="text-cyan-400" />,
description: "Manage all your connected social media profiles and channels in one place.",
link: "/social-media-channels",
title: "Toggle and manage your connected social media channels",
status: "active"
},
{
name: "Performance Insights",
icon: <BarChart3 size={32} className="text-green-500" />,
description: "Track engagement, reach, and growth metrics for your Instagram content.",
link: "/insights",
title: "View analytics and performance insights",
status: connectionStatus === "connected" ? "active" : "requires-connection"
},
{
name: "Comment Management",
icon: <MessageSquare size={32} className="text-blue-500" />,
description: "Reply, hide, or delete comments across your Instagram posts.",
link: "/comments",
title: "Manage Instagram comments from one dashboard",
status: connectionStatus === "connected" ? "active" : "requires-connection"
},
{
name: "Account Settings",
icon: <UserCircle2 size={32} className="text-purple-400" />,
description: "Manage your profile, billing, subscription, and preferences.",
link: "/account-settings",
title: "Account configuration and settings",
status: "active"
},
{
name: "Quick Actions",
icon: <Zap size={32} className="text-yellow-500" />,
description: "Quick access to common tasks and time-saving features.",
link: "/quick-actions",
title: "Quick action shortcuts",
status: "active"
},
];
const benefits = [
{
icon: <Clock className="w-5 h-5" />,
text: "Save time managing Instagram engagement"
},
{
icon: <Users className="w-5 h-5" />,
text: "Easily respond to your audience"
},
{
icon: <BarChart3 className="w-5 h-5" />,
text: "Track content performance with insights"
},
{
icon: <Shield className="w-5 h-5" />,
text: "Complete control over your content"
}
];
return (
<div className="min-h-screen bg-gradient-to-b from-[#0a0a0a] to-[#111111] p-4 md:p-6 relative overflow-x-hidden">
{/* Floating background bubbles */}
<div className="absolute top-20 left-[5%] w-24 h-24 rounded-full bg-pink-500/20 blur-xl animate-float"></div>
<div className="absolute top-40 right-[10%] w-32 h-32 rounded-full bg-blue-500/15 blur-xl animate-float-slow"></div>
<div className="absolute bottom-40 left-[20%] w-20 h-20 rounded-full bg-purple-500/20 blur-xl animate-float"></div>
<div className="absolute bottom-20 right-[15%] w-28 h-28 rounded-full bg-cyan-500/15 blur-xl animate-float-slow"></div>
{/* Main container */}
<div className="max-w-7xl mx-auto relative z-10">
{/* Header Section */}
<div className="mb-10">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8">
<div className="flex items-center gap-4">
<div className="relative">
<div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-blue-600 to-purple-600 flex items-center justify-center shadow-lg shadow-blue-500/30">
<Instagram className="w-8 h-8 text-white" />
</div>
<div className={`absolute -top-2 -right-2 w-6 h-6 rounded-full border-2 border-[#111111] flex items-center justify-center ${
connectionStatus === "connected" ? "bg-green-500" : "bg-gray-500"
}`}>
<div className="w-2 h-2 rounded-full bg-white"></div>
</div>
</div>
<div>
<h1 className="text-3xl md:text-4xl font-bold text-white">
Social Buddy Dashboard
</h1>
<p className="text-gray-300 mt-1">
Welcome back! Manage your social presence from one place.
</p>
</div>
</div>
{/* Connection Status Badge */}
<div className={`px-6 py-3 rounded-xl border backdrop-blur-sm ${
connectionStatus === "connected"
? "bg-green-900/20 border-green-500/30"
: connectionStatus === "not_connected"
? "bg-orange-900/20 border-orange-500/30"
: "bg-gray-900/20 border-gray-500/30"
}`}>
<div className="flex items-center gap-3">
<div className={`w-3 h-3 rounded-full ${
connectionStatus === "connected"
? "bg-green-500"
: connectionStatus === "not_connected"
? "bg-orange-500"
: "bg-gray-500 animate-pulse"
}`}></div>
<div>
<p className="text-white font-medium">
{connectionStatus === "connected"
? "Instagram Connected"
: connectionStatus === "not_connected"
? "Instagram Not Connected"
: "Checking Connection..."}
</p>
{connectionStatus === "connected" && instagramUsername && (
<p className="text-gray-400 text-sm">@{instagramUsername}</p>
)}
</div>
</div>
</div>
</div>
{/* Connection Status Overview */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-10">
<div className="bg-gradient-to-br from-gray-900/50 to-black/50 backdrop-blur-sm rounded-xl p-5 border border-white/10">
<div className="flex items-center justify-between mb-3">
<span className="text-gray-300 text-sm">Instagram Status</span>
{connectionStatus === "connected" ? (
<CheckCircle className="w-5 h-5 text-green-400" />
) : (
<Lock className="w-5 h-5 text-gray-400" />
)}
</div>
<div className="text-2xl font-bold text-white">
{connectionStatus === "connected" ? "Connected" : "Not Connected"}
</div>
</div>
<div className="bg-gradient-to-br from-gray-900/50 to-black/50 backdrop-blur-sm rounded-xl p-5 border border-white/10">
<div className="flex items-center justify-between mb-3">
<span className="text-gray-300 text-sm">Available Features</span>
<Sparkles className="w-5 h-5 text-yellow-400" />
</div>
<div className="text-2xl font-bold text-white">
{connectionStatus === "connected" ? "All" : "Basic"}
</div>
</div>
<div className="bg-gradient-to-br from-gray-900/50 to-black/50 backdrop-blur-sm rounded-xl p-5 border border-white/10">
<div className="flex items-center justify-between mb-3">
<span className="text-gray-300 text-sm">Media Access</span>
<ImageIcon className="w-5 h-5 text-pink-400" />
</div>
<div className="text-2xl font-bold text-white">
{connectionStatus === "connected" ? "Enabled" : "Connect to enable"}
</div>
</div>
</div>
</div>
{/* Main Content Grid */}
<div className="grid lg:grid-cols-3 gap-8">
{/* Left Column - Features Grid */}
<div className="lg:col-span-2">
<div className="mb-8">
<h2 className="text-2xl font-bold text-white mb-6 flex items-center gap-3">
<Sparkles className="w-6 h-6 text-yellow-400" />
Available Features
</h2>
<p className="text-gray-300 mb-6">
Connect your Instagram account to unlock all management tools.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{cards.map((card) => (
<Link
key={card.name}
href={card.link}
title={card.title}
className={`
relative group
bg-gradient-to-br from-gray-900/90 to-black/90
p-6
rounded-2xl
shadow-lg
border border-white/10
transition-all
duration-300
hover:scale-[1.02]
hover:shadow-2xl
hover:border-blue-500/30
min-h-[180px]
flex flex-col
overflow-hidden
${card.status === "requires-connection" && connectionStatus !== "connected"
? "opacity-70 cursor-not-allowed"
: ""
}
`}
onClick={(e) => {
if (card.status === "requires-connection" && connectionStatus !== "connected") {
e.preventDefault();
}
}}
>
{/* Lock icon for features requiring connection */}
{card.status === "requires-connection" && connectionStatus !== "connected" && (
<div className="absolute top-4 right-4 w-8 h-8 rounded-full bg-gray-800/80 flex items-center justify-center border border-gray-700">
<Lock className="w-4 h-4 text-gray-400" />
</div>
)}
{/* Icon */}
<div className={`w-12 h-12 rounded-xl flex items-center justify-center mb-4 ${
card.status === "requires-connection" && connectionStatus !== "connected"
? "bg-gray-800/50"
: "bg-gradient-to-br from-white/10 to-white/5 backdrop-blur-sm"
}`}>
{card.icon}
</div>
{/* Content */}
<div className="flex-grow">
<h2 className="text-xl font-bold text-white mb-2">
{card.name}
</h2>
<p className="text-gray-300 text-sm leading-relaxed">
{card.description}
</p>
</div>
{/* Status indicator */}
<div className="mt-4 flex justify-between items-center">
{card.status === "requires-connection" && connectionStatus !== "connected" ? (
<span className="text-sm text-orange-400 flex items-center gap-2">
<AlertCircle className="w-4 h-4" />
Connect Instagram to access
</span>
) : (
<span className="text-sm text-gray-400">Click to access</span>
)}
<div className={`
w-8 h-8
rounded-full
flex items-center justify-center
transition-colors duration-300
${card.status === "requires-connection" && connectionStatus !== "connected"
? "bg-gray-800 text-gray-500"
: "bg-white/10 text-white/60 group-hover:bg-blue-500/20 group-hover:text-white"
}
`}>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>
</div>
</div>
{/* Hover overlay */}
<div
className="
absolute inset-0 rounded-2xl
bg-gradient-to-br from-blue-500/5 via-transparent to-pink-500/5
opacity-0 group-hover:opacity-100
transition-opacity duration-500 pointer-events-none
"
></div>
</Link>
))}
</div>
</div>
{/* Connection Prompt */}
{connectionStatus === "not_connected" && (
<div className="bg-gradient-to-r from-blue-900/20 to-purple-900/20 rounded-2xl p-8 border border-blue-500/30 backdrop-blur-xl">
<div className="flex items-start gap-4">
<div className="w-12 h-12 rounded-xl bg-blue-500/20 flex items-center justify-center flex-shrink-0">
<Instagram className="w-6 h-6 text-blue-400" />
</div>
<div>
<h3 className="text-xl font-bold text-white mb-3">Connect Your Instagram Account</h3>
<p className="text-gray-300 mb-6">
Connect your Instagram account to access media management, insights,
and comment moderation tools. All features are user-controlled and
require manual action.
</p>
<Link
href="/social-media-connect"
className="inline-flex items-center gap-2 px-6 py-3 rounded-xl bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white font-semibold transition-all duration-300 hover:scale-105"
>
<Sparkles className="w-5 h-5" />
Connect Instagram Account
</Link>
</div>
</div>
</div>
)}
</div>
{/* Right Column - Sidebar */}
<div className="space-y-8">
{/* Benefits Card */}
<div className="bg-gradient-to-br from-gray-900/90 to-black/90 backdrop-blur-xl rounded-2xl p-6 border border-white/10 shadow-2xl">
<h3 className="text-xl font-bold text-white mb-6 flex items-center gap-3">
<Zap className="w-5 h-5 text-yellow-400" />
What You Can Do
</h3>
<div className="space-y-4">
{benefits.map((benefit, index) => (
<div key={index} className="flex items-start gap-3">
<div className="w-6 h-6 rounded-full bg-green-500/20 flex items-center justify-center flex-shrink-0 mt-1">
<div className="text-green-400">
{benefit.icon}
</div>
</div>
<p className="text-gray-300 text-sm">{benefit.text}</p>
</div>
))}
</div>
</div>
{/* Connection Status Card */}
<div className="bg-gradient-to-br from-gray-900/90 to-black/90 backdrop-blur-xl rounded-2xl p-6 border border-white/10 shadow-2xl">
<h3 className="text-xl font-bold text-white mb-4">Connection Overview</h3>
<div className="space-y-6">
<div className="flex items-center justify-between p-4 rounded-xl bg-white/5">
<div className="flex items-center gap-3">
<Instagram className={`w-6 h-6 ${
connectionStatus === "connected" ? "text-green-400" : "text-gray-400"
}`} />
<span className="text-white">Instagram Account</span>
</div>
<div className={`px-3 py-1 rounded-full text-sm font-medium ${
connectionStatus === "connected"
? "bg-green-500/20 text-green-400"
: connectionStatus === "not_connected"
? "bg-orange-500/20 text-orange-400"
: "bg-gray-500/20 text-gray-400"
}`}>
{connectionStatus === "connected" ? "Connected" :
connectionStatus === "not_connected" ? "Not Connected" :
"Checking..."}
</div>
</div>
<div className="space-y-3">
<div className="flex items-center justify-between text-sm">
<span className="text-gray-400">Media Management</span>
<span className={`${connectionStatus === "connected" ? "text-green-400" : "text-gray-500"}`}>
{connectionStatus === "connected" ? "Available" : "Requires connection"}
</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-gray-400">Performance Insights</span>
<span className={`${connectionStatus === "connected" ? "text-green-400" : "text-gray-500"}`}>
{connectionStatus === "connected" ? "Available" : "Requires connection"}
</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-gray-400">Comment Moderation</span>
<span className={`${connectionStatus === "connected" ? "text-green-400" : "text-gray-500"}`}>
{connectionStatus === "connected" ? "Available" : "Requires connection"}
</span>
</div>
</div>
<div className="pt-4 border-t border-white/10">
{connectionStatus === "not_connected" ? (
<Link
href="/social-media-connect"
className="block w-full text-center py-3 rounded-xl bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white font-medium transition-all duration-300 hover:scale-105"
>
Connect Instagram Account
</Link>
) : connectionStatus === "connected" ? (
<Link
href="/social-media-connect"
className="block w-full text-center py-3 rounded-xl bg-gradient-to-r from-gray-700 to-gray-800 hover:from-gray-800 hover:to-gray-900 text-white font-medium transition-all duration-300"
>
Manage Connection Settings
</Link>
) : null}
</div>
</div>
</div>
{/* Important Notes - For Meta Approval */}
<div className="bg-gradient-to-br from-blue-900/20 to-indigo-900/20 rounded-2xl p-6 border border-blue-500/30 backdrop-blur-xl">
<h4 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
<Shield className="w-5 h-5 text-blue-400" />
Important Notes
</h4>
<ul className="space-y-3 text-sm">
<li className="flex items-start gap-2 text-gray-300">
<div className="w-5 h-5 rounded-full bg-green-500/20 flex items-center justify-center flex-shrink-0 mt-0.5">
<CheckCircle className="w-3 h-3 text-green-400" />
</div>
<span>All actions are user-initiated and user-controlled</span>
</li>
<li className="flex items-start gap-2 text-gray-300">
<div className="w-5 h-5 rounded-full bg-green-500/20 flex items-center justify-center flex-shrink-0 mt-0.5">
<CheckCircle className="w-3 h-3 text-green-400" />
</div>
<span>No automated commenting or AI-generated responses</span>
</li>
<li className="flex items-start gap-2 text-gray-300">
<div className="w-5 h-5 rounded-full bg-green-500/20 flex items-center justify-center flex-shrink-0 mt-0.5">
<CheckCircle className="w-3 h-3 text-green-400" />
</div>
<span>Manual moderation tools only</span>
</li>
</ul>
</div>
</div>
</div>
</div>
{/* Animation Styles */}
<style jsx>{`
@keyframes float {
0%, 100% {
transform: translateY(0) translateX(0);
}
50% {
transform: translateY(-20px) translateX(10px);
}
}
@keyframes float-slow {
0%, 100% {
transform: translateY(0) translateX(0);
}
50% {
transform: translateY(-30px) translateX(-15px);
}
.animate-float {
animation: float 8s ease-in-out infinite;
}
.animate-float-slow {
animation: float-slow 10s ease-in-out infinite;
}
`}</style>
</div>
);
};
export default DashboardPage;

View File

@ -0,0 +1,535 @@
"use client";
import React, { useState, useEffect } from "react";
import Link from "next/link";
import axios from "axios";
import { ApiServerBaseUrl } from "@/utils/baseurl.utils";
import {
Instagram,
UserCircle2,
TrendingUp,
BarChart3,
MessageSquare,
Image as ImageIcon,
Shield,
CheckCircle,
AlertCircle,
Sparkles,
Zap,
Users,
Clock,
Globe
} from "lucide-react";
const DashboardPage = () => {
const [connectionStatus, setConnectionStatus] = useState<"connected" | "not_connected" | "loading" | "error">("loading");
const [instagramUsername, setInstagramUsername] = useState<string | null>(null);
const [stats, setStats] = useState({
posts: 0,
comments: 0,
followers: 0
});
// Check Instagram connection status
useEffect(() => {
async function checkConnection() {
try {
const userId = localStorage.getItem("user_email");
const res = await axios.get(
`${ApiServerBaseUrl}/social/auth/status`,
{ params: { userId }, withCredentials: true }
);
if (res.data.connected) {
setConnectionStatus("connected");
if (res.data.username) {
setInstagramUsername(res.data.username);
}
// Fetch basic stats if connected
fetchInstagramStats();
} else {
setConnectionStatus("not_connected");
}
} catch (err) {
console.error(err);
setConnectionStatus("error");
}
}
async function fetchInstagramStats() {
try {
// This would be replaced with your actual API call
// For now, using mock data
setStats({
posts: 42,
comments: 156,
followers: 1250
});
} catch (err) {
console.error("Failed to fetch stats:", err);
}
}
checkConnection();
}, []);
const cards = [
{
name: "Instagram Media",
icon: <ImageIcon size={32} className="text-pink-500" />,
description:
"Browse and manage your Instagram posts, stories, and media library.",
link: "/social-media-posts",
title: "View and organize your Instagram media content",
status: connectionStatus === "connected" ? "active" : "requires-connection",
badge: connectionStatus === "connected" ? "Connected" : "Connect Required"
},
{
name: "Social Media Channels",
icon: <Globe size={32} className="text-cyan-400" />,
description:
"Manage all your connected social media profiles and channels in one place.",
link: "/social-media-channels",
title: "Toggle and manage your connected social media channels",
status: "active",
badge: "Manage"
},
{
name: "Performance Insights",
icon: <BarChart3 size={32} className="text-green-500" />,
description:
"Track engagement, reach, and growth metrics for your Instagram content.",
link: "/insights",
title: "View analytics and performance insights",
status: connectionStatus === "connected" ? "active" : "requires-connection",
badge: connectionStatus === "connected" ? "Available" : "Connect Required"
},
{
name: "Comment Management",
icon: <MessageSquare size={32} className="text-blue-500" />,
description:
"Reply, hide, or delete comments across your Instagram posts efficiently.",
link: "/comments",
title: "Manage Instagram comments from one dashboard",
status: connectionStatus === "connected" ? "active" : "requires-connection",
badge: connectionStatus === "connected" ? "Ready" : "Connect Required"
},
{
name: "Account Settings",
icon: <UserCircle2 size={32} className="text-purple-400" />,
description: "Manage your profile, billing, subscription, and preferences.",
link: "/account-settings",
title: "Account configuration and settings",
status: "active",
badge: "Settings"
},
{
name: "Quick Actions",
icon: <Zap size={32} className="text-yellow-500" />,
description:
"Quick access to common tasks and time-saving features.",
link: "/quick-actions",
title: "Quick action shortcuts",
status: "active",
badge: "New"
},
];
const quickStats = [
{
label: "Instagram Posts",
value: stats.posts || "-",
icon: <ImageIcon className="w-5 h-5 text-pink-400" />,
color: "from-pink-500/20 to-pink-600/10"
},
{
label: "Total Comments",
value: stats.comments || "-",
icon: <MessageSquare className="w-5 h-5 text-blue-400" />,
color: "from-blue-500/20 to-blue-600/10"
},
{
label: "Followers",
value: stats.followers ? `${stats.followers}` : "-",
icon: <Users className="w-5 h-5 text-green-400" />,
color: "from-green-500/20 to-green-600/10"
}
];
const benefits = [
{
icon: <Clock className="w-5 h-5" />,
text: "Save hours weekly on social media management"
},
{
icon: <Users className="w-5 h-5" />,
text: "Engage with your audience more effectively"
},
{
icon: <BarChart3 className="w-5 h-5" />,
text: "Make data-driven decisions with insights"
},
{
icon: <Shield className="w-5 h-5" />,
text: "Complete control over your content and interactions"
}
];
return (
<div className="min-h-screen bg-gradient-to-b from-[#0a0a0a] to-[#111111] p-4 md:p-6 relative overflow-x-hidden">
{/* Floating background bubbles */}
<div className="absolute top-20 left-[5%] w-24 h-24 rounded-full bg-pink-500/20 blur-xl animate-float"></div>
<div className="absolute top-40 right-[10%] w-32 h-32 rounded-full bg-blue-500/15 blur-xl animate-float-slow"></div>
<div className="absolute bottom-40 left-[20%] w-20 h-20 rounded-full bg-purple-500/20 blur-xl animate-float"></div>
<div className="absolute bottom-20 right-[15%] w-28 h-28 rounded-full bg-cyan-500/15 blur-xl animate-float-slow"></div>
{/* Main container */}
<div className="max-w-7xl mx-auto relative z-10">
{/* Header Section */}
<div className="mb-10">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8">
<div className="flex items-center gap-4">
<div className="relative">
<div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-blue-600 to-purple-600 flex items-center justify-center shadow-lg shadow-blue-500/30">
<Instagram className="w-8 h-8 text-white" />
</div>
<div className="absolute -top-2 -right-2 w-6 h-6 rounded-full bg-green-500 border-2 border-[#111111] flex items-center justify-center">
<div className="w-2 h-2 rounded-full bg-white"></div>
</div>
</div>
<div>
<h1 className="text-3xl md:text-4xl font-bold text-white">
Social Buddy Dashboard
</h1>
<p className="text-gray-300 mt-1">
Welcome back! Manage your social presence effortlessly.
</p>
</div>
</div>
{/* Connection Status Badge */}
<div className={`px-6 py-3 rounded-xl border backdrop-blur-sm ${
connectionStatus === "connected"
? "bg-green-900/20 border-green-500/30"
: connectionStatus === "not_connected"
? "bg-orange-900/20 border-orange-500/30"
: "bg-gray-900/20 border-gray-500/30"
}`}>
<div className="flex items-center gap-3">
<div className={`w-3 h-3 rounded-full animate-pulse ${
connectionStatus === "connected"
? "bg-green-500"
: connectionStatus === "not_connected"
? "bg-orange-500"
: "bg-gray-500"
}`}></div>
<div>
<p className="text-white font-medium">
{connectionStatus === "connected"
? "Instagram Connected"
: connectionStatus === "not_connected"
? "Instagram Not Connected"
: "Checking Connection..."}
</p>
{connectionStatus === "connected" && instagramUsername && (
<p className="text-gray-400 text-sm">@{instagramUsername}</p>
)}
</div>
</div>
</div>
</div>
{/* Quick Stats */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-10">
{quickStats.map((stat, index) => (
<div
key={index}
className={`bg-gradient-to-br ${stat.color} backdrop-blur-sm rounded-xl p-5 border border-white/10`}
>
<div className="flex items-center justify-between mb-3">
<span className="text-gray-300 text-sm">{stat.label}</span>
{stat.icon}
</div>
<div className="text-2xl font-bold text-white">{stat.value}</div>
{connectionStatus !== "connected" && stat.value === "-" && (
<p className="text-gray-400 text-xs mt-2">Connect Instagram to view</p>
)}
</div>
))}
</div>
</div>
{/* Main Content Grid */}
<div className="grid lg:grid-cols-3 gap-8">
{/* Left Column - Features Grid */}
<div className="lg:col-span-2">
<div className="mb-8">
<h2 className="text-2xl font-bold text-white mb-6 flex items-center gap-3">
<Sparkles className="w-6 h-6 text-yellow-400" />
Available Features
</h2>
<p className="text-gray-300 mb-6">
All the tools you need to manage your Instagram presence efficiently.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{cards.map((card) => (
<Link
key={card.name}
href={card.link}
title={card.title}
className={`
relative group
bg-gradient-to-br from-gray-900/90 to-black/90
p-6
rounded-2xl
shadow-lg
border border-white/10
transition-all
duration-300
hover:scale-[1.02]
hover:shadow-2xl
hover:border-blue-500/30
min-h-[180px]
flex flex-col
overflow-hidden
${card.status === "requires-connection" ? "opacity-80" : ""}
`}
>
{/* Status Badge */}
<div className={`absolute top-4 right-4 px-3 py-1 rounded-full text-xs font-medium ${
card.status === "requires-connection"
? "bg-orange-500/20 text-orange-400 border border-orange-500/30"
: "bg-blue-500/20 text-blue-400 border border-blue-500/30"
}`}>
{card.badge}
</div>
{/* Icon */}
<div className="w-12 h-12 rounded-xl flex items-center justify-center mb-4 bg-gradient-to-br from-white/10 to-white/5 backdrop-blur-sm">
{card.icon}
</div>
{/* Content */}
<div className="flex-grow">
<h2 className="text-xl font-bold text-white mb-2">
{card.name}
</h2>
<p className="text-gray-300 text-sm leading-relaxed">
{card.description}
</p>
</div>
{/* Arrow indicator */}
<div className="mt-4 flex justify-between items-center">
{card.status === "requires-connection" && connectionStatus === "not_connected" ? (
<span className="text-sm text-orange-400 flex items-center gap-2">
<AlertCircle className="w-4 h-4" />
Connect Instagram first
</span>
) : (
<span className="text-sm text-gray-400">Click to access</span>
)}
<div className={`
w-8 h-8
rounded-full
flex items-center justify-center
transition-colors duration-300
${card.status === "requires-connection" && connectionStatus === "not_connected"
? "bg-gray-500/20 text-gray-400"
: "bg-white/10 text-white/60 group-hover:bg-blue-500/20 group-hover:text-white"
}
`}>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>
</div>
</div>
{/* Hover overlay */}
<div
className="
absolute inset-0 rounded-2xl
bg-gradient-to-br from-blue-500/5 via-transparent to-pink-500/5
opacity-0 group-hover:opacity-100
transition-opacity duration-500 pointer-events-none
"
></div>
</Link>
))}
</div>
</div>
{/* Connection Prompt */}
{connectionStatus === "not_connected" && (
<div className="bg-gradient-to-r from-blue-900/20 to-purple-900/20 rounded-2xl p-8 border border-blue-500/30 backdrop-blur-xl mb-8">
<div className="flex items-start gap-4">
<div className="w-12 h-12 rounded-xl bg-blue-500/20 flex items-center justify-center flex-shrink-0">
<Instagram className="w-6 h-6 text-blue-400" />
</div>
<div>
<h3 className="text-xl font-bold text-white mb-3">Connect Your Instagram Account</h3>
<p className="text-gray-300 mb-4">
Unlock the full potential of Social Buddy by connecting your Instagram account.
Access media management, insights, and comment tools in one dashboard.
</p>
<Link
href="/social-media-connect"
className="inline-flex items-center gap-2 px-6 py-3 rounded-xl bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white font-semibold transition-all duration-300 hover:scale-105"
>
<Sparkles className="w-5 h-5" />
Connect Instagram Now
</Link>
</div>
</div>
</div>
)}
</div>
{/* Right Column - Sidebar */}
<div className="space-y-8">
{/* Benefits Card */}
<div className="bg-gradient-to-br from-gray-900/90 to-black/90 backdrop-blur-xl rounded-2xl p-6 border border-white/10 shadow-2xl">
<h3 className="text-xl font-bold text-white mb-6 flex items-center gap-3">
<Zap className="w-5 h-5 text-yellow-400" />
Why Social Buddy?
</h3>
<div className="space-y-4">
{benefits.map((benefit, index) => (
<div key={index} className="flex items-start gap-3">
<div className="w-6 h-6 rounded-full bg-green-500/20 flex items-center justify-center flex-shrink-0 mt-1">
<div className="text-green-400">
{benefit.icon}
</div>
</div>
<p className="text-gray-300 text-sm">{benefit.text}</p>
</div>
))}
</div>
</div>
{/* Connection Status Card */}
<div className="bg-gradient-to-br from-gray-900/90 to-black/90 backdrop-blur-xl rounded-2xl p-6 border border-white/10 shadow-2xl">
<h3 className="text-xl font-bold text-white mb-4">Connection Status</h3>
<div className="space-y-4">
<div className="flex items-center justify-between p-4 rounded-xl bg-white/5">
<div className="flex items-center gap-3">
<Instagram className={`w-6 h-6 ${
connectionStatus === "connected" ? "text-green-400" : "text-gray-400"
}`} />
<span className="text-white">Instagram</span>
</div>
<div className={`px-3 py-1 rounded-full text-sm font-medium ${
connectionStatus === "connected"
? "bg-green-500/20 text-green-400"
: connectionStatus === "not_connected"
? "bg-orange-500/20 text-orange-400"
: "bg-gray-500/20 text-gray-400"
}`}>
{connectionStatus === "connected" ? "Connected" :
connectionStatus === "not_connected" ? "Not Connected" :
"Checking..."}
</div>
</div>
<div className="space-y-3">
<div className="flex items-center justify-between text-sm">
<span className="text-gray-400">Media Access</span>
<span className={`${connectionStatus === "connected" ? "text-green-400" : "text-gray-500"}`}>
{connectionStatus === "connected" ? "✓ Available" : "—"}
</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-gray-400">Insights Access</span>
<span className={`${connectionStatus === "connected" ? "text-green-400" : "text-gray-500"}`}>
{connectionStatus === "connected" ? "✓ Available" : "—"}
</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-gray-400">Comment Management</span>
<span className={`${connectionStatus === "connected" ? "text-green-400" : "text-gray-500"}`}>
{connectionStatus === "connected" ? "✓ Available" : "—"}
</span>
</div>
</div>
{connectionStatus === "not_connected" && (
<Link
href="/social-media-connect"
className="block w-full text-center py-3 rounded-xl bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white font-medium transition-all duration-300 hover:scale-105 mt-4"
>
Connect Instagram
</Link>
)}
{connectionStatus === "connected" && (
<Link
href="/social-media-connect"
className="block w-full text-center py-3 rounded-xl bg-gradient-to-r from-gray-700 to-gray-800 hover:from-gray-800 hover:to-gray-900 text-white font-medium transition-all duration-300 mt-4"
>
Manage Connection
</Link>
)}
</div>
</div>
{/* Quick Tips */}
<div className="bg-gradient-to-br from-purple-900/20 to-pink-900/20 rounded-2xl p-6 border border-purple-500/30 backdrop-blur-xl">
<h4 className="text-lg font-semibold text-white mb-4">Quick Tips</h4>
<ul className="space-y-3">
<li className="flex items-start gap-3 text-sm">
<div className="w-5 h-5 rounded-full bg-blue-500/30 flex items-center justify-center flex-shrink-0 mt-0.5">
<span className="text-blue-400 text-xs">1</span>
</div>
<span className="text-gray-300">Connect Instagram to unlock all features</span>
</li>
<li className="flex items-start gap-3 text-sm">
<div className="w-5 h-5 rounded-full bg-blue-500/30 flex items-center justify-center flex-shrink-0 mt-0.5">
<span className="text-blue-400 text-xs">2</span>
</div>
<span className="text-gray-300">Check your media library for post management</span>
</li>
<li className="flex items-start gap-3 text-sm">
<div className="w-5 h-5 rounded-full bg-blue-500/30 flex items-center justify-center flex-shrink-0 mt-0.5">
<span className="text-blue-400 text-xs">3</span>
</div>
<span className="text-gray-300">Use insights to track content performance</span>
</li>
</ul>
</div>
</div>
</div>
</div>
{/* Animation Styles */}
<style jsx>{`
@keyframes float {
0%, 100% {
transform: translateY(0) translateX(0);
}
50% {
transform: translateY(-20px) translateX(10px);
}
}
@keyframes float-slow {
0%, 100% {
transform: translateY(0) translateX(0);
}
50% {
transform: translateY(-30px) translateX(-15px);
}
}
.animate-float {
animation: float 8s ease-in-out infinite;
}
.animate-float-slow {
animation: float-slow 10s ease-in-out infinite;
}
`}</style>
</div>
);
};
export default DashboardPage;

View File

@ -0,0 +1,155 @@
'use client';
import React, { useEffect, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import axios from "axios";
import IconXCircle from "@/components/icon/icon-x-circle";
interface PaymentDetails {
email: string;
amount: number;
planId: string;
stripeSessionId: string;
status: string;
created_at: string;
}
const PaymentFailure: React.FC = () => {
const [payment, setPayment] = useState<any>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const router = useRouter();
const searchParams = useSearchParams();
const sessionId = searchParams.get("session_id"); // Stripe sends session_id in query
useEffect(() => {
if (!sessionId) {
setError("No session_id provided");
setLoading(false);
return;
}
const fetchPaymentDetails = async () => {
try {
const res:any = await axios.get(
`https://ebay.backend.data4autos.com/api/payment/details`,
{ params: { session_id: sessionId } }
);
setPayment(res.data.payment);
} catch (err: any) {
console.error(err);
setError(err.response?.data?.error || "Failed to fetch payment details");
} finally {
setLoading(false);
}
};
fetchPaymentDetails();
}, [sessionId]);
if (loading) {
return (
<div className="flex items-center justify-center min-h-[83vh] text-gray-500">
Loading payment details...
</div>
);
}
if (error) {
return (
<div className="flex items-center justify-center min-h-[83vh] text-red-500">
{error}
</div>
);
}
return (
<div className="flex flex-col items-center justify-center min-h-[83vh] bg-gradient-to-br from-red-50 via-white to-red-100 px-4">
{/* ❌ Error Icon */}
<div className="animate-bounce mb-6">
<IconXCircle className="w-24 h-24 text-red-500 drop-shadow-md" />
</div>
{/* ❌ Title */}
<h1 className="text-3xl font-bold text-gray-800 mb-2 text-center">
Payment Failed
</h1>
{/* ❌ Subtitle */}
<p className="text-gray-600 mb-6 text-center max-w-md">
Unfortunately, your payment could not be processed. Please try again or
contact support if the issue persists.
</p>
{/* ❌ Card with Payment Details */}
<div className="bg-white shadow-lg rounded-2xl p-6 w-full max-w-md border border-gray-100 transition-transform duration-300 hover:scale-[1.02] space-y-3">
<div className="flex justify-between items-start">
<span className="text-gray-500">Transaction ID:</span>
<span className="font-semibold text-gray-800 break-all max-w-[60%]">
{payment?.stripeSessionId}
</span>
</div>
<div className="flex justify-between items-start">
<span className="text-gray-500">Email:</span>
<span className="font-semibold text-gray-800 break-words max-w-[60%]">
{payment?.email}
</span>
</div>
<div className="flex justify-between items-start">
<span className="text-gray-500">Plan:</span>
<span className="font-semibold text-gray-800 break-words max-w-[60%]">
{payment?.planId}
</span>
</div>
<div className="flex justify-between items-start">
<span className="text-gray-500">Amount:</span>
<span className="font-semibold text-gray-800 break-words max-w-[60%]">
${payment?.amount / 100}
</span>
</div>
<div className="flex justify-between items-start">
<span className="text-gray-500">Payment Status:</span>
<span className="font-semibold text-red-600 break-words max-w-[60%]">
{payment?.status}
</span>
</div>
<div className="flex justify-between items-start">
<span className="text-gray-500">Date:</span>
<span className="font-semibold text-gray-800 break-words max-w-[60%]">
{new Date(payment?.created_at || "").toLocaleString()}
</span>
</div>
</div>
{/* ❌ Buttons */}
<div className="mt-8 flex flex-col sm:flex-row gap-4">
<button
onClick={() => router.push("/pricing")}
className="bg-red-500 hover:bg-red-600 text-white px-6 py-3 rounded-xl shadow-md font-medium transition-all duration-200 hover:shadow-lg"
>
Retry Payment
</button>
<button
onClick={() => router.push("/support")}
className="bg-gray-200 hover:bg-gray-300 text-gray-800 px-6 py-3 rounded-xl shadow-md font-medium transition-all duration-200 hover:shadow-lg"
>
Contact Support
</button>
</div>
{/* ❌ Footer Text */}
<p className="mt-6 text-sm text-gray-400 text-center">
Dont worry no amount has been deducted from your account.
</p>
</div>
);
};
export default PaymentFailure;

View File

@ -0,0 +1,153 @@
'use client';
import React, { useEffect, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import axios from "axios";
import IconCircleCheck from "@/components/icon/icon-circle-check";
import { ApiServerBaseUrl } from "@/utils/baseurl.utils";
const PaymentSuccess: React.FC = () => {
const [payment, setPayment] = useState<any>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const router = useRouter();
const searchParams = useSearchParams();
const sessionId = searchParams.get("session_id");
useEffect(() => {
if (!sessionId) {
setError("No session_id provided");
setLoading(false);
return;
}
const fetchPaymentDetails = async () => {
try {
const res: any = await axios.get(
`${ApiServerBaseUrl}/payment/details`,
{ params: { session_id: sessionId } }
);
setPayment(res.data.data);
localStorage.setItem('payment_session', res.data.data?.stripeSessionId);
} catch (err: any) {
console.error(err);
setError(err.response?.data?.error || "Failed to fetch payment details");
} finally {
setLoading(false);
}
};
fetchPaymentDetails();
}, [sessionId]);
if (loading) {
return (
<div className="flex items-center justify-center min-h-[88vh] text-gray-400 bg-[#111111]">
Loading payment details...
</div>
);
}
if (error) {
return (
<div className="flex items-center justify-center min-h-[88vh] text-red-500 bg-[#111111]">
{error}
</div>
);
}
return (
<div className="relative flex flex-col items-center justify-center min-h-[88vh] bg-[#111111] px-4 overflow-hidden">
{/* ------------------ GLOWING BUBBLES ------------------ */}
<div className="absolute bottom-[-100px] left-[10%] w-[70px] h-[70px] rounded-full bg-pink-500/60 blur-3xl shadow-lg animate-floatUp mix-blend-add"></div>
<div className="absolute bottom-[-120px] left-[30%] w-[90px] h-[90px] rounded-full bg-blue-500/60 blur-3xl shadow-lg animate-floatUpSlow mix-blend-add"></div>
<div className="absolute bottom-[-110px] left-[60%] w-[60px] h-[60px] rounded-full bg-green-400/60 blur-3xl shadow-lg animate-floatUpFast mix-blend-add"></div>
<div className="absolute bottom-[-130px] left-[80%] w-[100px] h-[100px] rounded-full bg-purple-500/60 blur-3xl shadow-lg animate-floatUpSlow mix-blend-add"></div>
<div className="absolute bottom-[-140px] left-[45%] w-[50px] h-[50px] rounded-full bg-yellow-400/60 blur-3xl shadow-lg animate-floatUp mix-blend-add"></div>
{/* Extra scattered bubbles */}
<div className="absolute bottom-[-150px] left-[20%] w-[40px] h-[40px] rounded-full bg-red-500/50 blur-2xl shadow-lg animate-floatUpFast mix-blend-add"></div>
<div className="absolute bottom-[-160px] left-[70%] w-[55px] h-[55px] rounded-full bg-cyan-400/50 blur-2xl shadow-lg animate-floatUp mix-blend-add"></div>
{/* ✅ Success Icon */}
<div className="animate-bounce mb-3 z-10">
<IconCircleCheck className="w-24 h-24 text-green-400 drop-shadow-lg" />
</div>
{/* ✅ Title */}
<h1 className="text-3xl font-bold text-white mb-2 text-center z-10">
Payment Successful 🎉
</h1>
{/* ✅ Subtitle */}
<p className="text-gray-300 mb-6 text-center max-w-md z-10">
Thank you for your purchase! Your payment has been processed successfully.
</p>
{/* Card with Details */}
<div className="bg-[#242424] shadow-xl rounded-2xl p-6 w-full max-w-md border border-gray-700 transition-transform duration-300 hover:scale-[1.02] space-y-3 z-10">
<div className="flex justify-between items-start mb-3">
<span className="text-gray-400">Transaction ID:</span>
<span className="font-semibold text-white break-all max-w-[60%]">
{payment?.stripeSessionId}
</span>
</div>
<div className="flex justify-between items-start mb-3">
<span className="text-gray-400">Email:</span>
<span className="font-semibold text-white break-words max-w-[60%]">
{payment?.email}
</span>
</div>
<div className="flex justify-between items-start mb-3">
<span className="text-gray-400">Plan:</span>
<span className="font-semibold text-white break-words max-w-[60%]">
{payment?.planId}
</span>
</div>
{/* <div className="flex justify-between items-start mb-3">
<span className="text-gray-400">Amount:</span>
<span className="font-semibold text-white break-words max-w-[60%]">
${payment?.amount}
</span>
</div> */}
<div className="flex justify-between items-start mb-3">
<span className="text-gray-400">Payment Status:</span>
<span className="font-semibold text-green-400 break-words max-w-[60%]">
{payment?.status}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Subscription:</span>
<span className="font-semibold text-white">
{payment?.startDate
? `${new Date(payment.startDate).toLocaleDateString()}${new Date(payment.endDate).toLocaleDateString()}`
: "Pending activation"}
</span>
</div>
</div>
{/* ✅ Button */}
<button
onClick={() => router.push("/")}
className="mt-8 bg-green-500 hover:bg-green-600 text-white px-6 py-3 rounded-xl shadow-lg font-medium transition-all duration-200 hover:shadow-2xl z-10"
>
Go to Dashboard
</button>
{/* ✅ Footer Text */}
<p className="mt-6 text-sm text-gray-400 text-center z-10">
You can view your payment history anytime from your dashboard.
</p>
</div>
);
};
export default PaymentSuccess;

View File

@ -0,0 +1,65 @@
import ComponentsPricingTableToggle from '@/components/pricing-table/components-pricing-table-toggle';
import { Metadata } from 'next';
import Link from 'next/link';
import React from 'react';
export const metadata: Metadata = {
title: 'Pricing Table',
};
const PricingTable = () => {
return (
<div className="min-h-screen bg-[#111111] flex items-center justify-center p-4 relative overflow-hidden">
{/* Background Glows */}
{/* blue left */}
<div className="absolute top-[180px] left-52 w-[100px] h-[100px]
bg-[#1d8be0] rounded-full blur-2xl opacity-[1.5] animate-zoomslow">
</div>
{/* green left */}
<div className="absolute top-10 left-0 w-[60px] h-[60px]
bg-[#6cb655] rounded-full blur-2xl opacity-[1.5] animate-zoomslower">
</div>
{/* pink big left */}
<div className="absolute -left-[80px] bottom-[140px] w-[100px] h-[200px]
bg-[#db21d9] blur-3xl opacity-1 animate-zoomslow">
</div>
{/* small pink under card */}
<div className="absolute bottom-20 left-[440px] w-[60px] h-[60px]
bg-[#db21d9] rounded-full blur-xl opacity-80 -translate-x-1/2 translate-y-1/2">
</div>
{/* orange */}
<div className="absolute top-[100px] right-[260px] w-[100px] h-[100px]
bg-[#f28f50] rounded-full blur-2xl opacity-80 animate-zoomfast">
</div>
{/* green right */}
<div className="absolute top-10 right-0 w-[60px] h-[60px]
bg-[#6cb655] rounded-full blur-2xl opacity-[1.5] animate-zoomslower">
</div>
{/* purple right */}
<div className="absolute bottom-20 right-[180px] w-[80px] h-[80px]
bg-[#783e8d] rounded-full blur-2xl opacity-80 animate-zoomslow">
</div>
{/* yellow bottom right */}
<div className="absolute top-[280px] -right-[20px] w-[160px] h-[160px]
bg-[#f1b74d] rounded-full blur-2xl opacity-1 animate-zoomslower">
</div>
{/* ===== Main Content ===== */}
<div className="relative z-10 w-full max-w-6xl text-white space-y-8">
{/* Toggle Pricing Table */}
<ComponentsPricingTableToggle />
</div>
</div>
);
};
export default PricingTable;

View File

@ -0,0 +1,617 @@
"use client";
import React, { useEffect, useState, useCallback } from "react";
import { useRouter } from "next/navigation";
import axios from "axios";
import { ApiServerBaseUrl } from "@/utils/baseurl.utils";
import { getSocialAuthStatus } from "@/utils/commonFunction.utils";
import {
Instagram,
CheckCircle,
XCircle,
Link2,
Users,
Image as ImageIcon,
Globe,
BarChart3,
Shield,
Zap,
ArrowRight,
ExternalLink,
RefreshCw
} from "lucide-react";
import Link from "next/link";
interface Channel {
_id: string;
name: string;
id?: string;
has_linked_account: boolean;
}
const SocialMediaChannels = () => {
const router = useRouter();
const [channels, setChannels] = useState<Channel[]>([]);
const [loading, setLoading] = useState(true);
const [openModal, setOpenModal] = useState(false);
const [successMsg, setSuccessMsg] = useState("");
const [connectedChannel, setConnectedChannel] = useState<string | null>(null);
const [accountData, setAccountData] = useState<any>(null);
const [refreshing, setRefreshing] = useState(false);
// ---------------------------------------------
// VALIDATE USER ACCESS
// ---------------------------------------------
useEffect(() => {
async function validate() {
const userEmail = localStorage.getItem("user_email");
if (!userEmail) {
router.push("/social-media-connect");
return;
}
const storedUser = localStorage.getItem("userDetails");
if (!storedUser) {
router.push("/social-media-connect");
return;
}
const user = JSON.parse(storedUser);
const role = user?.role;
if (role === "customer") {
const session = localStorage.getItem("payment_session");
if (!session) {
router.push("/pricing");
return;
}
}
const res = await getSocialAuthStatus(userEmail);
if (!res?.connected) {
router.push("/social-media-connect");
return;
}
}
validate();
}, [router]);
// ---------------------------------------------
// FETCH CHANNELS
// ---------------------------------------------
const loadChannels = useCallback(async () => {
try {
const user = localStorage.getItem("user_email");
const res = await axios.get(`${ApiServerBaseUrl}/social/channels?userId=${user}`);
const data: Channel[] = res.data || [];
setChannels(data);
} catch (err) {
console.error("Failed to load channels", err);
} finally {
setLoading(false);
}
}, []);
// ---------------------------------------------
// FETCH ACCOUNT DETAILS
// ---------------------------------------------
const loadAccountDetails = useCallback(async () => {
try {
const user = localStorage.getItem("user_email");
const connected = localStorage.getItem("connectedChannel");
if (!user || !connected) return;
const res = await axios.get(`${ApiServerBaseUrl}/social/account?userId=${user}`);
setAccountData(res.data || null);
} catch (err: any) {
if (err.response?.data?.error !== "Connect a channel first") {
console.error("Failed to load account details", err);
}
setAccountData(null);
}
}, []);
useEffect(() => {
loadChannels();
const saved = localStorage.getItem("connectedChannel");
if (saved) {
setConnectedChannel(saved);
loadAccountDetails();
}
}, [loadChannels, loadAccountDetails]);
// ---------------------------------------------
// CONNECT CHANNEL
// ---------------------------------------------
const handleConnectChannel = async (channelId: string, channelName: string) => {
try {
const user = localStorage.getItem("user_email");
const res = await axios.post(
`${ApiServerBaseUrl}/social/connect?userId=${user}`,
{ channel_id: channelId }
);
const savedId = res.data?.data?.channel_id || channelId;
localStorage.setItem("connectedChannel", savedId);
setConnectedChannel(savedId);
setSuccessMsg(`${channelName} connected successfully!`);
setOpenModal(false);
await loadAccountDetails();
} catch (err: any) {
setSuccessMsg(err.response?.data?.error || `Failed to connect ${channelName}`);
}
};
// ---------------------------------------------
// REMOVE CHANNEL
// ---------------------------------------------
const handleRemoveChannel = () => {
localStorage.removeItem("connectedChannel");
setConnectedChannel(null);
setAccountData(null);
setSuccessMsg("Channel removed successfully!");
};
// ---------------------------------------------
// REFRESH DATA
// ---------------------------------------------
const handleRefresh = async () => {
setRefreshing(true);
await loadAccountDetails();
setTimeout(() => setRefreshing(false), 1000);
};
return (
<div className="min-h-screen bg-gradient-to-b from-[#0a0a0a] to-[#111111] p-4 md:p-6 relative overflow-x-hidden">
{/* Floating background bubbles */}
<div className="absolute top-20 left-[5%] w-24 h-24 rounded-full bg-pink-500/20 blur-xl animate-float"></div>
<div className="absolute top-40 right-[10%] w-32 h-32 rounded-full bg-blue-500/15 blur-xl animate-float-slow"></div>
<div className="absolute bottom-40 left-[20%] w-20 h-20 rounded-full bg-purple-500/20 blur-xl animate-float"></div>
<div className="absolute bottom-20 right-[15%] w-28 h-28 rounded-full bg-cyan-500/15 blur-xl animate-float-slow"></div>
{/* Success Message */}
{successMsg && (
<div className="fixed top-4 right-4 z-50">
<div className={`px-6 py-4 rounded-xl backdrop-blur-xl border ${
successMsg.includes("success")
? "bg-green-900/30 border-green-500/30 text-green-400"
: "bg-red-900/30 border-red-500/30 text-red-400"
}`}>
<div className="flex items-center gap-3">
{successMsg.includes("success") ? (
<CheckCircle className="w-5 h-5" />
) : (
<XCircle className="w-5 h-5" />
)}
<span>{successMsg}</span>
<button
onClick={() => setSuccessMsg("")}
className="ml-4 text-gray-400 hover:text-white"
>
×
</button>
</div>
</div>
</div>
)}
{/* Main Container */}
<div className="max-w-6xl mx-auto relative z-10">
{/* Header */}
<div className="mb-10">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8">
<div className="flex items-center gap-4">
<div className="relative">
<div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-pink-500 to-purple-600 flex items-center justify-center shadow-lg shadow-pink-500/30">
<Instagram className="w-8 h-8 text-white" />
</div>
{connectedChannel && (
<div className="absolute -top-2 -right-2 w-6 h-6 rounded-full bg-green-500 border-2 border-[#111111] flex items-center justify-center">
<div className="w-2 h-2 rounded-full bg-white"></div>
</div>
)}
</div>
<div>
<h1 className="text-3xl md:text-4xl font-bold text-white">
Instagram Channels
</h1>
<p className="text-gray-300 mt-1">
Manage and connect your Instagram accounts
</p>
</div>
</div>
{/* Action Buttons */}
<div className="flex items-center gap-4">
{connectedChannel && accountData && (
<button
onClick={handleRefresh}
disabled={refreshing}
className="px-4 py-2 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-gray-300 hover:text-white transition-all duration-300 flex items-center gap-2"
>
<RefreshCw className={`w-4 h-4 ${refreshing ? 'animate-spin' : ''}`} />
Refresh
</button>
)}
{!connectedChannel && (
<button
onClick={() => setOpenModal(true)}
className="px-6 py-3 rounded-xl bg-gradient-to-r from-pink-600 to-purple-600 hover:from-pink-700 hover:to-purple-700 text-white font-semibold transition-all duration-300 hover:scale-105 flex items-center gap-2"
>
<Link2 className="w-5 h-5" />
Connect Channel
</button>
)}
</div>
</div>
</div>
{/* Main Content */}
{connectedChannel && accountData ? (
<div className="grid lg:grid-cols-3 gap-8">
{/* Left Column - Profile */}
<div className="lg:col-span-2 space-y-8">
{/* Profile Card */}
<div className="bg-gradient-to-br from-gray-900/90 to-black/90 backdrop-blur-xl rounded-2xl p-8 border border-white/10 shadow-2xl">
<div className="flex flex-col md:flex-row items-center md:items-start gap-8">
{/* Profile Image */}
<div className="relative">
<img
src={accountData.profile_picture_url}
alt={accountData.username || "Instagram profile"}
className="w-32 h-32 rounded-2xl object-cover border-2 border-white/20 shadow-lg"
/>
<div className="absolute -bottom-2 -right-2 w-10 h-10 rounded-full bg-gradient-to-r from-pink-500 to-purple-600 flex items-center justify-center">
<Instagram className="w-5 h-5 text-white" />
</div>
</div>
{/* Profile Info */}
<div className="flex-1">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-6">
<div>
<h2 className="text-2xl font-bold text-white">
{accountData.username}
</h2>
{accountData.name && (
<p className="text-gray-400">{accountData.name}</p>
)}
</div>
<div className="flex items-center gap-3">
<span className="px-3 py-1 rounded-full bg-green-500/20 text-green-400 text-sm font-medium border border-green-500/30">
Connected
</span>
</div>
</div>
{/* Stats */}
<div className="grid grid-cols-3 gap-6 mb-6">
<div className="text-center">
<div className="text-3xl font-bold text-white">{accountData.media_count}</div>
<div className="text-gray-400 text-sm">Posts</div>
</div>
<div className="text-center">
<div className="text-3xl font-bold text-white">{accountData.followers_count}</div>
<div className="text-gray-400 text-sm">Followers</div>
</div>
<div className="text-center">
<div className="text-3xl font-bold text-white">{accountData.follows_count}</div>
<div className="text-gray-400 text-sm">Following</div>
</div>
</div>
{/* Bio */}
{accountData.biography && (
<div className="mb-6">
<h3 className="text-white font-semibold mb-2">Bio</h3>
<p className="text-gray-300 whitespace-pre-line">{accountData.biography}</p>
</div>
)}
{/* Website */}
{accountData.website && (
<a
href={accountData.website}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-2 text-blue-400 hover:text-blue-300 transition-colors"
>
<Globe className="w-4 h-4" />
<span>{accountData.website}</span>
<ExternalLink className="w-3 h-3" />
</a>
)}
</div>
</div>
</div>
{/* Quick Actions */}
<div className="bg-gradient-to-br from-gray-900/90 to-black/90 backdrop-blur-xl rounded-2xl p-6 border border-white/10 shadow-2xl">
<h3 className="text-xl font-bold text-white mb-6 flex items-center gap-3">
<Zap className="w-5 h-5 text-yellow-400" />
Quick Actions
</h3>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<Link
href="/social-media-posts"
className="p-4 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 transition-all duration-300 hover:scale-105 group"
>
<div className="flex items-center gap-3 mb-2">
<ImageIcon className="w-6 h-6 text-pink-400" />
<span className="text-white font-medium">Media Library</span>
</div>
<p className="text-gray-400 text-sm">View and manage your posts</p>
</Link>
<Link
href="/comments"
className="p-4 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 transition-all duration-300 hover:scale-105 group"
>
<div className="flex items-center gap-3 mb-2">
<Users className="w-6 h-6 text-blue-400" />
<span className="text-white font-medium">Comments</span>
</div>
<p className="text-gray-400 text-sm">Manage comments & engagement</p>
</Link>
<Link
href="/insights"
className="p-4 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 transition-all duration-300 hover:scale-105 group"
>
<div className="flex items-center gap-3 mb-2">
<BarChart3 className="w-6 h-6 text-green-400" />
<span className="text-white font-medium">Insights</span>
</div>
<p className="text-gray-400 text-sm">View performance analytics</p>
</Link>
</div>
</div>
</div>
{/* Right Column - Settings & Info */}
<div className="space-y-8">
{/* Connection Status */}
<div className="bg-gradient-to-br from-gray-900/90 to-black/90 backdrop-blur-xl rounded-2xl p-6 border border-white/10 shadow-2xl">
<h3 className="text-xl font-bold text-white mb-4">Channel Status</h3>
<div className="space-y-6">
<div className="flex items-center justify-between p-4 rounded-xl bg-white/5">
<div className="flex items-center gap-3">
<Instagram className="w-6 h-6 text-pink-400" />
<span className="text-white">Instagram Account</span>
</div>
<div className="px-3 py-1 rounded-full text-sm font-medium bg-green-500/20 text-green-400 border border-green-500/30">
Connected
</div>
</div>
<div className="space-y-3">
<div className="flex items-center justify-between text-sm">
<span className="text-gray-400">Media Access</span>
<span className="text-green-400"> Available</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-gray-400">Comment Access</span>
<span className="text-green-400"> Available</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="text-gray-400">Insights Access</span>
<span className="text-green-400"> Available</span>
</div>
</div>
<div className="pt-4 border-t border-white/10">
<button
onClick={handleRemoveChannel}
className="w-full py-3 rounded-xl bg-gradient-to-r from-red-600 to-red-700 hover:from-red-700 hover:to-red-800 text-white font-medium transition-all duration-300 hover:scale-105 flex items-center justify-center gap-2"
>
<Link2 className="w-4 h-4" />
Disconnect Channel
</button>
<p className="text-gray-400 text-xs text-center mt-2">
This will revoke Social Buddy's access to this account
</p>
</div>
</div>
</div>
{/* Info Card */}
<div className="bg-gradient-to-br from-blue-900/20 to-indigo-900/20 rounded-2xl p-6 border border-blue-500/30 backdrop-blur-xl">
<h4 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
<Shield className="w-5 h-5 text-blue-400" />
Channel Information
</h4>
<ul className="space-y-3 text-sm">
<li className="flex items-start gap-2 text-gray-300">
<CheckCircle className="w-4 h-4 text-green-400 flex-shrink-0 mt-0.5" />
<span>All data is synced securely</span>
</li>
<li className="flex items-start gap-2 text-gray-300">
<CheckCircle className="w-4 h-4 text-green-400 flex-shrink-0 mt-0.5" />
<span>Manual management only</span>
</li>
<li className="flex items-start gap-2 text-gray-300">
<CheckCircle className="w-4 h-4 text-green-400 flex-shrink-0 mt-0.5" />
<span>Real-time data updates</span>
</li>
</ul>
</div>
</div>
</div>
) : (
/* No Channel Connected State */
<div className="flex flex-col items-center justify-center py-20">
<div className="w-32 h-32 rounded-2xl bg-gradient-to-br from-gray-900 to-black border border-white/10 flex items-center justify-center mb-8">
<Instagram className="w-16 h-16 text-gray-400" />
</div>
<h2 className="text-2xl font-bold text-white mb-4">No Channel Connected</h2>
<p className="text-gray-400 text-center max-w-md mb-8">
Connect an Instagram account to access media management, insights, and comment tools.
</p>
<button
onClick={() => setOpenModal(true)}
className="px-8 py-4 rounded-xl bg-gradient-to-r from-pink-600 to-purple-600 hover:from-pink-700 hover:to-purple-700 text-white font-semibold text-lg transition-all duration-300 hover:scale-105 flex items-center gap-3"
>
<Link2 className="w-6 h-6" />
Connect Instagram Channel
<ArrowRight className="w-5 h-5" />
</button>
</div>
)}
</div>
{/* Channel Selection Modal */}
{openModal && (
<div className="fixed inset-0 bg-black/70 backdrop-blur-sm flex items-center justify-center p-4 z-50">
<div className="bg-gradient-to-br from-gray-900 to-black backdrop-blur-xl rounded-2xl border border-white/10 shadow-2xl w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
{/* Modal Header */}
<div className="p-6 border-b border-white/10">
<div className="flex items-center justify-between">
<div>
<h2 className="text-2xl font-bold text-white">Connect Instagram Channel</h2>
<p className="text-gray-400 mt-1">Select an Instagram account to connect</p>
</div>
<button
onClick={() => setOpenModal(false)}
className="w-8 h-8 rounded-full bg-white/10 hover:bg-white/20 flex items-center justify-center text-gray-400 hover:text-white transition-colors"
>
×
</button>
</div>
</div>
{/* Modal Content */}
<div className="p-6 flex-1 overflow-y-auto">
{loading ? (
<div className="flex items-center justify-center py-20">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-pink-500"></div>
</div>
) : channels.length === 0 ? (
<div className="text-center py-20">
<Instagram className="w-16 h-16 text-gray-400 mx-auto mb-4" />
<p className="text-gray-400">No Instagram channels available</p>
<p className="text-gray-500 text-sm mt-2">Make sure your Instagram account is linked to Facebook</p>
</div>
) : (
<div className="grid gap-6 grid-cols-1 md:grid-cols-2">
{channels.map((channel) => (
<div
key={channel.id}
className="bg-gradient-to-br from-gray-900/50 to-black/50 backdrop-blur-sm rounded-2xl p-6 border border-white/10 hover:border-pink-500/30 transition-all duration-300 hover:scale-[1.02] group"
>
<div className="flex items-center gap-4 mb-4">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${
channel.has_linked_account
? "bg-gradient-to-br from-pink-500/20 to-purple-600/20"
: "bg-gray-800/50"
}`}>
<Instagram className={`w-6 h-6 ${
channel.has_linked_account ? "text-pink-400" : "text-gray-400"
}`} />
</div>
<div>
<h3 className="text-xl font-bold text-white">{channel.name}</h3>
<div className={`mt-1 text-sm ${
channel.has_linked_account
? "text-green-400 flex items-center gap-1"
: "text-orange-400 flex items-center gap-1"
}`}>
{channel.has_linked_account ? (
<>
<CheckCircle className="w-4 h-4" />
Ready to connect
</>
) : (
<>
<XCircle className="w-4 h-4" />
Requires authorization
</>
)}
</div>
</div>
</div>
<p className="text-gray-400 text-sm mb-6">
{channel.has_linked_account
? "Connect this Instagram account to access media, insights, and comments."
: "This account needs to be authorized through Facebook first."
}
</p>
<button
onClick={() => channel.has_linked_account && handleConnectChannel(channel.id!, channel.name)}
disabled={!channel.has_linked_account}
className={`w-full py-3 rounded-xl font-semibold transition-all duration-300 flex items-center justify-center gap-2 ${
channel.has_linked_account
? "bg-gradient-to-r from-pink-600 to-purple-600 hover:from-pink-700 hover:to-purple-700 text-white hover:scale-105"
: "bg-gray-800 text-gray-500 cursor-not-allowed"
}`}
>
{channel.has_linked_account ? (
<>
<Link2 className="w-5 h-5" />
Connect Channel
<ArrowRight className="w-4 h-4" />
</>
) : (
"Authorization Required"
)}
</button>
</div>
))}
</div>
)}
</div>
{/* Modal Footer */}
<div className="p-6 border-t border-white/10">
<div className="flex justify-between items-center">
<p className="text-gray-400 text-sm">
{channels.length} channel{channels.length !== 1 ? 's' : ''} available
</p>
<button
onClick={() => setOpenModal(false)}
className="px-6 py-2 rounded-xl bg-white/10 hover:bg-white/20 text-gray-300 hover:text-white font-medium transition-colors"
>
Cancel
</button>
</div>
</div>
</div>
</div>
)}
{/* Animation Styles */}
<style jsx>{`
@keyframes float {
0%, 100% {
transform: translateY(0) translateX(0);
}
50% {
transform: translateY(-20px) translateX(10px);
}
}
@keyframes float-slow {
0%, 100% {
transform: translateY(0) translateX(0);
}
50% {
transform: translateY(-30px) translateX(-15px);
}
}
.animate-float {
animation: float 8s ease-in-out infinite;
}
.animate-float-slow {
animation: float-slow 10s ease-in-out infinite;
}
`}</style>
</div>
);
};
export default SocialMediaChannels;

View File

@ -0,0 +1,65 @@
"use client";
import { ApiServerBaseUrl } from "@/utils/baseurl.utils";
import React from "react";
const SocialMediaConnect = () => {
const handleConnect = () => {
window.location.href = `${ApiServerBaseUrl}/social/auth/login?`;
};
return (
<div
className="
min-h-[83vh] flex items-center justify-center px-4
bg-gradient-to-br
from-[#0073C6]/30
via-[#5BBE5B]/30
via-[#E44DB3]/25
to-[#0047A3]/30
"
>
<div
className="
w-full max-w-md p-10 rounded-3xl text-center
bg-white/40 backdrop-blur-xl
border border-white/50 shadow-xl
"
>
{/* Title */}
<h1
className="
text-4xl font-extrabold mb-6 tracking-wide
bg-gradient-to-r from-[#0073C6] to-[#E44DB3]
bg-clip-text text-transparent
"
>
Social Buddy
</h1>
{/* Subtitle */}
<p className="text-gray-700 text-lg mb-10">
Connect & manage your social accounts securely.
</p>
{/* Connect Button */}
<button
onClick={handleConnect}
className="
w-full py-4 text-lg font-semibold rounded-full
bg-gradient-to-r
from-[#5BBE5B]/80 via-[#E44DB3]/80 via-[#0073C6]/80 to-[#0047A3]/80
hover:from-[#5BBE5B] hover:via-[#E44DB3] hover:to-[#0047A3]
text-white shadow-md hover:shadow-xl
transition-all duration-300 ease-out
"
>
Connect with Facebook
</button>
</div>
</div>
);
};
export default SocialMediaConnect;

View File

@ -0,0 +1,421 @@
"use client";
import React, { useEffect, useState } from "react";
import { useSearchParams, useRouter } from "next/navigation";
import axios from "axios";
import { ApiServerBaseUrl } from "@/utils/baseurl.utils";
import {
Instagram,
BarChart3,
MessageSquare,
Shield,
CheckCircle,
Zap,
Users,
Clock
} from "lucide-react";
type Status =
| "checking"
| "not_connected"
| "finalizing"
| "connected"
| "error";
const SocialMediaConnect = () => {
const [status, setStatus] = useState<Status>("checking");
const [error, setError] = useState<string | null>(null);
const searchParams = useSearchParams();
const router = useRouter();
// ---------------------- CHECK STATUS + FINALIZE AUTH ----------------------
useEffect(() => {
async function checkStatus() {
try {
setError(null);
setStatus("checking");
const userId = localStorage.getItem("user_email");
const res = await axios.get(
`${ApiServerBaseUrl}/social/auth/status`,
{ params: { userId }, withCredentials: true }
);
if (res.data.connected) {
setStatus("connected");
} else {
setStatus("not_connected");
}
} catch (err: any) {
console.error(err);
setError("Failed to load status");
setStatus("error");
}
}
async function finalizeAuth(authToken: string) {
try {
setStatus("finalizing");
setError(null);
const userId = localStorage.getItem("user_email");
const res = await axios.post(
`${ApiServerBaseUrl}/social/auth/save-oauth`,
{ auth: authToken, userId },
{ withCredentials: true }
);
if (!res.data.ok) throw new Error("Failed to finalize auth");
const url = new URL(window.location.href);
url.searchParams.delete("auth");
router.replace(url.pathname);
await checkStatus();
} catch (err: any) {
console.error(err);
setError("Error finalizing connection");
setStatus("error");
}
}
async function init() {
const auth = searchParams.get("auth");
if (auth) {
await finalizeAuth(auth);
} else {
await checkStatus();
}
}
init();
}, [searchParams, router]);
// ---------------------- DISCONNECT FB ACCOUNT ----------------------
const handleDisconnect = async () => {
try {
const userId = localStorage.getItem("user_email");
const res = await axios.post(
`${ApiServerBaseUrl}/social/auth/disconnect`,
{ userId },
{ withCredentials: true }
);
if (res.data.ok) {
setStatus("not_connected");
}
} catch (err: any) {
console.error(err);
setError("Failed to remove connected account");
setStatus("error");
}
};
// ---------------------- CONNECT BUTTON ----------------------
const handleConnect = () => {
window.location.href = `${ApiServerBaseUrl}/social/auth/login`;
};
// ---------------------- PAYMENT SESSION VALIDATION ----------------------
useEffect(() => {
const storedUser = localStorage.getItem("userDetails");
if (!storedUser) return;
const user = JSON.parse(storedUser);
const role = user?.role;
if (role === "admin" || role === "partner") {
return;
}
const session = localStorage.getItem("payment_session");
if (role === "customer" && !session) {
router.push("/pricing");
}
}, [router]);
// User-friendly features list
const features = [
{
icon: <Instagram className="w-6 h-6" />,
title: "View Instagram Profile & Posts",
description: "See your Instagram profile details and browse your published posts directly inside Social Buddy."
},
{
icon: <BarChart3 className="w-6 h-6" />,
title: "Track Performance Insights",
description: "Monitor key metrics like reach, impressions, and engagement to understand how your content is performing."
},
{
icon: <MessageSquare className="w-6 h-6" />,
title: "Manage Comments Easily",
description: "Reply to comments, hide inappropriate messages, or remove spam — all from one centralized place."
}
];
// Benefits list
const benefits = [
{
icon: <Zap className="w-5 h-5" />,
text: "One dashboard for posts, insights, and comments"
},
{
icon: <Clock className="w-5 h-5" />,
text: "Saves time managing Instagram engagement"
},
{
icon: <Users className="w-5 h-5" />,
text: "Helps you respond faster to your audience"
},
{
icon: <Shield className="w-5 h-5" />,
text: "Designed for businesses, creators, and agencies"
}
];
return (
<div className="min-h-screen bg-gradient-to-b from-[#111111] to-[#0a0a0a] px-4 py-8 md:py-12">
{/* Floating background bubbles */}
<div className="absolute top-20 left-5 w-20 h-20 rounded-full bg-pink-500/20 blur-xl animate-float"></div>
<div className="absolute top-40 right-10 w-32 h-32 rounded-full bg-blue-500/15 blur-xl animate-float-slow"></div>
<div className="absolute bottom-20 left-1/4 w-24 h-24 rounded-full bg-purple-500/20 blur-xl animate-float"></div>
<div className="absolute bottom-40 right-20 w-28 h-28 rounded-full bg-cyan-500/15 blur-xl animate-float-slow"></div>
{/* Main Container */}
<div className="max-w-6xl mx-auto relative z-10">
{/* Header Section */}
<div className="text-center mb-12">
<div className="flex justify-center mb-6">
<div className="w-20 h-20 rounded-2xl bg-gradient-to-r from-blue-500 to-purple-600 flex items-center justify-center shadow-lg shadow-blue-500/30">
<Instagram className="w-10 h-10 text-white" />
</div>
</div>
<h1 className="text-4xl md:text-5xl font-bold text-white mb-4">
Social Buddy
</h1>
<p className="text-gray-300 text-lg md:text-xl max-w-2xl mx-auto">
Connect your Facebook and Instagram accounts to manage your posts, track performance, and handle comments all from one simple dashboard.
</p>
</div>
<div className="grid lg:grid-cols-3 gap-8">
{/* Left Column - Connection Status */}
<div className="lg:col-span-2 space-y-8">
{/* Connection Card */}
<div className="bg-gradient-to-br from-gray-900/90 to-black/90 backdrop-blur-xl rounded-2xl p-8 shadow-2xl border border-white/10">
<h2 className="text-2xl font-bold text-white mb-6">Connection Status</h2>
<div className="space-y-6">
{/* Status Display */}
<div className="flex items-center gap-4 p-4 rounded-xl bg-white/5 border border-white/10">
{status === "checking" && (
<>
<div className="w-4 h-4 rounded-full bg-yellow-500 animate-pulse"></div>
<p className="text-gray-300">Checking connection status...</p>
</>
)}
{status === "finalizing" && (
<>
<div className="w-4 h-4 rounded-full bg-blue-500 animate-pulse"></div>
<p className="text-blue-400">Finalizing connection...</p>
</>
)}
{status === "connected" && (
<>
<CheckCircle className="w-6 h-6 text-green-500" />
<div>
<p className="text-green-400 font-semibold">Instagram & Facebook connected successfully</p>
<p className="text-gray-400 text-sm mt-1">
Your accounts are linked and ready to use.
</p>
</div>
</>
)}
{status === "not_connected" && (
<>
<div className="w-4 h-4 rounded-full bg-gray-500"></div>
<p className="text-gray-300">No Facebook account connected</p>
</>
)}
{status === "error" && (
<>
<div className="w-4 h-4 rounded-full bg-red-500"></div>
<p className="text-red-400">{error || "Connection error occurred"}</p>
</>
)}
</div>
{/* Action Buttons */}
<div className="space-y-4">
{status === "not_connected" && (
<button
onClick={handleConnect}
className="w-full py-4 px-6 rounded-xl bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white font-semibold text-lg transition-all duration-300 shadow-lg hover:shadow-xl transform hover:-translate-y-0.5 hover:shadow-blue-500/30"
>
Connect Facebook Business Account
</button>
)}
{status === "connected" && (
<>
<button
onClick={handleDisconnect}
className="w-full py-4 px-6 rounded-xl bg-gradient-to-r from-gray-700 to-gray-800 hover:from-gray-800 hover:to-gray-900 text-white font-semibold text-lg transition-all duration-300 shadow-lg hover:shadow-xl"
>
Disconnect Account
</button>
<p className="text-gray-400 text-sm text-center">
You can disconnect and reconnect your account anytime. Disconnecting will revoke Social Buddy's access to your Instagram data.
</p>
</>
)}
</div>
{/* Features Grid */}
<div className="mt-8 pt-8 border-t border-white/10">
<h3 className="text-xl font-bold text-white mb-6">What You Can Do with Social Buddy</h3>
<p className="text-gray-300 mb-6">
Social Buddy helps you stay on top of your Instagram presence without switching between apps.
</p>
<div className="grid md:grid-cols-3 gap-6">
{features.map((feature, index) => (
<div
key={index}
className="p-6 rounded-xl bg-gradient-to-br from-white/5 to-white/0 border border-white/10 hover:border-blue-500/30 transition-all duration-300 hover:bg-white/10"
>
<div className="w-12 h-12 rounded-lg bg-blue-500/20 flex items-center justify-center mb-4">
<div className="text-blue-400">
{feature.icon}
</div>
</div>
<h4 className="font-semibold text-white mb-2">{feature.title}</h4>
<p className="text-gray-300 text-sm">{feature.description}</p>
</div>
))}
</div>
</div>
</div>
</div>
{/* Privacy & Control Section */}
<div className="bg-gradient-to-r from-blue-900/20 to-indigo-900/20 rounded-2xl p-8 border border-blue-500/30 backdrop-blur-xl">
<div className="flex items-start gap-4">
<Shield className="w-8 h-8 text-blue-400 flex-shrink-0 mt-1" />
<div>
<h3 className="text-xl font-bold text-white mb-4">Privacy & Control</h3>
<div className="space-y-4">
<p className="text-gray-300">
Social Buddy only accesses data needed to provide these features. All actions are performed manually by you, and you can disconnect your account at any time.
</p>
<div className="flex items-center gap-2 text-gray-300">
<CheckCircle className="w-5 h-5 text-green-400" />
<span>No automation you stay fully in control</span>
</div>
</div>
</div>
</div>
</div>
</div>
{/* Right Column - Benefits & Info */}
<div className="space-y-8">
{/* Benefits Card */}
<div className="bg-gradient-to-br from-gray-900/90 to-black/90 backdrop-blur-xl rounded-2xl p-8 shadow-2xl border border-white/10">
<h3 className="text-2xl font-bold text-white mb-6">Why Use Social Buddy?</h3>
<div className="space-y-6">
{benefits.map((benefit, index) => (
<div key={index} className="flex items-start gap-4">
<div className="w-8 h-8 rounded-full bg-green-500/20 flex items-center justify-center flex-shrink-0 mt-1">
<div className="text-green-400">
{benefit.icon}
</div>
</div>
<p className="text-gray-300">{benefit.text}</p>
</div>
))}
</div>
{/* Stats Preview */}
<div className="mt-8 pt-8 border-t border-white/10">
<div className="grid grid-cols-2 gap-4">
<div className="text-center p-4 rounded-lg bg-white/5 border border-white/10">
<div className="text-2xl font-bold text-white">1</div>
<div className="text-sm text-gray-400">Dashboard</div>
</div>
<div className="text-center p-4 rounded-lg bg-white/5 border border-white/10">
<div className="text-2xl font-bold text-white">3</div>
<div className="text-sm text-gray-400">Core Features</div>
</div>
</div>
</div>
</div>
{/* Quick Start Guide */}
<div className="bg-gradient-to-br from-purple-900/20 to-pink-900/20 rounded-2xl p-8 border border-purple-500/30 backdrop-blur-xl">
<h4 className="text-lg font-semibold text-white mb-4">Get Started in 3 Steps</h4>
<ol className="space-y-4">
<li className="flex items-center gap-3">
<div className="w-6 h-6 rounded-full bg-blue-500/30 text-blue-400 flex items-center justify-center text-sm font-bold">1</div>
<span className="text-gray-300">Click "Connect Instagram Account"</span>
</li>
<li className="flex items-center gap-3">
<div className="w-6 h-6 rounded-full bg-blue-500/30 text-blue-400 flex items-center justify-center text-sm font-bold">2</div>
<span className="text-gray-300">Log in with your Facebook account</span>
</li>
<li className="flex items-center gap-3">
<div className="w-6 h-6 rounded-full bg-blue-500/30 text-blue-400 flex items-center justify-center text-sm font-bold">3</div>
<span className="text-gray-300">Select your Instagram account and permissions</span>
</li>
</ol>
</div>
{/* Footer Note */}
<div className="text-center p-6 rounded-xl bg-white/5 border border-white/10">
<p className="text-gray-400 text-sm">
© 2026 SocialBuddy. All rights reserved.
</p>
<p className="text-gray-500 text-xs mt-2">
Powered by MetatronCube · Privacy Policy · Terms of Use
</p>
</div>
</div>
</div>
</div>
{/* Animation Styles */}
<style jsx>{`
@keyframes float {
0%, 100% {
transform: translateY(0) translateX(0);
}
50% {
transform: translateY(-20px) translateX(10px);
}
}
@keyframes float-slow {
0%, 100% {
transform: translateY(0) translateX(0);
}
50% {
transform: translateY(-30px) translateX(-15px);
}
}
.animate-float {
animation: float 6s ease-in-out infinite;
}
.animate-float-slow {
animation: float-slow 8s ease-in-out infinite;
}
`}</style>
</div>
);
};
export default SocialMediaConnect;

View File

@ -0,0 +1,660 @@
"use client";
import axios from "axios";
import { ApiServerBaseUrl } from "@/utils/baseurl.utils";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { getSocialAuthStatus } from "@/utils/commonFunction.utils";
import SocialCommentItem from "@/components/SocialCommentItem";
import {
Image as ImageIcon,
Video,
MessageSquare,
Heart,
Calendar,
ExternalLink,
Send,
RefreshCw,
ChevronLeft,
MoreVertical,
BarChart3,
Users,
Clock,
Shield,
AlertCircle,
CheckCircle
} from "lucide-react";
type Reply = {
id: string;
text: string;
timestamp: string;
username: string;
hidden?: boolean;
like_count?: number;
};
type Comment = {
id: string;
text: string;
username: string;
timestamp: string;
like_count?: number;
hidden?: boolean;
replies?: { data: Reply[] };
};
type MediaDetails = {
id: string;
media_url: string;
caption?: string;
media_type: string;
timestamp: string;
like_count?: number;
comments_count?: number;
permalink?: string;
};
const MediaDetailsPage = ({ params }: { params: { id: string } }) => {
const { id } = params;
const router = useRouter();
const [media, setMedia] = useState<MediaDetails | null>(null);
const [comments, setComments] = useState<Comment[]>([]);
const [loading, setLoading] = useState(true);
const [commentsLoading, setCommentsLoading] = useState(false);
const [newCommentText, setNewCommentText] = useState("");
const [error, setError] = useState("");
const [refreshing, setRefreshing] = useState(false);
useEffect(() => {
async function validate() {
const userEmail = localStorage.getItem("user_email");
if (!userEmail) {
router.push("/social-media-connect");
return;
}
const storedUser = localStorage.getItem("userDetails");
if (!storedUser) {
router.push("/social-media-connect");
return;
}
const user = JSON.parse(storedUser);
const role = user?.role;
if (role === "customer") {
const session = localStorage.getItem("payment_session");
if (!session) {
router.push("/pricing");
return;
}
}
const res = await getSocialAuthStatus(userEmail);
if (!res?.connected) {
router.push("/social-media-connect");
return;
}
}
validate();
}, []);
// Fetch Media Details
useEffect(() => {
const fetchMedia = async () => {
try {
const res = await axios.get(`${ApiServerBaseUrl}/social/media/${id}?userId=${localStorage.getItem(
"user_email"
)}`);
setMedia(res.data);
} catch (err: any) {
setError(err.response?.data?.message || "Unable to load media");
} finally {
setLoading(false);
}
};
fetchMedia();
}, [id]);
// Fetch Comments
const loadComments = async () => {
setCommentsLoading(true);
try {
const res = await axios.get(
`${ApiServerBaseUrl}/social/media/${id}/comments?userId=${localStorage.getItem(
"user_email"
)}`
);
setComments(res.data?.data || []);
} catch {
console.error("Failed to load comments");
} finally {
setCommentsLoading(false);
}
};
useEffect(() => {
if (media) loadComments();
}, [media]);
// Post new top-level comment
const postNewComment = async () => {
if (!newCommentText.trim()) {
alert("Comment cannot be empty.");
return;
}
try {
const res = await axios.post(
`${ApiServerBaseUrl}/social/media/${id}/comments?userId=${localStorage.getItem(
"user_email"
)}`,
{ message: newCommentText }
);
const newComment: Comment = {
id: res.data?.comment_id || res.data?.id || `temp-${Date.now()}`,
text: newCommentText,
username: "Me",
timestamp: new Date().toISOString(),
replies: { data: [] }
};
setComments((prev) => [newComment, ...prev]);
setNewCommentText("");
} catch (err: any) {
alert(err.response?.data?.error || "Failed to post comment.");
}
};
// Reply handler
const handleReply = async (commentId: string, text: string) => {
try {
const res = await axios.post(
`${ApiServerBaseUrl}/social/comments/${commentId}/reply?userId=${localStorage.getItem(
"user_email"
)}`,
{ message: text }
);
const newReply: Reply = {
id: res.data?.reply_id || res.data?.id || `temp-${Date.now()}`,
text: text,
username: "Me",
timestamp: new Date().toISOString(),
};
setComments((prev) =>
prev.map((cm) =>
cm.id === commentId
? {
...cm,
replies: {
data: [...(cm.replies?.data || []), newReply],
},
}
: cm
)
);
} catch (err: any) {
alert(err.response?.data?.error || "Reply failed.");
}
};
// Delete handler
const handleDelete = async (id: string, isReply: boolean = false, parentId?: string) => {
const confirmed = window.confirm("Are you sure you want to delete this?");
if (!confirmed) return;
try {
await axios.delete(`${ApiServerBaseUrl}/social/comments/${id}?userId=${localStorage.getItem(
"user_email"
)}`);
if (isReply && parentId) {
setComments((prev) =>
prev.map((cm) => {
if (cm.id === parentId) {
return {
...cm,
replies: {
data: cm.replies?.data.filter((r) => r.id !== id) || [],
},
};
}
return cm;
})
);
} else {
setComments((prev) => prev.filter((c) => c.id !== id));
}
} catch (err: any) {
alert("Delete failed: " + (err.response?.data?.error || err.message));
}
};
// Hide/Unhide handler
const handleHide = async (id: string, currentStatus: boolean, isReply: boolean = false) => {
try {
const newHideStatus = !currentStatus;
await axios.post(`${ApiServerBaseUrl}/social/comments/${id}/hide?userId=${localStorage.getItem("user_email")}`, {
hide: newHideStatus,
});
if (isReply) {
setComments((prev) =>
prev.map((cm) => {
const replyIndex = cm.replies?.data.findIndex(r => r.id === id);
if (replyIndex !== undefined && replyIndex !== -1 && cm.replies?.data) {
const updatedReplies = [...cm.replies.data];
updatedReplies[replyIndex] = { ...updatedReplies[replyIndex], hidden: newHideStatus };
return { ...cm, replies: { data: updatedReplies } };
}
return cm;
})
);
} else {
setComments((prev) =>
prev.map((c) => (c.id === id ? { ...c, hidden: newHideStatus } : c))
);
}
} catch (err: any) {
alert(err.response?.data?.error || "Hide/Unhide failed.");
}
};
// Edit handler
const handleEdit = async (id: string, newText: string, isReply: boolean = false, parentId?: string) => {
if (id.toString().startsWith("temp-") || id.toString().startsWith("new-")) {
alert("Please refresh the page to get the real ID from Instagram before editing this comment again.");
return;
}
const confirmed = window.confirm(
"Compatible Edit Mode:\n\nInstagram does not allow editing comments directly.\n\nProceeding will DELETE the original comment (losing likes/replies) and POST a new one with the updated text.\n\nDo you want to continue?"
);
if (!confirmed) return;
try {
await axios.delete(`${ApiServerBaseUrl}/social/comments/${id}?userId=${localStorage.getItem("user_email")}`);
let res;
if (isReply && parentId) {
res = await axios.post(
`${ApiServerBaseUrl}/social/comments/${parentId}/reply?userId=${localStorage.getItem("user_email")}`,
{ message: newText }
);
} else {
res = await axios.post(
`${ApiServerBaseUrl}/social/media/${media?.id}/comments?userId=${localStorage.getItem("user_email")}`,
{ message: newText }
);
}
const newId = res.data?.id || res.data?.comment_id || res.data?.reply_id;
const safeId = newId || `new-${Date.now()}`;
if (isReply && parentId) {
setComments((prev) =>
prev.map((cm) => {
if (cm.id === parentId) {
const oldReplies = cm.replies?.data || [];
const filteredReplies = oldReplies.filter((r) => r.id !== id);
const newReplyObj: Reply = {
id: safeId,
text: newText,
username: "Me",
timestamp: new Date().toISOString(),
hidden: false,
like_count: 0
};
return {
...cm,
replies: { data: [...filteredReplies, newReplyObj] }
};
}
return cm;
})
);
} else {
setComments((prev) => {
const filtered = prev.filter((c) => c.id !== id);
const newCommentObj: Comment = {
id: safeId,
text: newText,
username: "Me",
timestamp: new Date().toISOString(),
like_count: 0,
hidden: false,
replies: { data: [] }
};
return [newCommentObj, ...filtered];
});
}
} catch (err: any) {
console.error(err);
alert("Edit failed: " + (err.response?.data?.error || err.message));
}
};
// Refresh comments
const handleRefreshComments = async () => {
setRefreshing(true);
await loadComments();
setTimeout(() => setRefreshing(false), 1000);
};
// Format date
const formatDate = (timestamp: string) => {
const date = new Date(timestamp);
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
};
if (loading) return (
<div className="min-h-screen bg-gradient-to-b from-[#0a0a0a] to-[#111111] flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-pink-500 mx-auto mb-4"></div>
<p className="text-gray-300">Loading media details...</p>
</div>
</div>
);
if (error) return (
<div className="min-h-screen bg-gradient-to-b from-[#0a0a0a] to-[#111111] p-6">
<div className="max-w-4xl mx-auto">
<div className="bg-red-900/20 backdrop-blur-xl rounded-2xl p-8 border border-red-500/30">
<div className="flex items-center gap-4">
<AlertCircle className="w-12 h-12 text-red-400" />
<div>
<h2 className="text-2xl font-bold text-white mb-2">Error Loading Media</h2>
<p className="text-gray-300">{error}</p>
</div>
</div>
</div>
</div>
</div>
);
if (!media) return (
<div className="min-h-screen bg-gradient-to-b from-[#0a0a0a] to-[#111111] flex items-center justify-center">
<p className="text-gray-300">No media found.</p>
</div>
);
return (
<div className="min-h-screen bg-gradient-to-b from-[#0a0a0a] to-[#111111] p-4 md:p-6">
{/* Floating background bubbles */}
<div className="absolute top-20 left-[5%] w-24 h-24 rounded-full bg-pink-500/20 blur-xl animate-float"></div>
<div className="absolute top-40 right-[10%] w-32 h-32 rounded-full bg-blue-500/15 blur-xl animate-float-slow"></div>
<div className="max-w-6xl mx-auto relative z-10">
{/* Header */}
<div className="mb-8">
<button
onClick={() => router.push("/social-media-posts")}
className="inline-flex items-center gap-2 text-gray-400 hover:text-white mb-6 transition-colors"
>
<ChevronLeft className="w-4 h-4" />
Back to Media Library
</button>
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<h1 className="text-3xl md:text-4xl font-bold text-white mb-2">Post Details</h1>
<p className="text-gray-300">Manage comments and view post details</p>
</div>
<div className="flex items-center gap-4">
<button
onClick={handleRefreshComments}
disabled={refreshing}
className="px-4 py-2 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-gray-300 hover:text-white transition-all duration-300 flex items-center gap-2"
>
<RefreshCw className={`w-4 h-4 ${refreshing ? 'animate-spin' : ''}`} />
Refresh Comments
</button>
</div>
</div>
</div>
{/* Main Content */}
<div className="grid lg:grid-cols-3 gap-8">
{/* Left Column - Media */}
<div className="lg:col-span-2">
{/* Media Card */}
<div className="bg-gradient-to-br from-gray-900/90 to-black/90 backdrop-blur-xl rounded-2xl border border-white/10 shadow-2xl overflow-hidden mb-8">
{/* Media Display */}
<div className="relative">
{media.media_type === "VIDEO" ? (
<video
src={media.media_url}
controls
className="w-full h-auto max-h-[600px] object-contain bg-black"
poster={media.media_url}
/>
) : (
<img
src={media.media_url}
alt="Instagram Media"
className="w-full h-auto max-h-[600px] object-contain bg-black"
/>
)}
<div className="absolute top-4 right-4">
<div className="px-3 py-1 rounded-full bg-black/80 backdrop-blur-sm text-sm font-medium text-white flex items-center gap-2">
{media.media_type === "VIDEO" ? (
<Video className="w-4 h-4" />
) : (
<ImageIcon className="w-4 h-4" />
)}
<span className="capitalize">{media.media_type.toLowerCase()}</span>
</div>
</div>
</div>
{/* Media Details */}
<div className="p-6">
{media.caption && (
<div className="mb-6">
<h3 className="text-white font-semibold mb-2">Caption</h3>
<p className="text-gray-300 leading-relaxed">{media.caption}</p>
</div>
)}
{/* Stats Grid */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
<div className="bg-white/5 backdrop-blur-sm rounded-xl p-4 border border-white/10">
<div className="flex items-center gap-2 mb-2">
<Heart className="w-4 h-4 text-pink-400" />
<span className="text-gray-400 text-sm">Likes</span>
</div>
<div className="text-2xl font-bold text-white">{media.like_count || 0}</div>
</div>
<div className="bg-white/5 backdrop-blur-sm rounded-xl p-4 border border-white/10">
<div className="flex items-center gap-2 mb-2">
<MessageSquare className="w-4 h-4 text-blue-400" />
<span className="text-gray-400 text-sm">Comments</span>
</div>
<div className="text-2xl font-bold text-white">{media.comments_count || 0}</div>
</div>
<div className="bg-white/5 backdrop-blur-sm rounded-xl p-4 border border-white/10">
<div className="flex items-center gap-2 mb-2">
<Calendar className="w-4 h-4 text-green-400" />
<span className="text-gray-400 text-sm">Posted</span>
</div>
<div className="text-lg font-bold text-white">
{new Date(media.timestamp).toLocaleDateString()}
</div>
</div>
<div className="bg-white/5 backdrop-blur-sm rounded-xl p-4 border border-white/10">
<div className="flex items-center gap-2 mb-2">
<Clock className="w-4 h-4 text-purple-400" />
<span className="text-gray-400 text-sm">Time</span>
</div>
<div className="text-lg font-bold text-white">
{new Date(media.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
</div>
</div>
</div>
{/* View on Instagram Button */}
{media.permalink && (
<a
href={media.permalink}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center justify-center gap-2 w-full py-3 rounded-xl bg-gradient-to-r from-pink-600 to-purple-600 hover:from-pink-700 hover:to-purple-700 text-white font-semibold transition-all duration-300 hover:scale-105"
>
<ExternalLink className="w-5 h-5" />
View on Instagram
</a>
)}
</div>
</div>
{/* New Comment Input */}
<div className="bg-gradient-to-br from-gray-900/90 to-black/90 backdrop-blur-xl rounded-2xl border border-white/10 shadow-2xl p-6 mb-8">
<h3 className="text-xl font-bold text-white mb-4 flex items-center gap-3">
<MessageSquare className="w-5 h-5 text-blue-400" />
Add a Comment
</h3>
<div className="space-y-4">
<textarea
placeholder="Write your comment here..."
className="w-full px-4 py-3 rounded-xl bg-white/5 border border-white/10 text-white placeholder-gray-400 focus:outline-none focus:border-blue-500/30 focus:ring-1 focus:ring-blue-500/30 min-h-[120px] resize-none"
value={newCommentText}
onChange={(e) => setNewCommentText(e.target.value)}
/>
<div className="flex justify-between items-center">
<p className="text-gray-400 text-sm">
Comments are posted directly to Instagram
</p>
<button
onClick={postNewComment}
disabled={!newCommentText.trim()}
className="px-6 py-3 rounded-xl bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white font-semibold transition-all duration-300 hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
>
<Send className="w-4 h-4" />
Post Comment
</button>
</div>
</div>
</div>
</div>
{/* Right Column - Comments & Info */}
<div className="space-y-8">
{/* Comments Header */}
<div className="bg-gradient-to-br from-gray-900/90 to-black/90 backdrop-blur-xl rounded-2xl border border-white/10 shadow-2xl p-6">
<div className="flex items-center justify-between mb-6">
<h3 className="text-xl font-bold text-white flex items-center gap-3">
<Users className="w-5 h-5 text-blue-400" />
Comments
</h3>
<div className="px-3 py-1 rounded-full bg-blue-500/20 text-blue-400 text-sm font-medium border border-blue-500/30">
{comments.length} total
</div>
</div>
{/* Comments List */}
<div className="space-y-4 max-h-[600px] overflow-y-auto pr-2">
{commentsLoading ? (
<div className="flex items-center justify-center py-8">
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500"></div>
</div>
) : comments.length === 0 ? (
<div className="text-center py-8">
<MessageSquare className="w-12 h-12 text-gray-400 mx-auto mb-3" />
<p className="text-gray-400">No comments yet</p>
<p className="text-gray-500 text-sm mt-1">Be the first to comment!</p>
</div>
) : (
comments.map((comment) => (
<SocialCommentItem
key={comment.id}
comment={comment}
onReply={handleReply}
onDelete={handleDelete}
onHide={handleHide}
onEdit={handleEdit}
/>
))
)}
</div>
</div>
{/* Info Card */}
<div className="bg-gradient-to-br from-blue-900/20 to-indigo-900/20 rounded-2xl p-6 border border-blue-500/30 backdrop-blur-xl">
<h4 className="text-lg font-semibold text-white mb-4 flex items-center gap-2">
<Shield className="w-5 h-5 text-blue-400" />
Comment Management
</h4>
<ul className="space-y-3 text-sm">
<li className="flex items-start gap-2 text-gray-300">
<div className="w-5 h-5 rounded-full bg-green-500/20 flex items-center justify-center flex-shrink-0 mt-0.5">
<CheckCircle className="w-3 h-3 text-green-400" />
</div>
<span>All actions are manual and user-controlled</span>
</li>
<li className="flex items-start gap-2 text-gray-300">
<div className="w-5 h-5 rounded-full bg-green-500/20 flex items-center justify-center flex-shrink-0 mt-0.5">
<CheckCircle className="w-3 h-3 text-green-400" />
</div>
<span>No automated responses or AI-generated comments</span>
</li>
<li className="flex items-start gap-2 text-gray-300">
<div className="w-5 h-5 rounded-full bg-green-500/20 flex items-center justify-center flex-shrink-0 mt-0.5">
<CheckCircle className="w-3 h-3 text-green-400" />
</div>
<span>Direct moderation tools for community management</span>
</li>
</ul>
</div>
</div>
</div>
</div>
{/* Animation Styles */}
<style jsx>{`
@keyframes float {
0%, 100% {
transform: translateY(0) translateX(0);
}
50% {
transform: translateY(-20px) translateX(10px);
}
}
@keyframes float-slow {
0%, 100% {
transform: translateY(0) translateX(0);
}
50% {
transform: translateY(-30px) translateX(-15px);
}
}
.animate-float {
animation: float 8s ease-in-out infinite;
}
.animate-float-slow {
animation: float-slow 10s ease-in-out infinite;
}
`}</style>
</div>
);
};
export default MediaDetailsPage;

View File

@ -0,0 +1,172 @@
"use client";
import axios from "axios";
import { ApiServerBaseUrl } from "@/utils/baseurl.utils";
import React, { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
type MediaItem = {
id: string;
caption?: string;
media_type: string;
media_url: string;
thumbnail_url?: string;
permalink: string;
timestamp: string;
like_count?: number;
comments_count?: number;
};
const Sales = () => {
const router = useRouter()
const [media, setMedia] = useState<MediaItem[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
const fetchMedia = async () => {
try {
const res = await axios.get(`${ApiServerBaseUrl}/social/media?userId=${localStorage.getItem('user_email')}`);
setMedia(res.data?.data || []);
} catch (err: any) {
setError(
err.response?.data?.message || err.message || "Failed to fetch media"
);
} finally {
setLoading(false);
}
};
fetchMedia();
}, []);
return (
<div className="
min-h-screen p-6
bg-gradient-to-br from-[#0073C6]/25 via-[#E44DB3]/20 to-[#0047A3]/25
">
<h2 className="text-3xl font-extrabold mb-8 tracking-wide text-gray-900">
Instagram Media Library
</h2>
{loading && (
<p className="text-gray-700 text-lg">Loading media...</p>
)}
{error && (
<p className="text-red-600 text-lg">Error: {error}</p>
)}
{/* Premium Media Grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 gap-7">
{media?.map((item) => (
<div
key={item.id}
className="
backdrop-blur-xl bg-white/30 border border-white/40
rounded-3xl shadow-2xl overflow-hidden
hover:scale-[1.03] hover:shadow-xl transition-all duration-300
"
>
{/* IMAGE */}
{item.media_type === "IMAGE" && (
<div className="relative">
<img
src={item.media_url}
alt="instagram media"
className="w-full h-72 object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/40 via-transparent"></div>
</div>
)}
{/* VIDEO */}
{item.media_type === "VIDEO" && (
<div className="relative">
<video
src={item.media_url}
poster={item.thumbnail_url}
className="w-full h-72 object-cover"
autoPlay
loop
muted
playsInline
/>
{/* Gradient Overlay */}
<div className="absolute inset-0 bg-gradient-to-t from-black/50 via-transparent"></div>
</div>
)}
{/* Content Section */}
<div className="p-4">
{/* Caption */}
{item.caption && (
<p className="text-sm text-gray-800 mb-3 line-clamp-3 leading-relaxed">
{item.caption}
</p>
)}
{/* Like + Comment */}
<div className="flex items-center justify-between text-sm text-gray-700 mb-4">
<p> {item.like_count || 0}</p>
<p>💬 {item.comments_count || 0}</p>
</div>
{/* Buttons */}
<div className="flex gap-3 mt-2">
{/* View on Instagram */}
<a
href={item.permalink}
target="_blank"
className="
flex-1 text-center
bg-gradient-to-r from-blue-600 to-purple-600
hover:from-blue-700 hover:to-purple-700
text-white py-2 rounded-xl
font-semibold shadow-lg
transition-all duration-300
"
>
View on Instagram
</a>
{/* View Details */}
<button
onClick={() => router.push(`/social-media-posts/${item.id}`)}
className="
flex-1 text-center
bg-gradient-to-r from-[#0073C6] to-[#E44DB3]
text-white shadow-md hover:to-purple-700
text-white py-2 rounded-xl
font-semibold shadow-lg
transition-all duration-300 "
>
View Details
</button>
</div>
</div>
{/* Badge */}
<span className="
absolute top-3 right-3
bg-black/30 backdrop-blur-md
text-white text-xs font-bold
px-3 py-1 rounded-full shadow-lg
">
{item.media_type}
</span>
</div>
))}
</div>
{media.length === 0 && !loading && (
<p className="text-gray-700 mt-8">No media available.</p>
)}
</div>
);
};
export default Sales;

View File

@ -0,0 +1,498 @@
"use client";
import axios from "axios";
import { ApiServerBaseUrl } from "@/utils/baseurl.utils";
import React, { useEffect, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import { getSocialAuthStatus } from "@/utils/commonFunction.utils";
import {
Image as ImageIcon,
Video,
Album,
Heart,
MessageCircle,
ExternalLink,
BarChart3,
Calendar,
Filter,
Search,
RefreshCw,
ChevronRight,
Eye,
MoreVertical,
CheckCircle
} from "lucide-react";
type MediaItem = {
id: string;
caption?: string;
media_type: string;
media_url: string;
thumbnail_url?: string;
permalink: string;
timestamp: string;
like_count?: number;
comments_count?: number;
children?: { data: MediaItem[] };
};
const LIMIT = 12;
const Posts = () => {
const router = useRouter();
const [media, setMedia] = useState<MediaItem[]>([]);
const [loading, setLoading] = useState(true);
const [loadingMore, setLoadingMore] = useState(false);
const [error, setError] = useState("");
const [afterCursor, setAfterCursor] = useState<string | null>(null);
const [hasMore, setHasMore] = useState(true);
const [isInitialLoaded, setIsInitialLoaded] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
const [refreshing, setRefreshing] = useState(false);
const observerRef = useRef<HTMLDivElement | null>(null);
/* 🔐 Validate + first fetch */
useEffect(() => {
async function validate() {
const userEmail = localStorage.getItem("user_email");
if (!userEmail) {
router.push("/social-media-connect");
return;
}
const storedUser = localStorage.getItem("userDetails");
if (!storedUser) {
router.push("/social-media-connect");
return;
}
const user = JSON.parse(storedUser);
const role = user?.role;
if (role === "customer") {
const session = localStorage.getItem("payment_session");
if (!session) {
router.push("/pricing");
return;
}
}
const res = await getSocialAuthStatus(userEmail);
if (!res?.connected) {
router.push("/social-media-connect");
return;
}
fetchInitialMedia();
}
validate();
}, []);
/* ✅ FIRST FETCH ONLY */
const fetchInitialMedia = async () => {
try {
setLoading(true);
const res: any = await axios.get(
`${ApiServerBaseUrl}/social/media`,
{
params: {
userId: localStorage.getItem("user_email"),
limit: LIMIT,
},
}
);
setMedia(res.data?.data || []);
setAfterCursor(res.data?.paging?.cursors?.after || null);
setHasMore(!!res.data?.paging?.cursors?.after);
setIsInitialLoaded(true);
} catch (err: any) {
setError(err.response?.data?.message || err.message);
} finally {
setLoading(false);
}
};
/* NEXT PAGE FETCH */
const fetchMoreMedia = async () => {
if (!afterCursor || loadingMore) return;
try {
setLoadingMore(true);
const res: any = await axios.get(
`${ApiServerBaseUrl}/social/media`,
{
params: {
userId: localStorage.getItem("user_email"),
limit: LIMIT,
after: afterCursor,
},
}
);
setMedia((prev) => [...prev, ...(res.data?.data || [])]);
setAfterCursor(res.data?.paging?.cursors?.after || null);
setHasMore(!!res.data?.paging?.cursors?.after);
} catch (err: any) {
setError(err.response?.data?.message || err.message);
} finally {
setLoadingMore(false);
}
};
/* 👀 OBSERVER — ONLY AFTER INITIAL LOAD */
useEffect(() => {
if (!observerRef.current || !isInitialLoaded || !hasMore) return;
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
fetchMoreMedia();
}
},
{ threshold: 1 }
);
observer.observe(observerRef.current);
return () => observer.disconnect();
}, [afterCursor, isInitialLoaded, hasMore]);
/* 🔄 REFRESH */
const handleRefresh = async () => {
setRefreshing(true);
await fetchInitialMedia();
setTimeout(() => setRefreshing(false), 1000);
};
/* 🔍 FILTERED MEDIA */
const filteredMedia = media.filter(item =>
!searchQuery ||
item.caption?.toLowerCase().includes(searchQuery.toLowerCase())
);
/* 📱 GET MEDIA TYPE ICON */
const getMediaTypeIcon = (type: string) => {
switch (type) {
case "IMAGE": return <ImageIcon className="w-4 h-4" />;
case "VIDEO": return <Video className="w-4 h-4" />;
case "CAROUSEL_ALBUM": return <Album className="w-4 h-4" />;
default: return <ImageIcon className="w-4 h-4" />;
}
};
/* 📅 FORMAT DATE */
const formatDate = (timestamp: string) => {
const date = new Date(timestamp);
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
});
};
return (
<div className="min-h-screen bg-gradient-to-b from-[#0a0a0a] to-[#111111] p-4 md:p-6">
{/* Floating background bubbles */}
<div className="absolute top-20 left-[5%] w-24 h-24 rounded-full bg-pink-500/20 blur-xl animate-float"></div>
<div className="absolute top-40 right-[10%] w-32 h-32 rounded-full bg-blue-500/15 blur-xl animate-float-slow"></div>
<div className="max-w-7xl mx-auto relative z-10">
{/* Header */}
<div className="mb-10">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8">
<div className="flex items-center gap-4">
<div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-pink-500 to-purple-600 flex items-center justify-center shadow-lg shadow-pink-500/30">
<ImageIcon className="w-8 h-8 text-white" />
</div>
<div>
<h1 className="text-3xl md:text-4xl font-bold text-white">
Instagram Media Library
</h1>
<p className="text-gray-300 mt-1">
Browse and manage all your Instagram posts
</p>
</div>
</div>
<div className="flex items-center gap-4">
<button
onClick={handleRefresh}
disabled={refreshing}
className="px-4 py-2 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-gray-300 hover:text-white transition-all duration-300 flex items-center gap-2"
>
<RefreshCw className={`w-4 h-4 ${refreshing ? 'animate-spin' : ''}`} />
Refresh
</button>
</div>
</div>
{/* Search and Filters */}
<div className="flex flex-col md:flex-row gap-4 mb-6">
<div className="flex-1">
<div className="relative">
<Search className="absolute left-4 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
<input
type="text"
placeholder="Search posts by caption..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-12 pr-4 py-3 rounded-xl bg-white/5 border border-white/10 text-white placeholder-gray-400 focus:outline-none focus:border-pink-500/30 focus:ring-1 focus:ring-pink-500/30"
/>
</div>
</div>
<div className="flex gap-2">
<button className="px-4 py-3 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-gray-300 hover:text-white transition-colors flex items-center gap-2">
<Filter className="w-4 h-4" />
Filter
</button>
<button className="px-4 py-3 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-gray-300 hover:text-white transition-colors flex items-center gap-2">
<Calendar className="w-4 h-4" />
Date
</button>
</div>
</div>
{/* Stats */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
<div className="bg-gradient-to-br from-gray-900/50 to-black/50 backdrop-blur-sm rounded-xl p-4 border border-white/10">
<div className="text-sm text-gray-400 mb-1">Total Posts</div>
<div className="text-2xl font-bold text-white">{media.length}</div>
</div>
<div className="bg-gradient-to-br from-gray-900/50 to-black/50 backdrop-blur-sm rounded-xl p-4 border border-white/10">
<div className="text-sm text-gray-400 mb-1">Images</div>
<div className="text-2xl font-bold text-white">
{media.filter(m => m.media_type === "IMAGE").length}
</div>
</div>
<div className="bg-gradient-to-br from-gray-900/50 to-black/50 backdrop-blur-sm rounded-xl p-4 border border-white/10">
<div className="text-sm text-gray-400 mb-1">Videos</div>
<div className="text-2xl font-bold text-white">
{media.filter(m => m.media_type === "VIDEO").length}
</div>
</div>
<div className="bg-gradient-to-br from-gray-900/50 to-black/50 backdrop-blur-sm rounded-xl p-4 border border-white/10">
<div className="text-sm text-gray-400 mb-1">Carousels</div>
<div className="text-2xl font-bold text-white">
{media.filter(m => m.media_type === "CAROUSEL_ALBUM").length}
</div>
</div>
</div>
</div>
{/* Loading State */}
{loading && !isInitialLoaded && (
<div className="flex items-center justify-center py-20">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-pink-500 mx-auto mb-4"></div>
<p className="text-gray-300">Loading your Instagram posts...</p>
</div>
</div>
)}
{/* Error State */}
{error && (
<div className="bg-red-900/20 backdrop-blur-xl rounded-2xl p-8 border border-red-500/30 mb-8">
<div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-full bg-red-500/20 flex items-center justify-center">
<div className="text-red-400">!</div>
</div>
<div>
<h3 className="text-xl font-bold text-white mb-2">Error Loading Posts</h3>
<p className="text-gray-300">{error}</p>
</div>
</div>
</div>
)}
{/* Media Grid */}
{!loading && (
<div className="mb-10">
{filteredMedia.length === 0 ? (
<div className="text-center py-20">
<ImageIcon className="w-16 h-16 text-gray-400 mx-auto mb-4" />
<h3 className="text-xl font-bold text-white mb-2">No posts found</h3>
<p className="text-gray-400">
{searchQuery ? "No posts match your search." : "No Instagram posts available."}
</p>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
{filteredMedia.map((item) => (
<div
key={item.id}
className="group bg-gradient-to-br from-gray-900/90 to-black/90 backdrop-blur-xl rounded-2xl border border-white/10 hover:border-pink-500/30 transition-all duration-300 hover:scale-[1.02] overflow-hidden shadow-lg hover:shadow-2xl"
>
{/* Media Type Badge */}
<div className="absolute top-3 right-3 z-10">
<div className="px-3 py-1 rounded-full bg-black/80 backdrop-blur-sm text-xs font-medium text-white flex items-center gap-1">
{getMediaTypeIcon(item.media_type)}
<span className="capitalize">{item.media_type.toLowerCase().replace('_', ' ')}</span>
</div>
</div>
{/* Media Preview */}
<div className="relative h-64 overflow-hidden bg-black">
{item.media_type === "IMAGE" && (
<img
src={item.media_url}
alt="Instagram post"
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
/>
)}
{item.media_type === "VIDEO" && (
<div className="relative h-full">
<video
src={item.media_url}
poster={item.thumbnail_url}
className="w-full h-full object-cover"
loop
muted
playsInline
/>
<div className="absolute inset-0 flex items-center justify-center">
<div className="w-12 h-12 rounded-full bg-black/50 backdrop-blur-sm flex items-center justify-center">
<Video className="w-6 h-6 text-white" />
</div>
</div>
</div>
)}
{item.media_type === "CAROUSEL_ALBUM" && item.children?.data && (
<div className="relative h-full">
<img
src={item.children.data[0].media_url}
alt="Carousel preview"
className="w-full h-full object-cover"
/>
<div className="absolute top-3 left-3 px-2 py-1 rounded bg-black/60 text-xs text-white">
{item.children.data.length} items
</div>
</div>
)}
{/* Overlay */}
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<div className="absolute bottom-4 left-4 right-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<div className="flex items-center gap-1 text-white">
<Heart className="w-4 h-4" />
<span className="text-sm">{item.like_count || 0}</span>
</div>
<div className="flex items-center gap-1 text-white">
<MessageCircle className="w-4 h-4" />
<span className="text-sm">{item.comments_count || 0}</span>
</div>
</div>
</div>
</div>
</div>
</div>
{/* Content */}
<div className="p-5">
{/* Date */}
<div className="flex items-center gap-2 text-gray-400 text-sm mb-3">
<Calendar className="w-4 h-4" />
{formatDate(item.timestamp)}
</div>
{/* Caption */}
{item.caption && (
<p className="text-gray-300 text-sm mb-4 line-clamp-2">
{item.caption}
</p>
)}
{/* Actions */}
<div className="flex gap-2">
<a
href={item.permalink}
target="_blank"
rel="noopener noreferrer"
className="flex-1 py-2 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-white text-sm font-medium transition-all duration-300 hover:scale-105 flex items-center justify-center gap-2"
title="View on Instagram"
>
<ExternalLink className="w-4 h-4" />
View
</a>
<button
onClick={() => router.push(`/social-media-posts/${item.id}`)}
className="flex-1 py-2 rounded-xl bg-gradient-to-r from-pink-600 to-purple-600 hover:from-pink-700 hover:to-purple-700 text-white text-sm font-medium transition-all duration-300 hover:scale-105 flex items-center justify-center gap-2"
title="Manage comments and details"
>
<BarChart3 className="w-4 h-4" />
Details
<ChevronRight className="w-4 h-4" />
</button>
</div>
</div>
</div>
))}
</div>
)}
</div>
)}
{/* Load More Trigger */}
{hasMore && isInitialLoaded && (
<div ref={observerRef} className="py-8">
<div className="text-center">
{loadingMore ? (
<div className="flex items-center justify-center gap-3 text-gray-400">
<div className="animate-spin rounded-full h-6 w-6 border-t-2 border-b-2 border-pink-500"></div>
<span>Loading more posts...</span>
</div>
) : (
<p className="text-gray-400">Scroll to load more posts</p>
)}
</div>
</div>
)}
{/* No More Posts */}
{!hasMore && isInitialLoaded && media.length > 0 && (
<div className="text-center py-12">
<div className="w-16 h-16 rounded-full bg-gradient-to-br from-gray-900 to-black border border-white/10 flex items-center justify-center mx-auto mb-4">
<CheckCircle className="w-8 h-8 text-green-400" />
</div>
<h3 className="text-xl font-bold text-white mb-2">All posts loaded</h3>
<p className="text-gray-400">You've reached the end of your Instagram media</p>
</div>
)}
</div>
{/* Animation Styles */}
<style jsx>{`
@keyframes float {
0%, 100% {
transform: translateY(0) translateX(0);
}
50% {
transform: translateY(-20px) translateX(10px);
}
}
@keyframes float-slow {
0%, 100% {
transform: translateY(0) translateX(0);
}
50% {
transform: translateY(-30px) translateX(-15px);
}
}
.animate-float {
animation: float 8s ease-in-out infinite;
}
.animate-float-slow {
animation: float-slow 10s ease-in-out infinite;
}
`}</style>
</div>
);
};
export default Posts;

BIN
app/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

28
app/layout.tsx Normal file
View File

@ -0,0 +1,28 @@
import ProviderComponent from '@/components/layouts/provider-component';
import 'react-perfect-scrollbar/dist/css/styles.css';
import '../styles/tailwind.css';
import { Metadata } from 'next';
import { Nunito } from 'next/font/google';
export const metadata: Metadata = {
title: {
template: 'Social Buddy | Social Media Manager',
default: 'Social Buddy | Social Media Manager',
},
};
const nunito = Nunito({
weight: ['400', '500', '600', '700', '800'],
subsets: ['latin'],
display: 'swap',
variable: '--font-nunito',
});
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body className={nunito.variable}>
<ProviderComponent>{children}</ProviderComponent>
</body>
</html>
);
}

8
app/loading.tsx Normal file
View File

@ -0,0 +1,8 @@
import Loading from '@/components/layouts/loading';
import React from 'react';
const loading = () => {
return <Loading />;
};
export default loading;

26
app/not-found.tsx Normal file
View File

@ -0,0 +1,26 @@
import { Metadata } from 'next';
import Link from 'next/link';
import React from 'react';
export const metadata: Metadata = {
title: 'Error 404',
};
const NotFound = () => {
return (
<div className="relative flex min-h-screen items-center justify-center overflow-hidden">
<div className="px-6 py-16 text-center font-semibold before:container before:absolute before:left-1/2 before:aspect-square before:-translate-x-1/2 before:rounded-full before:bg-[linear-gradient(180deg,#4361EE_0%,rgba(67,97,238,0)_50.73%)] before:opacity-10 md:py-20">
<div className="relative">
<img src="/assets/images/error/404-dark.svg" alt="404" className="dark-img mx-auto -mt-10 w-full max-w-xs object-cover md:-mt-14 md:max-w-xl" />
<img src="/assets/images/error/404-light.svg" alt="404" className="light-img mx-auto -mt-10 w-full max-w-xs object-cover md:-mt-14 md:max-w-xl" />
<p className="mt-5 text-base dark:text-white">The page you requested was not found!</p>
<Link href="/" className="btn btn-gradient mx-auto !mt-7 w-max border-0 uppercase shadow-none">
Home
</Link>
</div>
</div>
</div>
);
};
export default NotFound;

View File

@ -0,0 +1,51 @@
"use client";
import React from "react";
import Footer from "@/components/layouts/footer";
const PrivacyPolicy = () => {
return (
<div className="min-h-screen bg-[#111111] text-gray-200 p-8 flex flex-col items-center">
<div className="max-w-4xl w-full bg-[#242424] p-10 rounded-3xl border border-white/10 shadow-2xl">
<h1 className="text-4xl font-extrabold mb-8 text-white">Privacy Policy</h1>
<p className="mb-4">Last Updated: December 30, 2025</p>
<section className="mb-6">
<h2 className="text-2xl font-bold mb-3 text-white">1. Information We Collect</h2>
<p>
When you connect your Facebook or Instagram account to Social Buddy, we collect your basic profile information, media posts, and comments to provide our management services.
</p>
</section>
<section className="mb-6">
<h2 className="text-2xl font-bold mb-3 text-white">2. How We Use Your Information</h2>
<p>
We use the data to display your Instagram media library, allow you to reply to comments, and moderate your community through our dashboard.
</p>
</section>
<section className="mb-6">
<h2 className="text-2xl font-bold mb-3 text-white">3. Data Security</h2>
<p>
We implement industry-standard security measures to protect your access tokens and personal data.
</p>
</section>
<section className="mb-6">
<h2 className="text-2xl font-bold mb-3 text-white">4. Your Choices</h2>
<p>
You can disconnect your social accounts at any time through the "Connect" page, which will immediately revoke our access to your data.
</p>
</section>
<button
onClick={() => window.history.back()}
className="mt-8 px-6 py-2 bg-blue-600 text-white rounded-xl hover:bg-blue-700 transition"
>
Go Back
</button>
</div>
</div>
);
};
export default PrivacyPolicy;

View File

@ -0,0 +1,50 @@
"use client";
import React from "react";
const TermsOfService = () => {
return (
<div className="min-h-screen bg-[#111111] text-gray-200 p-8 flex flex-col items-center">
<div className="max-w-4xl w-full bg-[#242424] p-10 rounded-3xl border border-white/10 shadow-2xl">
<h1 className="text-4xl font-extrabold mb-8 text-white">Terms of Use</h1>
<p className="mb-4">Last Updated: December 30, 2025</p>
<section className="mb-6">
<h2 className="text-2xl font-bold mb-3 text-white">1. Acceptance of Terms</h2>
<p>
By using Social Buddy, you agree to comply with these terms and all applicable Meta Developer Policies.
</p>
</section>
<section className="mb-6">
<h2 className="text-2xl font-bold mb-3 text-white">2. Use of Service</h2>
<p>
You must provide accurate information when connecting your social media accounts. You are responsible for all actions taken through your connected accounts.
</p>
</section>
<section className="mb-6">
<h2 className="text-2xl font-bold mb-3 text-white">3. Third-Party Services</h2>
<p>
Social Buddy integrates with Meta (Facebook/Instagram). Your use is also subject to Meta's Terms of Service and Privacy Policies.
</p>
</section>
<section className="mb-6">
<h2 className="text-2xl font-bold mb-3 text-white">4. Termination</h2>
<p>
We reserve the right to suspend or terminate your access if you violate these terms or any platform policies.
</p>
</section>
<button
onClick={() => window.history.back()}
className="mt-8 px-6 py-2 bg-pink-600 text-white rounded-xl hover:bg-pink-700 transition"
>
Go Back
</button>
</div>
</div>
);
};
export default TermsOfService;

View File

@ -0,0 +1,228 @@
"use client";
import { useState } from "react";
import { FaReply, FaTrash, FaEye, FaEyeSlash, FaEdit, FaSave, FaTimes } from "react-icons/fa";
type Reply = {
id: string;
text: string;
timestamp: string;
username: string;
hidden?: boolean;
like_count?: number;
};
type Comment = {
id: string;
text: string;
username: string;
timestamp: string;
like_count?: number;
hidden?: boolean;
replies?: { data: Reply[] };
};
interface SocialCommentItemProps {
comment: Comment;
onReply: (commentId: string, text: string) => void;
onDelete: (commentId: string, isReply?: boolean, parentId?: string) => void;
onHide: (commentId: string, currentHiddenStatus: boolean, isReply?: boolean) => void;
onEdit: (commentId: string, newText: string, isReply: boolean, parentId?: string) => void;
}
const SocialCommentItem = ({ comment, onReply, onDelete, onHide, onEdit }: SocialCommentItemProps) => {
const [replyText, setReplyText] = useState("");
const [isReplying, setIsReplying] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const [editText, setEditText] = useState(comment.text);
const handlePostReply = () => {
if (!replyText.trim()) return;
onReply(comment.id, replyText);
setReplyText("");
setIsReplying(false);
};
const handleSaveEdit = () => {
if (!editText.trim()) return;
onEdit(comment.id, editText, false, undefined);
setIsEditing(false);
};
return (
<div className="bg-[#242424] backdrop-blur-lg border border-white/10 p-4 rounded-xl mb-4 shadow text-sm">
{/* TOP LEVEL COMMENT */}
<div className="flex justify-between items-start">
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<span className="font-bold text-white text-base">{comment.username}</span>
<span className="text-xs text-gray-500">{new Date(comment.timestamp).toLocaleString()}</span>
{comment.hidden && (
<span className="text-xs bg-red-500/20 text-red-400 px-2 py-0.5 rounded-full border border-red-500/20">
Hidden
</span>
)}
</div>
{isEditing ? (
<div className="mt-2">
<textarea
className="w-full bg-[#111111] border border-white/20 rounded p-2 text-gray-200 focus:outline-none focus:border-purple-500"
value={editText}
onChange={(e) => setEditText(e.target.value)}
/>
<div className="flex gap-2 mt-2">
<button onClick={handleSaveEdit} className="flex items-center gap-1 px-3 py-1 bg-green-600 rounded text-white text-xs hover:bg-green-700">
<FaSave /> Save
</button>
<button onClick={() => setIsEditing(false)} className="flex items-center gap-1 px-3 py-1 bg-gray-600 rounded text-white text-xs hover:bg-gray-700">
<FaTimes /> Cancel
</button>
</div>
</div>
) : (
<p className="text-gray-300 leading-relaxed text-base">{comment.text}</p>
)}
</div>
</div>
{/* ACTIONS BAR */}
<div className="flex items-center gap-4 mt-3 pt-2 border-t border-white/5">
<button
onClick={() => setIsReplying(!isReplying)}
className="flex items-center gap-1.5 text-indigo-400 hover:text-indigo-300 transition-colors text-xs font-medium uppercase tracking-wide"
>
<FaReply /> Reply
</button>
<button
onClick={() => setIsEditing(!isEditing)}
className="flex items-center gap-1.5 text-blue-400 hover:text-blue-300 transition-colors text-xs font-medium uppercase tracking-wide"
>
<FaEdit /> Edit
</button>
<button
onClick={() => onHide(comment.id, !!comment.hidden, false)}
className="flex items-center gap-1.5 text-yellow-500 hover:text-yellow-400 transition-colors text-xs font-medium uppercase tracking-wide"
>
{comment.hidden ? <FaEye /> : <FaEyeSlash />} {comment.hidden ? "Unhide" : "Hide"}
</button>
<button
onClick={() => onDelete(comment.id, false)}
className="flex items-center gap-1.5 text-red-500 hover:text-red-400 transition-colors text-xs font-medium uppercase tracking-wide"
>
<FaTrash /> Delete
</button>
</div>
{/* REPLY INPUT AREA */}
{isReplying && (
<div className="mt-3 flex gap-2 animate-fadeIn">
<input
type="text"
placeholder={`Reply to ${comment.username}...`}
value={replyText}
onChange={(e) => setReplyText(e.target.value)}
className="flex-1 bg-[#111111] border border-white/10 rounded-lg px-3 py-2 text-white focus:outline-none focus:border-indigo-500"
/>
<button
onClick={handlePostReply}
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg font-medium transition-colors"
>
Send
</button>
</div>
)}
{/* REPLIES LIST */}
{comment.replies?.data && comment.replies.data.length > 0 && (
<div className="mt-4 pl-4 border-l-2 border-white/10 space-y-4">
{comment.replies.data.map((reply) => (
<ReplyItem
key={reply.id}
reply={reply}
parentId={comment.id}
onDelete={onDelete}
onHide={onHide}
onEdit={onEdit}
/>
))}
</div>
)}
</div>
);
};
const ReplyItem = ({
reply,
parentId,
onDelete,
onHide,
onEdit
}: {
reply: Reply;
parentId: string;
onDelete: (id: string, isReply: boolean, parentId?: string) => void; // parentId needed to update state optimistically? Maybe not for DB but for state.
onHide: (id: string, currentStatus: boolean, isReply: boolean) => void;
onEdit: (id: string, text: string, isReply: boolean, parentId: string) => void;
}) => {
const [isEditing, setIsEditing] = useState(false);
const [editText, setEditText] = useState(reply.text);
const handleSaveEdit = () => {
if (!editText.trim()) return;
onEdit(reply.id, editText, true, parentId);
setIsEditing(false);
};
return (
<div className="bg-[#1a1a1a] p-3 rounded-lg border border-white/5 relative group">
<div className="flex items-center gap-2 mb-1">
<span className="font-bold text-white text-sm">{reply.username}</span>
<span className="text-xs text-gray-500">{new Date(reply.timestamp).toLocaleString()}</span>
{reply.hidden && (
<span className="text-xs bg-red-500/20 text-red-400 px-1.5 rounded border border-red-500/20">Hidden</span>
)}
</div>
{isEditing ? (
<div className="mt-1">
<textarea
className="w-full bg-[#0a0a0a] border border-white/20 rounded p-2 text-gray-200 text-sm focus:outline-none focus:border-purple-500"
value={editText}
onChange={(e) => setEditText(e.target.value)}
/>
<div className="flex gap-2 mt-2">
<button onClick={handleSaveEdit} className="text-xs px-2 py-1 bg-green-600 rounded text-white cursor-pointer">Save</button>
<button onClick={() => setIsEditing(false)} className="text-xs px-2 py-1 bg-gray-600 rounded text-white cursor-pointer">Cancel</button>
</div>
</div>
) : (
<p className="text-gray-400 text-sm">{reply.text}</p>
)}
{/* ACTION BUTTONS FOR REPLY */}
<div className="flex gap-3 mt-2 opacity-60 group-hover:opacity-100 transition-opacity">
<button
onClick={() => setIsEditing(!isEditing)}
className="flex items-center gap-1 text-[10px] uppercase font-bold text-blue-400 hover:text-blue-300"
>
<FaEdit /> Edit
</button>
<button
onClick={() => onHide(reply.id, !!reply.hidden, true)}
className="flex items-center gap-1 text-[10px] uppercase font-bold text-yellow-500 hover:text-yellow-400"
>
{reply.hidden ? <FaEye /> : <FaEyeSlash />} {reply.hidden ? "Unhide" : "Hide"}
</button>
<button
onClick={() => onDelete(reply.id, true, parentId)}
className="flex items-center gap-1 text-[10px] uppercase font-bold text-red-500 hover:text-red-400"
>
<FaTrash /> Delete
</button>
</div>
</div>
);
};
export default SocialCommentItem;

View File

@ -0,0 +1,183 @@
'use client';
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { useRouter } from 'next/navigation';
import IconLockDots from '@/components/icon/icon-lock-dots';
import Link from 'next/link';
import { ApiServerBaseUrl } from '@/utils/baseurl.utils';
import { Eye, EyeOff } from 'lucide-react';
const ComponentsAuthChangePasswordForm = () => {
const router = useRouter();
const [email, setEmail] = useState<string | null>(null);
const [currentPassword, setCurrentPassword] = useState('');
const [newPassword, setNewPassword] = useState('');
const [showCurrent, setShowCurrent] = useState(false);
const [showNew, setShowNew] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [success, setSuccess] = useState('');
useEffect(() => {
const storedEmail = localStorage.getItem('user_email');
if (!storedEmail) {
setError('User not authenticated. Please login again.');
setTimeout(() => router.push('/login'), 1500);
} else {
setEmail(storedEmail);
}
}, [router]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
setSuccess('');
if (!email || !currentPassword || !newPassword) {
setError('All fields are required');
return;
}
try {
setLoading(true);
await axios.post(`${ApiServerBaseUrl}/change-password`, {
email,
currentPassword,
newPassword,
});
setSuccess('Password changed successfully');
setTimeout(() => router.push('/login'), 1500);
} catch (err: any) {
setError(err.response?.data?.error || 'Password change failed');
} finally {
setLoading(false);
}
};
return (
<div className="relative z-10 w-full max-w-[420px] bg-[#0f1022] rounded-3xl p-8 shadow-2xl border border-white/10">
<h1
className="
text-3xl font-extrabold text-center mb-2
bg-gradient-to-r from-blue-500 via-cyan-400 to-pink-500
bg-clip-text text-transparent
"
>
Change Password
</h1>
<p className="text-gray-400 text-sm text-center mb-6">
Update your account password
</p>
<form className="space-y-5" onSubmit={handleSubmit}>
{error && <p className="text-red-500 text-sm text-center">{error}</p>}
{success && <p className="text-green-500 text-sm text-center">{success}</p>}
{email && (
<div className="text-sm text-gray-300 bg-[#15162e] border border-white/10 rounded-lg px-4 py-3 text-center">
Signed in as <b>{email}</b>
</div>
)}
{/* CURRENT PASSWORD */}
<div className="relative">
<input
type={showCurrent ? "text" : "password"}
placeholder="Current password"
className="
w-full bg-[#15162e] text-white
rounded-lg ps-10 pe-10 py-3 outline-none
border border-white/10 focus:border-blue-400
"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
/>
<span className="absolute start-3 top-1/2 -translate-y-1/2">
<IconLockDots fill />
</span>
{/* 🔥 FIXED EYE ICON — NO MOVING */}
<button
type="button"
onClick={() => setShowCurrent(!showCurrent)}
className="absolute end-3 top-1/2 -translate-y-1/2 text-gray-400"
>
<span className="flex items-center justify-center w-5 h-5">
{showCurrent ? (
<EyeOff size={18} strokeWidth={1.5} />
) : (
<Eye size={18} strokeWidth={1.5} />
)}
</span>
</button>
</div>
{/* NEW PASSWORD */}
<div className="relative">
<input
type={showNew ? "text" : "password"}
placeholder="New password"
className="
w-full bg-[#15162e] text-white
rounded-lg ps-10 pe-10 py-3 outline-none
border border-white/10 focus:border-blue-400
"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
/>
<span className="absolute start-3 top-1/2 -translate-y-1/2">
<IconLockDots fill />
</span>
{/* 🔥 FIXED EYE ICON — NO MOVING */}
<button
type="button"
onClick={() => setShowNew(!showNew)}
className="absolute end-3 top-1/2 -translate-y-1/2 text-gray-400"
>
<span className="flex items-center justify-center w-5 h-5">
{showNew ? (
<EyeOff size={18} strokeWidth={1.5} />
) : (
<Eye size={18} strokeWidth={1.5} />
)}
</span>
</button>
</div>
<button
type="submit"
disabled={loading}
className="
w-full py-3 rounded-xl text-lg font-semibold text-white
bg-gradient-to-r from-blue-600 to-pink-500
shadow-lg transition-all
hover:opacity-90 hover:scale-[1.02]
disabled:opacity-60
"
>
{loading ? 'Updating...' : 'Change Password'}
</button>
</form>
<p className="text-center text-sm text-gray-400 mt-6">
Back to{" "}
<Link href="/login" className="text-blue-400 hover:underline">
Login
</Link>
</p>
</div>
);
};
export default ComponentsAuthChangePasswordForm;

View File

@ -0,0 +1,123 @@
'use client';
import { useState } from 'react';
import axios from 'axios';
import Link from 'next/link';
import IconMail from '@/components/icon/icon-mail';
import { ApiServerBaseUrl } from '@/utils/baseurl.utils';
export default function ForgotPasswordForm() {
const [email, setEmail] = useState('');
const [loading, setLoading] = useState(false);
const [msg, setMsg] = useState('');
const [err, setErr] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setErr('');
setMsg('');
if (!email) {
setErr('Email is required');
return;
}
setLoading(true);
try {
await axios.post(`${ApiServerBaseUrl}/auth/forgot-password`, { email });
setMsg('Password reset link sent to your email');
} catch (error: any) {
setErr(error.response?.data?.error || 'Something went wrong');
} finally {
setLoading(false);
}
};
return (
<div className="relative group w-full max-w-[420px]">
{/* ✅ Hover Gradient Overlay */}
<div
className="
absolute inset-0 rounded-3xl
bg-gradient-to-br from-blue-700/20 via-transparent to-pink-600/20
opacity-0 group-hover:opacity-100
transition-opacity duration-500
"
/>
{/* ✅ Card */}
<div className="relative z-10 bg-[#0f1022] rounded-3xl p-8 shadow-2xl border border-white/10">
{/* ✅ Gradient Heading */}
<h1
className="
text-3xl font-extrabold text-center mb-2
bg-gradient-to-r from-blue-500 to-pink-500
bg-clip-text text-transparent
"
>
Forgot Password
</h1>
<p className="text-gray-400 text-sm text-center mb-6">
Enter your email to receive a reset link
</p>
{err && (
<p className="text-red-500 text-sm text-center mb-3">{err}</p>
)}
{msg && (
<p className="text-green-500 text-sm text-center mb-3">{msg}</p>
)}
<form onSubmit={handleSubmit} className="space-y-5">
{/* ✅ Email Input (Dark on all devices) */}
<div className="relative">
<input
type="email"
placeholder="Email address"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="
w-full bg-[#15162e] text-white
rounded-lg ps-10 py-3 outline-none
border border-white/10
focus:border-blue-500
placeholder-gray-400
"
/>
<span className="absolute start-3 top-1/2 -translate-y-1/2 text-gray-300">
<IconMail fill />
</span>
</div>
{/* ✅ Button with SAME Dashboard Gradient Feel */}
<button
type="submit"
disabled={loading}
className="
relative w-full py-3 rounded-xl
font-semibold text-white
bg-gradient-to-r from-blue-600 to-pink-500
shadow-lg transition-all
hover:opacity-95 hover:scale-[1.02]
disabled:opacity-60
"
>
{loading ? 'Sending...' : 'Send Reset Link'}
</button>
</form>
{/* ✅ Footer */}
<p className="text-center text-sm text-gray-400 mt-6">
Remembered your password?{' '}
<Link href="/login" className="text-blue-400 hover:underline">
Login
</Link>
</p>
</div>
</div>
);
}

View File

@ -0,0 +1,137 @@
'use client';
import IconLockDots from '@/components/icon/icon-lock-dots';
import IconMail from '@/components/icon/icon-mail';
import { useRouter } from 'next/navigation';
import React, {useState} from 'react';
import axios from 'axios';
import Link from 'next/link';
import { Eye, EyeOff } from "lucide-react";
import { ApiServerBaseUrl } from '@/utils/baseurl.utils';
const ComponentsAuthLoginForm = () => {
const router = useRouter();
// ✅ Form state as object
const [formData, setFormData] = useState({
email: '',
password: '',
subscribe: false,
});
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [err, setErr] = useState<string | null>(null);
const [msg, setMsg] = useState<string | null>(null);
const [showPassword, setShowPassword] = useState(false);
// ✅ Handle input changes
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value, type, checked } = e.target;
setFormData((prev) => ({
...prev,
[name]: type === 'checkbox' ? checked : value,
}));
};
//submit form
const submitForm = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setErr(null);
setMsg(null);
if (!formData.email || !formData.password) {
setErr('Please enter email and password.');
return;
}
setLoading(true);
try {
const res = await axios.post(`${ApiServerBaseUrl}/auth/login`, formData);
// Assuming backend returns token
const data = res.data;
console.log('Login success:', data);
localStorage.setItem('token', data.token);
router.push('/');
} catch (err: any) {
if (err.response) {
setErr(err.response.data.error || 'Login failed');
} else {
setErr(err.message);
}
} finally {
setLoading(false);
}
};
return (
<form className="space-y-5 dark:text-white" onSubmit={submitForm}>
{/* Alerts */}
{err && <div className="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700">{err}</div>}
{msg && <div className="rounded-md border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700">{msg}</div>}
<div>
<label htmlFor="Email">Email</label>
<div className="relative text-white-dark">
<input
id="Email"
name="email"
type="email"
placeholder="Enter Email"
className="form-input ps-10 placeholder:text-white-dark"
value={formData.email}
onChange={handleChange}
required />
<span className="absolute start-4 top-1/2 -translate-y-1/2">
<IconMail fill={true} />
</span>
</div>
</div>
<div>
<label htmlFor="Password">Password</label>
<div className="relative text-white-dark">
<input id="Password"
name="password"
type={showPassword ? "text" : "password"}
placeholder="Enter Password"
className="form-input ps-10 placeholder:text-white-dark"
value={formData.password}
onChange={handleChange}
required
/>
<span className="absolute start-4 top-1/2 -translate-y-1/2">
<IconLockDots fill={true} />
</span>
{/* 👁️ Eye toggle (right) */}
<button
type="button"
className="absolute end-3 top-1/2 -translate-y-1/2 text-gray-400"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div>
</div>
<div className="text-end mt-4">
<Link href="/forgot-password" className="text-sm text-blue-500 hover:underline">
Forgot Password?
</Link>
</div>
<div>
<label className="flex cursor-pointer items-center">
<input type="checkbox"
checked={formData.subscribe}
onChange={handleChange}
className="form-checkbox bg-white dark:bg-black" />
<span className="text-white-dark">Subscribe to weekly newsletter</span>
</label>
</div>
<button type="submit" className="btn btn-gradient !mt-6 w-full border-0 uppercase shadow-[0_10px_20px_-10px_rgba(67,97,238,0.44)]">
Sign in
</button>
</form>
);
};
export default ComponentsAuthLoginForm;

View File

@ -0,0 +1,185 @@
'use client';
import IconLockDots from '@/components/icon/icon-lock-dots';
import IconMail from '@/components/icon/icon-mail';
import { useRouter } from 'next/navigation';
import React, { useState } from 'react';
import axios from 'axios';
import Link from 'next/link';
import { Eye, EyeOff } from 'lucide-react';
import { FcGoogle } from 'react-icons/fc';
import { ApiServerBaseUrl } from '@/utils/baseurl.utils';
const ComponentsAuthLoginForm = () => {
const router = useRouter();
const [formData, setFormData] = useState({
email: '',
password: '',
});
const [loading, setLoading] = useState(false);
const [err, setErr] = useState<string | null>(null);
const [showPassword, setShowPassword] = useState(false);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
};
const submitForm = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setErr(null);
if (!formData.email || !formData.password) {
setErr('Please enter email and password.');
return;
}
setLoading(true);
try {
const res = await axios.post(`${ApiServerBaseUrl}/auth/login`, formData);
const data = res.data;
localStorage.setItem('token', data.token);
if (data?.user) {
localStorage.setItem("userDetails", JSON.stringify(data?.user))
localStorage.setItem('user_email', data?.user?.email);
localStorage.setItem('userId', data?.user?.id);
}
if (data?.payment) {
localStorage.setItem('paymentDetails', JSON.stringify(data.payment));
data?.payment?.sessionId &&
localStorage.setItem('payment_session', data.payment.sessionId);
}
router.push('/');
} catch (err: any) {
setErr(err.response?.data?.error || 'Login failed');
} finally {
setLoading(false);
}
};
return (
<form className="dark:text-white" onSubmit={submitForm}>
{err && (
<div className="mb-4 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700">
{err}
</div>
)}
<div className="relative z-10 w-full min-w-[500px] p-8 glass-card rounded-3xl">
{/* Logo */}
<div className="flex flex-col items-center mb-6">
<img
src="/assets/images/logo_sb.png"
alt="Logo"
className="h-[120px] w-[120px] neon-logo"
/>
<h1 className="text-xl text-white font-medium mt-3">
Sign in to SocialBuddy
</h1>
</div>
{/* Email */}
<div className="mb-4 relative">
<input
name="email"
type="email"
placeholder="Email Address"
className="w-full ps-10 py-3 bg-[rgba(7,13,30,0.7)]
border border-white/15 rounded-lg text-white
focus:outline-none focus:border-white/30"
value={formData.email}
onChange={handleChange}
/>
<span className="absolute start-4 top-1/2 -translate-y-1/2 text-white">
<IconMail fill />
</span>
</div>
{/* Password */}
<div className="mb-2 relative">
<input
name="password"
type={showPassword ? 'text' : 'password'}
placeholder="Password"
className="w-full ps-10 py-3 bg-[rgba(7,13,30,0.7)]
border border-white/15 rounded-lg text-white
focus:outline-none focus:border-white/30"
value={formData.password}
onChange={handleChange}
/>
<span className="absolute start-4 top-1/2 -translate-y-1/2 text-white">
<IconLockDots fill />
</span>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-4 top-1/2 -translate-y-1/2 text-gray-400"
>
{showPassword ? <EyeOff size={20} /> : <Eye size={20} />}
</button>
</div>
{/* Forgot password */}
<div className="flex justify-end mb-6">
<Link href="/forgot-password" className="text-sm text-blue-400 hover:underline">
Forgot Password?
</Link>
</div>
{/* Login button */}
<button
disabled={loading}
className="
w-full py-3 rounded-xl text-lg font-semibold text-white
bg-gradient-to-r from-blue-600 to-pink-500
shadow-lg transition-all
hover:opacity-90 hover:scale-[1.02]
disabled:opacity-60
mb-3
"
>
{loading ? 'Logging in...' : 'LOG IN'}
</button>
{/* Divider */}
<div className="relative text-center my-3">
<span className="absolute inset-x-0 top-1/2 h-px bg-white/20" />
<span className="relative bg-[#0b0d1c] px-3 text-xs uppercase text-gray-300">
or
</span>
</div>
{/* Google Sign-in */}
<div className="flex justify-center px-8 mb-4">
<Link
href="https://ebay.backend.data4autos.com/api/auth/google/"
className="flex items-center gap-3 px-8 py-3 rounded-lg
border border-white/20 text-white
hover:bg-white/10 transition active:scale-[0.98]"
>
<FcGoogle size={20} />
<span className="text-sm">Continue with Google</span>
</Link>
</div>
{/* Footer */}
<div className="text-center text-sm text-gray-400">
Don&rsquo;t have an account?{' '}
<Link href="/signup" className="text-blue-400 hover:underline">
SIGN UP
</Link>
</div>
</div>
</form>
);
};
export default ComponentsAuthLoginForm;

View File

@ -0,0 +1,171 @@
'use client';
import IconLockDots from '@/components/icon/icon-lock-dots';
import IconMail from '@/components/icon/icon-mail';
import IconUser from '@/components/icon/icon-user';
import { useRouter } from 'next/navigation';
import { Eye, EyeOff } from 'lucide-react';
import axios from 'axios';
import React, { useState } from 'react';
import { ApiServerBaseUrl } from '@/utils/baseurl.utils';
const DARK_INPUT =
'w-full bg-[rgba(7,13,30,0.75)] text-white rounded-lg px-10 py-3 outline-none border border-white/10 placeholder:text-gray-400 focus:border-primary focus:ring-1 focus:ring-primary';
const ComponentsAuthRegisterForm = () => {
const router = useRouter();
const [form, setForm] = useState({
name: '',
email: '',
password: '',
mobileNumber: '',
});
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [showPassword, setShowPassword] = useState(false);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setForm((prev) => ({ ...prev, [name]: value }));
};
const submitForm = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setError(null);
setLoading(true);
try {
await axios.post(`${ApiServerBaseUrl}/auth/signup`, form);
router.push('/login');
} catch (err: any) {
setError(err.response?.data?.error || 'Signup failed');
} finally {
setLoading(false);
}
};
return (
<form className="space-y-5 text-white" onSubmit={submitForm}>
{error && <p className="text-red-500 text-sm text-center">{error}</p>}
{/* Name */}
<div className="relative">
<input
name="name"
type="text"
placeholder="Full Name"
className={DARK_INPUT}
value={form.name}
onChange={handleChange}
/>
<span className="absolute start-3 top-1/2 -translate-y-1/2">
<IconUser fill />
</span>
</div>
{/* Email */}
<div className="relative">
<input
name="email"
type="email"
placeholder="Email Address"
className={DARK_INPUT}
value={form.email}
onChange={handleChange}
/>
<span className="absolute start-3 top-1/2 -translate-y-1/2">
<IconMail fill />
</span>
</div>
{/* Password */}
<div className="relative">
<input
name="password"
type={showPassword ? 'text' : 'password'}
placeholder="Password"
className={DARK_INPUT}
value={form.password}
onChange={handleChange}
/>
<span className="absolute start-3 top-1/2 -translate-y-1/2">
<IconLockDots fill />
</span>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute end-3 top-1/2 -translate-y-1/2 text-gray-400"
>
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div>
{/* Mobile */}
<div className="relative">
<input
name="mobileNumber"
type="text"
placeholder="Mobile Number"
className={DARK_INPUT}
value={form.mobileNumber}
onChange={handleChange}
/>
<span className="absolute start-3 top-1/2 -translate-y-1/2">📞</span>
</div>
{/* ✅ Gradient SIGN UP Button */}
<button
type="submit"
disabled={loading}
className="
w-full bg-gradient-to-r from-blue-600 to-pink-500
text-white py-3 rounded-xl font-semibold shadow-lg
transition-all hover:opacity-90 hover:scale-[1.01]
disabled:opacity-50 disabled:cursor-not-allowed
"
>
{loading ? 'Signing up...' : 'SIGN UP'}
</button>
{/* Divider */}
<div className="text-center relative my-6">
<span className="absolute inset-x-0 top-1/2 h-px bg-white/20"></span>
<span className="relative bg-[#0b0f1f] px-3 text-xs uppercase text-gray-400">
or
</span>
</div>
{/* Google */}
{/* <a
href={`${ApiServerBaseUrl}/auth/google`}
className="flex items-center justify-center gap-3 w-full py-3 rounded-lg
border border-gray-600 hover:bg-white/10 transition"
>
<svg className="w-5 h-5" viewBox="0 0 24 24">
<path fill="#4285F4" d="M22.56 12.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22z"/>
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
</svg>
Sign up with Google
</a>*/}
{/* Facebook */}
{/*<a
href={`${ApiServerBaseUrl}/auth/facebook`}
className="flex items-center justify-center gap-3 w-full py-3 rounded-lg
border border-gray-600 hover:bg-blue-600/20 transition"
>
<svg className="w-5 h-5" viewBox="0 0 24 24" fill="#1877F2">
<path d="M22 12A10 10 0 1 0 10.67 21.87v-6.99H7.9V12h2.77V9.8c0-2.74 1.63-4.26 4.13-4.26 1.2 0 2.46.22 2.46.22v2.7h-1.39c-1.37 0-1.8.85-1.8 1.72V12h3.07l-.49 2.88h-2.58v6.99A10 10 0 0 0 22 12z"/>
</svg>
Sign up with Facebook
</a> */}
</form>
);
};
export default ComponentsAuthRegisterForm;

View File

@ -0,0 +1,106 @@
'use client';
import IconMail from '@/components/icon/icon-mail';
import { useRouter } from 'next/navigation';
import React, { useState } from 'react';
import axios from 'axios';
import { ApiServerBaseUrl } from '@/utils/baseurl.utils';
interface ResetPasswordFormValues {
email: string;
}
const ComponentsAuthResetPasswordForm: React.FC = () => {
const router = useRouter();
const [formData, setFormData] = useState<ResetPasswordFormValues>({ email: '' });
const [loading, setLoading] = useState(false);
const [err, setErr] = useState<string | null>(null);
const [msg, setMsg] = useState<string | null>(null);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData({ email: e.target.value });
};
const submitForm = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setErr(null);
setMsg(null);
if (!formData.email) {
setErr('Please enter your email.');
return;
}
setLoading(true);
try {
const res = await axios.post(`${ApiServerBaseUrl}/auth/forgot-password`, {
email: formData.email,
});
setMsg(res.data.message || 'Password reset link sent to your email.');
setFormData({ email: '' });
// Redirect after 2 seconds
setTimeout(() => {
router.push('/login');
}, 2000);
} catch (error: any) {
setErr(error.response?.data?.error || 'Something went wrong.');
} finally {
setLoading(false);
}
};
return (
<form className="space-y-5" onSubmit={submitForm}>
{err && (
<div className="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700">
{err}
</div>
)}
{msg && (
<div className="rounded-md border border-green-200 bg-green-50 px-3 py-2 text-sm text-green-700">
{msg} <br /> Redirecting to login...
</div>
)}
<div>
<label htmlFor="Email" className="dark:text-white">
Email
</label>
<div className="relative text-white-dark">
<input
id="Email"
type="email"
placeholder="Enter Email"
className="form-input ps-10 placeholder:text-white-dark"
value={formData.email}
onChange={handleChange}
required
/>
<span className="absolute start-4 top-1/2 -translate-y-1/2">
<IconMail fill />
</span>
</div>
</div>
{/* UPDATED BUTTON WITH LOGIN-PAGE STYLE */}
<button
type="submit"
disabled={loading}
className="w-full
bg-[radial-gradient(circle,#1d8be0_0%,#6cb655_100%)]
hover:opacity-95 transition-all duration-300
hover:shadow-[0_0_30px_rgba(255,255,255,0.20)]
hover:scale-[1.015]
text-white text-lg font-medium py-3 rounded-lg mt-6"
>
{loading ? 'Sending...' : 'RECOVER'}
</button>
</form>
);
};
export default ComponentsAuthResetPasswordForm;

View File

@ -0,0 +1,166 @@
'use client';
import IconLockDots from '@/components/icon/icon-lock-dots';
import { useRouter } from 'next/navigation';
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import { Eye, EyeOff } from "lucide-react";
import { ApiServerBaseUrl } from '@/utils/baseurl.utils';
const ComponentsAuthUnlockForm = () => {
const router = useRouter();
const [currentPassword, setCurrentPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [success, setSuccess] = useState("");
const [userEmail, setUserEmail] = useState<string | null>(null);
const [redirecting, setRedirecting] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [showPassword1, setShowPassword1] = useState(false);
interface ChangePasswordResponse {
message?: string;
error?: string;
}
// ✅ Load user email & verify auth
useEffect(() => {
// 🧠 Prevent running this effect after password update redirect starts
if (redirecting) return;
const token = localStorage.getItem("token");
const email = localStorage.getItem("d4a_email");
if (!token) {
setError("You are not logged in. Redirecting to login...");
setTimeout(() => router.push("/login"), 1500);
} else {
setUserEmail(email);
}
}, [router, redirecting]);
const submitForm = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
setSuccess("");
const token = localStorage.getItem("token");
const email = localStorage.getItem("d4a_email");
if (!token || !email) {
setError("Session expired or missing. Please log in again.");
return;
}
if (!currentPassword || !newPassword) {
setError("Please fill all fields.");
return;
}
setLoading(true);
try {
const res = await axios.post<ChangePasswordResponse>(
`${ApiServerBaseUrl}/auth/change-password`,
{ email, currentPassword, newPassword },
{
headers: { Authorization: `Bearer ${token}` },
}
);
setSuccess(res.data.message || "Password updated successfully!");
console.log("Password updated successfully, redirecting to login in 2 seconds...");
setRedirecting(true); // ✅ prevents useEffect from running again
setTimeout(() => {
localStorage.removeItem("token");
router.push("/login");
}, 1200);
} catch (err: any) {
console.error("Change Password Error:", err);
const msg =
err.response?.data?.error ||
err.response?.data?.message ||
err.message ||
"Something went wrong. Please try again.";
if (msg.toLowerCase().includes("expired")) {
setError("Session expired. Please log in again.");
setTimeout(() => router.push("/login"), 1500);
} else {
setError(msg);
}
} finally {
setLoading(false);
}
};
return (
<form className="space-y-5" onSubmit={submitForm}>
<div>
<label htmlFor="Password" className="dark:text-white">
Current Password
</label>
<div className="relative text-white-dark">
<input
id="currentPassword"
type={showPassword ? "text" : "password"}
placeholder="Enter Current Password"
className="form-input ps-10 placeholder:text-white-dark"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
required />
<span className="absolute start-4 top-1/2 -translate-y-1/2">
<IconLockDots fill={true} />
</span>
{/* 👁️ Eye toggle (right) */}
<button
type="button"
className="absolute end-3 top-1/2 -translate-y-1/2 text-gray-400"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div>
</div>
{/* 🔑 New Password */}
<div>
<label htmlFor="newPassword" className="dark:text-white">
New Password
</label>
<div className="relative text-white-dark">
<input
id="newPassword"
type={showPassword1 ? "text" : "password"}
placeholder="Enter New Password"
className="form-input ps-10 placeholder:text-white-dark"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
required
/>
<span className="absolute start-4 top-1/2 -translate-y-1/2">
<IconLockDots fill={true} />
</span>
{/* 👁️ Eye toggle (right) */}
<button
type="button"
className="absolute end-3 top-1/2 -translate-y-1/2 text-gray-400"
onClick={() => setShowPassword1(!showPassword1)}
>
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div>
</div>
{error && <p className="text-red-500 text-sm">{error}</p>}
{success && <p className="text-green-500 text-sm">{success}</p>}
<button type="submit"
disabled={loading}
className="btn btn-gradient !mt-6 w-full border-0 uppercase shadow-[0_10px_20px_-10px_rgba(67,97,238,0.44)]">
{loading ? "UPDATING…" : "CHANGE PASSWORD"}
</button>
</form>
);
};
export default ComponentsAuthUnlockForm;

57
components/dropdown.tsx Normal file
View File

@ -0,0 +1,57 @@
'use client';
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
import { usePopper } from 'react-popper';
const Dropdown = (props: any, forwardedRef: any) => {
const [visibility, setVisibility] = useState<any>(false);
const referenceRef = useRef<any>();
const popperRef = useRef<any>();
const { styles, attributes } = usePopper(referenceRef.current, popperRef.current, {
placement: props.placement || 'bottom-end',
modifiers: [
{
name: 'offset',
options: {
offset: props.offset || [0],
},
},
],
});
const handleDocumentClick = (event: any) => {
if (referenceRef.current.contains(event.target) || popperRef.current.contains(event.target)) {
return;
}
setVisibility(false);
};
useEffect(() => {
document.addEventListener('mousedown', handleDocumentClick);
return () => {
document.removeEventListener('mousedown', handleDocumentClick);
};
}, []);
useImperativeHandle(forwardedRef, () => ({
close() {
setVisibility(false);
},
}));
return (
<>
<button ref={referenceRef} type="button" className={props.btnClassName} onClick={() => setVisibility(!visibility)}>
{props.button}
</button>
<div ref={popperRef} style={styles.popper} {...attributes.popper} className="z-50" onClick={() => setVisibility(!visibility)}>
{visibility && props.children}
</div>
</>
);
};
export default forwardRef(Dropdown);

View File

@ -0,0 +1,25 @@
import React from 'react';
interface IconKeyProps {
className?: string;
fill?: boolean;
}
const IconKey: React.FC<IconKeyProps> = ({ className = 'w-5 h-5', fill = false }) => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill={fill ? 'currentColor' : 'none'}
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
className={className}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M15 7a4 4 0 1 1-8 0 4 4 0 0 1 8 0zM19 11l-5 5m0 0v4h4l5-5-4-4z"
/>
</svg>
);
export default IconKey;

View File

@ -0,0 +1,43 @@
import { FC } from 'react';
interface IconAirplayProps {
className?: string;
fill?: boolean;
}
const IconAirplay: FC<IconAirplayProps> = ({ className, fill = false }) => {
return (
<>
{!fill ? (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
opacity="0.5"
d="M7.142 18.9706C5.18539 18.8995 3.99998 18.6568 3.17157 17.8284C2 16.6569 2 14.7712 2 11C2 7.22876 2 5.34315 3.17157 4.17157C4.34315 3 6.22876 3 10 3H14C17.7712 3 19.6569 3 20.8284 4.17157C22 5.34315 22 7.22876 22 11C22 14.7712 22 16.6569 20.8284 17.8284C20.0203 18.6366 18.8723 18.8873 17 18.965"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
<path
d="M9.94955 16.0503C10.8806 15.1192 11.3461 14.6537 11.9209 14.6234C11.9735 14.6206 12.0261 14.6206 12.0787 14.6234C12.6535 14.6537 13.119 15.1192 14.0501 16.0503C16.0759 18.0761 17.0888 19.089 16.8053 19.963C16.7809 20.0381 16.7506 20.1112 16.7147 20.1815C16.2973 21 14.8648 21 11.9998 21C9.13482 21 7.70233 21 7.28489 20.1815C7.249 20.1112 7.21873 20.0381 7.19436 19.963C6.91078 19.089 7.92371 18.0761 9.94955 16.0503Z"
stroke="currentColor"
strokeWidth="1.5"
/>
</svg>
) : (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
opacity="0.5"
d="M14 3H10C6.22876 3 4.34315 3 3.17157 4.17157C2 5.34315 2 7.22876 2 11C2 14.7712 2 16.6569 3.17157 17.8284C4.34315 19 6.22876 19 10 19H14C17.7712 19 19.6569 19 20.8284 17.8284C22 16.6569 22 14.7712 22 11C22 7.22876 22 5.34315 20.8284 4.17157C19.6569 3 17.7712 3 14 3Z"
fill="currentColor"
/>
<path
d="M9.94955 16.0503C10.8806 15.1192 11.3461 14.6537 11.9209 14.6234C11.9735 14.6206 12.0261 14.6206 12.0787 14.6234C12.6535 14.6537 13.119 15.1192 14.0501 16.0503C16.0759 18.0761 17.0888 19.089 16.8053 19.963C16.7809 20.0381 16.7506 20.1112 16.7147 20.1815C16.2973 21 14.8648 21 11.9998 21C9.13482 21 7.70233 21 7.28489 20.1815C7.249 20.1112 7.21873 20.0381 7.19436 19.963C6.91078 19.089 7.92371 18.0761 9.94955 16.0503Z"
fill="currentColor"
/>
</svg>
)}
</>
);
};
export default IconAirplay;

View File

@ -0,0 +1,31 @@
import { FC } from 'react';
interface IconArchiveProps {
className?: string;
}
const IconArchive: FC<IconArchiveProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
d="M9 12C9 11.5341 9 11.3011 9.07612 11.1173C9.17761 10.8723 9.37229 10.6776 9.61732 10.5761C9.80109 10.5 10.0341 10.5 10.5 10.5H13.5C13.9659 10.5 14.1989 10.5 14.3827 10.5761C14.6277 10.6776 14.8224 10.8723 14.9239 11.1173C15 11.3011 15 11.5341 15 12C15 12.4659 15 12.6989 14.9239 12.8827C14.8224 13.1277 14.6277 13.3224 14.3827 13.4239C14.1989 13.5 13.9659 13.5 13.5 13.5H10.5C10.0341 13.5 9.80109 13.5 9.61732 13.4239C9.37229 13.3224 9.17761 13.1277 9.07612 12.8827C9 12.6989 9 12.4659 9 12Z"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.5"
d="M20.5 7V13C20.5 16.7712 20.5 18.6569 19.3284 19.8284C18.1569 21 16.2712 21 12.5 21H11.5C7.72876 21 5.84315 21 4.67157 19.8284C3.5 18.6569 3.5 16.7712 3.5 13V7"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
<path
d="M2 5C2 4.05719 2 3.58579 2.29289 3.29289C2.58579 3 3.05719 3 4 3H20C20.9428 3 21.4142 3 21.7071 3.29289C22 3.58579 22 4.05719 22 5C22 5.94281 22 6.41421 21.7071 6.70711C21.4142 7 20.9428 7 20 7H4C3.05719 7 2.58579 7 2.29289 6.70711C2 6.41421 2 5.94281 2 5Z"
stroke="currentColor"
strokeWidth="1.5"
/>
</svg>
);
};
export default IconArchive;

View File

@ -0,0 +1,16 @@
import { FC } from 'react';
interface IconArrowBackwardProps {
className?: string;
}
const IconArrowBackward: FC<IconArrowBackwardProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path d="M9.5 7L4.5 12L9.5 17" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path opacity="0.5" d="M4.5 12L14.5 12C16.1667 12 19.5 13 19.5 17" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
);
};
export default IconArrowBackward;

View File

@ -0,0 +1,16 @@
import { FC } from 'react';
interface IconArrowForwardProps {
className?: string;
}
const IconArrowForward: FC<IconArrowForwardProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path d="M14.5 7L19.5 12L14.5 17" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path opacity="0.5" d="M19.5 12L9.5 12C7.83333 12 4.5 13 4.5 17" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
);
};
export default IconArrowForward;

View File

@ -0,0 +1,15 @@
import { FC } from 'react';
interface IconArrowLeftProps {
className?: string;
}
const IconArrowLeft: FC<IconArrowLeftProps> = ({ className }) => {
return (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path d="M4 12H20M20 12L14 6M20 12L14 18" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
};
export default IconArrowLeft;

View File

@ -0,0 +1,18 @@
import { FC } from 'react';
interface IconArrowWaveLeftUpProps {
className?: string;
}
const IconArrowWaveLeftUp: FC<IconArrowWaveLeftUpProps> = ({ className }) => {
return (
<svg width="111" height="22" viewBox="0 0 116 22" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
d="M0.645796 11.44C0.215273 11.6829 0.0631375 12.2287 0.305991 12.6593C0.548845 13.0898 1.09472 13.2419 1.52525 12.9991L0.645796 11.44ZM49.0622 20.4639L48.9765 19.5731L48.9765 19.5731L49.0622 20.4639ZM115.315 2.33429L105.013 3.14964L110.87 11.6641L115.315 2.33429ZM1.52525 12.9991C10.3971 7.99452 17.8696 10.3011 25.3913 13.8345C29.1125 15.5825 32.9505 17.6894 36.8117 19.2153C40.7121 20.7566 44.7862 21.7747 49.148 21.3548L48.9765 19.5731C45.0058 19.9553 41.2324 19.0375 37.4695 17.5505C33.6675 16.0481 30.0265 14.0342 26.1524 12.2143C18.4834 8.61181 10.3 5.99417 0.645796 11.44L1.52525 12.9991ZM49.148 21.3548C52.4593 21.0362 54.7308 19.6545 56.4362 17.7498C58.1039 15.8872 59.2195 13.5306 60.2695 11.3266C61.3434 9.07217 62.3508 6.97234 63.8065 5.35233C65.2231 3.77575 67.0736 2.6484 69.8869 2.40495L69.7326 0.62162C66.4361 0.906877 64.1742 2.26491 62.475 4.15595C60.8148 6.00356 59.703 8.35359 58.6534 10.5568C57.5799 12.8105 56.5678 14.9194 55.1027 16.5557C53.6753 18.1499 51.809 19.3005 48.9765 19.5731L49.148 21.3548ZM69.8869 2.40495C72.2392 2.2014 75.0889 2.61953 78.2858 3.35001C81.4816 4.08027 84.9116 5.09374 88.4614 6.04603C91.9873 6.99189 95.6026 7.86868 99.0694 8.28693C102.533 8.70483 105.908 8.67299 108.936 7.75734L108.418 6.04396C105.72 6.85988 102.621 6.91239 99.2838 6.50981C95.9496 6.10757 92.4363 5.25904 88.9252 4.31715C85.4382 3.38169 81.9229 2.34497 78.6845 1.60499C75.4471 0.865243 72.3735 0.393097 69.7326 0.62162L69.8869 2.40495Z"
fill="currentColor"
/>
</svg>
);
};
export default IconArrowWaveLeftUp;

View File

@ -0,0 +1,21 @@
import { FC } from 'react';
interface IconAtProps {
className?: string;
}
const IconAt: FC<IconAtProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
d="M12 18C8.68629 18 6 15.3137 6 12C6 8.68629 8.68629 6 12 6C15.3137 6 18 8.68629 18 12C18 12.7215 17.8726 13.4133 17.6392 14.054C17.5551 14.285 17.4075 14.4861 17.2268 14.6527L17.1463 14.727C16.591 15.2392 15.7573 15.3049 15.1288 14.8858C14.6735 14.5823 14.4 14.0713 14.4 13.5241V12M14.4 12C14.4 13.3255 13.3255 14.4 12 14.4C10.6745 14.4 9.6 13.3255 9.6 12C9.6 10.6745 10.6745 9.6 12 9.6C13.3255 9.6 14.4 10.6745 14.4 12Z"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
<path opacity="0.5" d="M2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12Z" stroke="currentColor" strokeWidth="1.5" />
</svg>
);
};
export default IconAt;

View File

@ -0,0 +1,28 @@
import { FC } from 'react';
interface IconAwardProps {
className?: string;
}
const IconAward: FC<IconAwardProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
opacity="0.5"
d="M11.1459 7.02251C11.5259 6.34084 11.7159 6 12 6C12.2841 6 12.4741 6.34084 12.8541 7.02251L12.9524 7.19887C13.0603 7.39258 13.1143 7.48944 13.1985 7.55334C13.2827 7.61725 13.3875 7.64097 13.5972 7.68841L13.7881 7.73161C14.526 7.89857 14.895 7.98205 14.9828 8.26432C15.0706 8.54659 14.819 8.84072 14.316 9.42898L14.1858 9.58117C14.0429 9.74833 13.9714 9.83191 13.9392 9.93531C13.9071 10.0387 13.9179 10.1502 13.9395 10.3733L13.9592 10.5763C14.0352 11.3612 14.0733 11.7536 13.8435 11.9281C13.6136 12.1025 13.2682 11.9435 12.5773 11.6254L12.3986 11.5431C12.2022 11.4527 12.1041 11.4075 12 11.4075C11.8959 11.4075 11.7978 11.4527 11.6014 11.5431L11.4227 11.6254C10.7318 11.9435 10.3864 12.1025 10.1565 11.9281C9.92674 11.7536 9.96476 11.3612 10.0408 10.5763L10.0605 10.3733C10.0821 10.1502 10.0929 10.0387 10.0608 9.93531C10.0286 9.83191 9.95713 9.74833 9.81418 9.58117L9.68403 9.42898C9.18097 8.84072 8.92945 8.54659 9.01723 8.26432C9.10501 7.98205 9.47396 7.89857 10.2119 7.73161L10.4028 7.68841C10.6125 7.64097 10.7173 7.61725 10.8015 7.55334C10.8857 7.48944 10.9397 7.39258 11.0476 7.19887L11.1459 7.02251Z"
stroke="currentColor"
strokeWidth="1.5"
/>
<path d="M19 9C19 12.866 15.866 16 12 16C8.13401 16 5 12.866 5 9C5 5.13401 8.13401 2 12 2C15.866 2 19 5.13401 19 9Z" stroke="currentColor" strokeWidth="1.5" />
<path
opacity="0.5"
d="M7.35111 15L6.71424 17.323C6.0859 19.6148 5.77173 20.7607 6.19097 21.3881C6.3379 21.6079 6.535 21.7844 6.76372 21.9008C7.41635 22.2331 8.42401 21.7081 10.4393 20.658C11.1099 20.3086 11.4452 20.1339 11.8014 20.0959C11.9335 20.0818 12.0665 20.0818 12.1986 20.0959C12.5548 20.1339 12.8901 20.3086 13.5607 20.658C15.576 21.7081 16.5837 22.2331 17.2363 21.9008C17.465 21.7844 17.6621 21.6079 17.809 21.3881C18.2283 20.7607 17.9141 19.6148 17.2858 17.323L16.6489 15"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
</svg>
);
};
export default IconAward;

View File

@ -0,0 +1,18 @@
import { FC } from 'react';
interface IconBarChartProps {
className?: string;
}
const IconBarChart: FC<IconBarChartProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path d="M22 22H2" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path opacity="0.5" d="M21 22V14.5C21 13.6716 20.3284 13 19.5 13H16.5C15.6716 13 15 13.6716 15 14.5V22" stroke="currentColor" strokeWidth="1.5" />
<path d="M15 22V5C15 3.58579 15 2.87868 14.5607 2.43934C14.1213 2 13.4142 2 12 2C10.5858 2 9.87868 2 9.43934 2.43934C9 2.87868 9 3.58579 9 5V22" stroke="currentColor" strokeWidth="1.5" />
<path opacity="0.5" d="M9 22V9.5C9 8.67157 8.32843 8 7.5 8H4.5C3.67157 8 3 8.67157 3 9.5V22" stroke="currentColor" strokeWidth="1.5" />
</svg>
);
};
export default IconBarChart;

View File

@ -0,0 +1,21 @@
import { FC } from 'react';
interface IconBellBingProps {
className?: string;
}
const IconBellBing: FC<IconBellBingProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
d="M19.0001 9.7041V9C19.0001 5.13401 15.8661 2 12.0001 2C8.13407 2 5.00006 5.13401 5.00006 9V9.7041C5.00006 10.5491 4.74995 11.3752 4.28123 12.0783L3.13263 13.8012C2.08349 15.3749 2.88442 17.5139 4.70913 18.0116C9.48258 19.3134 14.5175 19.3134 19.291 18.0116C21.1157 17.5139 21.9166 15.3749 20.8675 13.8012L19.7189 12.0783C19.2502 11.3752 19.0001 10.5491 19.0001 9.7041Z"
stroke="currentColor"
strokeWidth="1.5"
/>
<path d="M7.5 19C8.15503 20.7478 9.92246 22 12 22C14.0775 22 15.845 20.7478 16.5 19" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path d="M12 6V10" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
);
};
export default IconBellBing;

View File

@ -0,0 +1,20 @@
import { FC } from 'react';
interface IconBellProps {
className?: string;
}
const IconBell: FC<IconBellProps> = ({ className }) => {
return (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
d="M19.0001 9.7041V9C19.0001 5.13401 15.8661 2 12.0001 2C8.13407 2 5.00006 5.13401 5.00006 9V9.7041C5.00006 10.5491 4.74995 11.3752 4.28123 12.0783L3.13263 13.8012C2.08349 15.3749 2.88442 17.5139 4.70913 18.0116C9.48258 19.3134 14.5175 19.3134 19.291 18.0116C21.1157 17.5139 21.9166 15.3749 20.8675 13.8012L19.7189 12.0783C19.2502 11.3752 19.0001 10.5491 19.0001 9.7041Z"
stroke="currentColor"
strokeWidth="1.5"
/>
<path opacity="0.5" d="M7.5 19C8.15503 20.7478 9.92246 22 12 22C14.0775 22 15.845 20.7478 16.5 19" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
);
};
export default IconBell;

View File

@ -0,0 +1,22 @@
import { FC } from 'react';
interface IconBinanceProps {
className?: string;
}
const IconBinance: FC<IconBinanceProps> = ({ className }) => {
return (
<svg width="100%" height="100%" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" className={className}>
<g id="Icon">
<circle cx="512" cy="512" r="512" style={{fill: '#f3ba2f'}} />
<path className="st1 fill-white" d="M404.9 468 512 360.9l107.1 107.2 62.3-62.3L512 236.3 342.6 405.7z" />
<path transform="rotate(-45.001 298.629 511.998)" className="st1 fill-white" d="M254.6 467.9h88.1V556h-88.1z" />
<path className="st1 fill-white" d="M404.9 556 512 663.1l107.1-107.2 62.4 62.3h-.1L512 787.7 342.6 618.3l-.1-.1z" />
<path transform="rotate(-45.001 725.364 512.032)" className="st1 fill-white" d="M681.3 468h88.1v88.1h-88.1z" />
<path className="st1 fill-white" d="M575.2 512 512 448.7l-46.7 46.8-5.4 5.3-11.1 11.1-.1.1.1.1 63.2 63.2 63.2-63.3z" />
</g>
</svg>
);
};
export default IconBinance;

View File

@ -0,0 +1,42 @@
import { FC } from 'react';
interface IconBitcoinProps {
className?: string;
}
const IconBitcoin: FC<IconBitcoinProps> = ({ className }) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
xmlSpace="preserve"
width="100%"
height="100%"
version="1.1"
shapeRendering="geometricPrecision"
textRendering="geometricPrecision"
imageRendering="optimizeQuality"
fillRule="evenodd"
clipRule="evenodd"
viewBox="0 0 4091.27 4091.73"
className={className}
>
<g id="Layer_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer" />
<g id="_1421344023328">
<path
fill="#F7931A"
fillRule="nonzero"
d="M4030.06 2540.77c-273.24,1096.01 -1383.32,1763.02 -2479.46,1489.71 -1095.68,-273.24 -1762.69,-1383.39 -1489.33,-2479.31 273.12,-1096.13 1383.2,-1763.19 2479,-1489.95 1096.06,273.24 1763.03,1383.51 1489.76,2479.57l0.02 -0.02z"
/>
<path
fill="white"
fillRule="nonzero"
d="M2947.77 1754.38c40.72,-272.26 -166.56,-418.61 -450,-516.24l91.95 -368.8 -224.5 -55.94 -89.51 359.09c-59.02,-14.72 -119.63,-28.59 -179.87,-42.34l90.16 -361.46 -224.36 -55.94 -92 368.68c-48.84,-11.12 -96.81,-22.11 -143.35,-33.69l0.26 -1.16 -309.59 -77.31 -59.72 239.78c0,0 166.56,38.18 163.05,40.53 90.91,22.69 107.35,82.87 104.62,130.57l-104.74 420.15c6.26,1.59 14.38,3.89 23.34,7.49 -7.49,-1.86 -15.46,-3.89 -23.73,-5.87l-146.81 588.57c-11.11,27.62 -39.31,69.07 -102.87,53.33 2.25,3.26 -163.17,-40.72 -163.17,-40.72l-111.46 256.98 292.15 72.83c54.35,13.63 107.61,27.89 160.06,41.3l-92.9 373.03 224.24 55.94 92 -369.07c61.26,16.63 120.71,31.97 178.91,46.43l-91.69 367.33 224.51 55.94 92.89 -372.33c382.82,72.45 670.67,43.24 791.83,-303.02 97.63,-278.78 -4.86,-439.58 -206.26,-544.44 146.69,-33.83 257.18,-130.31 286.64,-329.61l-0.07 -0.05zm-512.93 719.26c-69.38,278.78 -538.76,128.08 -690.94,90.29l123.28 -494.2c152.17,37.99 640.17,113.17 567.67,403.91zm69.43 -723.3c-63.29,253.58 -453.96,124.75 -580.69,93.16l111.77 -448.21c126.73,31.59 534.85,90.55 468.94,355.05l-0.02 0z"
/>
</g>
</g>
</svg>
);
};
export default IconBitcoin;

View File

@ -0,0 +1,25 @@
import { FC } from 'react';
interface IconBoltProps {
className?: string;
}
const IconBolt: FC<IconBoltProps> = ({ className }) => {
return (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
d="M13.926 9.70541C13.5474 9.33386 13.5474 8.74151 13.5474 7.55682V7.24712C13.5474 3.96249 13.5474 2.32018 12.6241 2.03721C11.7007 1.75425 10.711 3.09327 8.73167 5.77133L5.66953 9.91436C4.3848 11.6526 3.74244 12.5217 4.09639 13.205C4.10225 13.2164 4.10829 13.2276 4.1145 13.2387C4.48945 13.9117 5.59888 13.9117 7.81775 13.9117C9.05079 13.9117 9.6673 13.9117 10.054 14.2754"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.5"
d="M13.9259 9.70557L13.9459 9.72481C14.3326 10.0885 14.9492 10.0885 16.1822 10.0885C18.4011 10.0885 19.5105 10.0885 19.8854 10.7615C19.8917 10.7726 19.8977 10.7838 19.9036 10.7951C20.2575 11.4785 19.6151 12.3476 18.3304 14.0858L15.2682 18.2288C13.2888 20.9069 12.2991 22.2459 11.3758 21.9629C10.4524 21.68 10.4524 20.0376 10.4525 16.753L10.4525 16.4434C10.4525 15.2587 10.4525 14.6663 10.074 14.2948L10.054 14.2755"
stroke="currentColor"
strokeWidth="1.5"
/>
</svg>
);
};
export default IconBolt;

View File

@ -0,0 +1,24 @@
import { FC } from 'react';
interface IconBookProps {
className?: string;
}
const IconBook: FC<IconBookProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
d="M4 8C4 5.17157 4 3.75736 4.87868 2.87868C5.75736 2 7.17157 2 10 2H14C16.8284 2 18.2426 2 19.1213 2.87868C20 3.75736 20 5.17157 20 8V16C20 18.8284 20 20.2426 19.1213 21.1213C18.2426 22 16.8284 22 14 22H10C7.17157 22 5.75736 22 4.87868 21.1213C4 20.2426 4 18.8284 4 16V8Z"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.5"
d="M6.12132 16.1022L5.92721 15.3778L6.12132 16.1022ZM3.27556 18.0294C3.16835 18.4295 3.40579 18.8408 3.80589 18.948C4.20599 19.0552 4.61724 18.8178 4.72444 18.4177L3.27556 18.0294ZM6.25 16C6.25 16.4142 6.58579 16.75 7 16.75C7.41421 16.75 7.75 16.4142 7.75 16H6.25ZM7.75 2.5C7.75 2.08579 7.41421 1.75 7 1.75C6.58579 1.75 6.25 2.08579 6.25 2.5H7.75ZM7.89778 16.75H19.8978V15.25H7.89778V16.75ZM7.89778 15.25C7.01609 15.25 6.42812 15.2436 5.92721 15.3778L6.31543 16.8267C6.57752 16.7564 6.91952 16.75 7.89778 16.75V15.25ZM5.92721 15.3778C4.63311 15.7245 3.62231 16.7353 3.27556 18.0294L4.72444 18.4177C4.9325 17.6412 5.53898 17.0347 6.31543 16.8267L5.92721 15.3778ZM7.75 16V2.5H6.25V16H7.75Z"
fill="currentColor"
/>
</svg>
);
};
export default IconBook;

View File

@ -0,0 +1,21 @@
import { FC } from 'react';
interface IconBookmarkProps {
className?: string;
bookmark?: boolean;
}
const IconBookmark: FC<IconBookmarkProps> = ({ className, bookmark = true }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
d="M21 16.0909V11.0975C21 6.80891 21 4.6646 19.682 3.3323C18.364 2 16.2426 2 12 2C7.75736 2 5.63604 2 4.31802 3.3323C3 4.6646 3 6.80891 3 11.0975V16.0909C3 19.1875 3 20.7358 3.73411 21.4123C4.08421 21.735 4.52615 21.9377 4.99692 21.9915C5.98402 22.1045 7.13673 21.0849 9.44216 19.0458C10.4612 18.1445 10.9708 17.6938 11.5603 17.5751C11.8506 17.5166 12.1494 17.5166 12.4397 17.5751C13.0292 17.6938 13.5388 18.1445 14.5578 19.0458C16.8633 21.0849 18.016 22.1045 19.0031 21.9915C19.4739 21.9377 19.9158 21.735 20.2659 21.4123C21 20.7358 21 19.1875 21 16.0909Z"
stroke="currentColor"
strokeWidth="1.5"
/>
{bookmark && <path opacity="0.5" d="M15 6H9" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />}
</svg>
);
};
export default IconBookmark;

View File

@ -0,0 +1,42 @@
import { FC } from 'react';
interface IconBoxProps {
className?: string;
fill?: boolean;
}
const IconBox: FC<IconBoxProps> = ({ className, fill = false }) => {
return (
<>
{fill ? (
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
d="M11.2296 27.4907C13.5704 28.7191 14.7408 29.3333 15.9998 29.3333V16L3.51719 9.43018C3.49882 9.45971 3.48077 9.48953 3.46303 9.51965C2.6665 10.8723 2.6665 12.5555 2.6665 15.922V16.078C2.6665 19.4444 2.6665 21.1277 3.46303 22.4803C4.25956 23.833 5.69401 24.5858 8.5629 26.0913L11.2296 27.4907Z"
fill="currentColor"
/>
<path
opacity="0.7"
d="M23.4367 5.90853L20.77 4.50913C18.4292 3.28071 17.2588 2.6665 15.9997 2.6665C14.7407 2.6665 13.5703 3.28071 11.2295 4.50912L8.56279 5.90853C5.75778 7.38053 4.32404 8.13292 3.51709 9.43002L15.9997 15.9998L28.4824 9.43002C27.6754 8.13292 26.2417 7.38054 23.4367 5.90853Z"
fill="currentColor"
/>
<path
opacity="0.5"
d="M28.5368 9.51965C28.5191 9.48953 28.501 9.45971 28.4826 9.43018L16 16V29.3333C17.259 29.3333 18.4294 28.7191 20.7703 27.4907L23.4369 26.0913C26.3058 24.5858 27.7403 23.833 28.5368 22.4803C29.3333 21.1277 29.3333 19.4444 29.3333 16.078V15.922C29.3333 12.5555 29.3333 10.8723 28.5368 9.51965Z"
fill="currentColor"
/>
</svg>
) : (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
d="M15.5777 3.38197L17.5777 4.43152C19.7294 5.56066 20.8052 6.12523 21.4026 7.13974C22 8.15425 22 9.41667 22 11.9415V12.0585C22 14.5833 22 15.8458 21.4026 16.8603C20.8052 17.8748 19.7294 18.4393 17.5777 19.5685L15.5777 20.618C13.8221 21.5393 12.9443 22 12 22C11.0557 22 10.1779 21.5393 8.42229 20.618L6.42229 19.5685C4.27063 18.4393 3.19479 17.8748 2.5974 16.8603C2 15.8458 2 14.5833 2 12.0585V11.9415C2 9.41667 2 8.15425 2.5974 7.13974C3.19479 6.12523 4.27063 5.56066 6.42229 4.43152L8.42229 3.38197C10.1779 2.46066 11.0557 2 12 2C12.9443 2 13.8221 2.46066 15.5777 3.38197Z"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
<path opacity="0.5" d="M21 7.5L12 12M12 12L3 7.5M12 12V21.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
)}
</>
);
};
export default IconBox;

View File

@ -0,0 +1,22 @@
import { FC } from 'react';
interface IconCalendarProps {
className?: string;
}
const IconCalendar: FC<IconCalendarProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
d="M2 12C2 8.22876 2 6.34315 3.17157 5.17157C4.34315 4 6.22876 4 10 4H14C17.7712 4 19.6569 4 20.8284 5.17157C22 6.34315 22 8.22876 22 12V14C22 17.7712 22 19.6569 20.8284 20.8284C19.6569 22 17.7712 22 14 22H10C6.22876 22 4.34315 22 3.17157 20.8284C2 19.6569 2 17.7712 2 14V12Z"
stroke="currentColor"
strokeWidth="1.5"
/>
<path opacity="0.5" d="M7 4V2.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path opacity="0.5" d="M17 4V2.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path opacity="0.5" d="M2 9H22" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
);
};
export default IconCalendar;

View File

@ -0,0 +1,22 @@
import { FC } from 'react';
interface IconCameraProps {
className?: string;
}
const IconCamera: FC<IconCameraProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<circle cx="12" cy="13" r="3" stroke="currentColor" strokeWidth="1.5" />
<path
opacity="0.5"
d="M9.77778 21H14.2222C17.3433 21 18.9038 21 20.0248 20.2646C20.51 19.9462 20.9267 19.5371 21.251 19.0607C22 17.9601 22 16.4279 22 13.3636C22 10.2994 22 8.76721 21.251 7.6666C20.9267 7.19014 20.51 6.78104 20.0248 6.46268C19.3044 5.99013 18.4027 5.82123 17.022 5.76086C16.3631 5.76086 15.7959 5.27068 15.6667 4.63636C15.4728 3.68489 14.6219 3 13.6337 3H10.3663C9.37805 3 8.52715 3.68489 8.33333 4.63636C8.20412 5.27068 7.63685 5.76086 6.978 5.76086C5.59733 5.82123 4.69555 5.99013 3.97524 6.46268C3.48995 6.78104 3.07328 7.19014 2.74902 7.6666C2 8.76721 2 10.2994 2 13.3636C2 16.4279 2 17.9601 2.74902 19.0607C3.07328 19.5371 3.48995 19.9462 3.97524 20.2646C5.09624 21 6.65675 21 9.77778 21Z"
stroke="currentColor"
strokeWidth="1.5"
/>
<path d="M19 10H18" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
);
};
export default IconCamera;

View File

@ -0,0 +1,15 @@
import { FC } from 'react';
interface IconCaretDownProps {
className?: string;
}
const IconCaretDown: FC<IconCaretDownProps> = ({ className }) => {
return (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path d="M19 9L12 15L5 9" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
};
export default IconCaretDown;

View File

@ -0,0 +1,34 @@
import { FC } from 'react';
interface IconCaretsDownProps {
className?: string;
fill?: boolean;
}
const IconCaretsDown: FC<IconCaretsDownProps> = ({ className, fill = false }) => {
return (
<>
{!fill ? (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path d="M19 11L12 17L5 11" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path opacity="0.5" d="M19 7L12 13L5 7" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
) : (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
opacity="0.5"
d="M5.00004 6.25C4.68618 6.25 4.40551 6.44543 4.29662 6.73979C4.18773 7.03415 4.27364 7.36519 4.51194 7.56944L11.5119 13.5694C11.7928 13.8102 12.2073 13.8102 12.4881 13.5694L19.4881 7.56944C19.7264 7.36519 19.8123 7.03415 19.7035 6.73979C19.5946 6.44543 19.3139 6.25 19 6.25H5.00004Z"
fill="currentColor"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4.43057 10.5119C4.70014 10.1974 5.17361 10.161 5.48811 10.4306L12 16.0122L18.5119 10.4306C18.8264 10.161 19.2999 10.1974 19.5695 10.5119C19.839 10.8264 19.8026 11.2999 19.4881 11.5695L12.4881 17.5695C12.2072 17.8102 11.7928 17.8102 11.5119 17.5695L4.51192 11.5695C4.19743 11.2999 4.161 10.8264 4.43057 10.5119Z"
fill="currentColor"
/>
</svg>
)}
</>
);
};
export default IconCaretsDown;

View File

@ -0,0 +1,28 @@
import { FC } from 'react';
interface IconCashBanknotesProps {
className?: string;
}
const IconCashBanknotes: FC<IconCashBanknotesProps> = ({ className }) => {
return (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
d="M2 10C2 7.17157 2 5.75736 2.87868 4.87868C3.75736 4 5.17157 4 8 4H13C15.8284 4 17.2426 4 18.1213 4.87868C19 5.75736 19 7.17157 19 10C19 12.8284 19 14.2426 18.1213 15.1213C17.2426 16 15.8284 16 13 16H8C5.17157 16 3.75736 16 2.87868 15.1213C2 14.2426 2 12.8284 2 10Z"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.5"
d="M19.0003 7.07617C19.9754 7.17208 20.6317 7.38885 21.1216 7.87873C22.0003 8.75741 22.0003 10.1716 22.0003 13.0001C22.0003 15.8285 22.0003 17.2427 21.1216 18.1214C20.2429 19.0001 18.8287 19.0001 16.0003 19.0001H11.0003C8.17187 19.0001 6.75766 19.0001 5.87898 18.1214C5.38909 17.6315 5.17233 16.9751 5.07642 16"
stroke="currentColor"
strokeWidth="1.5"
/>
<path d="M13 10C13 11.3807 11.8807 12.5 10.5 12.5C9.11929 12.5 8 11.3807 8 10C8 8.61929 9.11929 7.5 10.5 7.5C11.8807 7.5 13 8.61929 13 10Z" stroke="currentColor" strokeWidth="1.5" />
<path opacity="0.5" d="M16 12L16 8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path opacity="0.5" d="M5 12L5 8" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
);
};
export default IconCashBanknotes;

View File

@ -0,0 +1,23 @@
import { FC } from 'react';
interface IconChartSquareProps {
className?: string;
}
const IconChartSquare: FC<IconChartSquareProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
opacity="0.5"
d="M2 12C2 7.28595 2 4.92893 3.46447 3.46447C4.92893 2 7.28595 2 12 2C16.714 2 19.0711 2 20.5355 3.46447C22 4.92893 22 7.28595 22 12C22 16.714 22 19.0711 20.5355 20.5355C19.0711 22 16.714 22 12 22C7.28595 22 4.92893 22 3.46447 20.5355C2 19.0711 2 16.714 2 12Z"
stroke="currentColor"
strokeWidth="1.5"
/>
<path d="M7 18L7 15" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path d="M17 18V9" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path d="M12 18V12" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
);
};
export default IconChartSquare;

View File

@ -0,0 +1,24 @@
import { FC } from 'react';
interface IconChatDotProps {
className?: string;
}
const IconChatDot: FC<IconChatDotProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<g opacity="0.5">
<path d="M9 12C9 12.5523 8.55228 13 8 13C7.44772 13 7 12.5523 7 12C7 11.4477 7.44772 11 8 11C8.55228 11 9 11.4477 9 12Z" fill="currentColor" />
<path d="M13 12C13 12.5523 12.5523 13 12 13C11.4477 13 11 12.5523 11 12C11 11.4477 11.4477 11 12 11C12.5523 11 13 11.4477 13 12Z" fill="currentColor" />
<path d="M17 12C17 12.5523 16.5523 13 16 13C15.4477 13 15 12.5523 15 12C15 11.4477 15.4477 11 16 11C16.5523 11 17 11.4477 17 12Z" fill="currentColor" />
</g>
<path
d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 13.5997 2.37562 15.1116 3.04346 16.4525C3.22094 16.8088 3.28001 17.2161 3.17712 17.6006L2.58151 19.8267C2.32295 20.793 3.20701 21.677 4.17335 21.4185L6.39939 20.8229C6.78393 20.72 7.19121 20.7791 7.54753 20.9565C8.88837 21.6244 10.4003 22 12 22Z"
stroke="currentColor"
strokeWidth="1.5"
/>
</svg>
);
};
export default IconChatDot;

View File

@ -0,0 +1,30 @@
import { FC } from 'react';
interface IconChatDotsProps {
className?: string;
}
const IconChatDots: FC<IconChatDotsProps> = ({ className }) => {
return (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
d="M10 22C14.4183 22 18 18.4183 18 14C18 9.58172 14.4183 6 10 6C5.58172 6 2 9.58172 2 14C2 15.2355 2.28008 16.4056 2.7802 17.4502C2.95209 17.8093 3.01245 18.2161 2.90955 18.6006L2.58151 19.8267C2.32295 20.793 3.20701 21.677 4.17335 21.4185L5.39939 21.0904C5.78393 20.9876 6.19071 21.0479 6.54976 21.2198C7.5944 21.7199 8.76449 22 10 22Z"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.5"
d="M18 14.5018C18.0665 14.4741 18.1324 14.4453 18.1977 14.4155C18.5598 14.2501 18.9661 14.1882 19.3506 14.2911L19.8267 14.4185C20.793 14.677 21.677 13.793 21.4185 12.8267L21.2911 12.3506C21.1882 11.9661 21.2501 11.5598 21.4155 11.1977C21.7908 10.376 22 9.46242 22 8.5C22 4.91015 19.0899 2 15.5 2C12.7977 2 10.4806 3.64899 9.5 5.9956"
stroke="currentColor"
strokeWidth="1.5"
/>
<g opacity="0.5">
<path d="M7.5 14C7.5 14.5523 7.05228 15 6.5 15C5.94772 15 5.5 14.5523 5.5 14C5.5 13.4477 5.94772 13 6.5 13C7.05228 13 7.5 13.4477 7.5 14Z" fill="currentColor" />
<path d="M11 14C11 14.5523 10.5523 15 10 15C9.44772 15 9 14.5523 9 14C9 13.4477 9.44772 13 10 13C10.5523 13 11 13.4477 11 14Z" fill="currentColor" />
<path d="M14.5 14C14.5 14.5523 14.0523 15 13.5 15C12.9477 15 12.5 14.5523 12.5 14C12.5 13.4477 12.9477 13 13.5 13C14.0523 13 14.5 13.4477 14.5 14Z" fill="currentColor" />
</g>
</svg>
);
};
export default IconChatDots;

View File

@ -0,0 +1,22 @@
import { FC } from 'react';
interface IconChatNotificationProps {
className?: string;
}
const IconChatNotification: FC<IconChatNotificationProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<circle r="3" transform="matrix(-1 0 0 1 19 5)" stroke="currentColor" strokeWidth="1.5" />
<path
opacity="0.5"
d="M14 2.20004C13.3538 2.06886 12.6849 2 12 2C6.47715 2 2 6.47715 2 12C2 13.5997 2.37562 15.1116 3.04346 16.4525C3.22094 16.8088 3.28001 17.2161 3.17712 17.6006L2.58151 19.8267C2.32295 20.793 3.20701 21.677 4.17335 21.4185L6.39939 20.8229C6.78393 20.72 7.19121 20.7791 7.54753 20.9565C8.88837 21.6244 10.4003 22 12 22C17.5228 22 22 17.5228 22 12C22 11.3151 21.9311 10.6462 21.8 10"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
</svg>
);
};
export default IconChatNotification;

View File

@ -0,0 +1,16 @@
import { FC } from 'react';
interface IconChecksProps {
className?: string;
}
const IconChecks: FC<IconChecksProps> = ({ className }) => {
return (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path opacity="0.5" d="M4 12.9L7.14286 16.5L15 7.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path d="M20.0002 7.5625L11.4286 16.5625L11.0002 16" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
};
export default IconChecks;

View File

@ -0,0 +1,30 @@
import { FC } from 'react';
interface IconChromeProps {
className?: string;
}
const IconChrome: FC<IconChromeProps> = ({ className }) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24px"
height="24px"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<circle cx="12" cy="12" r="10"></circle>
<circle opacity="0.5" cx="12" cy="12" r="4"></circle>
<line opacity="0.5" x1="21.17" y1="8" x2="12" y2="8"></line>
<line opacity="0.5" x1="3.95" y1="6.06" x2="8.54" y2="14"></line>
<line opacity="0.5" x1="10.88" y1="21.94" x2="15.46" y2="14"></line>
</svg>
);
};
export default IconChrome;

View File

@ -0,0 +1,16 @@
import { FC } from 'react';
interface IconCircleCheckProps {
className?: string;
}
const IconCircleCheck: FC<IconCircleCheckProps> = ({ className }) => {
return (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<circle opacity="0.5" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="1.5" />
<path d="M8.5 12.5L10.5 14.5L15.5 9.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
};
export default IconCircleCheck;

View File

@ -0,0 +1,24 @@
import { FC } from 'react';
interface IconClipboardTextProps {
className?: string;
}
const IconClipboardText: FC<IconClipboardTextProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
opacity="0.5"
d="M16 4.00195C18.175 4.01406 19.3529 4.11051 20.1213 4.87889C21 5.75757 21 7.17179 21 10.0002V16.0002C21 18.8286 21 20.2429 20.1213 21.1215C19.2426 22.0002 17.8284 22.0002 15 22.0002H9C6.17157 22.0002 4.75736 22.0002 3.87868 21.1215C3 20.2429 3 18.8286 3 16.0002V10.0002C3 7.17179 3 5.75757 3.87868 4.87889C4.64706 4.11051 5.82497 4.01406 8 4.00195"
stroke="currentColor"
strokeWidth="1.5"
/>
<path d="M8 14H16" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path d="M7 10.5H17" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path d="M9 17.5H15" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path d="M8 3.5C8 2.67157 8.67157 2 9.5 2H14.5C15.3284 2 16 2.67157 16 3.5V4.5C16 5.32843 15.3284 6 14.5 6H9.5C8.67157 6 8 5.32843 8 4.5V3.5Z" stroke="currentColor" strokeWidth="1.5" />
</svg>
);
};
export default IconClipboardText;

View File

@ -0,0 +1,16 @@
import { FC } from 'react';
interface IconClockProps {
className?: string;
}
const IconClock: FC<IconClockProps> = ({ className }) => {
return (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<circle opacity="0.5" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="1.5" />
<path d="M12 8V12L14.5 14.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
};
export default IconClock;

View File

@ -0,0 +1,22 @@
import { FC } from 'react';
interface IconCloudDownloadProps {
className?: string;
}
const IconCloudDownload: FC<IconCloudDownloadProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
opacity="0.5"
d="M6.28571 19C3.91878 19 2 17.1038 2 14.7647C2 12.4256 3.91878 10.5294 6.28571 10.5294C6.56983 10.5294 6.8475 10.5567 7.11616 10.6089M14.381 8.02721C14.9767 7.81911 15.6178 7.70588 16.2857 7.70588C16.9404 7.70588 17.5693 7.81468 18.1551 8.01498M7.11616 10.6089C6.88706 9.9978 6.7619 9.33687 6.7619 8.64706C6.7619 5.52827 9.32028 3 12.4762 3C15.4159 3 17.8371 5.19371 18.1551 8.01498M7.11616 10.6089C7.68059 10.7184 8.20528 10.9374 8.66667 11.2426M18.1551 8.01498C20.393 8.78024 22 10.8811 22 13.3529C22 16.0599 20.0726 18.3221 17.5 18.8722"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
<path d="M12 22V16M12 22L14 20M12 22L10 20" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
};
export default IconCloudDownload;

View File

@ -0,0 +1,27 @@
import { FC } from 'react';
interface IconCodeProps {
className?: string;
}
const IconCode: FC<IconCodeProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
d="M17 7.82959L18.6965 9.35641C20.239 10.7447 21.0103 11.4389 21.0103 12.3296C21.0103 13.2203 20.239 13.9145 18.6965 15.3028L17 16.8296"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
<path opacity="0.5" d="M13.9868 5L10.0132 19.8297" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path
d="M7.00005 7.82959L5.30358 9.35641C3.76102 10.7447 2.98975 11.4389 2.98975 12.3296C2.98975 13.2203 3.76102 13.9145 5.30358 15.3028L7.00005 16.8296"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
</svg>
);
};
export default IconCode;

View File

@ -0,0 +1,42 @@
import { FC } from 'react';
interface IconCoffeeProps {
className?: string;
}
const IconCoffee: FC<IconCoffeeProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
d="M2.3153 12.6978C2.26536 12.2706 2.2404 12.057 2.2509 11.8809C2.30599 10.9577 2.98677 10.1928 3.89725 10.0309C4.07094 10 4.286 10 4.71612 10H15.2838C15.7139 10 15.929 10 16.1027 10.0309C17.0132 10.1928 17.694 10.9577 17.749 11.8809C17.7595 12.057 17.7346 12.2706 17.6846 12.6978L17.284 16.1258C17.1031 17.6729 16.2764 19.0714 15.0081 19.9757C14.0736 20.6419 12.9546 21 11.8069 21H8.19303C7.04537 21 5.9263 20.6419 4.99182 19.9757C3.72352 19.0714 2.89681 17.6729 2.71598 16.1258L2.3153 12.6978Z"
stroke="currentColor"
strokeWidth="1.5"
/>
<path opacity="0.5" d="M17 17H19C20.6569 17 22 15.6569 22 14C22 12.3431 20.6569 11 19 11H17.5" stroke="currentColor" strokeWidth="1.5" />
<path
opacity="0.5"
d="M10.0002 2C9.44787 2.55228 9.44787 3.44772 10.0002 4C10.5524 4.55228 10.5524 5.44772 10.0002 6"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M4.99994 7.5L5.11605 7.38388C5.62322 6.87671 5.68028 6.0738 5.24994 5.5C4.81959 4.9262 4.87665 4.12329 5.38382 3.61612L5.49994 3.5"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M14.4999 7.5L14.6161 7.38388C15.1232 6.87671 15.1803 6.0738 14.7499 5.5C14.3196 4.9262 14.3767 4.12329 14.8838 3.61612L14.9999 3.5"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export default IconCoffee;

View File

@ -0,0 +1,25 @@
import { FC } from 'react';
interface IconCopyProps {
className?: string;
}
const IconCopy: FC<IconCopyProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
d="M6 11C6 8.17157 6 6.75736 6.87868 5.87868C7.75736 5 9.17157 5 12 5H15C17.8284 5 19.2426 5 20.1213 5.87868C21 6.75736 21 8.17157 21 11V16C21 18.8284 21 20.2426 20.1213 21.1213C19.2426 22 17.8284 22 15 22H12C9.17157 22 7.75736 22 6.87868 21.1213C6 20.2426 6 18.8284 6 16V11Z"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.5"
d="M6 19C4.34315 19 3 17.6569 3 16V10C3 6.22876 3 4.34315 4.17157 3.17157C5.34315 2 7.22876 2 11 2H15C16.6569 2 18 3.34315 18 5"
stroke="currentColor"
strokeWidth="1.5"
/>
</svg>
);
};
export default IconCopy;

View File

@ -0,0 +1,38 @@
import { FC } from 'react';
interface IconCpuBoltProps {
className?: string;
}
const IconCpuBolt: FC<IconCpuBoltProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
opacity="0.5"
d="M7 10C7 8.58579 7 7.87868 7.43934 7.43934C7.87868 7 8.58579 7 10 7H14C15.4142 7 16.1213 7 16.5607 7.43934C17 7.87868 17 8.58579 17 10V14C17 15.4142 17 16.1213 16.5607 16.5607C16.1213 17 15.4142 17 14 17H10C8.58579 17 7.87868 17 7.43934 16.5607C7 16.1213 7 15.4142 7 14V10Z"
stroke="currentColor"
strokeWidth="1.5"
/>
<path d="M12.4286 10L11 12H13L11.5714 14" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
<path
d="M4 12C4 8.22876 4 6.34315 5.17157 5.17157C6.34315 4 8.22876 4 12 4C15.7712 4 17.6569 4 18.8284 5.17157C20 6.34315 20 8.22876 20 12C20 15.7712 20 17.6569 18.8284 18.8284C17.6569 20 15.7712 20 12 20C8.22876 20 6.34315 20 5.17157 18.8284C4 17.6569 4 15.7712 4 12Z"
stroke="currentColor"
strokeWidth="1.5"
/>
<path opacity="0.5" d="M4 12H2" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path opacity="0.5" d="M22 12H20" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path opacity="0.5" d="M4 9H2" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path opacity="0.5" d="M22 9H20" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path opacity="0.5" d="M4 15H2" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path opacity="0.5" d="M22 15H20" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path opacity="0.5" d="M12 20L12 22" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path opacity="0.5" d="M12 2L12 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path opacity="0.5" d="M9 20L9 22" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path opacity="0.5" d="M9 2L9 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path opacity="0.5" d="M15 20L15 22" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path opacity="0.5" d="M15 2L15 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
);
};
export default IconCpuBolt;

View File

@ -0,0 +1,22 @@
import { FC } from 'react';
interface IconCreditCardProps {
className?: string;
}
const IconCreditCard: FC<IconCreditCardProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
d="M2 12C2 8.22876 2 6.34315 3.17157 5.17157C4.34315 4 6.22876 4 10 4H14C17.7712 4 19.6569 4 20.8284 5.17157C22 6.34315 22 8.22876 22 12C22 15.7712 22 17.6569 20.8284 18.8284C19.6569 20 17.7712 20 14 20H10C6.22876 20 4.34315 20 3.17157 18.8284C2 17.6569 2 15.7712 2 12Z"
stroke="currentColor"
strokeWidth="1.5"
/>
<path opacity="0.5" d="M10 16H6" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path opacity="0.5" d="M14 16H12.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path opacity="0.5" d="M2 10L22 10" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
);
};
export default IconCreditCard;

View File

@ -0,0 +1,37 @@
import { FC } from 'react';
interface IconDesktopProps {
className?: string;
fill?: boolean;
}
const IconDesktop: FC<IconDesktopProps> = ({ className, fill = false }) => {
return (
<>
{fill ? (
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
opacity="0.5"
d="M13.3332 2.6665H18.6665C23.6948 2.6665 26.209 2.6665 27.7711 4.2286C29.3332 5.7907 29.3332 8.30486 29.3332 13.3332V14.6665C29.3332 15.402 29.3332 16.7324 29.3245 17.3332H2.67519C2.6665 16.7324 2.6665 15.402 2.6665 14.6665V13.3332C2.6665 8.30486 2.6665 5.7907 4.2286 4.2286C5.7907 2.6665 8.30486 2.6665 13.3332 2.6665Z"
fill="currentColor"
/>
<path
d="M10.646 23.3335C6.86021 23.3335 4.96734 23.3335 3.79125 22.1619C3.02926 21.4029 2.76097 20.344 2.6665 18.6668V17.3335H29.3332V18.6668C29.2387 20.344 28.9704 21.4029 28.2084 22.1619C27.0323 23.3335 25.1395 23.3335 21.3537 23.3335H17.0037V28.6668H21.3537C21.9081 28.6668 22.3576 29.1145 22.3576 29.6668C22.3576 30.2191 21.9081 30.6668 21.3537 30.6668H10.646C10.0916 30.6668 9.64212 30.2191 9.64212 29.6668C9.64212 29.1145 10.0916 28.6668 10.646 28.6668H14.996V23.3335H10.646Z"
fill="currentColor"
/>
</svg>
) : (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
d="M2 10C2 6.22876 2 4.34315 3.17157 3.17157C4.34315 2 6.22876 2 10 2H14C17.7712 2 19.6569 2 20.8284 3.17157C22 4.34315 22 6.22876 22 10V11C22 13.8284 22 15.2426 21.1213 16.1213C20.2426 17 18.8284 17 16 17H8C5.17157 17 3.75736 17 2.87868 16.1213C2 15.2426 2 13.8284 2 11V10Z"
stroke="currentColor"
strokeWidth="1.5"
/>
<path opacity="0.5" d="M16 22H8M12 17V22" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path opacity="0.5" d="M22 13H2" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
)}
</>
);
};
export default IconDesktop;

View File

@ -0,0 +1,40 @@
import { FC } from 'react';
interface IconDollarSignCircleProps {
className?: string;
fill?: boolean;
}
const IconDollarSignCircle: FC<IconDollarSignCircleProps> = ({ className, fill = false }) => {
return (
<>
{!fill ? (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<circle opacity="0.5" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="1.5" />
<path d="M12 6V18" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path
d="M15 9.5C15 8.11929 13.6569 7 12 7C10.3431 7 9 8.11929 9 9.5C9 10.8807 10.3431 12 12 12C13.6569 12 15 13.1193 15 14.5C15 15.8807 13.6569 17 12 17C10.3431 17 9 15.8807 9 14.5"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
</svg>
) : (
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
opacity="0.5"
fillRule="evenodd"
clipRule="evenodd"
d="M29.3332 15.9998C29.3332 23.3636 23.3636 29.3332 15.9998 29.3332C8.63604 29.3332 2.6665 23.3636 2.6665 15.9998C2.6665 8.63604 8.63604 2.6665 15.9998 2.6665C23.3636 2.6665 29.3332 8.63604 29.3332 15.9998Z"
fill="currentColor"
/>
<path
d="M17 8C17 7.44772 16.5523 7 16 7C15.4477 7 15 7.44772 15 8V8.42231C12.8261 8.81156 11 10.4448 11 12.6667C11 15.2229 13.417 17 16 17C17.8353 17 19 18.2076 19 19.3333C19 20.459 17.8353 21.6667 16 21.6667C14.1647 21.6667 13 20.459 13 19.3333C13 18.781 12.5523 18.3333 12 18.3333C11.4477 18.3333 11 18.781 11 19.3333C11 21.5552 12.8261 23.1884 15 23.5777V24C15 24.5523 15.4477 25 16 25C16.5523 25 17 24.5523 17 24V23.5777C19.1739 23.1884 21 21.5552 21 19.3333C21 16.7771 18.583 15 16 15C14.1647 15 13 13.7924 13 12.6667C13 11.541 14.1647 10.3333 16 10.3333C17.8353 10.3333 19 11.541 19 12.6667C19 13.219 19.4477 13.6667 20 13.6667C20.5523 13.6667 21 13.219 21 12.6667C21 10.4448 19.1739 8.81156 17 8.42231V8Z"
fill="currentColor"
/>
</svg>
)}
</>
);
};
export default IconDollarSignCircle;

View File

@ -0,0 +1,21 @@
import { FC } from 'react';
interface IconDollarSignProps {
className?: string;
}
const IconDollarSign: FC<IconDollarSignProps> = ({ className }) => {
return (
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path d="M12 6V18" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path
d="M15 9.5C15 8.11929 13.6569 7 12 7C10.3431 7 9 8.11929 9 9.5C9 10.8807 10.3431 12 12 12C13.6569 12 15 13.1193 15 14.5C15 15.8807 13.6569 17 12 17C10.3431 17 9 15.8807 9 14.5"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
</svg>
);
};
export default IconDollarSign;

View File

@ -0,0 +1,22 @@
import { FC } from 'react';
interface IconDownloadProps {
className?: string;
}
const IconDownload: FC<IconDownloadProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
opacity="0.5"
d="M17 9.00195C19.175 9.01406 20.3529 9.11051 21.1213 9.8789C22 10.7576 22 12.1718 22 15.0002V16.0002C22 18.8286 22 20.2429 21.1213 21.1215C20.2426 22.0002 18.8284 22.0002 16 22.0002H8C5.17157 22.0002 3.75736 22.0002 2.87868 21.1215C2 20.2429 2 18.8286 2 16.0002L2 15.0002C2 12.1718 2 10.7576 2.87868 9.87889C3.64706 9.11051 4.82497 9.01406 7 9.00195"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
<path d="M12 2L12 15M12 15L9 11.5M12 15L15 11.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
};
export default IconDownload;

View File

@ -0,0 +1,26 @@
import { FC } from 'react';
interface IconDribbbleProps {
className?: string;
}
const IconDribbble: FC<IconDribbbleProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
d="M3.33946 16.9997C6.10089 21.7826 12.2168 23.4214 16.9997 20.66C18.9493 19.5344 20.3765 17.8514 21.1962 15.9286C22.3875 13.1341 22.2958 9.83304 20.66 6.99972C19.0242 4.1664 16.2112 2.43642 13.1955 2.07088C11.1204 1.81935 8.94932 2.21386 6.99972 3.33946C2.21679 6.10089 0.578039 12.2168 3.33946 16.9997Z"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.5"
d="M16.9497 20.5732C16.9497 20.5732 16.0107 13.9821 14.0004 10.5001C11.99 7.01803 7.05018 3.42681 7.05018 3.42681M7.57711 20.8175C9.05874 16.3477 16.4525 11.3931 21.8635 12.5801M16.4139 3.20898C14.926 7.63004 7.67424 12.5123 2.28857 11.4516"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
</svg>
);
};
export default IconDribbble;

View File

@ -0,0 +1,21 @@
import { FC } from 'react';
interface IconDropletProps {
className?: string;
}
const IconDroplet: FC<IconDropletProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
opacity="0.5"
d="M3 13.1928C3 18.0569 6.85549 22 11.6115 22H12.3885C17.1445 22 21 18.0569 21 13.1928V12.9281C21 8.31651 18.2715 4.16347 14.0967 2.42077C12.7527 1.85974 11.2473 1.85974 9.90329 2.42077C5.72854 4.16347 3 8.31651 3 12.9281V13.1928Z"
stroke="currentColor"
strokeWidth="1.5"
/>
<path d="M7.61475 10.7237C8.2495 8.71826 9.63062 7.08805 11.3858 6.27637" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
</svg>
);
};
export default IconDroplet;

View File

@ -0,0 +1,32 @@
import { FC } from 'react';
interface IconEditProps {
className?: string;
}
const IconEdit: FC<IconEditProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
opacity="0.5"
d="M22 10.5V12C22 16.714 22 19.0711 20.5355 20.5355C19.0711 22 16.714 22 12 22C7.28595 22 4.92893 22 3.46447 20.5355C2 19.0711 2 16.714 2 12C2 7.28595 2 4.92893 3.46447 3.46447C4.92893 2 7.28595 2 12 2H13.5"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
<path
d="M17.3009 2.80624L16.652 3.45506L10.6872 9.41993C10.2832 9.82394 10.0812 10.0259 9.90743 10.2487C9.70249 10.5114 9.52679 10.7957 9.38344 11.0965C9.26191 11.3515 9.17157 11.6225 8.99089 12.1646L8.41242 13.9L8.03811 15.0229C7.9492 15.2897 8.01862 15.5837 8.21744 15.7826C8.41626 15.9814 8.71035 16.0508 8.97709 15.9619L10.1 15.5876L11.8354 15.0091C12.3775 14.8284 12.6485 14.7381 12.9035 14.6166C13.2043 14.4732 13.4886 14.2975 13.7513 14.0926C13.9741 13.9188 14.1761 13.7168 14.5801 13.3128L20.5449 7.34795L21.1938 6.69914C22.2687 5.62415 22.2687 3.88124 21.1938 2.80624C20.1188 1.73125 18.3759 1.73125 17.3009 2.80624Z"
stroke="currentColor"
strokeWidth="1.5"
/>
<path
opacity="0.5"
d="M16.6522 3.45508C16.6522 3.45508 16.7333 4.83381 17.9499 6.05034C19.1664 7.26687 20.5451 7.34797 20.5451 7.34797M10.1002 15.5876L8.4126 13.9"
stroke="currentColor"
strokeWidth="1.5"
/>
</svg>
);
};
export default IconEdit;

View File

@ -0,0 +1,40 @@
import { FC } from 'react';
interface IconEthereumProps {
className?: string;
}
const IconEthereum: FC<IconEthereumProps> = ({ className }) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
xmlSpace="preserve"
width="100%"
height="100%"
version="1.1"
shapeRendering="geometricPrecision"
textRendering="geometricPrecision"
imageRendering="optimizeQuality"
fillRule="evenodd"
clipRule="evenodd"
viewBox="0 0 784.37 1277.39"
className={className}
>
<g id="Layer_x0020_1">
<metadata id="CorelCorpID_0Corel-Layer" />
<g id="_1421394342400">
<g>
<polygon fill="#343434" fillRule="nonzero" points="392.07,0 383.5,29.11 383.5,873.74 392.07,882.29 784.13,650.54 " />
<polygon fill="#8C8C8C" fillRule="nonzero" points="392.07,0 -0,650.54 392.07,882.29 392.07,472.33 " />
<polygon fill="#3C3C3B" fillRule="nonzero" points="392.07,956.52 387.24,962.41 387.24,1263.28 392.07,1277.38 784.37,724.89 " />
<polygon fill="#8C8C8C" fillRule="nonzero" points="392.07,1277.38 392.07,956.52 -0,724.89 " />
<polygon fill="#141414" fillRule="nonzero" points="392.07,882.29 784.13,650.54 392.07,472.33 " />
<polygon fill="#393939" fillRule="nonzero" points="0,650.54 392.07,882.29 392.07,472.33 " />
</g>
</g>
</g>
</svg>
);
};
export default IconEthereum;

View File

@ -0,0 +1,21 @@
import { FC } from 'react';
interface IconEyeProps {
className?: string;
}
const IconEye: FC<IconEyeProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
opacity="0.5"
d="M3.27489 15.2957C2.42496 14.1915 2 13.6394 2 12C2 10.3606 2.42496 9.80853 3.27489 8.70433C4.97196 6.49956 7.81811 4 12 4C16.1819 4 19.028 6.49956 20.7251 8.70433C21.575 9.80853 22 10.3606 22 12C22 13.6394 21.575 14.1915 20.7251 15.2957C19.028 17.5004 16.1819 20 12 20C7.81811 20 4.97196 17.5004 3.27489 15.2957Z"
stroke="currentColor"
strokeWidth="1.5"
></path>
<path d="M15 12C15 13.6569 13.6569 15 12 15C10.3431 15 9 13.6569 9 12C9 10.3431 10.3431 9 12 9C13.6569 9 15 10.3431 15 12Z" stroke="currentColor" strokeWidth="1.5"></path>
</svg>
);
};
export default IconEye;

View File

@ -0,0 +1,18 @@
import { FC } from 'react';
interface IconFacebookCircleProps {
className?: string;
}
const IconFacebookCircle: FC<IconFacebookCircleProps> = ({ className }) => {
return (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" className={className}>
<path
d="M14 7C14 3.15 10.85 0 7 0C3.15 0 0 3.15 0 7C0 10.5 2.5375 13.3875 5.8625 13.9125V9.0125H4.1125V7H5.8625V5.425C5.8625 3.675 6.9125 2.7125 8.4875 2.7125C9.275 2.7125 10.0625 2.8875 10.0625 2.8875V4.6375H9.1875C8.3125 4.6375 8.05 5.1625 8.05 5.6875V7H9.975L9.625 9.0125H7.9625V14C11.4625 13.475 14 10.5 14 7Z"
fill="currentColor"
/>
</svg>
);
};
export default IconFacebookCircle;

View File

@ -0,0 +1,26 @@
import { FC } from 'react';
interface IconFacebookProps {
className?: string;
}
const IconFacebook: FC<IconFacebookProps> = ({ className }) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"></path>
</svg>
);
};
export default IconFacebook;

View File

@ -0,0 +1,19 @@
import { FC } from 'react';
interface IconFileProps {
className?: string;
}
const IconFile: FC<IconFileProps> = ({ className }) => {
return (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
d="M15.3929 4.05365L14.8912 4.61112L15.3929 4.05365ZM19.3517 7.61654L18.85 8.17402L19.3517 7.61654ZM21.654 10.1541L20.9689 10.4592V10.4592L21.654 10.1541ZM3.17157 20.8284L3.7019 20.2981H3.7019L3.17157 20.8284ZM20.8284 20.8284L20.2981 20.2981L20.2981 20.2981L20.8284 20.8284ZM14 21.25H10V22.75H14V21.25ZM2.75 14V10H1.25V14H2.75ZM21.25 13.5629V14H22.75V13.5629H21.25ZM14.8912 4.61112L18.85 8.17402L19.8534 7.05907L15.8947 3.49618L14.8912 4.61112ZM22.75 13.5629C22.75 11.8745 22.7651 10.8055 22.3391 9.84897L20.9689 10.4592C21.2349 11.0565 21.25 11.742 21.25 13.5629H22.75ZM18.85 8.17402C20.2034 9.3921 20.7029 9.86199 20.9689 10.4592L22.3391 9.84897C21.9131 8.89241 21.1084 8.18853 19.8534 7.05907L18.85 8.17402ZM10.0298 2.75C11.6116 2.75 12.2085 2.76158 12.7405 2.96573L13.2779 1.5653C12.4261 1.23842 11.498 1.25 10.0298 1.25V2.75ZM15.8947 3.49618C14.8087 2.51878 14.1297 1.89214 13.2779 1.5653L12.7405 2.96573C13.2727 3.16993 13.7215 3.55836 14.8912 4.61112L15.8947 3.49618ZM10 21.25C8.09318 21.25 6.73851 21.2484 5.71085 21.1102C4.70476 20.975 4.12511 20.7213 3.7019 20.2981L2.64124 21.3588C3.38961 22.1071 4.33855 22.4392 5.51098 22.5969C6.66182 22.7516 8.13558 22.75 10 22.75V21.25ZM1.25 14C1.25 15.8644 1.24841 17.3382 1.40313 18.489C1.56076 19.6614 1.89288 20.6104 2.64124 21.3588L3.7019 20.2981C3.27869 19.8749 3.02502 19.2952 2.88976 18.2892C2.75159 17.2615 2.75 15.9068 2.75 14H1.25ZM14 22.75C15.8644 22.75 17.3382 22.7516 18.489 22.5969C19.6614 22.4392 20.6104 22.1071 21.3588 21.3588L20.2981 20.2981C19.8749 20.7213 19.2952 20.975 18.2892 21.1102C17.2615 21.2484 15.9068 21.25 14 21.25V22.75ZM21.25 14C21.25 15.9068 21.2484 17.2615 21.1102 18.2892C20.975 19.2952 20.7213 19.8749 20.2981 20.2981L21.3588 21.3588C22.1071 20.6104 22.4392 19.6614 22.5969 18.489C22.7516 17.3382 22.75 15.8644 22.75 14H21.25ZM2.75 10C2.75 8.09318 2.75159 6.73851 2.88976 5.71085C3.02502 4.70476 3.27869 4.12511 3.7019 3.7019L2.64124 2.64124C1.89288 3.38961 1.56076 4.33855 1.40313 5.51098C1.24841 6.66182 1.25 8.13558 1.25 10H2.75ZM10.0298 1.25C8.15538 1.25 6.67442 1.24842 5.51887 1.40307C4.34232 1.56054 3.39019 1.8923 2.64124 2.64124L3.7019 3.7019C4.12453 3.27928 4.70596 3.02525 5.71785 2.88982C6.75075 2.75158 8.11311 2.75 10.0298 2.75V1.25Z"
fill="currentColor"
/>
<path opacity="0.5" d="M13 2.5V5C13 7.35702 13 8.53553 13.7322 9.26777C14.4645 10 15.643 10 18 10H22" stroke="currentColor" strokeWidth="1.5" />
</svg>
);
};
export default IconFile;

View File

@ -0,0 +1,20 @@
import { FC } from 'react';
interface IconFolderMinusProps {
className?: string;
}
const IconFolderMinus: FC<IconFolderMinusProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path opacity="0.5" d="M10 14H12M12 14H14M12 14V16M12 14V12" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path
d="M2 6.94975C2 6.06722 2 5.62595 2.06935 5.25839C2.37464 3.64031 3.64031 2.37464 5.25839 2.06935C5.62595 2 6.06722 2 6.94975 2C7.33642 2 7.52976 2 7.71557 2.01738C8.51665 2.09229 9.27652 2.40704 9.89594 2.92051C10.0396 3.03961 10.1763 3.17633 10.4497 3.44975L11 4C11.8158 4.81578 12.2237 5.22367 12.7121 5.49543C12.9804 5.64471 13.2651 5.7626 13.5604 5.84678C14.0979 6 14.6747 6 15.8284 6H16.2021C18.8345 6 20.1506 6 21.0062 6.76946C21.0849 6.84024 21.1598 6.91514 21.2305 6.99383C22 7.84935 22 9.16554 22 11.7979V14C22 17.7712 22 19.6569 20.8284 20.8284C19.6569 22 17.7712 22 14 22H10C6.22876 22 4.34315 22 3.17157 20.8284C2 19.6569 2 17.7712 2 14V6.94975Z"
stroke="currentColor"
strokeWidth="1.5"
/>
</svg>
);
};
export default IconFolderMinus;

View File

@ -0,0 +1,20 @@
import { FC } from 'react';
interface IconFolderPlusProps {
className?: string;
}
const IconFolderPlus: FC<IconFolderPlusProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path opacity="0.5" d="M14 14H10" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path
d="M2 6.94975C2 6.06722 2 5.62595 2.06935 5.25839C2.37464 3.64031 3.64031 2.37464 5.25839 2.06935C5.62595 2 6.06722 2 6.94975 2C7.33642 2 7.52976 2 7.71557 2.01738C8.51665 2.09229 9.27652 2.40704 9.89594 2.92051C10.0396 3.03961 10.1763 3.17633 10.4497 3.44975L11 4C11.8158 4.81578 12.2237 5.22367 12.7121 5.49543C12.9804 5.64471 13.2651 5.7626 13.5604 5.84678C14.0979 6 14.6747 6 15.8284 6H16.2021C18.8345 6 20.1506 6 21.0062 6.76946C21.0849 6.84024 21.1598 6.91514 21.2305 6.99383C22 7.84935 22 9.16554 22 11.7979V14C22 17.7712 22 19.6569 20.8284 20.8284C19.6569 22 17.7712 22 14 22H10C6.22876 22 4.34315 22 3.17157 20.8284C2 19.6569 2 17.7712 2 14V6.94975Z"
stroke="currentColor"
strokeWidth="1.5"
/>
</svg>
);
};
export default IconFolderPlus;

View File

@ -0,0 +1,20 @@
import { FC } from 'react';
interface IconFolderProps {
className?: string;
}
const IconFolder: FC<IconFolderProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path opacity="0.5" d="M18 10L13 10" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
<path
d="M2 6.94975C2 6.06722 2 5.62595 2.06935 5.25839C2.37464 3.64031 3.64031 2.37464 5.25839 2.06935C5.62595 2 6.06722 2 6.94975 2C7.33642 2 7.52976 2 7.71557 2.01738C8.51665 2.09229 9.27652 2.40704 9.89594 2.92051C10.0396 3.03961 10.1763 3.17633 10.4497 3.44975L11 4C11.8158 4.81578 12.2237 5.22367 12.7121 5.49543C12.9804 5.64471 13.2651 5.7626 13.5604 5.84678C14.0979 6 14.6747 6 15.8284 6H16.2021C18.8345 6 20.1506 6 21.0062 6.76946C21.0849 6.84024 21.1598 6.91514 21.2305 6.99383C22 7.84935 22 9.16554 22 11.7979V14C22 17.7712 22 19.6569 20.8284 20.8284C19.6569 22 17.7712 22 14 22H10C6.22876 22 4.34315 22 3.17157 20.8284C2 19.6569 2 17.7712 2 14V6.94975Z"
stroke="currentColor"
strokeWidth="1.5"
/>
</svg>
);
};
export default IconFolder;

View File

@ -0,0 +1,27 @@
import { FC } from 'react';
interface IconGalleryProps {
className?: string;
}
const IconGallery: FC<IconGalleryProps> = ({ className }) => {
return (
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
d="M2 12C2 7.28595 2 4.92893 3.46447 3.46447C4.92893 2 7.28595 2 12 2C16.714 2 19.0711 2 20.5355 3.46447C22 4.92893 22 7.28595 22 12C22 16.714 22 19.0711 20.5355 20.5355C19.0711 22 16.714 22 12 22C7.28595 22 4.92893 22 3.46447 20.5355C2 19.0711 2 16.714 2 12Z"
stroke="currentColor"
strokeWidth="1.5"
/>
<circle opacity="0.5" cx="16" cy="8" r="2" stroke="currentColor" strokeWidth="1.5" />
<path
opacity="0.5"
d="M2 12.5001L3.75159 10.9675C4.66286 10.1702 6.03628 10.2159 6.89249 11.0721L11.1822 15.3618C11.8694 16.0491 12.9512 16.1428 13.7464 15.5839L14.0446 15.3744C15.1888 14.5702 16.7369 14.6634 17.7765 15.599L21 18.5001"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
</svg>
);
};
export default IconGallery;

View File

@ -0,0 +1,26 @@
import { FC } from 'react';
interface IconGithubProps {
className?: string;
}
const IconGithub: FC<IconGithubProps> = ({ className }) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
>
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
</svg>
);
};
export default IconGithub;

Some files were not shown because too many files have changed in this diff Show More