commit 0f94d1300eca51e332af08fc703f4c84d26f8a46 Author: Aidan Date: Wed Jul 2 20:05:23 2025 -0400 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f0aad8 --- /dev/null +++ b/.gitignore @@ -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* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c32dd18 --- /dev/null +++ b/LICENSE @@ -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 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e215bc4 --- /dev/null +++ b/README.md @@ -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. diff --git a/app/about/page.tsx b/app/about/page.tsx new file mode 100644 index 0000000..6430075 --- /dev/null +++ b/app/about/page.tsx @@ -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 ( +
+
+ ); +} \ No newline at end of file diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..1db9ba2 --- /dev/null +++ b/app/globals.css @@ -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); +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..a79b83f --- /dev/null +++ b/app/layout.tsx @@ -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 ( + + + {children} + + + ); +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..b6711f5 --- /dev/null +++ b/app/page.tsx @@ -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 ( +
+
+ ); +} diff --git a/app/servers/page.tsx b/app/servers/page.tsx new file mode 100644 index 0000000..3ea2399 --- /dev/null +++ b/app/servers/page.tsx @@ -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 ( +
+
+ ); +} \ No newline at end of file diff --git a/app/services/[slug]/page.tsx b/app/services/[slug]/page.tsx new file mode 100644 index 0000000..cb568a2 --- /dev/null +++ b/app/services/[slug]/page.tsx @@ -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 ( + + ) +} \ No newline at end of file diff --git a/app/services/page.tsx b/app/services/page.tsx new file mode 100644 index 0000000..c30db4e --- /dev/null +++ b/app/services/page.tsx @@ -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 ( +
+
+ ) +} diff --git a/components/core/nav.tsx b/components/core/nav.tsx new file mode 100644 index 0000000..f77e2d3 --- /dev/null +++ b/components/core/nav.tsx @@ -0,0 +1,19 @@ +import Link from "next/link"; + +export function Nav() { + return ( +
+ +

+ p0ntus +

+ +
+ Home + About + Servers + Services +
+
+ ); +} \ No newline at end of file diff --git a/components/front/services.tsx b/components/front/services.tsx new file mode 100644 index 0000000..56be64b --- /dev/null +++ b/components/front/services.tsx @@ -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 ( +
+
+ ) +} \ No newline at end of file diff --git a/config/services.ts b/config/services.ts new file mode 100644 index 0000000..9b065ba --- /dev/null +++ b/config/services.ts @@ -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; +} + +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.", + }, + } + }, +] \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..c85fb67 --- /dev/null +++ b/eslint.config.mjs @@ -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; diff --git a/next.config.ts b/next.config.ts new file mode 100644 index 0000000..e9ffa30 --- /dev/null +++ b/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + /* config options here */ +}; + +export default nextConfig; diff --git a/package.json b/package.json new file mode 100644 index 0000000..f5ecfe2 --- /dev/null +++ b/package.json @@ -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" + } +} diff --git a/postcss.config.mjs b/postcss.config.mjs new file mode 100644 index 0000000..c7bcb4b --- /dev/null +++ b/postcss.config.mjs @@ -0,0 +1,5 @@ +const config = { + plugins: ["@tailwindcss/postcss"], +}; + +export default config; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d8b9323 --- /dev/null +++ b/tsconfig.json @@ -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"] +}