feat: ui/ux improvements, updates, + update dependencies

- new mobile menu
- better display and viewing for service cards
- nicer request layout
- should exit after seeding db!
- add missing link for pass
- chat viewing by admins has been disabled for open webui
This commit is contained in:
Aidan 2025-07-24 20:45:50 -07:00
parent 647932b76f
commit 59f9c709ce
9 changed files with 342 additions and 211 deletions

View file

@ -67,7 +67,7 @@ export default function About() {
</div>
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 my-3">
<Link href="https://t.me/p0ntu5">
<button className="flex flex-row items-center justify-center gap-2 bg-blue-500 text-white px-4 py-2 rounded-md w-full sm:w-auto">
<button className="flex flex-row items-center justify-center gap-2 bg-blue-500 text-white px-4 py-2 rounded-md w-full sm:w-auto cursor-pointer">
<RiTelegram2Line size={24} />
<span className="text-sm sm:text-base">
contact
@ -75,7 +75,7 @@ export default function About() {
</button>
</Link>
<Link href="https://t.me/pontushub">
<button className="flex flex-row items-center justify-center gap-2 bg-blue-500 text-white px-4 py-2 rounded-md w-full sm:w-auto">
<button className="flex flex-row items-center justify-center gap-2 bg-blue-500 text-white px-4 py-2 rounded-md w-full sm:w-auto cursor-pointer">
<RiTelegram2Line size={24} />
<span className="text-sm sm:text-base">
join channel

View file

@ -11,7 +11,7 @@ async function getChallenge() {
const challenge = await createChallenge({
hmacKey,
maxNumber: 1400000,
maxNumber: 1000000,
})
return NextResponse.json(challenge)

View file

@ -77,14 +77,21 @@ export default function Home() {
<h2 className="text-2xl sm:text-3xl font-bold text-center w-full">Services</h2>
<h3 className="text-lg sm:text-xl italic text-center w-full text-gray-600 hidden sm:block">what can we offer you?</h3>
{loading ? (
<div className="animate-pulse text-lg">Loading services...</div>
<div className="grid grid-cols-3 gap-8 sm:gap-10 my-6 sm:my-8">
{[...Array(6)].map((_, index) => (
<div key={index} className="flex flex-col items-center justify-center gap-3 animate-pulse">
<div className="w-10 h-10 sm:w-12 sm:h-12 bg-gray-200 dark:bg-gray-700 rounded-lg"></div>
<div className="w-16 h-4 sm:h-5 bg-gray-200 dark:bg-gray-700 rounded"></div>
</div>
))}
</div>
) : (
<div className="grid grid-cols-2 sm:grid-cols-3 gap-8 sm:gap-10 my-6 sm:my-8">
<div className="grid grid-cols-3 gap-8 sm:gap-10 my-6 sm:my-8">
{services.map((service) => {
const IconComponent = getServiceIcon(service.name);
return (
<div key={service.id} className="flex flex-col items-center justify-center gap-3">
<Link href={`/services/${service.name}`} className="flex flex-col items-center gap-2 hover:opacity-75 transition-opacity">
<Link href={`/services/${service.name}`} className="flex flex-col items-center gap-2 hover:opacity-75 transition-opacity duration-200">
<IconComponent size={40} className="sm:w-12 sm:h-12" />
<h3 className="text-base sm:text-lg font-bold">{service.name}</h3>
</Link>
@ -96,14 +103,14 @@ export default function Home() {
</div>
<div className="flex flex-col items-center justify-start gap-6 w-full lg:max-w-md">
<h2 className="text-2xl sm:text-3xl font-bold text-center w-full">Where we are</h2>
<h2 className="text-2xl sm:text-3xl font-bold text-center w-full">Where we operate</h2>
<h3 className="text-lg sm:text-xl italic text-center w-full text-gray-600 hidden sm:block">how can you find us?</h3>
<div className="flex flex-col items-center gap-6 mt-6">
<p className="text-base sm:text-lg text-center">
p0ntus is fully on the public internet! our servers are mainly located in the united states.
p0ntus operates fully on the internet! our servers are mainly located in the united states.
</p>
<p className="text-base sm:text-lg text-center">
we also operate servers in the united states, canada and germany.
we also operate servers located in canada and germany.
</p>
<Link href="/servers" className="flex flex-row items-center gap-2 text-base sm:text-lg text-center text-blue-500 hover:underline transition-colors">
our servers <TbArrowRight size={20} />
@ -116,10 +123,10 @@ export default function Home() {
<h3 className="text-lg sm:text-xl italic text-center w-full text-gray-600 hidden sm:block">what&apos;s the point?</h3>
<div className="flex flex-col items-center gap-6 mt-6">
<p className="text-base sm:text-lg text-center">
everything today includes microtransactions, and we were fed up with it.
everything today revolves around microtransactions, and we&apos;re fed up with it.
</p>
<p className="text-base sm:text-lg text-center">
p0ntus exists to show that it is possible to have a free and open set of services that people have fun using.
p0ntus exists to show that it&apos;s possible to have a free and open set of services that people enjoy using.
</p>
</div>
</div>

View file

@ -199,12 +199,13 @@ export default function ServiceRequests() {
return (
<main>
<Nav />
<div className="max-w-4xl mx-auto px-4 py-8">
<div className="max-w-6xl mx-auto px-4 py-8">
<div className="flex flex-row items-center justify-start gap-3 mb-8">
<TbSend size={32} className="text-blue-500" />
<h1 className="text-3xl sm:text-4xl font-bold">Service Requests</h1>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 md:gap-4">
<div className="bg-white dark:bg-gray-800 p-6 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 mb-8">
<h2 className="text-xl font-semibold mb-4">Request Service Access</h2>
<div className="bg-blue-50 dark:bg-blue-900/20 p-4 rounded-lg border border-blue-200 dark:border-blue-700 mb-6">
@ -288,7 +289,7 @@ export default function ServiceRequests() {
)}
</div>
<div className="bg-white dark:bg-gray-800 p-6 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700">
<div className="bg-white dark:bg-gray-800 p-6 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 mb-8">
<h2 className="text-xl font-semibold mb-4 flex items-center gap-2">
<TbEye className="w-5 h-5" />
My Requests
@ -350,6 +351,7 @@ export default function ServiceRequests() {
)}
</div>
</div>
</div>
</main>
);
}

View file

@ -2,7 +2,7 @@
import { Nav } from "@/components/core/nav"
import { SiForgejo, SiJellyfin, SiOllama, SiVaultwarden } from "react-icons/si"
import { TbMail, TbServer, TbTool, TbKey, TbLogin, TbSend, TbExternalLink, TbInfoCircle } from "react-icons/tb"
import { TbMail, TbServer, TbTool, TbKey, TbSend, TbExternalLink, TbInfoCircle } from "react-icons/tb"
import Link from "next/link"
import { useEffect, useState } from "react"
import { authClient } from "@/util/auth-client"
@ -155,8 +155,8 @@ export default function Services() {
return (
<Link href="/login">
<button className="flex flex-row items-center justify-center gap-1 text-white bg-blue-600 px-3 py-1.5 rounded-lg text-sm hover:bg-blue-700 transition-all duration-300 cursor-pointer">
<TbLogin size={14} />
Login
<TbSend size={14} />
Request
</button>
</Link>
);
@ -231,32 +231,35 @@ export default function Services() {
<div className="animate-pulse text-lg">Loading services...</div>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 my-4 w-full max-w-6xl mx-auto px-4">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 gap-y-14 sm:gap-y-6 my-8 w-full max-w-6xl mx-auto px-4">
{services.map((service) => {
const IconComponent = getServiceIcon(service.name);
return (
<div key={service.id} className="flex flex-col gap-4">
<div className={`flex flex-col gap-4 text-base sm:text-lg px-6 sm:px-8 py-6 sm:py-8 rounded-2xl sm:rounded-4xl transition-all ${getServiceCardColor(service)}`}>
<Link href={`/services/${service.name}`} className="hover:opacity-90 transition-opacity">
<div key={service.id} className="group">
<Link href={`/services/${service.name}`} className="block">
<div className={`flex flex-col gap-4 text-base sm:text-lg px-6 sm:px-8 py-6 sm:py-8 rounded-2xl transition-all duration-300 transform group-hover:scale-105 group-hover:shadow-xl cursor-pointer ${getServiceCardColor(service)} shadow-lg hover:shadow-2xl`}>
<div className="flex flex-row items-center gap-3">
<IconComponent size={28} className="sm:w-9 sm:h-9" />
<IconComponent size={32} className="sm:w-10 sm:h-10" />
<span className="text-xl sm:text-2xl font-bold">
{service.name === 'mail' ? 'email' : service.name}
</span>
</div>
<p className="text-sm sm:text-base opacity-90 line-clamp-2">
{service.description}
</p>
</div>
</Link>
<div className="flex flex-row mt-2 gap-3">
<div className="flex flex-row mt-4 gap-3 justify-center">
{getServiceButtonContent(service)}
<Link href={`/services/${service.name}`} target="_blank" rel="noopener noreferrer">
<button className="flex flex-row items-center justify-center gap-1 text-white bg-green-600 px-3 py-1.5 rounded-lg text-sm hover:bg-green-700 transition-all duration-300 cursor-pointer">
<Link href={`/services/${service.name}`}>
<button className="flex flex-row items-center justify-center gap-1 text-white bg-gray-600 px-3 py-1.5 rounded-lg text-sm hover:bg-gray-700 transition-all duration-300 cursor-pointer">
<TbInfoCircle size={14} />
Info
</button>
</Link>
</div>
</div>
</div>
);
})}
</div>

View file

@ -3,6 +3,8 @@
import Link from "next/link";
import { authClient } from "@/util/auth-client";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { TbMenu2, TbX } from "react-icons/tb";
interface ExtendedUser {
id: string;
@ -18,6 +20,7 @@ interface ExtendedUser {
export function Nav() {
const { data: session, isPending } = authClient.useSession();
const router = useRouter();
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const handleSignOut = async () => {
try {
@ -32,43 +35,162 @@ export function Nav() {
};
return (
<div className="flex flex-col sm:flex-row items-center justify-between px-4 sm:px-5 py-3 gap-3 sm:gap-0">
<nav className="relative">
<div className="flex items-center justify-between px-4 sm:px-5 py-3">
<Link href="/">
<h1 className="text-2xl sm:text-3xl font-bold font-mono">
p0ntus
</h1>
</Link>
<div className="flex flex-row flex-wrap items-center justify-center gap-3 sm:gap-4 text-sm sm:text-base">
<Link href="/" className="hover:underline">Home</Link>
<Link href="/about" className="hover:underline">About</Link>
<Link href="/servers" className="hover:underline">Servers</Link>
<Link href="/services" className="hover:underline">Services</Link>
<button
className="md:hidden p-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
aria-label="Toggle menu"
>
{isMobileMenuOpen ? <TbX size={24} /> : <TbMenu2 size={24} />}
</button>
<div className="hidden md:flex flex-row items-center gap-4 text-sm lg:text-base">
<Link href="/" className="hover:underline transition-colors">Home</Link>
<Link href="/about" className="hover:underline transition-colors">About</Link>
<Link href="/servers" className="hover:underline transition-colors">Servers</Link>
<Link href="/services" className="hover:underline transition-colors">Services</Link>
{isPending ? (
<div className="text-gray-500">Loading...</div>
<div className="text-gray-500 dark:text-gray-400 animate-pulse">Loading...</div>
) : session ? (
<div className="flex items-center gap-3">
<Link href="/dashboard" className="hover:underline">Dashboard</Link>
<Link href="/requests" className="hover:underline">Requests</Link>
<div className="flex items-center gap-3 lg:gap-4">
<Link href="/dashboard" className="hover:underline transition-colors">Dashboard</Link>
<Link href="/requests" className="hover:underline transition-colors">Requests</Link>
{(session.user as ExtendedUser).role === 'admin' && (
<Link href="/admin" className="hover:underline text-red-500">Admin</Link>
<Link href="/admin" className="hover:underline text-red-500 transition-colors">Admin</Link>
)}
<span className="text-foreground-muted-light ml-6">Hi, <span className="font-bold text-foreground">{session.user.name || session.user.email}</span></span>
<div className="hidden lg:flex items-center gap-3 ml-4">
<span className="text-gray-600 dark:text-gray-300">Hi, <span className="font-bold text-gray-900 dark:text-gray-100">{session.user.name || session.user.email}</span></span>
<button
onClick={handleSignOut}
className="text-red-400 hover:underline cursor-pointer"
className="text-red-400 dark:text-red-400 hover:text-red-600 dark:hover:text-red-300 hover:underline cursor-pointer transition-colors"
>
Sign Out
</button>
</div>
<div className="lg:hidden">
<button
onClick={handleSignOut}
className="text-red-400 dark:text-red-400 hover:text-red-600 dark:hover:text-red-300 hover:underline cursor-pointer transition-colors text-sm"
>
Sign Out
</button>
</div>
</div>
) : (
<div className="flex items-center gap-3">
<Link href="/login" className="hover:underline">Login</Link>
<Link href="/signup" className="bg-blue-400 text-white px-3 py-1 rounded-md hover:bg-blue-500">
<Link href="/login" className="hover:underline transition-colors">Login</Link>
<Link href="/signup" className="bg-blue-500 dark:bg-blue-600 text-white px-3 py-1.5 rounded-md hover:bg-blue-600 dark:hover:bg-blue-700 transition-colors">
Sign Up
</Link>
</div>
)}
</div>
</div>
<div className={`md:hidden transition-all duration-300 ease-in-out overflow-hidden ${
isMobileMenuOpen ? 'max-h-96 opacity-100' : 'max-h-0 opacity-0'
}`}>
<div className="px-4 py-3 bg-gray-50 dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700">
<div className="flex flex-col gap-3">
<Link
href="/"
className="hover:underline transition-colors py-1"
onClick={() => setIsMobileMenuOpen(false)}
>
Home
</Link>
<Link
href="/about"
className="hover:underline transition-colors py-1"
onClick={() => setIsMobileMenuOpen(false)}
>
About
</Link>
<Link
href="/servers"
className="hover:underline transition-colors py-1"
onClick={() => setIsMobileMenuOpen(false)}
>
Servers
</Link>
<Link
href="/services"
className="hover:underline transition-colors py-1"
onClick={() => setIsMobileMenuOpen(false)}
>
Services
</Link>
{isPending ? (
<div className="text-gray-500 dark:text-gray-400 animate-pulse py-1">Loading...</div>
) : session ? (
<div className="flex flex-col gap-3 pt-2 border-t border-gray-200 dark:border-gray-700">
<Link
href="/dashboard"
className="hover:underline transition-colors py-1"
onClick={() => setIsMobileMenuOpen(false)}
>
Dashboard
</Link>
<Link
href="/requests"
className="hover:underline transition-colors py-1"
onClick={() => setIsMobileMenuOpen(false)}
>
Requests
</Link>
{(session.user as ExtendedUser).role === 'admin' && (
<Link
href="/admin"
className="hover:underline text-red-500 transition-colors py-1"
onClick={() => setIsMobileMenuOpen(false)}
>
Admin
</Link>
)}
<div className="py-2 border-t border-gray-200 dark:border-gray-700">
<span className="text-gray-600 dark:text-gray-300 text-sm block mb-2">
Hi, <span className="font-bold text-gray-900 dark:text-gray-100">{session.user.name || session.user.email}</span>
</span>
<button
onClick={() => {
handleSignOut();
setIsMobileMenuOpen(false);
}}
className="text-red-400 dark:text-red-400 hover:text-red-600 dark:hover:text-red-300 hover:underline cursor-pointer transition-colors text-sm"
>
Sign Out
</button>
</div>
</div>
) : (
<div className="flex flex-col gap-3 pt-2 border-t border-gray-200 dark:border-gray-700">
<Link
href="/login"
className="hover:underline transition-colors py-1"
onClick={() => setIsMobileMenuOpen(false)}
>
Login
</Link>
<Link
href="/signup"
className="bg-blue-500 dark:bg-blue-600 text-white px-3 py-2 rounded-md hover:bg-blue-600 dark:hover:bg-blue-700 transition-colors text-center"
onClick={() => setIsMobileMenuOpen(false)}
>
Sign Up
</Link>
</div>
)}
</div>
</div>
</div>
</nav>
);
}

View file

@ -6,7 +6,6 @@ import {
} from "react-icons/si"
import {
TbBrowser,
TbBubbleText,
TbDeviceTv,
TbGitBranch,
TbKey,
@ -101,10 +100,6 @@ export const services = [
icon: SiOllama,
priceStatus: "invite-only",
adminView: {
"Your chats": {
icon: TbBubbleText,
description: "Your chats are visible to admins.",
},
"Your email address": {
icon: TbMail,
description: "Your email address is visible to admins.",
@ -200,6 +195,7 @@ export const services = [
description: "A private password manager. Powered by Vaultwarden.",
icon: SiVaultwarden,
priceStatus: "open",
joinLink: "https://pass.librecloud.cc",
adminView: {
"Your total entry count": {
icon: TbServer,

View file

@ -11,27 +11,27 @@
},
"dependencies": {
"@types/react-world-flags": "^1.6.0",
"altcha": "^2.0.5",
"altcha": "^2.1.0",
"altcha-lib": "^1.3.0",
"better-auth": "^1.2.12",
"drizzle-orm": "^0.44.2",
"nanoid": "^5.0.0",
"better-auth": "^1.3.3",
"drizzle-orm": "^0.44.3",
"nanoid": "^5.1.5",
"next": "15.3.4",
"postgres": "^3.4.7",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-hook-form": "^7.60.0",
"react-hook-form": "^7.61.1",
"react-icons": "^5.5.0",
"react-world-flags": "^1.6.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
"@tailwindcss/postcss": "^4.1.11",
"@types/node": "^20.19.4",
"@types/node": "^20.19.9",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"drizzle-kit": "^0.31.4",
"eslint": "^9.30.1",
"eslint": "^9.31.0",
"eslint-config-next": "15.3.4",
"tailwindcss": "^4.1.11",
"tsx": "^4.20.3",

View file

@ -19,6 +19,7 @@ async function seedDatabase() {
console.log(`✓ Added service: ${service.name}`);
}
console.log("Database seeded!");
process.exit(0);
} catch (error) {
console.error("Error seeding database:", error);
process.exit(1);