feat: add Kind Acts Pool system and allocation dashboard

Introduce community funding model and update dashboard for allocation power.

#VERCEL_SKIP

Co-authored-by: Jeff Emmett <46964190+Jeff-Emmett@users.noreply.github.com>
This commit is contained in:
v0 2025-11-24 04:43:42 +00:00
parent 0f738ffedc
commit 2fb1d767a8
8 changed files with 120 additions and 272 deletions

View File

@ -1,9 +0,0 @@
Dockerfile
.dockerignore
node_modules
npm-debug.log
.next
.git
.gitignore
README.md
nginx

View File

@ -1,136 +0,0 @@
# Deployment Instructions for dokindthings.fund
This guide will help you deploy the Kindness Fund website to your private server at dokindthings.fund.
## GitHub to Gitea Mirror Setup
This repository is configured to automatically mirror changes from GitHub to Gitea using GitHub Actions. To enable this:
1. **Go to your GitHub repository settings**: https://github.com/Jeff-Emmett/kindness-fund-website/settings/secrets/actions
2. **Add the following secrets**:
- `GITEA_USERNAME`: Your Gitea username (e.g., `jeffemmett`)
- `GITEA_TOKEN`: Your Gitea API token (the same one you used earlier: `da10d10da546ac78490140871536cf48166d5c92`)
3. **The workflow will automatically**:
- Trigger on every push to the main/master branch
- Push all branches and tags to your Gitea instance
- Can also be manually triggered from the Actions tab
This ensures bidirectional syncing between GitHub and Gitea.
## Prerequisites
- Docker and Docker Compose installed on your server
- Nginx installed and configured
- Domain `dokindthings.fund` pointing to your server's IP address
- SSL certificate (Let's Encrypt recommended)
## Step 1: Clone the Repository
On your server, clone the repository from your Gitea instance:
```bash
git clone https://gitea.jeffemmett.com/jeffemmett/kindness-fund-website.git
cd kindness-fund-website
```
## Step 2: Build and Start the Docker Container
```bash
# Create the external network if it doesn't exist
docker network create web
# Build and start the container
docker compose up -d --build
```
The application will be available on port 3001 locally.
## Step 3: Configure Nginx
Copy the Nginx configuration to your Nginx sites-available directory:
```bash
sudo cp nginx/dokindthings.fund.conf /etc/nginx/sites-available/dokindthings.fund
sudo ln -s /etc/nginx/sites-available/dokindthings.fund /etc/nginx/sites-enabled/
```
## Step 4: Set Up SSL Certificate
If you don't have an SSL certificate yet, use Let's Encrypt:
```bash
sudo certbot --nginx -d dokindthings.fund -d www.dokindthings.fund
```
This will automatically obtain and configure the SSL certificate.
If you already have certificates, update the paths in the Nginx configuration file.
## Step 5: Test and Reload Nginx
Test the Nginx configuration:
```bash
sudo nginx -t
```
If the test passes, reload Nginx:
```bash
sudo systemctl reload nginx
```
## Step 6: Verify Deployment
Visit https://dokindthings.fund in your browser to verify the site is working.
## Updating the Site
To update the site with new changes:
```bash
cd kindness-fund-website
git pull
docker compose down
docker compose up -d --build
```
## Troubleshooting
### Check Docker Container Logs
```bash
docker logs kindness-fund-website
```
### Check Nginx Logs
```bash
sudo tail -f /var/log/nginx/dokindthings.fund.error.log
sudo tail -f /var/log/nginx/dokindthings.fund.access.log
```
### Check Container Status
```bash
docker ps
```
### Rebuild Container
If you need to completely rebuild:
```bash
docker compose down
docker compose build --no-cache
docker compose up -d
```
## Notes
- The application runs on port 3001 internally
- Nginx acts as a reverse proxy, forwarding HTTPS traffic from port 443 to port 3001
- The Docker container will automatically restart unless stopped manually
- Make sure your firewall allows traffic on ports 80 and 443

View File

@ -1,50 +0,0 @@
FROM node:20-alpine AS base
# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json pnpm-lock.yaml* ./
RUN corepack enable pnpm && pnpm i --frozen-lockfile
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Disable telemetry during build
ENV NEXT_TELEMETRY_DISABLED=1
RUN corepack enable pnpm && pnpm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]

View File

@ -76,6 +76,97 @@ export default function Home() {
</div>
</section>
{/* Kind Acts Pool explanation section */}
<section className="py-24 container mx-auto px-4">
<div className="glass rounded-3xl p-8 md:p-12 relative overflow-hidden">
{/* Background gradient */}
<div className="absolute inset-0 bg-gradient-to-br from-primary/10 via-secondary/5 to-accent/10 opacity-50" />
<div className="relative z-10 space-y-8">
<div className="text-center max-w-3xl mx-auto space-y-4">
<h2 className="text-4xl md:text-5xl font-bold">
The <span className="text-primary">Kind Acts Pool</span>
</h2>
<p className="text-xl text-muted-foreground leading-relaxed">
A communal fund that anyone can contribute to, and the community collectively directs toward acts of
kindness
</p>
</div>
<div className="grid md:grid-cols-2 gap-8 max-w-5xl mx-auto">
<div className="space-y-6">
<div className="flex items-start space-x-4">
<div className="w-12 h-12 rounded-xl bg-primary/20 flex items-center justify-center flex-shrink-0">
<Zap className="w-6 h-6 text-primary" />
</div>
<div>
<h3 className="text-xl font-bold mb-2">Open Contribution</h3>
<p className="text-muted-foreground leading-relaxed">
Anyone can seed the Kind Acts Pool with funds. Every contribution grows the collective ability
to reward kindness in the community.
</p>
</div>
</div>
<div className="flex items-start space-x-4">
<div className="w-12 h-12 rounded-xl bg-secondary/20 flex items-center justify-center flex-shrink-0">
<Heart className="w-6 h-6 text-secondary" />
</div>
<div>
<h3 className="text-xl font-bold mb-2">Community Allocation</h3>
<p className="text-muted-foreground leading-relaxed">
Members direct the flow of funds to acts they find meaningful. The more acts of kindness you've
done, the greater your allocation power.
</p>
</div>
</div>
</div>
<div className="space-y-6">
<div className="flex items-start space-x-4">
<div className="w-12 h-12 rounded-xl bg-accent/20 flex items-center justify-center flex-shrink-0">
<Share2 className="w-6 h-6 text-accent" />
</div>
<div>
<h3 className="text-xl font-bold mb-2">No Account Required to Receive</h3>
<p className="text-muted-foreground leading-relaxed">
Recipients don't need to be members. They receive an email notification that #RealValue is
waiting for them as a reward for their kindness.
</p>
</div>
</div>
<div className="flex items-start space-x-4">
<div className="w-12 h-12 rounded-xl bg-primary/20 flex items-center justify-center flex-shrink-0">
<ArrowRight className="w-6 h-6 text-primary" />
</div>
<div>
<h3 className="text-xl font-bold mb-2">Earn Allocation Power</h3>
<p className="text-muted-foreground leading-relaxed">
Do more acts of kindness, gain more influence. Your history of good deeds amplifies your voice
in directing the pool to others.
</p>
</div>
</div>
</div>
</div>
<div className="pt-8 text-center">
<div className="inline-flex flex-col items-center space-y-4 glass rounded-2xl p-6 bg-white/5">
<p className="text-sm text-muted-foreground uppercase tracking-widest font-medium">
Current Pool Balance
</p>
<p className="text-5xl font-bold text-primary font-mono">$18,450</p>
<Button className="rounded-full bg-primary hover:bg-primary/90 text-background font-bold shadow-[0_0_20px_rgba(0,243,255,0.4)] transition-all hover:scale-105">
Seed the Pool
<Zap className="ml-2 h-4 w-4 fill-current" />
</Button>
</div>
</div>
</div>
</div>
</section>
{/* Submission Form Section */}
<section id="submit" className="py-24 relative">
<div className="container mx-auto px-4">

View File

@ -5,7 +5,7 @@ import { initialActs } from "@/lib/mock-data"
import { FlowVisual } from "./flow-visual"
import { Slider } from "@/components/ui/slider"
import { Button } from "@/components/ui/button"
import { ArrowUpRight, Droplets, TrendingUp } from "lucide-react"
import { ArrowUpRight, Droplets, TrendingUp, Award } from "lucide-react"
import { cn } from "@/lib/utils"
const categoryColors: Record<string, string> = {
@ -26,6 +26,8 @@ const categoryHex: Record<string, string> = {
export function AllocationDashboard() {
const [acts, setActs] = useState(initialActs)
const [userKindnessScore] = useState(3) // Number of acts done by current user
const [userAllocationPower] = useState(Math.min(100, userKindnessScore * 25)) // % of pool they can allocate
const [totalFlow, setTotalFlow] = useState(acts.reduce((acc, act) => acc + act.allocation, 0))
const handleAllocationChange = (id: string, newValue: number[]) => {
@ -38,18 +40,32 @@ export function AllocationDashboard() {
return (
<div className="w-full bg-background/50 backdrop-blur-xl rounded-3xl border border-white/10 overflow-hidden flex flex-col min-h-[800px] relative">
{/* Header / Source */}
<div className="relative z-20 p-8 border-b border-white/5 bg-black/20 text-center">
<div className="inline-flex flex-col items-center justify-center">
<div className="w-16 h-16 rounded-full bg-primary/20 flex items-center justify-center mb-4 ring-4 ring-primary/10 animate-pulse">
<Droplets className="w-8 h-8 text-primary" />
<div className="relative z-20 p-8 border-b border-white/5 bg-black/20">
<div className="flex flex-col md:flex-row items-center justify-between gap-6">
<div className="inline-flex flex-col items-center md:items-start justify-center">
<div className="w-16 h-16 rounded-full bg-primary/20 flex items-center justify-center mb-4 ring-4 ring-primary/10 animate-pulse">
<Droplets className="w-8 h-8 text-primary" />
</div>
<h2 className="text-3xl font-bold tracking-tight text-center md:text-left">Kind Acts Pool</h2>
<div className="flex items-center space-x-2 mt-2 text-muted-foreground">
<TrendingUp className="w-4 h-4" />
<span>
Total Active Flow:{" "}
<span className="text-primary font-mono font-bold">${totalFlow.toLocaleString()}</span> / hr
</span>
</div>
</div>
<h2 className="text-3xl font-bold tracking-tight">Community Stream Source</h2>
<div className="flex items-center space-x-2 mt-2 text-muted-foreground">
<TrendingUp className="w-4 h-4" />
<span>
Total Active Flow: <span className="text-primary font-mono font-bold">${totalFlow.toLocaleString()}</span>{" "}
/ hr
</span>
<div className="glass rounded-2xl p-6 bg-secondary/5 border border-secondary/20 text-center md:text-right">
<div className="flex items-center justify-center md:justify-end space-x-2 mb-2">
<Award className="w-5 h-5 text-secondary" />
<p className="text-sm text-muted-foreground font-medium">Your Allocation Power</p>
</div>
<p className="text-4xl font-bold text-secondary">{userAllocationPower}%</p>
<p className="text-xs text-muted-foreground mt-2">
Based on <span className="text-secondary font-bold">{userKindnessScore}</span> acts of kindness
</p>
<p className="text-xs text-muted-foreground mt-1 italic">Do more kindness to increase your influence</p>
</div>
</div>
</div>

View File

@ -1,17 +0,0 @@
services:
kindness-fund-website:
build:
context: .
dockerfile: Dockerfile
container_name: kindness-fund-website
restart: unless-stopped
ports:
- "3001:3000"
environment:
- NODE_ENV=production
networks:
- web
networks:
web:
external: true

View File

@ -1,13 +1,12 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
typescript: {
ignoreBuildErrors: true,
},
images: {
unoptimized: true,
},
}
export default nextConfig

View File

@ -1,46 +0,0 @@
server {
listen 80;
listen [::]:80;
server_name dokindthings.fund www.dokindthings.fund;
# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name dokindthings.fund www.dokindthings.fund;
# SSL Certificate paths (update these with your actual certificate paths)
ssl_certificate /etc/letsencrypt/live/dokindthings.fund/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dokindthings.fund/privkey.pem;
# SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Logging
access_log /var/log/nginx/dokindthings.fund.access.log;
error_log /var/log/nginx/dokindthings.fund.error.log;
# Proxy settings
location / {
proxy_pass http://localhost:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
}