initial commit

This commit is contained in:
Aidan 2025-07-02 20:05:23 -04:00
commit 0f94d1300e
18 changed files with 947 additions and 0 deletions

44
.gitignore vendored Normal file
View file

@ -0,0 +1,44 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# bun
bun.lock*

24
LICENSE Normal file
View file

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org/>

36
README.md Normal file
View file

@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.

85
app/about/page.tsx Normal file
View file

@ -0,0 +1,85 @@
import { Nav } from "@/components/core/nav";
import { GiStoneWheel } from "react-icons/gi";
import { TbUserSquareRounded } from "react-icons/tb";
import { RiTelegram2Line } from "react-icons/ri";
export default function About() {
return (
<main>
<Nav />
<div className="flex flex-col items-center justify-between gap-3 my-20">
<div className="flex flex-row items-center justify-between gap-2">
<TbUserSquareRounded size={36} />
<h1 className="text-4xl font-bold">
About
</h1>
</div>
</div>
<div className="flex flex-col items-center justify-between gap-3">
<h2 className="text-2xl font-semibold text-center w-full flex flex-wrap items-center justify-center">
p0ntus is a small team of developers working towards
<span className="bg-red-400 text-white rounded-full italic ml-2 px-3 pr-4 py-1">one goal</span>.
</h2>
<h2 className="text-xl text-center w-full flex flex-wrap items-center justify-center">
we want to make the cloud accessible to <span className="ml-1 italic">everyone</span>.
</h2>
<h2 className="text-lg text-center w-full flex flex-wrap items-center justify-center">
no corporate sponsors, no closed source, no microtransactions.
</h2>
</div>
<div className="flex flex-col items-center justify-between gap-3 my-20">
<div className="flex flex-col items-center justify-between gap-3">
<GiStoneWheel size={60} />
<h2 className="text-3xl font-semibold text-center w-full flex flex-wrap items-center justify-center">
we don&apos;t reinvent the wheel,
</h2>
<h2 className="text-2xl text-center w-full flex flex-wrap items-center justify-center">
but we get the job done.
</h2>
</div>
<div className="flex flex-col items-center justify-between gap-3 my-2 max-w-3xl">
<p className="text-md text-center w-full flex flex-wrap items-center justify-center">
we put effort into finding, creating, and building on the best tools avaliable to bring the magic of the cloud to you.
</p>
<p className="text-md text-center w-full flex flex-wrap items-center justify-center">
we believe using cloud services is <span className="ml-1 italic">more than just a way to store your data.</span>
<span className="font-bold">your experience should be valued.</span>
</p>
</div>
</div>
<div className="flex flex-col items-center justify-between gap-3 my-20">
<div className="flex flex-col items-center justify-between gap-3">
<RiTelegram2Line size={60} />
<h2 className="text-3xl font-semibold text-center w-full flex flex-wrap items-center justify-center">
let&apos;s talk.
</h2>
<div className="flex flex-col items-center justify-between gap-2">
<p className="text-md text-center w-full flex flex-wrap items-center justify-center">
we&apos;re always looking for new people to help out.
</p>
<p className="text-md text-center w-full flex flex-wrap items-center justify-center">
we&apos;re here for everything else, too! account support, deployment, service, and more.
</p>
<p className="text-md text-center w-full flex flex-wrap items-center justify-center">
join us on telegram for support, questions, chatting, and more.
</p>
</div>
<div className="flex flex-row items-center justify-center gap-4 my-3">
<button className="flex flex-row items-center justify-center gap-2 bg-blue-500 text-white px-4 py-2 rounded-md">
<RiTelegram2Line size={24} />
<span className="text-md">
contact
</span>
</button>
<button className="flex flex-row items-center justify-center gap-2 bg-blue-500 text-white px-4 py-2 rounded-md">
<RiTelegram2Line size={24} />
<span className="text-md">
join group
</span>
</button>
</div>
</div>
</div>
</main>
);
}

26
app/globals.css Normal file
View file

@ -0,0 +1,26 @@
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: var(--font-geist-sans);
}

34
app/layout.tsx Normal file
View file

@ -0,0 +1,34 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "p0ntus",
description: "p0ntus is a free and open set of services for the public",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
</body>
</html>
);
}

94
app/page.tsx Normal file
View file

@ -0,0 +1,94 @@
import Link from "next/link";
import { SiForgejo, SiJellyfin, SiOllama } from "react-icons/si";
import { TbMail, TbKey, TbServer, TbArrowRight } from "react-icons/tb";
import { Nav } from "@/components/core/nav";
export default function Home() {
return (
<main>
<Nav />
<div className="flex flex-col items-center justify-between gap-3 my-20">
<h1 className="text-4xl font-bold">
p0ntus
</h1>
<h3 className="text-2xl">
open source at your fingertips
</h3>
</div>
<hr className="border-black mt-24 mb-24" />
<div className="max-w-6xl mx-auto w-full px-4 md:px-10">
<div className="grid grid-cols-1 md:grid-cols-3 gap-x-38 gap-y-16">
<div className="flex flex-col items-center justify-start gap-6 h-full">
<h2 className="text-3xl font-bold text-center w-full whitespace-nowrap">Services</h2>
<h3 className="text-xl italic text-center w-full">what can we offer you?</h3>
<div className="grid grid-cols-3 gap-10 my-8">
<div className="flex flex-col items-center justify-center gap-3">
<Link href="/services/git" className="flex flex-col items-center gap-2">
<SiForgejo size={50} />
<h3 className="text-lg font-bold">git</h3>
</Link>
</div>
<div className="flex flex-col items-center justify-center gap-3">
<Link href="/services/mail" className="flex flex-col items-center gap-2">
<TbMail size={50} />
<h3 className="text-lg font-bold">mail</h3>
</Link>
</div>
<div className="flex flex-col items-center justify-center gap-3">
<Link href="/services/ai" className="flex flex-col items-center gap-2">
<SiOllama size={50} />
<h3 className="text-lg font-bold">ai</h3>
</Link>
</div>
<div className="flex flex-col items-center justify-center gap-3">
<Link href="/services/tv" className="flex flex-col items-center gap-2">
<SiJellyfin size={50} />
<h3 className="text-lg font-bold">tv</h3>
</Link>
</div>
<div className="flex flex-col items-center justify-center gap-3">
<Link href="/services/keybox" className="flex flex-col items-center gap-2">
<TbKey size={50} />
<h3 className="text-lg font-bold">keybox</h3>
</Link>
</div>
<div className="flex flex-col items-center justify-center gap-3">
<Link href="/services/keybox" className="flex flex-col items-center gap-2">
<TbServer size={50} />
<h3 className="text-lg font-bold">hosting</h3>
</Link>
</div>
</div>
</div>
<div className="flex flex-col items-center justify-start gap-6 h-full">
<h2 className="text-3xl font-bold text-center w-full whitespace-nowrap">Where we are</h2>
<h3 className="text-xl italic text-center w-full">how can you find us?</h3>
<div className="flex flex-col items-center gap-6 mt-6">
<p className="text-lg text-center">
p0ntus is fully on the public internet! our servers are mainly located in the united states.
</p>
<p className="text-lg text-center">
we also operate servers in the united states, canada and germany.
</p>
<Link href="/servers" className="flex flex-row items-center gap-2 text-lg text-center text-blue-500 hover:underline">
our servers <TbArrowRight size={20} />
</Link>
</div>
</div>
<div className="flex flex-col items-center justify-start gap-6 h-full">
<h2 className="text-3xl font-bold text-center w-full whitespace-nowrap">Why is p0ntus free?</h2>
<h3 className="text-xl italic text-center w-full">what&apos;s the point?</h3>
<div className="flex flex-col items-center gap-6 mt-6">
<p className="text-lg text-center">
everything today includes microtransactions, and we were fed up with it.
</p>
<p className="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.
</p>
</div>
</div>
</div>
</div>
</main>
);
}

130
app/servers/page.tsx Normal file
View file

@ -0,0 +1,130 @@
import { Nav } from "@/components/core/nav";
import { TbServer } from "react-icons/tb";
import Flag from 'react-world-flags';
export default function Servers() {
return (
<main>
<Nav />
<div className="flex flex-col items-center justify-between gap-3 my-20">
<div className="flex flex-row items-center justify-between gap-2">
<TbServer size={36} />
<h1 className="text-4xl font-bold">
servers and infrastructure
</h1>
</div>
</div>
<div className="flex flex-col items-center justify-between gap-3 my-20">
<h2 className="text-2xl font-semibold text-center w-full flex flex-wrap items-center justify-center">
where we host out of
</h2>
<div className="grid grid-cols-3 gap-4 my-4">
<p className="flex flex-row items-center justify-between gap-2 text-lg bg-blue-400 text-white px-4 py-2 rounded-full">
<Flag code="US" className="w-6 h-6" /> usa
</p>
<p className="flex flex-row items-center justify-between gap-2 text-lg bg-red-400 text-white px-4 py-2 rounded-full">
<Flag code="CA" className="w-6 h-6" /> canada
</p>
<p className="flex flex-row items-center justify-between gap-2 text-lg bg-orange-400 text-white px-4 py-2 rounded-full">
<Flag code="DE" className="mr-4 w-6 h-6" /> germany
</p>
</div>
</div>
<div className="flex flex-col items-center justify-between gap-3">
<h2 className="text-2xl font-semibold text-center w-full flex flex-wrap items-center justify-center">
hardware
</h2>
<div className="grid grid-cols-3 gap-4 my-4 w-5xl">
<div className="flex flex-col gap-2 text-lg bg-blue-400 text-white px-8 py-8 rounded-4xl">
<div className="flex flex-row items-center justify-between gap-2">
<TbServer size={36} />
<span className="text-2xl font-bold">
NY-1
</span>
</div>
<p className="flex flex-col text-sm my-2 gap-4">
<span><span className="font-bold">CPU:</span> 2x Intel Xeon E5-2699 v4 @ 3.60 GHz</span>
<span><span className="font-bold">RAM:</span> 256GB (8x Samsung 32GB DDR4)</span>
<span><span className="font-bold">Boot Drive:</span> Samsung Evo 850 250GB</span>
<span><span className="font-bold">Storage:</span> HP FX900 Pro 4TB NVMe</span>
<span><span className="font-bold">Bandwidth:</span> 40TB</span>
<span><span className="font-bold">Location:</span> Buffalo, New York, USA</span>
<span><span className="font-bold">Provider:</span> ColoCrossing</span>
</p>
</div>
<div className="flex flex-col gap-2 text-lg bg-red-400 text-white px-8 py-8 rounded-4xl">
<div className="flex flex-row items-center justify-between gap-2">
<TbServer size={36} />
<span className="text-2xl font-bold">
CA-1
</span>
</div>
<p className="flex flex-col text-sm my-2 gap-4">
<span><span className="font-bold">CPU:</span> 2 cores shared</span>
<span><span className="font-bold">RAM:</span> 2GB</span>
<span><span className="font-bold">Disk:</span> 3.5TB Raidz2</span>
<span><span className="font-bold">Storage:</span> HP FX900 Pro 4TB NVMe</span>
<span><span className="font-bold">Bandwidth:</span> Unlimited @ 250Mbps</span>
<span><span className="font-bold">Location:</span> Montreal, Canada</span>
<span><span className="font-bold">Provider:</span> Serverica</span>
</p>
</div>
<div className="flex flex-col gap-2 text-lg bg-orange-400 text-white px-8 py-8 rounded-4xl">
<div className="flex flex-row items-center justify-between gap-2">
<TbServer size={36} />
<span className="text-2xl font-bold">
DE-1
</span>
</div>
<p className="flex flex-col text-sm my-2 gap-4">
<span><span className="font-bold">CPU:</span> 1vCPU AMD EPYC</span>
<span><span className="font-bold">RAM:</span> 1GB</span>
<span><span className="font-bold">Storage:</span> 153GB</span>
<span><span className="font-bold">Location:</span> Frankfurt, Germany</span>
<span><span className="font-bold">Bandwidth:</span> Unlimited</span>
<span><span className="font-bold">Provider:</span> Oracle Cloud</span>
</p>
</div>
<div className="flex flex-col gap-2 text-lg bg-orange-400 text-white px-8 py-8 rounded-4xl">
<div className="flex flex-row items-center justify-between gap-2">
<TbServer size={36} />
<span className="text-2xl font-bold">
DE-2
</span>
</div>
<p className="flex flex-col text-sm my-2 gap-4">
<span><span className="font-bold">CPU:</span> 1vCPU AMD EPYC</span>
<span><span className="font-bold">RAM:</span> 1GB</span>
<span><span className="font-bold">Storage:</span> 47GB</span>
<span><span className="font-bold">Location:</span> Frankfurt, Germany</span>
<span><span className="font-bold">Bandwidth:</span> Unlimited</span>
<span><span className="font-bold">Provider:</span> Oracle Cloud</span>
</p>
</div>
</div>
<div className="flex flex-col items-center justify-between gap-3 my-20">
<h2 className="text-2xl font-semibold text-center w-full flex flex-wrap items-center justify-center">
our ip addresses
</h2>
<p className="text-center text-md my-2">
if you own a mail server/service, please consider whitelisting our ip addresses.
</p>
<div className="grid grid-cols-3 gap-4 my-4">
<p className="flex flex-row items-center gap-2 text-lg bg-blue-400 text-white px-4 py-2 rounded-full">
<Flag code="US" className="w-6 h-6" /> <span className="font-bold">NY1:</span> 192.3.178.206
</p>
<p className="flex flex-row items-center gap-2 text-lg bg-red-400 text-white px-4 py-2 rounded-full">
<Flag code="CA" className="w-6 h-6" /> <span className="font-bold">CA1:</span> 209.209.9.109
</p>
<p className="flex flex-row items-center gap-2 text-lg bg-orange-400 text-white px-4 py-2 rounded-full">
<Flag code="DE" className="w-6 h-6" /> <span className="font-bold">DE1:</span> 138.2.154.209
</p>
<p className="flex flex-row items-center gap-2 text-lg bg-orange-400 text-white px-4 py-2 rounded-full">
<Flag code="DE" className="w-6 h-6" /> <span className="font-bold">DE2:</span> 158.180.60.92
</p>
</div>
</div>
</div>
</main>
);
}

View file

@ -0,0 +1,12 @@
"use client"
import { ServicesShell } from "@/components/front/services"
import { useParams } from "next/navigation"
export default function Service() {
const slug = useParams().slug
return (
<ServicesShell slug={slug as string} />
)
}

87
app/services/page.tsx Normal file
View file

@ -0,0 +1,87 @@
import { Nav } from "@/components/core/nav"
import { SiForgejo, SiJellyfin, SiOllama } from "react-icons/si"
import { TbKey, TbMail, TbServer, TbTool } from "react-icons/tb"
import Link from "next/link"
export default function Services() {
return (
<main>
<Nav />
<div className="flex flex-col items-center justify-between gap-10 my-16">
<div className="flex flex-row items-center justify-between gap-2">
<TbTool size={36} />
<h1 className="text-4xl font-bold">
services
</h1>
</div>
<div className="flex flex-col items-center justify-between gap-2">
<h2 className="text-3xl font-light text-center w-full flex flex-wrap items-center justify-center">
please select a service.
</h2>
</div>
</div>
<div className="grid grid-cols-4 gap-4 my-4 w-3/4 mx-auto">
<Link href="/services/git">
<div className="flex flex-col gap-2 text-lg bg-blue-400 text-white px-8 py-8 rounded-4xl">
<div className="flex flex-row items-center justify-between gap-2">
<SiForgejo size={36} />
<span className="text-2xl font-bold">
git
</span>
</div>
</div>
</Link>
<Link href="/services/mail">
<div className="flex flex-col gap-2 text-lg bg-blue-400 text-white px-8 py-8 rounded-4xl">
<div className="flex flex-row items-center justify-between gap-2">
<TbMail size={36} />
<span className="text-2xl font-bold">
email
</span>
</div>
</div>
</Link>
<Link href="/services/ai">
<div className="flex flex-col gap-2 text-lg bg-blue-400 text-white px-8 py-8 rounded-4xl">
<div className="flex flex-row items-center justify-between gap-2">
<SiOllama size={36} />
<span className="text-2xl font-bold">
ai
</span>
</div>
</div>
</Link>
<Link href="/services/tv">
<div className="flex flex-col gap-2 text-lg bg-blue-400 text-white px-8 py-8 rounded-4xl">
<div className="flex flex-row items-center justify-between gap-2">
<SiJellyfin size={36} />
<span className="text-2xl font-bold">
tv
</span>
</div>
</div>
</Link>
<Link href="/services/keybox">
<div className="flex flex-col gap-2 text-lg bg-blue-400 text-white px-8 py-8 rounded-4xl">
<div className="flex flex-row items-center justify-between gap-2">
<TbKey size={36} />
<span className="text-2xl font-bold">
keybox
</span>
</div>
</div>
</Link>
<Link href="/services/hosting">
<div className="flex flex-col gap-2 text-lg bg-blue-400 text-white px-8 py-8 rounded-4xl">
<div className="flex flex-row items-center justify-between gap-2">
<TbServer size={36} />
<span className="text-2xl font-bold">
hosting
</span>
</div>
</div>
</Link>
</div>
</main>
)
}

19
components/core/nav.tsx Normal file
View file

@ -0,0 +1,19 @@
import Link from "next/link";
export function Nav() {
return (
<div className="flex flex-row items-center justify-between px-5 py-3">
<Link href="/">
<h1 className="text-3xl font-bold font-mono">
p0ntus
</h1>
</Link>
<div className="flex flex-row gap-4">
<Link href="/">Home</Link>
<Link href="/about">About</Link>
<Link href="/servers">Servers</Link>
<Link href="/services">Services</Link>
</div>
</div>
);
}

View file

@ -0,0 +1,125 @@
import Link from "next/link";
import { Nav } from "../core/nav";
import { services } from "@/config/services";
import { TbArrowLeft, TbEye, TbShieldLock } from "react-icons/tb";
function HumanPriceStatus(priceStatus: "open" | "invite-only" | "by-request") {
switch (priceStatus) {
case "open":
return "Open";
case "invite-only":
return "Invite only";
case "by-request":
return "By request";
}
}
function HumanPriceStatusColor(priceStatus: "open" | "invite-only" | "by-request") {
switch (priceStatus) {
case "open":
return "bg-green-500";
case "invite-only":
return "bg-yellow-500";
case "by-request":
return "bg-red-500";
}
}
function PriceStatusDesc(priceStatus: "open" | "invite-only" | "by-request", serviceName: string) {
switch (priceStatus) {
case "open":
return `${serviceName} is open for public, self-service registration.`;
case "invite-only":
return `${serviceName} is invite-only. Please request an invite from an admin.`;
case "by-request":
return `${serviceName} is by-request. You may request access from an admin.`;
}
}
export function ServicesShell({ slug }: { slug: string }) {
const service = services.find((service) => service.name === slug);
const Icon = service?.icon;
return (
<main>
<Nav />
<div className="flex flex-col items-center justify-between gap-4 my-20">
<div className="flex flex-row items-center justify-between gap-2">
{Icon && <Icon size={36} />}
<h1 className="text-4xl font-bold">
{service?.name}
</h1>
</div>
<p className="text-lg">
{service?.description}
</p>
<Link href={`/services`}>
<button className="flex flex-row items-center justify-between gap-2 text-blue-500 px-4 py-2 rounded-2xl hover:underline transition-all duration-300 cursor-pointer">
<TbArrowLeft size={16} />
Back to services
</button>
</Link>
</div>
<div className="grid grid-cols-4 gap-4 px-14">
<div className={`flex flex-col justify-between gap-4 rounded-2xl px-8 py-4 ${HumanPriceStatusColor(service?.priceStatus as "open" | "invite-only" | "by-request")}`}>
<div className="flex flex-row items-center justify-between gap-2 w-full my-2">
<h2 className="text-2xl font-semibold text-white">
{HumanPriceStatus(service?.priceStatus as "open" | "invite-only" | "by-request")}
</h2>
{service?.joinLink && (
<Link href={service.joinLink}>
<button className="flex flex-row items-center justify-between gap-2 text-white bg-green-600 px-4 py-2 rounded-full hover:underline transition-all duration-300 cursor-pointer">
Join!
</button>
</Link>
)}
</div>
<p className="text-md text-white mb-3">
{PriceStatusDesc(service?.priceStatus as "open" | "invite-only" | "by-request", service?.name as string)}
</p>
</div>
<div className={`flex flex-col justify-between gap-4 rounded-2xl px-8 py-4 bg-gray-200`}>
<div className="flex flex-row items-center gap-2 w-full my-2">
<h2 className="flex flex-row items-center gap-2 text-2xl font-semibold text-black">
<TbEye size={32} />
What admins can see
</h2>
</div>
{Object.entries((service?.adminView ?? {}) as Record<string, {
icon: React.ElementType;
description: string;
}>)
.filter(([, value]) => value !== undefined)
.map(([key, value], index) => (
<div className="flex flex-col w-full mb-2" key={index}>
<p className="flex flex-row items-center gap-1 text-md font-semibold text-black">
<value.icon size={16} /> {key}
</p>
<p className="text-sm text-black">
{value.description}
</p>
</div>
))}
</div>
<div className={`flex flex-col gap-4 rounded-2xl px-8 py-4 bg-gray-200`}>
<div className="flex flex-row items-center gap-2 w-full my-2">
<h2 className="flex flex-row items-center gap-2 text-2xl font-semibold text-black">
<TbShieldLock size={32} />
Our commitment to privacy
</h2>
</div>
<p className="text-md text-black">
Privacy is a big concern to us, too. That&apos;s why we:
</p>
<ul className="list-disc list-inside text-md text-black">
<li>Never share your data to third parties.</li>
<li>Never use your data for advertising.</li>
<li>Never use your data for any other purpose than to provide you with the service you have requested.</li>
<li>Always delete data upon request.</li>
<li>Provide additional options to manage your data.</li>
</ul>
</div>
</div>
</main>
)
}

146
config/services.ts Normal file
View file

@ -0,0 +1,146 @@
import { SiForgejo, SiJellyfin, SiOllama } from "react-icons/si"
import { TbBrowser, TbBubbleText, TbDeviceTv, TbGitBranch, TbKey, TbLink, TbLock, TbMail, TbServer, TbUser } from "react-icons/tb"
export interface Service {
name: string;
description: string;
icon: React.ElementType;
priceStatus: "open" | "invite-only" | "by-request"; /*
open -> open, public registration
invite-only -> manual registration/invites
by-request -> must be requested by user */
adminView: Record<string, {
icon: React.ElementType;
description: string;
}>;
}
export const services = [
{
name: "git",
description: "Easy-to-use git server w/ Actions support. Powered by Forgejo.",
icon: SiForgejo,
priceStatus: "open",
joinLink: "https://git.p0ntus.com",
adminView: {
"Your private repositories": {
icon: TbGitBranch,
description: "Your private repositories are visible to admins.",
},
"Your email address": {
icon: TbMail,
description: "Your email address is visible to admins.",
},
"Change your password": {
icon: TbLock,
description: "Your password can be changed by an admin. It is not visible.",
},
}
},
{
name: "tv",
description: "Private screening movies and tv shows. Powered by Jellyfin.",
icon: SiJellyfin,
priceStatus: "invite-only",
adminView: {
"Your devices": {
icon: TbDeviceTv,
description: "Your devices and their IP addresses.",
},
"Your email address": {
icon: TbMail,
description: "Your email address is visible to admins.",
},
"The content you watch": {
icon: TbUser,
description: "The content you watch is visible to admins.",
},
"Change settings and preferences": {
icon: TbKey,
description: "Admins can change settings and preferences.",
},
}
},
{
name: "ai",
description: "Invite-only Open WebUI instance w/ Ollama and popular models.",
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.",
},
}
},
{
name: "keybox",
description: "Need integrity? We do our best to provide you STRONG",
icon: TbKey,
priceStatus: "open",
joinLink: "/keybox",
adminView: {
"Your email address": {
icon: TbMail,
description: "Your email address is visible to admins.",
},
"Your sessions": {
icon: TbBrowser,
description: "Your sessions are visible to admins.",
},
"Your connections": {
icon: TbLink,
description: "If you authenticate with SSO, your connections are visible to admins.",
},
}
},
{
name: "mail",
description: "A private mail server with full data control. Powered by Mailu.",
icon: TbMail,
priceStatus: "open",
joinLink: "https://pontusmail.org",
adminView: {
"Change user settings": {
icon: TbUser,
description: "Admins can change and view user settings.",
},
"Subject lines": {
icon: TbMail,
description: "Subject lines are visible to admins.",
},
"Your email address": {
icon: TbMail,
description: "Your email address is visible to admins.",
},
}
},
{
name: "hosting",
description: "By-request server and service hosting.",
icon: TbServer,
priceStatus: "by-request",
adminView: {
"Your data": {
icon: TbServer,
description: "Your instance data is visible to admins.",
},
"Your email address": {
icon: TbMail,
description: "Your email address is visible to admins.",
},
"Your connections": {
icon: TbLink,
description: "If you authenticate with SSO, your connections are visible to admins.",
},
"Your sessions": {
icon: TbBrowser,
description: "Your sessions are visible to admins.",
},
}
},
]

16
eslint.config.mjs Normal file
View file

@ -0,0 +1,16 @@
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
});
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
];
export default eslintConfig;

7
next.config.ts Normal file
View file

@ -0,0 +1,7 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
};
export default nextConfig;

30
package.json Normal file
View file

@ -0,0 +1,30 @@
{
"name": "pontus",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@types/react-world-flags": "^1.6.0",
"next": "15.3.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-icons": "^5.5.0",
"react-world-flags": "^1.6.0"
},
"devDependencies": {
"typescript": "^5",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@tailwindcss/postcss": "^4",
"tailwindcss": "^4",
"eslint": "^9",
"eslint-config-next": "15.3.4",
"@eslint/eslintrc": "^3"
}
}

5
postcss.config.mjs Normal file
View file

@ -0,0 +1,5 @@
const config = {
plugins: ["@tailwindcss/postcss"],
};
export default config;

27
tsconfig.json Normal file
View file

@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}