Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
53 changes: 50 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,51 @@
<div name="readme-top"></div>

<!-- Organization Logo -->
<div align="center" style="display: flex; align-items: center; justify-content: center; gap: 16px;">
<img alt="Stability Nexus" src="web/public/stability.svg" width="175">
<img src="public/todo-project-logo.svg" width="175" />
</div>
Comment on lines +4 to +7
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add alt text for the secondary logo image.

Line 6 renders an <img> without an alt attribute, tripping markdownlint MD045 and breaking screen-reader accessibility. Please add a meaningful alt (or alt="" if it’s purely decorative).

-  <img src="public/todo-project-logo.svg" width="175" />
+  <img alt="VouchMe logo" src="public/todo-project-logo.svg" width="175" />

Based on static analysis hints.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div align="center" style="display: flex; align-items: center; justify-content: center; gap: 16px;">
<img alt="Stability Nexus" src="web/public/stability.svg" width="175">
<img src="public/todo-project-logo.svg" width="175" />
</div>
<div align="center" style="display: flex; align-items: center; justify-content: center; gap: 16px;">
<img alt="Stability Nexus" src="web/public/stability.svg" width="175">
<img alt="VouchMe logo" src="public/todo-project-logo.svg" width="175" />
</div>
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

6-6: Images should have alternate text (alt text)

(MD045, no-alt-text)

🤖 Prompt for AI Agents
In README.md around lines 4 to 7 the second <img> tag lacks an alt attribute
causing markdownlint MD045 and accessibility issues; add an appropriate alt
attribute to the second image (e.g., alt="Todo Project logo") or use alt="" if
the graphic is purely decorative, ensuring the tag matches the existing
style/width attributes.


&nbsp;

<!-- Organization Name -->
<div align="center">

[![Static Badge](https://img.shields.io/badge/Stability_Nexus-/TODO-228B22?style=for-the-badge&labelColor=FFC517)](https://TODO.stability.nexus/)

<!-- Correct deployed url to be added -->

</div>

<!-- Organization/Project Social Handles -->
<p align="center">
<!-- Telegram -->
<a href="https://t.me/StabilityNexus">
<img src="https://img.shields.io/badge/Telegram-black?style=flat&logo=telegram&logoColor=white&logoSize=auto&color=24A1DE" alt="Telegram Badge"/></a>
&nbsp;&nbsp;
<!-- X (formerly Twitter) -->
<a href="https://x.com/StabilityNexus">
<img src="https://img.shields.io/twitter/follow/StabilityNexus" alt="X (formerly Twitter) Badge"/></a>
&nbsp;&nbsp;
<!-- Discord -->
<a href="https://discord.gg/YzDKeEfWtS">
<img src="https://img.shields.io/discord/995968619034984528?style=flat&logo=discord&logoColor=white&logoSize=auto&label=Discord&labelColor=5865F2&color=57F287" alt="Discord Badge"/></a>
&nbsp;&nbsp;
<!-- Medium -->
<a href="https://news.stability.nexus/">
<img src="https://img.shields.io/badge/Medium-black?style=flat&logo=medium&logoColor=black&logoSize=auto&color=white" alt="Medium Badge"></a>
&nbsp;&nbsp;
<!-- LinkedIn -->
<a href="https://linkedin.com/company/stability-nexus">
<img src="https://img.shields.io/badge/LinkedIn-black?style=flat&logo=LinkedIn&logoColor=white&logoSize=auto&color=0A66C2" alt="LinkedIn Badge"></a>
&nbsp;&nbsp;
<!-- Youtube -->
<a href="https://www.youtube.com/@StabilityNexus">
<img src="https://img.shields.io/youtube/channel/subscribers/UCZOG4YhFQdlGaLugr_e5BKw?style=flat&logo=youtube&logoColor=white&logoSize=auto&labelColor=FF0000&color=FF0000" alt="Youtube Badge"></a>
</p>

---

# VouchMe

VouchMe is a blockchain-based testimonial system that enables users to provide testimonials securely and transparently, building a transparent and verifiable reputation system.
Expand Down Expand Up @@ -43,7 +91,7 @@ Follow these steps to set up VouchMe locally:
2. **Install Dependencies**

```bash
npm install
npm install
```

3. **Setup Environment Variables**
Expand All @@ -58,8 +106,7 @@ Follow these steps to set up VouchMe locally:
4. **Run the Development Server**

```bash
npm run dev
npm run dev
```

The app will be available at `http://localhost:3000/`.

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.

46 changes: 46 additions & 0 deletions contracts/broadcast/DeployVouchMe.s.sol/61/run-1759575443.json

Large diffs are not rendered by default.

28 changes: 14 additions & 14 deletions contracts/broadcast/DeployVouchMe.s.sol/61/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
14 changes: 14 additions & 0 deletions web/public/stability.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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