feat: phase 3 redesign, game images, auth system, vm guides, service isolation
Some checks failed
Deploy Multi-VM / Deploy VM Web (push) Has been cancelled
Deploy Multi-VM / Deploy VM Auth (push) Has been cancelled
Deploy Multi-VM / Deploy Game Servers (docker-compose.fusionfall.yml, VM_FUSIONFALL_HOST, VM_FUSIONFALL_SSH_KEY, VM_FUSIONFALL_USER, fusionfall) (push) Has been cancelled
Deploy Multi-VM / Deploy Game Servers (docker-compose.maple2.yml, VM_MAPLE2_HOST, VM_MAPLE2_SSH_KEY, VM_MAPLE2_USER, maple2) (push) Has been cancelled
Deploy Multi-VM / Deploy Game Servers (docker-compose.minecraft.yml, VM_MINECRAFT_HOST, VM_MINECRAFT_SSH_KEY, VM_MINECRAFT_USER, minecraft) (push) Has been cancelled
Deploy Multi-VM / Deploy Game Servers (docker-compose.retro.yml, VM_RETRO_HOST, VM_RETRO_SSH_KEY, VM_RETRO_USER, retro) (push) Has been cancelled
Some checks failed
Deploy Multi-VM / Deploy VM Web (push) Has been cancelled
Deploy Multi-VM / Deploy VM Auth (push) Has been cancelled
Deploy Multi-VM / Deploy Game Servers (docker-compose.fusionfall.yml, VM_FUSIONFALL_HOST, VM_FUSIONFALL_SSH_KEY, VM_FUSIONFALL_USER, fusionfall) (push) Has been cancelled
Deploy Multi-VM / Deploy Game Servers (docker-compose.maple2.yml, VM_MAPLE2_HOST, VM_MAPLE2_SSH_KEY, VM_MAPLE2_USER, maple2) (push) Has been cancelled
Deploy Multi-VM / Deploy Game Servers (docker-compose.minecraft.yml, VM_MINECRAFT_HOST, VM_MINECRAFT_SSH_KEY, VM_MINECRAFT_USER, minecraft) (push) Has been cancelled
Deploy Multi-VM / Deploy Game Servers (docker-compose.retro.yml, VM_RETRO_HOST, VM_RETRO_SSH_KEY, VM_RETRO_USER, retro) (push) Has been cancelled
- Redesign all internal pages to warm/gold aesthetic (catalog, game detail, documentary, about, donate, community, guides, contact, server-status, login, profile, admin, not-found) - Add real cover images for all 4 games via Strapi CMS with getImageUrl helper - Integrate NextAuth v5 with Authentik OIDC authentication - Add new public pages: community, guides, contact, server-status - Add new protected pages: login, profile, admin dashboard - Remove legacy AFC/MercadoPago system entirely - Add Docker Compose split files for service isolation (main, auth, fusionfall, nier) - Add OpenFusion VM deployment configs (config.vm.ini, systemd service, README-VM) - Add NieR Reincarnation server guide and desktop client guide - Add architecture docs for multi-VM deployment - Add healthcheck, SSE, contact, newsletter, admin API routes - Add reusable UI components, skeleton loaders, activity feed, bookmark system - Update deployment and game server documentation
This commit is contained in:
98
apps/web/src/components/activity/ActivityFeed.tsx
Normal file
98
apps/web/src/components/activity/ActivityFeed.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { LiveIndicator } from "@/components/live/LiveIndicator";
|
||||
|
||||
interface Activity {
|
||||
id: number;
|
||||
type: string;
|
||||
details: Record<string, unknown>;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
const typeConfig: Record<string, { label: string; color: string; icon: string }> = {
|
||||
newsletter_subscribe: { label: "New subscriber", color: "text-emerald-400 bg-emerald-400/10", icon: "✉️" },
|
||||
contact_message: { label: "Contact message", color: "text-blue-400 bg-blue-400/10", icon: "💬" },
|
||||
server_online: { label: "Server online", color: "text-green-400 bg-green-400/10", icon: "🟢" },
|
||||
server_offline: { label: "Server offline", color: "text-red-400 bg-red-400/10", icon: "🔴" },
|
||||
};
|
||||
|
||||
function formatTimeAgo(iso: string) {
|
||||
const diff = Date.now() - new Date(iso).getTime();
|
||||
const minutes = Math.floor(diff / 60000);
|
||||
const hours = Math.floor(diff / 3600000);
|
||||
const days = Math.floor(diff / 86400000);
|
||||
|
||||
if (minutes < 1) return "just now";
|
||||
if (minutes < 60) return `${minutes}m ago`;
|
||||
if (hours < 24) return `${hours}h ago`;
|
||||
return `${days}d ago`;
|
||||
}
|
||||
|
||||
export function ActivityFeed() {
|
||||
const [activities, setActivities] = useState<Activity[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/activities?limit=10")
|
||||
.then((r) => r.json())
|
||||
.then((data) => {
|
||||
setActivities(data.activities || []);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto px-4 py-8">
|
||||
<div className="space-y-3">
|
||||
{Array.from({ length: 3 }).map((_, i) => (
|
||||
<div key={i} className="h-12 bg-[#1a1a24] rounded-lg animate-pulse" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (activities.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto px-4 py-8">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-sm font-semibold text-[#a0a0a8] uppercase tracking-wider">Recent Activity</h3>
|
||||
<LiveIndicator />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{activities.map((activity, i) => {
|
||||
const config = typeConfig[activity.type] || { label: activity.type, color: "text-[#a0a0a8] bg-[rgba(160,160,168,0.1)]", icon: "•" };
|
||||
const details = activity.details as Record<string, string>;
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
key={activity.id}
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: i * 0.05, duration: 0.3 }}
|
||||
className="flex items-center gap-3 px-4 py-3 rounded-lg bg-[rgba(255,255,255,0.02)] border border-[rgba(255,255,255,0.04)] hover:border-[rgba(255,255,255,0.08)] transition-colors"
|
||||
>
|
||||
<span className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center text-xs ${config.color}`}>
|
||||
{config.icon}
|
||||
</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm text-[#a0a0a8] truncate">
|
||||
<span className="font-medium">{config.label}</span>
|
||||
{details.email && <span className="text-[#6b6b75]"> — {details.email}</span>}
|
||||
{details.name && <span className="text-[#6b6b75]"> — {details.name}</span>}
|
||||
{details.server && <span className="text-[#6b6b75]"> — {details.server}</span>}
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-xs text-[#3a3a44] flex-shrink-0">{formatTimeAgo(activity.created_at)}</span>
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user