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:
parent
0f738ffedc
commit
2fb1d767a8
|
|
@ -1,9 +0,0 @@
|
|||
Dockerfile
|
||||
.dockerignore
|
||||
node_modules
|
||||
npm-debug.log
|
||||
.next
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
nginx
|
||||
136
DEPLOYMENT.md
136
DEPLOYMENT.md
|
|
@ -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
|
||||
50
Dockerfile
50
Dockerfile
|
|
@ -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"]
|
||||
91
app/page.tsx
91
app/page.tsx
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: 'standalone',
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
|
|
@ -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;
|
||||
}
|
||||
Loading…
Reference in New Issue