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>
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 my-3"> <div className="flex flex-col sm:flex-row items-center justify-center gap-4 my-3">
<Link href="https://t.me/p0ntu5"> <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} /> <RiTelegram2Line size={24} />
<span className="text-sm sm:text-base"> <span className="text-sm sm:text-base">
contact contact
@ -75,7 +75,7 @@ export default function About() {
</button> </button>
</Link> </Link>
<Link href="https://t.me/pontushub"> <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} /> <RiTelegram2Line size={24} />
<span className="text-sm sm:text-base"> <span className="text-sm sm:text-base">
join channel join channel

View file

@ -11,7 +11,7 @@ async function getChallenge() {
const challenge = await createChallenge({ const challenge = await createChallenge({
hmacKey, hmacKey,
maxNumber: 1400000, maxNumber: 1000000,
}) })
return NextResponse.json(challenge) 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> <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> <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 ? ( {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) => { {services.map((service) => {
const IconComponent = getServiceIcon(service.name); const IconComponent = getServiceIcon(service.name);
return ( return (
<div key={service.id} className="flex flex-col items-center justify-center gap-3"> <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" /> <IconComponent size={40} className="sm:w-12 sm:h-12" />
<h3 className="text-base sm:text-lg font-bold">{service.name}</h3> <h3 className="text-base sm:text-lg font-bold">{service.name}</h3>
</Link> </Link>
@ -96,14 +103,14 @@ export default function Home() {
</div> </div>
<div className="flex flex-col items-center justify-start gap-6 w-full lg:max-w-md"> <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> <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"> <div className="flex flex-col items-center gap-6 mt-6">
<p className="text-base sm:text-lg text-center"> <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>
<p className="text-base sm:text-lg text-center"> <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> </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"> <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} /> 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> <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"> <div className="flex flex-col items-center gap-6 mt-6">
<p className="text-base sm:text-lg text-center"> <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>
<p className="text-base sm:text-lg text-center"> <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> </p>
</div> </div>
</div> </div>

View file

@ -199,12 +199,13 @@ export default function ServiceRequests() {
return ( return (
<main> <main>
<Nav /> <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"> <div className="flex flex-row items-center justify-start gap-3 mb-8">
<TbSend size={32} className="text-blue-500" /> <TbSend size={32} className="text-blue-500" />
<h1 className="text-3xl sm:text-4xl font-bold">Service Requests</h1> <h1 className="text-3xl sm:text-4xl font-bold">Service Requests</h1>
</div> </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"> <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> <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"> <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>
<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"> <h2 className="text-xl font-semibold mb-4 flex items-center gap-2">
<TbEye className="w-5 h-5" /> <TbEye className="w-5 h-5" />
My Requests My Requests
@ -350,6 +351,7 @@ export default function ServiceRequests() {
)} )}
</div> </div>
</div> </div>
</div>
</main> </main>
); );
} }

View file

@ -2,7 +2,7 @@
import { Nav } from "@/components/core/nav" import { Nav } from "@/components/core/nav"
import { SiForgejo, SiJellyfin, SiOllama, SiVaultwarden } from "react-icons/si" 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 Link from "next/link"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import { authClient } from "@/util/auth-client" import { authClient } from "@/util/auth-client"
@ -155,8 +155,8 @@ export default function Services() {
return ( return (
<Link href="/login"> <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"> <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} /> <TbSend size={14} />
Login Request
</button> </button>
</Link> </Link>
); );
@ -231,32 +231,35 @@ export default function Services() {
<div className="animate-pulse text-lg">Loading services...</div> <div className="animate-pulse text-lg">Loading services...</div>
</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) => { {services.map((service) => {
const IconComponent = getServiceIcon(service.name); const IconComponent = getServiceIcon(service.name);
return ( return (
<div key={service.id} className="flex flex-col gap-4"> <div key={service.id} className="group">
<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="block">
<Link href={`/services/${service.name}`} className="hover:opacity-90 transition-opacity"> <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"> <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"> <span className="text-xl sm:text-2xl font-bold">
{service.name === 'mail' ? 'email' : service.name} {service.name === 'mail' ? 'email' : service.name}
</span> </span>
</div> </div>
<p className="text-sm sm:text-base opacity-90 line-clamp-2">
{service.description}
</p>
</div>
</Link> </Link>
<div className="flex flex-row mt-2 gap-3"> <div className="flex flex-row mt-4 gap-3 justify-center">
{getServiceButtonContent(service)} {getServiceButtonContent(service)}
<Link href={`/services/${service.name}`} target="_blank" rel="noopener noreferrer"> <Link href={`/services/${service.name}`}>
<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"> <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} /> <TbInfoCircle size={14} />
Info Info
</button> </button>
</Link> </Link>
</div> </div>
</div> </div>
</div>
); );
})} })}
</div> </div>

View file

@ -3,6 +3,8 @@
import Link from "next/link"; import Link from "next/link";
import { authClient } from "@/util/auth-client"; import { authClient } from "@/util/auth-client";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useState } from "react";
import { TbMenu2, TbX } from "react-icons/tb";
interface ExtendedUser { interface ExtendedUser {
id: string; id: string;
@ -18,6 +20,7 @@ interface ExtendedUser {
export function Nav() { export function Nav() {
const { data: session, isPending } = authClient.useSession(); const { data: session, isPending } = authClient.useSession();
const router = useRouter(); const router = useRouter();
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const handleSignOut = async () => { const handleSignOut = async () => {
try { try {
@ -32,43 +35,162 @@ export function Nav() {
}; };
return ( 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="/"> <Link href="/">
<h1 className="text-2xl sm:text-3xl font-bold font-mono"> <h1 className="text-2xl sm:text-3xl font-bold font-mono">
p0ntus p0ntus
</h1> </h1>
</Link> </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> <button
<Link href="/about" className="hover:underline">About</Link> className="md:hidden p-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
<Link href="/servers" className="hover:underline">Servers</Link> onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
<Link href="/services" className="hover:underline">Services</Link> 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 ? ( {isPending ? (
<div className="text-gray-500">Loading...</div> <div className="text-gray-500 dark:text-gray-400 animate-pulse">Loading...</div>
) : session ? ( ) : session ? (
<div className="flex items-center gap-3"> <div className="flex items-center gap-3 lg:gap-4">
<Link href="/dashboard" className="hover:underline">Dashboard</Link> <Link href="/dashboard" className="hover:underline transition-colors">Dashboard</Link>
<Link href="/requests" className="hover:underline">Requests</Link> <Link href="/requests" className="hover:underline transition-colors">Requests</Link>
{(session.user as ExtendedUser).role === 'admin' && ( {(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 <button
onClick={handleSignOut} 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 Sign Out
</button> </button>
</div> </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"> <div className="flex items-center gap-3">
<Link href="/login" className="hover:underline">Login</Link> <Link href="/login" className="hover:underline transition-colors">Login</Link>
<Link href="/signup" className="bg-blue-400 text-white px-3 py-1 rounded-md hover:bg-blue-500"> <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 Sign Up
</Link> </Link>
</div> </div>
)} )}
</div> </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" } from "react-icons/si"
import { import {
TbBrowser, TbBrowser,
TbBubbleText,
TbDeviceTv, TbDeviceTv,
TbGitBranch, TbGitBranch,
TbKey, TbKey,
@ -101,10 +100,6 @@ export const services = [
icon: SiOllama, icon: SiOllama,
priceStatus: "invite-only", priceStatus: "invite-only",
adminView: { adminView: {
"Your chats": {
icon: TbBubbleText,
description: "Your chats are visible to admins.",
},
"Your email address": { "Your email address": {
icon: TbMail, icon: TbMail,
description: "Your email address is visible to admins.", description: "Your email address is visible to admins.",
@ -200,6 +195,7 @@ export const services = [
description: "A private password manager. Powered by Vaultwarden.", description: "A private password manager. Powered by Vaultwarden.",
icon: SiVaultwarden, icon: SiVaultwarden,
priceStatus: "open", priceStatus: "open",
joinLink: "https://pass.librecloud.cc",
adminView: { adminView: {
"Your total entry count": { "Your total entry count": {
icon: TbServer, icon: TbServer,

View file

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

View file

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