profiles init

This commit is contained in:
sewmina7@gmail.com 2024-07-13 12:51:57 +05:30
parent 37f62edfe6
commit 3f4d8167e3
9 changed files with 583 additions and 64 deletions

View File

@ -1,32 +1,56 @@
import React, { useEffect, useState } from 'react';
import { motion } from 'framer-motion';
import axios from 'axios';
interface Detail {
token: string;
gains: number;
price_at_creation: number;
price_now: number;
icon_url: string;
ca: string; // Add contract address to the Detail interface
}
const Modal: React.FC<{ isOpen: boolean; onClose: () => void; item: { username: string; points: number }; period:string; }> = ({ isOpen, onClose, item, period }) => {
const [details, setDetails] = useState<Detail[] | null>(null); // Specify null as the initial type
const Modal: React.FC<{
isOpen: boolean;
onClose: () => void;
item: { username: string; points: number; img_url: string }; // Include img_url in the item prop
period: string;
}> = ({ isOpen, onClose, item, period }) => {
const [details, setDetails] = useState<Detail[] | null>(null);
const [urls, setUrls] = useState<{ [key: string]: string }>({}); // State to store Dexscreener URLs
useEffect(() => {
if (isOpen && item) {
const fetchDetails = async () => {
try {
let urlAddition = "";
if(period == "Weekly"){
urlAddition = "_weekly";
}else if(period == "Monthly"){
urlAddition = "_monthly";
let urlAddition = '';
if (period === 'Weekly') {
urlAddition = '_weekly';
} else if (period === 'Monthly') {
urlAddition = '_monthly';
}
const response = await fetch(`https://api.callfi.io/get_user_callouts${urlAddition}.php?tag=${item.username}`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
const data: Detail[] = await response.json();
setDetails(data);
// Fetch URLs for each detail
const newUrls: { [key: string]: string } = {};
for (const detail of data) {
try {
const response = await axios.get(`https://api.dexscreener.com/latest/dex/tokens/${detail.ca}`);
if (response.data && response.data.pairs && response.data.pairs.length > 0) {
newUrls[detail.ca] = response.data.pairs[0].url;
}
} catch (error) {
console.error(`Failed to fetch URL for ${detail.ca}:`, error);
}
}
setUrls(newUrls);
} catch (error) {
console.error('Failed to fetch user details:', error);
}
@ -34,7 +58,7 @@ const Modal: React.FC<{ isOpen: boolean; onClose: () => void; item: { username:
fetchDetails();
}
}, [isOpen, item]);
}, [isOpen, item, period]);
if (!isOpen) return null;
@ -49,9 +73,9 @@ const Modal: React.FC<{ isOpen: boolean; onClose: () => void; item: { username:
<div className="relative w-auto max-w-3xl mx-auto my-6 modal">
<div className="relative flex flex-col border-0 rounded-lg shadow-lg outline-none focus:outline-none">
<div className="flex items-start justify-between p-5 border-b border-solid rounded-t border-blueGray-200">
<div className="flex items-center">
<div className="flex items-center">
<img
src={item["img_url"]} // Assuming img_url is a valid image URL
src={item.img_url}
alt={`${item.username}'s profile`}
className="w-12 h-12 rounded-full mr-4"
/>
@ -71,18 +95,30 @@ const Modal: React.FC<{ isOpen: boolean; onClose: () => void; item: { username:
{details !== null ? (
<div>
{details.map((detail) => (
<p
<a
key={detail.token}
className={`my-4 text-lg leading-relaxed ${
detail.gains > 0 ? 'text-green-500' : 'text-red-400'
}`}
href={urls[detail.ca] || '#'}
target="_blank"
rel="noopener noreferrer"
className="block"
>
{detail.token}: {parseFloat(detail.gains.toString()).toFixed(2)}x [
{parseFloat(detail.price_at_creation.toString()).toFixed(8) +
' => ' +
parseFloat(detail.price_now.toString()).toFixed(8)}
]
</p>
<p
className={`my-4 text-lg leading-relaxed ${
detail.gains > 0 ? 'text-green-500' : 'text-red-400'
}`}
>
<img
src={detail.icon_url}
alt={`${detail.token} icon`}
className="inline-block w-6 h-6 mr-2"
/>
{detail.token}: {parseFloat(detail.gains.toString()).toFixed(2)}x [
{parseFloat(detail.price_at_creation.toString()).toFixed(8) +
' => ' +
parseFloat(detail.price_now.toString()).toFixed(8)}
]
</p>
</a>
))}
</div>
) : (

View File

@ -11,6 +11,9 @@ import CalloutModal from './callout';
import { motion } from 'framer-motion';
import UserDetailModal from "@/components/UserDetailsModal";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
// Helper functions to generate pseudo data
const generateRandomName = () => {
const names = ["Alice", "Bob", "Charlie", "David", "Eva", "Frank", "Grace", "Hannah", "Ivan", "Judy"];
@ -178,42 +181,7 @@ const Home: React.FC = () => {
return (
<main className={`flex flex-col min-h-screen items-center justify-between text-white`}>
<header className="w-full flex justify-between items-center p-4 bg-gray-800 text-white glassmorphism">
<div className="flex items-center">
<h1 className="text-xl font-bold px-10">CallFi</h1>
<nav className="flex space-x-4 hidden">
<Link href="/users">
<p className="text-white hover:underline">User Ranking</p>
</Link>
</nav></div>
<div className="flex items-center">
{ready && user ? (
<div className="flex items-center bg-white bg-opacity-20 rounded-full px-4 py-2 glassmorphism">
<button className={`relative ${twitterConnected ? "bg-black" : "bg-gray-700"} hover:bg-gray-500 text-white font-bold py-2 px-4 rounded-full mr-2 glassmorphism`} onClick={linkTwitter}>
<FaTwitter className="h-5 w-5 text-white" />
{twitterConnected && (
<CheckIcon className="absolute top-0 right-0 h-4 w-4 text-green-500" />
)}
</button>
<button className={`relative ${walletConnected ? "bg-black" : "bg-gray-700"} hover:bg-gray-500 text-white font-bold py-2 px-4 rounded-full mr-2 glassmorphism`} onClick={linkWallet}>
<FaWallet className="h-5 w-5 text-white" />
{walletConnected && (
<CheckIcon className="absolute top-0 right-0 h-4 w-4 text-green-500" />
)}
</button>
<button onClick={logout} className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded glassmorphism">
Logout
</button>
</div>
) : !ready ? (
<p>Loading...</p>
) : (
<button onClick={login} className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded glassmorphism">
Login
</button>
)}
</div>
</header>
<Header/>
{ready && user?.twitter && !isJoined && (
<div className="note-card-container w-full p-4 flex flex-col items-center justify-center">
@ -285,7 +253,7 @@ const Home: React.FC = () => {
className="w-12 h-12 rounded-full mr-4"
/>
<div>
<p className="text-center font-bold" onClick={()=>openUserDetailModal(item["username"])}>{item["username"]}</p>
<Link href={`/users/${item["username"]}`}><p className="text-center font-bold">{item["username"]}</p></Link>
<p className="text-center">{parseFloat(item["points"]).toFixed(2)}x</p>
</div>
</div>
@ -295,13 +263,11 @@ const Home: React.FC = () => {
</div>
</div>
<Modal isOpen={modalOpen} onClose={closeModal} item={selectedItem ?? { username: "", points: 2 }} period={selectedPeriod} />
<Modal isOpen={modalOpen} onClose={closeModal} item={selectedItem ?? { username: "", points: 2, img_url: "" }} period={selectedPeriod} />
<CalloutModal isOpen={newCallModalOpen} onClose={closeNewCallModal} onSubmit={handleNewCallSubmit} />
<UserDetailModal isOpen={userDetailModalOpen} onClose={closeUserDetailModal} user={selectedItem ?? {username:""}} />
<footer className="w-full p-4 bg-gray-800 text-white text-center glassmorphism">
<p>&copy; 2024 CallFi. All rights reserved.</p>
</footer>
<Footer/>
</main>
);
}

265
app/users/[id]/page.tsx Normal file
View File

@ -0,0 +1,265 @@
"use client";
import React, { useEffect, useState } from 'react';
import Header from '@/components/Header';
import Footer from '@/components/Footer';
import Head from 'next/head'
import UserDetailModal from '@/components/UserDetailsModal';
import { motion } from 'framer-motion';
import CustomHeader from '@/components/CustomHeader';
import { FaArrowDown, FaArrowUp, FaIcons, FaShare } from 'react-icons/fa';
import { FaArrowLeft } from 'react-icons/fa6';
import { NextSeo } from 'next-seo';
interface UserDetail {
tag: string;
img_url: string;
email: string;
points: string;
joined_date: string;
calloutCount: number;
// Add more fields as necessary
}
interface Callout {
price_at_creation: string;
froze_price: string;
gains: string;
ca: string;
id: string;
icon_url: string;
}
export default function UserPage({ params }: { params: { id: string } }) {
const [userDetail, setUserDetail] = useState<UserDetail | null>(null);
const [callouts, setCallouts] = useState<Callout[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [activeSelected, setActiveSelected] = useState(true);
const [expandedInfo, setExpandedInfo] = useState(false);
const [weekSelected, SetWeekSelected] = useState(true);
const [allTimeSelected, SetAllTimeSelected] = useState(true);
const id = params.id;
useEffect(() => {
const fetchUserDetail = async () => {
if (!id) return;
try {
const response = await fetch(`https://api.callfi.io/get_user_data.php?username=${id}`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
setUserDetail(data);
} catch (error: any) {
setError(error.message);
} finally {
setLoading(false);
}
};
fetchUserDetail();
}, [id]);
useEffect(() => {
const fetchCalloutHistory = async () => {
try {
const response = await fetch(`https://api.callfi.io/get_callouts_history.php?username=${id}&period=${weekSelected ? 'week' : "month"}`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
setCallouts(data);
} catch (error: any) {
setError(error.message);
}
};
fetchCalloutHistory();
}, [id]);
const formatDate = (dateString: string) => {
const date = new Date(dateString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
return (
<>
<main className='flex flex-col min-h-screen text-white'>
<section className='glassmorphism'>
<div className='grid grid-cols-3 gap-4 flex w-full flex-row p-3'>
<button className='mr-3'>
<FaArrowLeft />
</button>
<h1 className='text-center'>CallFi Profile</h1>
</div>
<div className='rounded-3xl'>
<div className='grid grid-cols-3 flex-row p-2'>
<img
src={userDetail?.img_url}
alt={`${userDetail?.tag}'s profile`}
className="rounded-full w-full sm:w-36 p-5"
/>
<div className='flex flex-col p-6 px-2 col-span'>
<div>
<p className='font-bold mb-2'>{userDetail?.tag}</p>
<p className='text-sm'>500x Gains</p>
<p className='text-sm'>50 Points</p>
<p className='text-sm'>10 Followers</p>
</div>
<div className={expandedInfo ? "" : "hidden"}>
<p className='text-sm'>150 Callouts</p>
<p className='text-sm'>75% Accuracy</p>
<p className='text-sm'>200 Days</p>
</div>
</div>
<div className='flex col-span-1 justify-end m-3 mb-6 items-end'>
{expandedInfo ? (<button onClick={() => { setExpandedInfo(false) }}><FaArrowUp /></button>) : (<button onClick={() => { setExpandedInfo(true) }}><FaArrowDown /></button>)}
</div>
</div>
<div className='m-2 grid grid-cols-5 gap-2 px-2'>
<button className='glassmorphism p-1 col-span-4 rounded-xl hover:bg-blue-900'>Follow</button>
<button className='glassmorphism p-1 flex items-center justify-center rounded-xl hover:bg-blue-900'><FaShare /></button>
</div>
<div className='grid grid-cols-2 mt-4'>
<button className={`justify-center text-center ${activeSelected ? "text-blue-300 bg-opacity-20 bg-black" : " text-sm text-gray-300"}`} onClick={() => { setActiveSelected(true) }}>
<p>Active</p>
</button>
<button className={`justify-center text-center ${!activeSelected ? "text-blue-300 bg-opacity-20 bg-black" : " text-sm text-gray-300"}`} onClick={() => { setActiveSelected(false) }}>
<p>History</p>
</button>
</div>
</div>
</section>
{/* Header Ends */}
<section className={`flex w-full ${activeSelected ? "" : "hidden"}`}>
<section className="flex flex-col w-full m-2">
<div className='flex justify-center p-4'>
<div className='glassmorphism rounded-full flex p-1'>
<button className={`glassmorphism m-1 mx-1 p-1 px-5 rounded-full ${allTimeSelected ? "glassmorphism-dark" : ""}`} onClick={()=>{SetAllTimeSelected(true)}}>
All Time
</button>
<button className={`glassmorphism m-1 mx-1 p-1 px-5 rounded-full ${!allTimeSelected ? "glassmorphism-dark" : ""}`} onClick={()=>{SetAllTimeSelected(false)}}>
Timed
</button>
</div>
</div>
</section>
</section>
<div className={`${activeSelected ? "" : "hidden"}`}>
<div className={`glassmorphism rounded-full m-2 grid grid-cols-4 h-20`}>
<img
src="https://s2.coinmarketcap.com/static/img/coins/64x64/12870.png"
className="rounded-full m-2 flex"
/>
<div className='grid grid-rows-2'>
<div className='items-end justify-start text-center flex text-lg'>
COC
</div>
<p className='text-xs items-center flex justify-start'>
0x22...e3caf
</p>
</div>
<div className=' grid grid-rows-2 col-span-2 justify-end mx-5'>
<p className='items-end flex'>{"$19.15 => $100"}</p>
<p className='justify-end flex'>{"100%"}</p>
</div>
</div>
</div>
<section className={`flex flex-1 w-full ${!activeSelected ? "" : "hidden"}`}>
<section className="flex flex-col w-full m-2">
<div className='flex justify-center p-4'>
<div className='glassmorphism rounded-full w-min flex p-1'>
<button className={`glassmorphism m-1 mx-1 p-1 px-5 rounded-full ${weekSelected ? "glassmorphism-dark" : ""}`} onClick={()=>{SetWeekSelected(true)}}>
Weekly
</button>
<button className={`glassmorphism m-1 mx-1 p-1 px-5 rounded-full ${!weekSelected ? "glassmorphism-dark" : ""}`} onClick={()=>{SetWeekSelected(false)}}>
Monthly
</button>
</div>
</div>
</section>
</section>
<div className='flex-1'></div>
<Footer />
</main>
</>
);
// return (
// <main className="flex flex-col min-h-screen text-white">
// <Header />
// <section className="flex flex-1 w-full p-4">
// {loading ? (
// <p>Loading...</p>
// ) : error ? (
// <p>Error: {error}</p>
// ) : userDetail ? (
// <div>
// <div className="user-detail-pane py-10 px-20 glassmorphism rounded-lg flex flex-col items-center md:items-start md:mr-4">
// <img
// src={userDetail.img_url}
// alt={`${userDetail.tag}'s profile`}
// className="w-24 h-24 rounded-full mb-10"
// />
// <h1 className="text-2xl font-bold mb-2">{userDetail.tag}</h1>
// <p className="mb-2">Joined: {formatDate(userDetail.joined_date)}</p>
// <p className="mb-2">Callout count: {userDetail.calloutCount}</p>
// </div>
// <div className="content flex-1 p-4">
// <div className="tabs flex items-center space-x-4 mb-4 justify-center">
// {periods.map((period) => (
// <button
// key={period}
// className={`px-4 py-2 rounded ${selectedPeriod === period ? 'bg-black text-white glassmorphism-dark' : 'bg-gray-700 text-gray-400 glassmorphism'}`}
// onClick={() => setSelectedPeriod(period)}
// >
// {period}
// </button>
// ))}
// </div>
// {callouts.map((callout) => (
// <div key={callout.ca} className="leaderboard-card glassmorphism mx-auto mb-4 p-6 w-full md:w-2/3 lg:w-1/2">
// <div className="flex items-center space-x-4">
// <img src={callout.icon_url} alt="Coin Icon" className="w-16 h-16 rounded-full" />
// <div className="flex-grow">
// <p className="font-bold text-lg">{callout.id}</p>
// <p>{`$${callout.price_at_creation} => $${callout.froze_price}`}</p>
// </div>
// <div className="flex items-center">
// <p className="text-right">{`${callout.gains}%`}</p>
// </div>
// </div>
// </div>
// ))}
// </div>
// </div>
// ) : (
// <p>User not found</p>
// )}
// </section>
// <Footer />
// </main>
// );
}

85
app/users/page.tsx Normal file
View File

@ -0,0 +1,85 @@
// pages/page.tsx
"use client";
import React, { useEffect, useState } from 'react';
import { motion } from 'framer-motion';
import Link from 'next/link';
import Header from '@/components/Header';
import Footer from '@/components/Footer';
interface User {
username: string;
total_gains: string;
img_url: string;
}
const Page: React.FC = () => {
const [userData, setUserData] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch("https://api.callfi.io/get_user_list.php");
if (!response.ok) {
throw new Error("Network response was not ok");
}
const data = await response.json();
setUserData(data);
} catch (error: any) {
setError(error.message);
} finally {
setLoading(false);
}
};
// Conditionally execute useEffect on the client side only
if (typeof window !== 'undefined') {
fetchData();
}
}, []);
// Sort userData by total_gains before rendering
const sortedUserData = [...userData].sort((a, b) => parseFloat(b.total_gains) - parseFloat(a.total_gains));
return (
<>
<main className="flex flex-col min-h-screen items-center justify-between text-white">
<Header />
<section className="w-full p-4 flex flex-col items-center justify-center">
<h1 className="text-2xl font-bold mb-4">User Rankings</h1>
{loading ? (
<p>Loading...</p>
) : error ? (
<p>Error: {error}</p>
) : (
<ul className="w-full max-w-4xl">
{sortedUserData.map((user) => (
<motion.div key={user.username} whileHover={{ scale: 1.05, transition: { duration: 0.15 } }}>
<Link href={`/users/${user.username}`}>
<li className="leaderboard-card glassmorphism mx-auto mb-4 p-4 rounded-lg flex items-center">
<div className="flex items-center">
<img
src={user.img_url}
alt={`${user.username}'s profile`}
className="w-12 h-12 rounded-full mr-4"
/>
<div className="flex flex-col items-center justify-center flex-1">
<span className="text-center font-bold">{user.username}</span>
<span className="text-center">Points: {(parseFloat(user.total_gains) * 100).toFixed(2)}</span>
</div>
</div>
</li>
</Link>
</motion.div>
))}
</ul>
)}
</section>
<Footer />
</main>
</>
);
};
export default Page;

View File

@ -0,0 +1,16 @@
"use client";
import React, { useState, useEffect } from "react";
const CustomHeader: React.FC = () => {
return (
<header className="w-full flex justify-between items-center p-4 bg-gray-800 text-white glassmorphism">
<div className="flex items-center">
</div>
</header>
);
}
export default CustomHeader;

13
components/Footer.tsx Normal file
View File

@ -0,0 +1,13 @@
// components/Footer.tsx
import React from "react";
const Footer: React.FC = () => {
return (
<footer className="w-full p-4 bg-gray-800 text-white text-center glassmorphism">
<p>&copy; 2024 CallFi. All rights reserved.</p>
</footer>
);
}
export default Footer;

88
components/Header.tsx Normal file
View File

@ -0,0 +1,88 @@
// components/Header.tsx
"use client";
import React, { useState, useEffect } from "react";
import Link from 'next/link';
import { FaTwitter, FaWallet } from "react-icons/fa";
import { CheckIcon } from "@heroicons/react/outline";
import { usePrivy } from "@privy-io/react-auth";
const Header: React.FC = () => {
const { login, user, ready, logout, linkTwitter, unlinkTwitter, linkWallet, unlinkWallet } = usePrivy();
const [twitterConnected, setTwitterConnected] = useState(false);
const [walletConnected, setWalletConnected] = useState(false);
const postLoginAPI = async (usertag) => {
try {
const response = await fetch('https://api.callfi.io/register_twitter_user.php?tag=' + usertag);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
console.log('API call successful:', data);
setTwitterConnected(true);
} catch (error) {
console.error('Error during API call:', error);
}
};
useEffect(() => {
if (ready && user) {
const username = user.twitter?.username ? `@${user.twitter.username}` : '@unknownUser';
if (username !== "@unknownUser") {
postLoginAPI(username);
}
const walletStatus = user.wallet;
if (walletStatus) {
setWalletConnected(true);
}
}
}, [ready, user]);
return (
<header className="w-full flex justify-between items-center p-4 bg-gray-800 text-white glassmorphism">
<div className="flex items-center">
<h1 className="text-xl font-bold px-10">CallFi</h1>
<nav className="flex space-x-4">
<Link href="/">
<p className={` text-white hover:underline`}>Dashboard</p>
</Link>
<Link href="/users">
<p className={` text-white hover:underline`}>User Ranking</p>
</Link>
</nav>
</div>
<div className="flex items-center">
{ready && user ? (
<div className="flex items-center bg-white bg-opacity-20 rounded-full px-4 py-2 glassmorphism">
<button className={`relative ${twitterConnected ? "bg-black" : "bg-gray-700"} hover:bg-gray-500 text-white font-bold py-2 px-4 rounded-full mr-2 glassmorphism`} onClick={linkTwitter}>
<FaTwitter className="h-5 w-5 text-white" />
{twitterConnected && (
<CheckIcon className="absolute top-0 right-0 h-4 w-4 text-green-500" />
)}
</button>
<button className={`relative ${walletConnected ? "bg-black" : "bg-gray-700"} hover:bg-gray-500 text-white font-bold py-2 px-4 rounded-full mr-2 glassmorphism`} onClick={linkWallet}>
<FaWallet className="h-5 w-5 text-white" />
{walletConnected && (
<CheckIcon className="absolute top-0 right-0 h-4 w-4 text-green-500" />
)}
</button>
<button onClick={logout} className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded glassmorphism">
Logout
</button>
</div>
) : !ready ? (
<p>Loading...</p>
) : (
<button onClick={login} className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded glassmorphism">
Login
</button>
)}
</div>
</header>
);
}
export default Header;

48
package-lock.json generated
View File

@ -17,6 +17,8 @@
"framer-motion": "^11.2.12",
"lodash": "^4.17.21",
"next": "14.2.4",
"next-seo": "^6.5.0",
"next-share": "^0.27.0",
"react": "^18",
"react-dom": "^18",
"react-icons": "^5.2.1",
@ -6155,6 +6157,27 @@
"json5": "lib/cli.js"
}
},
"node_modules/jsonp": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/jsonp/-/jsonp-0.2.1.tgz",
"integrity": "sha512-pfog5gdDxPdV4eP7Kg87M8/bHgshlZ5pybl+yKxAnCZ5O7lCIn7Ixydj03wOlnDQesky2BPyA91SQ+5Y/mNwzw==",
"dependencies": {
"debug": "^2.1.3"
}
},
"node_modules/jsonp/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/jsonp/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/jsx-ast-utils": {
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
@ -6621,6 +6644,31 @@
}
}
},
"node_modules/next-seo": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/next-seo/-/next-seo-6.5.0.tgz",
"integrity": "sha512-MfzUeWTN/x/rsKp/1n0213eojO97lIl0unxqbeCY+6pAucViHDA8GSLRRcXpgjsSmBxfCFdfpu7LXbt4ANQoNQ==",
"peerDependencies": {
"next": "^8.1.1-canary.54 || >=9.0.0",
"react": ">=16.0.0",
"react-dom": ">=16.0.0"
}
},
"node_modules/next-share": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/next-share/-/next-share-0.27.0.tgz",
"integrity": "sha512-Fmfl4LIL61g10vtiDECVldxtNgVNd4OQgnI5vk9IJxn/WMZLdhniUtV15xz4PGpo8UuDdTHwmDT8R/CP60EQHA==",
"dependencies": {
"jsonp": "^0.2.1"
},
"engines": {
"node": ">=8",
"npm": ">=5"
},
"peerDependencies": {
"react": ">=17.0.2"
}
},
"node_modules/next-tick": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",

View File

@ -18,6 +18,8 @@
"framer-motion": "^11.2.12",
"lodash": "^4.17.21",
"next": "14.2.4",
"next-seo": "^6.5.0",
"next-share": "^0.27.0",
"react": "^18",
"react-dom": "^18",
"react-icons": "^5.2.1",