Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions contracts/broadcast/DeployVouchMe.s.sol/534351/run-1757402841.json

Large diffs are not rendered by default.

30 changes: 15 additions & 15 deletions contracts/broadcast/DeployVouchMe.s.sol/534351/run-latest.json

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions contracts/src/VouchMe.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ contract VouchMe is ERC721URIStorage {
using Strings for uint256;

uint256 private _tokenIdTracker; // Manually track token IDs
uint256 public totalProfiles; // Counter for total profiles created
uint256 public totalTestimonials; // Counter for total testimonials created

// Maps user address to their received testimonial token IDs
mapping(address => uint256[]) private _receivedTestimonials;
Expand Down Expand Up @@ -86,7 +88,9 @@ contract VouchMe is ERC721URIStorage {

// Check if there's an existing testimonial from this sender to this receiver
uint256 existingTokenId = _testimonial[senderAddress][msg.sender];
bool isUpdate = false;
if (existingTokenId != 0) {
isUpdate = true;
// Remove the existing testimonial
_removeTestimonialFromList(existingTokenId, senderAddress, msg.sender);
}
Expand Down Expand Up @@ -115,6 +119,11 @@ contract VouchMe is ERC721URIStorage {
// Update testimonial mapping
_testimonial[senderAddress][msg.sender] = newTokenId;

// Increment testimonials counter only for new testimonials (not updates)
if (!isUpdate) {
totalTestimonials++;
}

// Generate token URI
string memory tokenURI = generateTokenURI(newTokenId);
_setTokenURI(newTokenId, tokenURI);
Expand Down Expand Up @@ -163,6 +172,22 @@ contract VouchMe is ERC721URIStorage {
return _receivedTestimonials[receiver].length;
}

/**
* @dev Gets the total number of profiles created on the platform
* @return The total count of profiles
*/
function getTotalProfiles() external view returns (uint256) {
return totalProfiles;
}

/**
* @dev Gets the total number of testimonials created on the platform
* @return The total count of testimonials
*/
function getTotalTestimonials() external view returns (uint256) {
return totalTestimonials;
}

/**
* @dev Generate token URI with testimonial data
* @param tokenId The token ID
Expand Down Expand Up @@ -213,12 +238,20 @@ contract VouchMe is ERC721URIStorage {
string calldata contact,
string calldata bio
) external {
// Check if this is a new profile (first time setting)
bool isNewProfile = bytes(userProfiles[msg.sender].name).length == 0;

userProfiles[msg.sender] = Profile({
name: name,
contact: contact,
bio: bio
});

// Increment profiles counter only for new profiles
if (isNewProfile) {
totalProfiles++;
}

emit ProfileUpdated(msg.sender);
}

Expand Down
26 changes: 13 additions & 13 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"ethers": "^6.13.5",
"framer-motion": "^12.4.7",
"framer-motion": "^12.23.12",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Framer Motion upgrade: verify peer-compat and duplication with motion.

With React 19/Next 15, confirm framer-motion@^12.23.12 plays nicely and whether both framer-motion and motion are needed. If only one is used, drop the other to avoid bloat.

Run:


🏁 Script executed:

#!/bin/bash
# Check imports
rg -nP --glob 'web/src/**' -C2 'from\s+[\'"]framer-motion|from\s+[\'"]motion'

# Quick peer check (local)
node -e 'const p=require("./web/package.json");console.log({react:p.dependencies.react,next:p.dependencies.next,framer:p.dependencies["framer-motion"],motion:p.dependencies.motion})'

Length of output: 269


🏁 Script executed:

#!/usr/bin/env bash
# Search for imports of framer-motion and motion in the web/src tree
rg -nP --glob 'web/src/**/*.{js,jsx,ts,tsx}' -n -C2 "from\s+['\"](framer-motion|motion)['\"]"

# List local versions of React, Next, framer-motion, and motion (if present)
echo "Local dependencies:"
jq -r '.dependencies | to_entries[] | "\(.key): \(.value)"' web/package.json

# Show peerDependencies for [email protected]
echo "[email protected] peerDependencies:"
npm view [email protected] peerDependencies --json

Length of output: 1299


Remove unused motion dependency
All components import from framer-motion, never the standalone motion package, and [email protected]’s peerDependencies (react@^18||^19, react-dom@^18||^19) are already satisfied. Drop "motion": "^12.4.7" from web/package.json.

🤖 Prompt for AI Agents
In web/package.json at line 24, remove the unused "motion": "^12.4.7" dependency
from the dependencies list; update the package.json by deleting that entry and
then run a package manager cleanup (npm/yarn pnpm install or npm prune) and
lockfile update to ensure the dependency is removed from node_modules and the
lockfile.

"lucide-react": "^0.475.0",
"motion": "^12.4.7",
"next": "15.1.7",
Expand Down
1,496 changes: 1,495 additions & 1 deletion web/src/abis/VouchMe.json

Large diffs are not rendered by default.

18 changes: 12 additions & 6 deletions web/src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ export default function Dashboard() {
isRefreshing,
} = useWaku();

// Message constants for sharing
const TESTIMONIAL_REQUEST_MESSAGE =
"Hi! I'd love to get your feedback. You can share your testimonial here:";
const SHOWCASE_MESSAGE =
"Check out my testimonials and recommendations here:";

// State declarations - activeView must be declared before useEffect that uses it
const [testimonials, setTestimonials] = useState<Testimonial[]>([]);
const [newTestimonial, setNewTestimonial] = useState("");
Expand Down Expand Up @@ -1276,7 +1282,7 @@ export default function Dashboard() {
onClick={() => {
shareOnX(
shareableLink,
"Hi! I'd love to get your feedback. You can share your testimonial here:"
TESTIMONIAL_REQUEST_MESSAGE
);
setShowRequestShareMenu(false);
}}
Expand Down Expand Up @@ -1310,7 +1316,7 @@ export default function Dashboard() {
onClick={() => {
shareOnWhatsApp(
shareableLink,
"Hi! I'd love to get your feedback. You can share your testimonial here:"
TESTIMONIAL_REQUEST_MESSAGE
);
setShowRequestShareMenu(false);
}}
Expand All @@ -1327,7 +1333,7 @@ export default function Dashboard() {
shareViaEmail(
shareableLink,
"Request for Testimonial",
"Hi! I'd love to get your feedback. You can share your testimonial here:"
TESTIMONIAL_REQUEST_MESSAGE
);
setShowRequestShareMenu(false);
}}
Expand Down Expand Up @@ -1396,7 +1402,7 @@ export default function Dashboard() {
onClick={() => {
shareOnX(
`${baseUrl}/testimonials?address=${address}`,
"Check out my testimonials and recommendations here:"
SHOWCASE_MESSAGE
);
setShowShowcaseShareMenu(false);
}}
Expand Down Expand Up @@ -1432,7 +1438,7 @@ export default function Dashboard() {
onClick={() => {
shareOnWhatsApp(
`${baseUrl}/testimonials?address=${address}`,
"Check out my testimonials and recommendations here:"
SHOWCASE_MESSAGE
);
setShowShowcaseShareMenu(false);
}}
Expand All @@ -1449,7 +1455,7 @@ export default function Dashboard() {
shareViaEmail(
`${baseUrl}/testimonials?address=${address}`,
"My Testimonials and Recommendations",
"Check out my testimonials and recommendations here:"
SHOWCASE_MESSAGE
);
setShowShowcaseShareMenu(false);
}}
Expand Down
84 changes: 49 additions & 35 deletions web/src/components/LandingPage/FeaturesSection.tsx
Original file line number Diff line number Diff line change
@@ -1,73 +1,87 @@
import { Plus, BarChart2, Lock } from "lucide-react";
"use client";

import { UserPlus, BarChart2, Shield } from "lucide-react";
import { Card, CardContent } from "@/components/ui/card";

const FeaturesSection = () => {
const features = [
{
icon: Plus,
icon: UserPlus,
title: "Easy to Request Testimonials",
description:
"Simple one-click process to submit verified testimonials through your personalized link.",
iconBg: "bg-indigo-600",
gradient: "from-indigo-500 to-purple-600",
},
{
icon: BarChart2,
title: "Analytics Dashboard",
description:
"Track performance metrics and gain insights from your testimonial collection.",
iconBg: "bg-indigo-600",
gradient: "from-blue-400 to-blue-600",
},
{
icon: Lock,
title: "Secure Storage",
icon: Shield,
title: "Blockchain Verified",
description:
"Your testimonials are securely stored and backed up on decentralized networks.",
iconBg: "bg-indigo-600",
"Each testimonial is cryptographically signed and verified on the blockchain for authenticity.",
gradient: "from-teal-400 to-teal-600",
},
];

return (
<div id="features" className="py-24 px-8 bg-white">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-16">
<h2 className="md:text-5xl text-3xl font-bold mb-4">
VouchMe Features
<section
id="features"
className="py-20 sm:py-32 bg-gradient-to-b from-black via-gray-900 to-gray-950 relative overflow-hidden"
>
{/* Background effects */}
<div className="absolute inset-0 bg-[radial-gradient(circle_at_20%_50%,rgba(99,102,241,0.05),transparent_50%)]" />
<div className="absolute inset-0 bg-[radial-gradient(circle_at_80%_20%,rgba(20,184,166,0.03),transparent_50%)]" />

<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div className="text-center mb-16 sm:mb-20">
<h2 className="text-3xl sm:text-4xl lg:text-5xl font-bold mb-6 text-white">
VouchMe
<span className="bg-gradient-to-r from-indigo-400 to-purple-400 bg-clip-text text-transparent">
{" "}
Features
</span>
</h2>
<p className="text-gray-600 text-lg max-w-3xl mx-auto">
Everything you need to Request, verify, and showcase authentic

<p className="text-lg sm:text-xl text-gray-300 max-w-3xl mx-auto leading-relaxed">
Everything you need to request, verify, and showcase authentic
testimonials on the blockchain
</p>
</div>

<div className="grid md:grid-cols-3 gap-8">
<div className="grid md:grid-cols-3 gap-6 lg:gap-8">
{features.map((feature, index) => (
<Card
<div
key={index}
className="relative overflow-hidden transition-all duration-300 hover:shadow-xl hover:-translate-y-1 border border-gray-100 group"
className="transition-all duration-1000"
style={{ transitionDelay: `${index * 150}ms` }}
>
<CardContent className="p-6">
<div className="mb-6">
<Card className="bg-gray-900/50 border-gray-800/50 backdrop-blur-sm hover:bg-gray-900/70 hover:border-gray-700/50 transition-all duration-300 group h-full">
<CardContent className="p-6 sm:p-8">
<div
className={`${feature.iconBg} w-12 h-12 rounded-lg flex items-center justify-center text-white
transition-transform duration-300 group-hover:scale-110`}
className={`w-10 h-10 sm:w-12 sm:h-12 bg-gradient-to-br ${feature.gradient} rounded-xl flex items-center justify-center mb-6 group-hover:scale-110 transition-transform duration-200`}
>
<feature.icon size={24} />
<feature.icon className="w-5 h-5 sm:w-6 sm:h-6 text-white" />
</div>
</div>
<h3 className="text-xl font-semibold mb-3 transition-colors duration-300 group-hover:text-blue-600">
{feature.title}
</h3>
<p className="text-gray-600">{feature.description}</p>
<div
className="absolute inset-0 bg-gradient-to-r from-blue-600/5 to-purple-600/5 opacity-0
transition-opacity duration-300 group-hover:opacity-100"
/>
</CardContent>
</Card>

<h3 className="text-xl font-bold mb-4 text-white group-hover:text-indigo-400 transition-colors duration-200">
{feature.title}
</h3>

<p className="text-gray-300 leading-relaxed">
{feature.description}
</p>
</CardContent>
</Card>
</div>
))}
</div>
</div>
</div>
</section>
);
};

Expand Down
Loading