diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..89b75eb --- /dev/null +++ b/.editorconfig @@ -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 \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..f0f3abe --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "extends": "next/core-web-vitals", + "rules": { + "@next/next/no-img-element": "off" + } +} diff --git a/.gitignore b/.gitignore index ceaea36..687ea1d 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,8 @@ build/Release node_modules/ jspm_packages/ +.next/ + # Snowpack dependency directory (https://snowpack.dev/) web_modules/ diff --git a/.gitignore copy b/.gitignore copy new file mode 100644 index 0000000..c87c9b3 --- /dev/null +++ b/.gitignore copy @@ -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 diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..9068716 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "semi": true, + "singleQuote": true, + "printWidth": 200 +} diff --git a/App.tsx b/App.tsx new file mode 100644 index 0000000..d5c2a47 --- /dev/null +++ b/App.tsx @@ -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 ( +
+ {isLoading ? : children} +
+ ); +} + +export default App; diff --git a/README copy.md b/README copy.md new file mode 100644 index 0000000..965a122 --- /dev/null +++ b/README copy.md @@ -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. diff --git a/app/(auth)/boxed-lockscreen/page.tsx b/app/(auth)/boxed-lockscreen/page.tsx new file mode 100644 index 0000000..db69c23 --- /dev/null +++ b/app/(auth)/boxed-lockscreen/page.tsx @@ -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 ( +
+
+ image +
+ +
+ image + image + image + image +
+
+ {/*
+ +
*/} +
+
+
+ images +
+
+

Shaun Park

+

Enter your password to unlock your ID

+
+
+ +
+
+
+
+
+ ); +}; + +export default BoxedLockScreen; diff --git a/app/(auth)/boxed-password-reset/page.tsx b/app/(auth)/boxed-password-reset/page.tsx new file mode 100644 index 0000000..26e65bd --- /dev/null +++ b/app/(auth)/boxed-password-reset/page.tsx @@ -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 ( +
+
+ image +
+ +
+ image + image + image + image +
+
+ {/*
+ +
*/} +
+
+

Password Reset

+

Enter your email to recover your ID

+
+ +
+
+
+
+
+ ); +}; + +export default BoxedPasswordReset; diff --git a/app/(auth)/change-password/page.tsx b/app/(auth)/change-password/page.tsx new file mode 100644 index 0000000..2d81e13 --- /dev/null +++ b/app/(auth)/change-password/page.tsx @@ -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 ( +
+ + {/* Background glows (same as login page) */} + +
+
+
+
+
+
+
+
+ + {/* Card */} + +
+ ); +} diff --git a/app/(auth)/forgot-password/page.tsx b/app/(auth)/forgot-password/page.tsx new file mode 100644 index 0000000..6213311 --- /dev/null +++ b/app/(auth)/forgot-password/page.tsx @@ -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 ( +
+ + {/* Background glows */} +
+
+
+
+
+
+
+
+ + +
+ ); +} diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx new file mode 100644 index 0000000..483a128 --- /dev/null +++ b/app/(auth)/layout.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +const AuthLayout = ({ children }: { children: React.ReactNode }) => { + return
{children}
; +}; + +export default AuthLayout; diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx new file mode 100644 index 0000000..79f6271 --- /dev/null +++ b/app/(auth)/login/page.tsx @@ -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 ( +
+ + {/* Background glows */} + + {/* blue left */} +
+ + {/* green left */} +
+ + {/* pink left */} +
+ +{/* pink - on card-small pink color bottom*/} +
+ + +{/* orange*/} +
+ + {/* green right */} +
+ +{/* purple - right*/} +
+ + +{/* yellow bottom right */} +
+ + + + {/* Login Form */} + +
+ ); +} \ No newline at end of file diff --git a/app/(auth)/reset-password/page.tsx b/app/(auth)/reset-password/page.tsx new file mode 100644 index 0000000..e37406a --- /dev/null +++ b/app/(auth)/reset-password/page.tsx @@ -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 ( +
+ + {/* Background glows */} +
+
+
+
+ + {/* Card */} +
+ + {/* Logo */} +
+ Logo + +

+ Reset Your Password +

+ +

+ Enter your new password below +

+
+ + {err &&

{err}

} + {msg &&

{msg}

} + +
+ + {/* 🔥 NEW PASSWORD FIELD */} +
+ 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 */} + +
+ + {/* 🔥 CONFIRM PASSWORD FIELD */} +
+ 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 */} + +
+
+
+ ); +} diff --git a/app/(auth)/signup/page.tsx b/app/(auth)/signup/page.tsx new file mode 100644 index 0000000..b279189 --- /dev/null +++ b/app/(auth)/signup/page.tsx @@ -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 ( +
+ + {/* Background glows */} +
+
+
+
+
+
+
+
+ + {/* SIGNUP CARD — SLIGHTLY WIDER */} +
+ + {/* Logo */} +
+ Logo + +

+ Create Your SocialBuddy Account +

+
+ + {/* Form */} +
+ +
+ + {/* Footer */} +
+ Already have an account?{' '} + + SIGN IN + +
+ +
+
+ ); +} diff --git a/app/(defaults)/account-settings/invoice/[id]/page.tsx b/app/(defaults)/account-settings/invoice/[id]/page.tsx new file mode 100644 index 0000000..09a8172 --- /dev/null +++ b/app/(defaults)/account-settings/invoice/[id]/page.tsx @@ -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(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(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 ( +
+ Loading invoice details... +
+ ); + } + + if (error || !invoice) { + return ( +
+

{error || "Invoice not found"}

+ +
+ ); + } + + return ( +
+ + {/* ===================== PRICING-PAGE BACKGROUND GLOWS ===================== */} + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + {/* ===================== PAGE CONTENT ===================== */} + +
+ + {/* Back Button */} + + + {/* Invoice Card */} +
+
+
+

INVOICE

+ +
+

Invoice No:

+

{invoice.number}

+
+
+ +
+
+
+ logo +
+
+

SOCIAL BUDDY

+

Social Media Manager

+
+
+ +

Social Buddy

+

support@socialbuddy.com

+

+1-555-SOCIAL-1

+
+
+ + {/* Invoice info */} +
+
+
+
+

Issue For:

+

{invoice.createdAt}

+
+ +

Customer ID: N/A

+

Plan: {invoice.plan}

+
+
+ +
+
+

Issue Date:

+

{invoice.issueDate}

+
+ +
+

Valid Till:

+

{invoice.validTill}

+
+ +
+

Status:

+ + {invoice.status} + +
+
+
+ + {/* Table */} +
+ + + + + + + + + + + + + {invoice.items.map((item) => ( + + + + + + + + ))} + +
S.NOPLANQTYPRICEAMOUNT
{item.sno}{item.plan}{item.qty}${item.price.toFixed(2)} + ${item.amount.toFixed(2)} +
+
+ + {/* Totals */} +
+
+
+ Subtotal: + ${invoice.subtotal.toFixed(2)} +
+ +
+ Tax: + ${invoice.tax.toFixed(2)} +
+ +
+ Grand Total: + + ${invoice.grandTotal.toFixed(2)} + +
+
+
+ + {/* Print Button */} +
+ +
+
+
+ + {/* Print Styles */} + +
+ ); +}; + +export default InvoiceDetail; \ No newline at end of file diff --git a/app/(defaults)/account-settings/page.tsx b/app/(defaults)/account-settings/page.tsx new file mode 100644 index 0000000..89781a1 --- /dev/null +++ b/app/(defaults)/account-settings/page.tsx @@ -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(null); + const [subStatus, setSubStatus] = useState(null); + const [billingInfo, setBillingInfo] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [userDetails, setUserDetails] = useState(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 ( +
+ Loading account details... +
+ ); + } + + // Removed the blocking error page so users can still see trial options even if they haven't paid yet. + + return ( +
+ {/* Background Glows by User Request */} +
+
+
+
+
+
+
+
+ +
+ {/* Header */} +
+

+ Account Settings +

+

Manage your account preferences and billing

+
+ + {/* Tabs */} +
+ + + +
+ + {/* Content */} +
+ {/* Subscription Section */} + {activeTab === "subscription" && ( + <> +
+

Subscription Details

+ + {/* 1. Active Subscription (Paid or Trial) */} + {subStatus?.isTrialActive ? ( + // CASE A: ACTIVE FREE TRIAL +
+
+
+

Free Trial Active

+ + ACTIVE + +
+

+ You are currently enjoying the 7-day free trial. +

+
+ +
+
+ Plan Type + Free Trial +
+
+ Ends On + + {subStatus?.trialEndsAt + ? new Date(subStatus.trialEndsAt).toLocaleDateString() + : "N/A"} + +
+
+ + +
+ ) : (paymentData && paymentData.planId !== 'free_trial') ? ( + // CASE B: ACTIVE PAID SUBSCRIPTION +
+
+
+

+ {paymentData?.planId ? paymentData.planId.replace(/_/g, " ").toUpperCase() : "CURRENT PLAN"} +

+ + ACTIVE + +
+

+ Your subscription is active and running. +

+
+ +
+
+ Plan Type + + {paymentData?.planId?.replace(/_/g, " ") || "N/A"} + +
+
+ Renews On + + {paymentData?.subscriptionEndDate + ? new Date(paymentData.subscriptionEndDate).toLocaleDateString() + : "N/A"} + +
+
+
+ ) : ( + // CASE C: NO ACTIVE SUBSCRIPTION +
+

You don't have an active subscription.

+ + {/* Show Activate Trial if not used yet */} + {!subStatus?.hasUsedTrial ? ( +
+

Start Your 7-Day Free Trial

+

+ Experience all premium features for free. No credit card required (optional). +

+ +
+ ) : ( +
+ Your free trial has expired. Please upgrade to continue. +
+ )} + + +
+ )} +
+ + )} + + {/* Billing Tab Content */} + {activeTab === "billing" && ( +
+
+

Billing Methods

+ + {billingInfo?.hasPaymentMethod ? ( +
+
+
+
+ {billingInfo.brand?.toUpperCase()} +
+
+

+ {billingInfo.brand?.charAt(0).toUpperCase() + billingInfo.brand?.slice(1)} ending in {billingInfo.last4} +

+

+ Expires {billingInfo.exp_month}/{billingInfo.exp_year} +

+
+
+ Default +
+
+ ) : ( +
+

No payment method saved.

+
+ )} + +
+ +

+ Redirects to secure Stripe Billing Portal +

+
+
+
+ )} + + {/* Profile Tab Content */} + {activeTab === "profile" && ( +
+
+

Profile

+
+
+ Full Name + {userDetails?.name || "N/A"} +
+
+ Email + {userDetails?.email || "N/A"} +
+
+ Phone + {userDetails?.mobileNumber || "N/A"} +
+
+
+
+ )} +
+ + {/* Billing History - Always visible */} +
+
+

Billing History

+ +
+ + + + + + + + + + + {invoices.length > 0 ? invoices.map((invoice) => ( + + + + + + + )) : ( + + + + )} + +
+ Invoice Number + + Date + + Status + + Amount +
{invoice.number}{invoice.date} + + {invoice?.status} + + + ${invoice?.amount} +
+ No billing history found. +
+
+
+
+
+
+ ); +}; + +export default AccountSettings; \ No newline at end of file diff --git a/app/(defaults)/comments-automation/page.tsx b/app/(defaults)/comments-automation/page.tsx new file mode 100644 index 0000000..cf3aa59 --- /dev/null +++ b/app/(defaults)/comments-automation/page.tsx @@ -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(20); + const [loading, setLoading] = useState(false); + const [lastRun, setLastRun] = useState(""); + const [nextRunIn, setNextRunIn] = useState(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 ( +
+ + {/* Floating background bubbles */} +
+
+
+ +
+

+ Instagram Auto Reply +

+ +
+ + setLimit(Number(e.target.value))} + /> +
+ + + +

+ * This tool uses the `instagram_manage_comments` permission to fetch your recent posts' comments and apply pre-configured automated responses. +

+ + {lastRun && ( +

+ Last executed: {lastRun} +

+ )} + +

+ ⏳ Next auto-run in:{" "} + {formatTime(nextRunIn)} +

+ +

+ Auto reply runs automatically every 1 hour. +

+
+
+ ); +}; + +export default AutomationPage; diff --git a/app/(defaults)/layout.tsx b/app/(defaults)/layout.tsx new file mode 100644 index 0000000..f30dbb9 --- /dev/null +++ b/app/(defaults)/layout.tsx @@ -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 ( + <> + + {/* BEGIN MAIN CONTAINER */} +
+ + + + {/* BEGIN APP SETTING LAUNCHER */} + {/* */} + {/* END APP SETTING LAUNCHER */} + + + {/* BEGIN SIDEBAR */} + + {/* END SIDEBAR */} +
+ {/* BEGIN TOP NAVBAR */} +
+ {/* END TOP NAVBAR */} + + {/* BEGIN CONTENT AREA */} + {children} + {/* END CONTENT AREA */} + + {/* BEGIN FOOTER */} +
+ {/* END FOOTER */} + +
+
+
+
+ + ); +} diff --git a/app/(defaults)/page.tsx b/app/(defaults)/page.tsx new file mode 100644 index 0000000..c7f90af --- /dev/null +++ b/app/(defaults)/page.tsx @@ -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(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: , + 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: , + 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: , + 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: , + 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: , + description: "Manage your profile, billing, subscription, and preferences.", + link: "/account-settings", + title: "Account configuration and settings", + status: "active" + }, + { + name: "Quick Actions", + icon: , + description: "Quick access to common tasks and time-saving features.", + link: "/quick-actions", + title: "Quick action shortcuts", + status: "active" + }, + ]; + + const benefits = [ + { + icon: , + text: "Save time managing Instagram engagement" + }, + { + icon: , + text: "Easily respond to your audience" + }, + { + icon: , + text: "Track content performance with insights" + }, + { + icon: , + text: "Complete control over your content" + } + ]; + + return ( +
+ {/* Floating background bubbles */} +
+
+
+
+ + {/* Main container */} +
+ {/* Header Section */} +
+
+
+
+
+ +
+
+
+
+
+
+

+ Social Buddy Dashboard +

+

+ Welcome back! Manage your social presence from one place. +

+
+
+ + {/* Connection Status Badge */} +
+
+
+
+

+ {connectionStatus === "connected" + ? "Instagram Connected" + : connectionStatus === "not_connected" + ? "Instagram Not Connected" + : "Checking Connection..."} +

+ {connectionStatus === "connected" && instagramUsername && ( +

@{instagramUsername}

+ )} +
+
+
+
+ + {/* Connection Status Overview */} +
+
+
+ Instagram Status + {connectionStatus === "connected" ? ( + + ) : ( + + )} +
+
+ {connectionStatus === "connected" ? "Connected" : "Not Connected"} +
+
+ +
+
+ Available Features + +
+
+ {connectionStatus === "connected" ? "All" : "Basic"} +
+
+ +
+
+ Media Access + +
+
+ {connectionStatus === "connected" ? "Enabled" : "Connect to enable"} +
+
+
+
+ + {/* Main Content Grid */} +
+ {/* Left Column - Features Grid */} +
+
+

+ + Available Features +

+

+ Connect your Instagram account to unlock all management tools. +

+ +
+ {cards.map((card) => ( + { + if (card.status === "requires-connection" && connectionStatus !== "connected") { + e.preventDefault(); + } + }} + > + {/* Lock icon for features requiring connection */} + {card.status === "requires-connection" && connectionStatus !== "connected" && ( +
+ +
+ )} + + {/* Icon */} +
+ {card.icon} +
+ + {/* Content */} +
+

+ {card.name} +

+

+ {card.description} +

+
+ + {/* Status indicator */} +
+ {card.status === "requires-connection" && connectionStatus !== "connected" ? ( + + + Connect Instagram to access + + ) : ( + Click to access + )} +
+ + + +
+
+ + {/* Hover overlay */} +
+ + ))} +
+
+ + {/* Connection Prompt */} + {connectionStatus === "not_connected" && ( +
+
+
+ +
+
+

Connect Your Instagram Account

+

+ Connect your Instagram account to access media management, insights, + and comment moderation tools. All features are user-controlled and + require manual action. +

+ + + Connect Instagram Account + +
+
+
+ )} +
+ + {/* Right Column - Sidebar */} +
+ {/* Benefits Card */} +
+

+ + What You Can Do +

+
+ {benefits.map((benefit, index) => ( +
+
+
+ {benefit.icon} +
+
+

{benefit.text}

+
+ ))} +
+
+ + {/* Connection Status Card */} +
+

Connection Overview

+
+
+
+ + Instagram Account +
+
+ {connectionStatus === "connected" ? "Connected" : + connectionStatus === "not_connected" ? "Not Connected" : + "Checking..."} +
+
+ +
+
+ Media Management + + {connectionStatus === "connected" ? "Available" : "Requires connection"} + +
+
+ Performance Insights + + {connectionStatus === "connected" ? "Available" : "Requires connection"} + +
+
+ Comment Moderation + + {connectionStatus === "connected" ? "Available" : "Requires connection"} + +
+
+ +
+ {connectionStatus === "not_connected" ? ( + + Connect Instagram Account + + ) : connectionStatus === "connected" ? ( + + Manage Connection Settings + + ) : null} +
+
+
+ + {/* Important Notes - For Meta Approval */} +
+

+ + Important Notes +

+
    +
  • +
    + +
    + All actions are user-initiated and user-controlled +
  • +
  • +
    + +
    + No automated commenting or AI-generated responses +
  • +
  • +
    + +
    + Manual moderation tools only +
  • +
+
+
+
+
+ + {/* Animation Styles */} + +
+ ); +}; + +export default DashboardPage; \ No newline at end of file diff --git a/app/(defaults)/page_advanced_ui.tsx b/app/(defaults)/page_advanced_ui.tsx new file mode 100644 index 0000000..7f3c767 --- /dev/null +++ b/app/(defaults)/page_advanced_ui.tsx @@ -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(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: , + 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: , + 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: , + 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: , + 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: , + description: "Manage your profile, billing, subscription, and preferences.", + link: "/account-settings", + title: "Account configuration and settings", + status: "active", + badge: "Settings" + }, + { + name: "Quick Actions", + icon: , + 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: , + color: "from-pink-500/20 to-pink-600/10" + }, + { + label: "Total Comments", + value: stats.comments || "-", + icon: , + color: "from-blue-500/20 to-blue-600/10" + }, + { + label: "Followers", + value: stats.followers ? `${stats.followers}` : "-", + icon: , + color: "from-green-500/20 to-green-600/10" + } + ]; + + const benefits = [ + { + icon: , + text: "Save hours weekly on social media management" + }, + { + icon: , + text: "Engage with your audience more effectively" + }, + { + icon: , + text: "Make data-driven decisions with insights" + }, + { + icon: , + text: "Complete control over your content and interactions" + } + ]; + + return ( +
+ {/* Floating background bubbles */} +
+
+
+
+ + {/* Main container */} +
+ {/* Header Section */} +
+
+
+
+
+ +
+
+
+
+
+
+

+ Social Buddy Dashboard +

+

+ Welcome back! Manage your social presence effortlessly. +

+
+
+ + {/* Connection Status Badge */} +
+
+
+
+

+ {connectionStatus === "connected" + ? "Instagram Connected" + : connectionStatus === "not_connected" + ? "Instagram Not Connected" + : "Checking Connection..."} +

+ {connectionStatus === "connected" && instagramUsername && ( +

@{instagramUsername}

+ )} +
+
+
+
+ + {/* Quick Stats */} +
+ {quickStats.map((stat, index) => ( +
+
+ {stat.label} + {stat.icon} +
+
{stat.value}
+ {connectionStatus !== "connected" && stat.value === "-" && ( +

Connect Instagram to view

+ )} +
+ ))} +
+
+ + {/* Main Content Grid */} +
+ {/* Left Column - Features Grid */} +
+
+

+ + Available Features +

+

+ All the tools you need to manage your Instagram presence efficiently. +

+ +
+ {cards.map((card) => ( + + {/* Status Badge */} +
+ {card.badge} +
+ + {/* Icon */} +
+ {card.icon} +
+ + {/* Content */} +
+

+ {card.name} +

+

+ {card.description} +

+
+ + {/* Arrow indicator */} +
+ {card.status === "requires-connection" && connectionStatus === "not_connected" ? ( + + + Connect Instagram first + + ) : ( + Click to access + )} +
+ + + +
+
+ + {/* Hover overlay */} +
+ + ))} +
+
+ + {/* Connection Prompt */} + {connectionStatus === "not_connected" && ( +
+
+
+ +
+
+

Connect Your Instagram Account

+

+ Unlock the full potential of Social Buddy by connecting your Instagram account. + Access media management, insights, and comment tools in one dashboard. +

+ + + Connect Instagram Now + +
+
+
+ )} +
+ + {/* Right Column - Sidebar */} +
+ {/* Benefits Card */} +
+

+ + Why Social Buddy? +

+
+ {benefits.map((benefit, index) => ( +
+
+
+ {benefit.icon} +
+
+

{benefit.text}

+
+ ))} +
+
+ + {/* Connection Status Card */} +
+

Connection Status

+
+
+
+ + Instagram +
+
+ {connectionStatus === "connected" ? "Connected" : + connectionStatus === "not_connected" ? "Not Connected" : + "Checking..."} +
+
+
+
+ Media Access + + {connectionStatus === "connected" ? "✓ Available" : "—"} + +
+
+ Insights Access + + {connectionStatus === "connected" ? "✓ Available" : "—"} + +
+
+ Comment Management + + {connectionStatus === "connected" ? "✓ Available" : "—"} + +
+
+ {connectionStatus === "not_connected" && ( + + Connect Instagram + + )} + {connectionStatus === "connected" && ( + + Manage Connection + + )} +
+
+ + {/* Quick Tips */} +
+

Quick Tips

+
    +
  • +
    + 1 +
    + Connect Instagram to unlock all features +
  • +
  • +
    + 2 +
    + Check your media library for post management +
  • +
  • +
    + 3 +
    + Use insights to track content performance +
  • +
+
+
+
+
+ + {/* Animation Styles */} + +
+ ); +}; + +export default DashboardPage; \ No newline at end of file diff --git a/app/(defaults)/payment/cancel/page.tsx b/app/(defaults)/payment/cancel/page.tsx new file mode 100644 index 0000000..b703bee --- /dev/null +++ b/app/(defaults)/payment/cancel/page.tsx @@ -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(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(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 ( +
+ Loading payment details... +
+ ); + } + + if (error) { + return ( +
+ {error} +
+ ); + } + + return ( +
+ {/* ❌ Error Icon */} +
+ +
+ + {/* ❌ Title */} +

+ Payment Failed ❌ +

+ + {/* ❌ Subtitle */} +

+ Unfortunately, your payment could not be processed. Please try again or + contact support if the issue persists. +

+ + {/* ❌ Card with Payment Details */} +
+
+ Transaction ID: + + {payment?.stripeSessionId} + +
+ +
+ Email: + + {payment?.email} + +
+ +
+ Plan: + + {payment?.planId} + +
+ +
+ Amount: + + ${payment?.amount / 100} + +
+ +
+ Payment Status: + + {payment?.status} + +
+ +
+ Date: + + {new Date(payment?.created_at || "").toLocaleString()} + +
+
+ + {/* ❌ Buttons */} +
+ + + +
+ + {/* ❌ Footer Text */} +

+ Don’t worry — no amount has been deducted from your account. +

+
+ ); +}; + +export default PaymentFailure; diff --git a/app/(defaults)/payment/success/page.tsx b/app/(defaults)/payment/success/page.tsx new file mode 100644 index 0000000..d432ac1 --- /dev/null +++ b/app/(defaults)/payment/success/page.tsx @@ -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(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(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 ( +
+ Loading payment details... +
+ ); + } + + if (error) { + return ( +
+ {error} +
+ ); + } + + return ( +
+ + {/* ------------------ GLOWING BUBBLES ------------------ */} +
+
+
+
+
+ + {/* Extra scattered bubbles */} +
+
+ + {/* ✅ Success Icon */} +
+ +
+ + {/* ✅ Title */} +

+ Payment Successful 🎉 +

+ + {/* ✅ Subtitle */} +

+ Thank you for your purchase! Your payment has been processed successfully. +

+ + {/* Card with Details */} +
+ +
+ Transaction ID: + + {payment?.stripeSessionId} + +
+ +
+ Email: + + {payment?.email} + +
+ +
+ Plan: + + {payment?.planId} + +
+ + {/*
+ Amount: + + ${payment?.amount} + +
*/} + +
+ Payment Status: + + {payment?.status} + +
+ +
+ Subscription: + + {payment?.startDate + ? `${new Date(payment.startDate).toLocaleDateString()} → ${new Date(payment.endDate).toLocaleDateString()}` + : "Pending activation"} + +
+
+ + {/* ✅ Button */} + + + {/* ✅ Footer Text */} +

+ You can view your payment history anytime from your dashboard. +

+
+ ); +}; + +export default PaymentSuccess; diff --git a/app/(defaults)/pricing/page.tsx b/app/(defaults)/pricing/page.tsx new file mode 100644 index 0000000..cfee411 --- /dev/null +++ b/app/(defaults)/pricing/page.tsx @@ -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 ( +
+ + {/* Background Glows */} + + {/* blue left */} +
+
+ + {/* green left */} +
+
+ + {/* pink big left */} +
+
+ + {/* small pink under card */} +
+
+ + {/* orange */} +
+
+ + {/* green right */} +
+
+ + {/* purple right */} +
+
+ + {/* yellow bottom right */} +
+
+ + {/* ===== Main Content ===== */} +
+ {/* Toggle Pricing Table */} + +
+
+ ); +}; + +export default PricingTable; diff --git a/app/(defaults)/social-media-channels/page.tsx b/app/(defaults)/social-media-channels/page.tsx new file mode 100644 index 0000000..a112c56 --- /dev/null +++ b/app/(defaults)/social-media-channels/page.tsx @@ -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([]); + const [loading, setLoading] = useState(true); + const [openModal, setOpenModal] = useState(false); + const [successMsg, setSuccessMsg] = useState(""); + const [connectedChannel, setConnectedChannel] = useState(null); + const [accountData, setAccountData] = useState(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 ( +
+ {/* Floating background bubbles */} +
+
+
+
+ + {/* Success Message */} + {successMsg && ( +
+
+
+ {successMsg.includes("success") ? ( + + ) : ( + + )} + {successMsg} + +
+
+
+ )} + + {/* Main Container */} +
+ {/* Header */} +
+
+
+
+
+ +
+ {connectedChannel && ( +
+
+
+ )} +
+
+

+ Instagram Channels +

+

+ Manage and connect your Instagram accounts +

+
+
+ + {/* Action Buttons */} +
+ {connectedChannel && accountData && ( + + )} + {!connectedChannel && ( + + )} +
+
+
+ + {/* Main Content */} + {connectedChannel && accountData ? ( +
+ {/* Left Column - Profile */} +
+ {/* Profile Card */} +
+
+ {/* Profile Image */} +
+ {accountData.username +
+ +
+
+ + {/* Profile Info */} +
+
+
+

+ {accountData.username} +

+ {accountData.name && ( +

{accountData.name}

+ )} +
+
+ + Connected + +
+
+ + {/* Stats */} +
+
+
{accountData.media_count}
+
Posts
+
+
+
{accountData.followers_count}
+
Followers
+
+
+
{accountData.follows_count}
+
Following
+
+
+ + {/* Bio */} + {accountData.biography && ( +
+

Bio

+

{accountData.biography}

+
+ )} + + {/* Website */} + {accountData.website && ( + + + {accountData.website} + + + )} +
+
+
+ + {/* Quick Actions */} +
+

+ + Quick Actions +

+
+ +
+ + Media Library +
+

View and manage your posts

+ + +
+ + Comments +
+

Manage comments & engagement

+ + +
+ + Insights +
+

View performance analytics

+ +
+
+
+ + {/* Right Column - Settings & Info */} +
+ {/* Connection Status */} +
+

Channel Status

+
+
+
+ + Instagram Account +
+
+ Connected +
+
+ +
+
+ Media Access + ✓ Available +
+
+ Comment Access + ✓ Available +
+
+ Insights Access + ✓ Available +
+
+ +
+ +

+ This will revoke Social Buddy's access to this account +

+
+
+
+ + {/* Info Card */} +
+

+ + Channel Information +

+
    +
  • + + All data is synced securely +
  • +
  • + + Manual management only +
  • +
  • + + Real-time data updates +
  • +
+
+
+
+ ) : ( + /* No Channel Connected State */ +
+
+ +
+

No Channel Connected

+

+ Connect an Instagram account to access media management, insights, and comment tools. +

+ +
+ )} +
+ + {/* Channel Selection Modal */} + {openModal && ( +
+
+ {/* Modal Header */} +
+
+
+

Connect Instagram Channel

+

Select an Instagram account to connect

+
+ +
+
+ + {/* Modal Content */} +
+ {loading ? ( +
+
+
+ ) : channels.length === 0 ? ( +
+ +

No Instagram channels available

+

Make sure your Instagram account is linked to Facebook

+
+ ) : ( +
+ {channels.map((channel) => ( +
+
+
+ +
+
+

{channel.name}

+
+ {channel.has_linked_account ? ( + <> + + Ready to connect + + ) : ( + <> + + Requires authorization + + )} +
+
+
+ +

+ {channel.has_linked_account + ? "Connect this Instagram account to access media, insights, and comments." + : "This account needs to be authorized through Facebook first." + } +

+ + +
+ ))} +
+ )} +
+ + {/* Modal Footer */} +
+
+

+ {channels.length} channel{channels.length !== 1 ? 's' : ''} available +

+ +
+
+
+
+ )} + + {/* Animation Styles */} + +
+ ); +}; + +export default SocialMediaChannels; \ No newline at end of file diff --git a/app/(defaults)/social-media-connect/page copy.tsx b/app/(defaults)/social-media-connect/page copy.tsx new file mode 100644 index 0000000..3f8239f --- /dev/null +++ b/app/(defaults)/social-media-connect/page copy.tsx @@ -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 ( +
+
+ {/* Title */} +

+ Social Buddy +

+ + {/* Subtitle */} +

+ Connect & manage your social accounts securely. +

+ + {/* Connect Button */} + + +
+
+ ); +}; + +export default SocialMediaConnect; diff --git a/app/(defaults)/social-media-connect/page.tsx b/app/(defaults)/social-media-connect/page.tsx new file mode 100644 index 0000000..8807cae --- /dev/null +++ b/app/(defaults)/social-media-connect/page.tsx @@ -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("checking"); + const [error, setError] = useState(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: , + title: "View Instagram Profile & Posts", + description: "See your Instagram profile details and browse your published posts directly inside Social Buddy." + }, + { + icon: , + title: "Track Performance Insights", + description: "Monitor key metrics like reach, impressions, and engagement to understand how your content is performing." + }, + { + icon: , + title: "Manage Comments Easily", + description: "Reply to comments, hide inappropriate messages, or remove spam — all from one centralized place." + } + ]; + + // Benefits list + const benefits = [ + { + icon: , + text: "One dashboard for posts, insights, and comments" + }, + { + icon: , + text: "Saves time managing Instagram engagement" + }, + { + icon: , + text: "Helps you respond faster to your audience" + }, + { + icon: , + text: "Designed for businesses, creators, and agencies" + } + ]; + + return ( +
+ {/* Floating background bubbles */} +
+
+
+
+ + {/* Main Container */} +
+ {/* Header Section */} +
+
+
+ +
+
+

+ Social Buddy +

+

+ Connect your Facebook and Instagram accounts to manage your posts, track performance, and handle comments — all from one simple dashboard. +

+
+ +
+ {/* Left Column - Connection Status */} +
+ {/* Connection Card */} +
+

Connection Status

+ +
+ {/* Status Display */} +
+ {status === "checking" && ( + <> +
+

Checking connection status...

+ + )} + {status === "finalizing" && ( + <> +
+

Finalizing connection...

+ + )} + {status === "connected" && ( + <> + +
+

Instagram & Facebook connected successfully

+

+ Your accounts are linked and ready to use. +

+
+ + )} + {status === "not_connected" && ( + <> +
+

No Facebook account connected

+ + )} + {status === "error" && ( + <> +
+

{error || "Connection error occurred"}

+ + )} +
+ + {/* Action Buttons */} +
+ {status === "not_connected" && ( + + )} + + {status === "connected" && ( + <> + +

+ You can disconnect and reconnect your account anytime. Disconnecting will revoke Social Buddy's access to your Instagram data. +

+ + )} +
+ + {/* Features Grid */} +
+

What You Can Do with Social Buddy

+

+ Social Buddy helps you stay on top of your Instagram presence without switching between apps. +

+ +
+ {features.map((feature, index) => ( +
+
+
+ {feature.icon} +
+
+

{feature.title}

+

{feature.description}

+
+ ))} +
+
+
+
+ + {/* Privacy & Control Section */} +
+
+ +
+

Privacy & Control

+
+

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

+
+ + No automation — you stay fully in control +
+
+
+
+
+
+ + {/* Right Column - Benefits & Info */} +
+ {/* Benefits Card */} +
+

Why Use Social Buddy?

+ +
+ {benefits.map((benefit, index) => ( +
+
+
+ {benefit.icon} +
+
+

{benefit.text}

+
+ ))} +
+ + {/* Stats Preview */} +
+
+
+
1
+
Dashboard
+
+
+
3
+
Core Features
+
+
+
+
+ + {/* Quick Start Guide */} +
+

Get Started in 3 Steps

+
    +
  1. +
    1
    + Click "Connect Instagram Account" +
  2. +
  3. +
    2
    + Log in with your Facebook account +
  4. +
  5. +
    3
    + Select your Instagram account and permissions +
  6. +
+
+ + {/* Footer Note */} +
+

+ © 2026 SocialBuddy. All rights reserved. +

+

+ Powered by MetatronCube · Privacy Policy · Terms of Use +

+
+
+
+
+ + {/* Animation Styles */} + +
+ ); +}; + +export default SocialMediaConnect; \ No newline at end of file diff --git a/app/(defaults)/social-media-posts/[id]/page.tsx b/app/(defaults)/social-media-posts/[id]/page.tsx new file mode 100644 index 0000000..7e3393d --- /dev/null +++ b/app/(defaults)/social-media-posts/[id]/page.tsx @@ -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(null); + const [comments, setComments] = useState([]); + 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 ( +
+
+
+

Loading media details...

+
+
+ ); + + if (error) return ( +
+
+
+
+ +
+

Error Loading Media

+

{error}

+
+
+
+
+
+ ); + + if (!media) return ( +
+

No media found.

+
+ ); + + return ( +
+ {/* Floating background bubbles */} +
+
+ +
+ {/* Header */} +
+ + +
+
+

Post Details

+

Manage comments and view post details

+
+ +
+ +
+
+
+ + {/* Main Content */} +
+ {/* Left Column - Media */} +
+ {/* Media Card */} +
+ {/* Media Display */} +
+ {media.media_type === "VIDEO" ? ( +
+ + {/* Media Details */} +
+ {media.caption && ( +
+

Caption

+

{media.caption}

+
+ )} + + {/* Stats Grid */} +
+
+
+ + Likes +
+
{media.like_count || 0}
+
+ +
+
+ + Comments +
+
{media.comments_count || 0}
+
+ +
+
+ + Posted +
+
+ {new Date(media.timestamp).toLocaleDateString()} +
+
+ +
+
+ + Time +
+
+ {new Date(media.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} +
+
+
+ + {/* View on Instagram Button */} + {media.permalink && ( + + + View on Instagram + + )} +
+
+ + {/* New Comment Input */} +
+

+ + Add a Comment +

+
+