Initial commit

This commit is contained in:
Daniel Alves Rösel
2026-04-02 18:47:14 +02:00
committed by GitHub
commit 90ad5e0260
94 changed files with 7797 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
import { type EmailOtpType } from '@supabase/supabase-js'
import { type NextRequest } from 'next/server'
import { createClient } from '@/utils/supabase/server'
import { redirect } from 'next/navigation'
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const token_hash = searchParams.get('token_hash')
const type = searchParams.get('type') as EmailOtpType | null
const next = searchParams.get('next') ?? '/'
if (token_hash && type) {
const supabase = await createClient()
const { error } = await supabase.auth.verifyOtp({
type,
token_hash,
})
if (!error) {
// redirect user to specified redirect URL or root of app
redirect(next)
}
}
// redirect the user to an error page with some instructions
redirect('/error')
}

View File

@@ -0,0 +1,10 @@
export default function BlogPage() {
return (
<main className="mx-auto max-w-3xl px-6 py-16">
<h1 className="text-3xl font-semibold tracking-tight">Blog</h1>
<p className="mt-4 text-sm text-neutral-600">
Publish product updates, engineering notes, and launch posts here.
</p>
</main>
);
}

View File

@@ -0,0 +1,12 @@
'use server'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
import { createClient } from '@/utils/supabase/server'
export async function logout() {
const supabase = await createClient()
await supabase.auth.signOut()
revalidatePath('/', 'layout')
redirect('/login')
}

View File

@@ -0,0 +1,14 @@
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div>
<nav>
<h1>Dashboard</h1>
</nav>
<main>{children}</main>
</div>
)
}

View File

@@ -0,0 +1,21 @@
import { redirect } from 'next/navigation'
import { createClient } from '@/utils/supabase/server'
import { logout } from './actions'
export default async function DashboardPage() {
const supabase = await createClient()
const { data, error } = await supabase.auth.getUser()
if (error || !data?.user) {
redirect('/login')
}
return (
<div>
<p>Welcome, {data.user.email}</p>
<form>
<button formAction={logout}>Logout</button>
</form>
</div>
)
}

View File

@@ -0,0 +1,20 @@
'use client';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div>
{/* TODO: Style this error page when implementing in your project */}
<h2>Something went wrong!</h2>
<p>{error.message || 'An unexpected error occurred'}</p>
<button onClick={() => reset()}>
Try again
</button>
</div>
);
}

View File

@@ -0,0 +1,5 @@
'use client'
export default function ErrorPage() {
return <p>Sorry, something went wrong</p>
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

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: Arial, Helvetica, sans-serif;
}

View File

@@ -0,0 +1,8 @@
import { createClient } from '@/utils/supabase/server';
export default async function Instruments() {
const supabase = await createClient();
const { data: instruments } = await supabase.from("instruments").select();
return <pre>{JSON.stringify(instruments, null, 2)}</pre>
}

View File

@@ -0,0 +1,29 @@
import type { Metadata } from "next";
import "./globals.css";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
const fontVariables = "font-sans";
export const metadata: Metadata = {
title: "Ultiplate - Ultimate Boilerplate",
description: "AI-native template for any project with deployment ready setup",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={`${fontVariables} antialiased`}>
<Header />
<main className="min-h-screen">
{children}
</main>
<Footer />
</body>
</html>
);
}

View File

@@ -0,0 +1,46 @@
'use server'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
import { createClient } from '@/utils/supabase/server'
export async function login(formData: FormData) {
const supabase = await createClient()
// type-casting here for convenience
// in practice, you should validate your inputs
const data = {
email: formData.get('email') as string,
password: formData.get('password') as string,
}
const { error } = await supabase.auth.signInWithPassword(data)
if (error) {
redirect('/error')
}
revalidatePath('/', 'layout')
redirect('/')
}
export async function signup(formData: FormData) {
const supabase = await createClient()
// type-casting here for convenience
// in practice, you should validate your inputs
const data = {
email: formData.get('email') as string,
password: formData.get('password') as string,
}
const { error } = await supabase.auth.signUp(data)
if (error) {
redirect('/error')
}
revalidatePath('/', 'layout')
redirect('/')
}

View File

@@ -0,0 +1,14 @@
import { login, signup } from './actions'
export default function LoginPage() {
return (
<form>
<label htmlFor="email">Email:</label>
<input id="email" name="email" type="email" required />
<label htmlFor="password">Password:</label>
<input id="password" name="password" type="password" required />
<button formAction={login}>Log in</button>
<button formAction={signup}>Sign up</button>
</form>
)
}

View File

@@ -0,0 +1,12 @@
import Link from 'next/link';
export default function NotFound() {
return (
<div>
{/* TODO: Style this 404 page when implementing in your project */}
<h2>Not Found</h2>
<p>Could not find requested resource</p>
<Link href="/">Return Home</Link>
</div>
);
}

View File

@@ -0,0 +1,17 @@
import Hero from "@/components/Hero";
import FeaturesGrid from "@/components/FeaturesGrid";
import Testimonials1 from "@/components/Testimonials1";
import Pricing from "@/components/Pricing";
//import FAQ from "@/components/FAQ";
//import CTA from "@/components/CTA";
export default function Home() {
return (
<div>
<Hero />
<FeaturesGrid />
<Testimonials1 />
<Pricing />
</div>
);
}

View File

@@ -0,0 +1,10 @@
export default function PrivacyPolicyPage() {
return (
<main className="mx-auto max-w-3xl px-6 py-16">
<h1 className="text-3xl font-semibold tracking-tight">Privacy Policy</h1>
<p className="mt-4 text-sm text-neutral-600">
Describe what data you collect, how it is used, and how users can request deletion.
</p>
</main>
);
}

View File

@@ -0,0 +1,10 @@
export default function TermsOfServicePage() {
return (
<main className="mx-auto max-w-3xl px-6 py-16">
<h1 className="text-3xl font-semibold tracking-tight">Terms of Service</h1>
<p className="mt-4 text-sm text-neutral-600">
Add your product terms, responsibilities, and legal limitations in this page.
</p>
</main>
);
}

View File

@@ -0,0 +1,22 @@
import { getLocale } from "@/libs/locales";
export default function FeaturesGrid() {
const { common } = getLocale('en');
return (
<section>
{/* TODO: Style this features grid when implementing in your project */}
<div>
<h2>{common.features.title}</h2>
<div>
{common.features.items.map((feature, index) => (
<div key={index}>
<h3>{feature.title}</h3>
<p>{feature.description}</p>
</div>
))}
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,37 @@
import Link from "next/link";
import { getLocale } from "@/libs/locales";
export default function Footer() {
const { common } = getLocale('en');
return (
<footer>
{/* TODO: Style this footer when implementing in your project */}
<div>
<div>
<div>
<h3>{common.footer.brand}</h3>
<p>{common.footer.description}</p>
</div>
<div>
<h4>{common.footer.legal.title}</h4>
<ul>
<li><Link href="/privacy-policy">{common.footer.legal.privacyPolicy}</Link></li>
<li><Link href="/tos">{common.footer.legal.termsOfService}</Link></li>
</ul>
</div>
<div>
<h4>{common.footer.company.title}</h4>
<ul>
<li><Link href="/blog">{common.footer.company.blog}</Link></li>
<li><Link href="/dashboard">{common.footer.company.dashboard}</Link></li>
</ul>
</div>
</div>
<div>
<p>{common.footer.copyright}</p>
</div>
</div>
</footer>
);
}

View File

@@ -0,0 +1,27 @@
import Link from "next/link";
import { getLocale } from "@/libs/locales";
export default function Header() {
const { common } = getLocale('en');
return (
<header>
{/* TODO: Style this header when implementing in your project */}
<div>
<div>
<div>
<Link href="/">{common.header.brand}</Link>
</div>
<nav>
<Link href="/">{common.header.nav.home}</Link>
<Link href="/dashboard">{common.header.nav.dashboard}</Link>
<Link href="/blog">{common.header.nav.blog}</Link>
</nav>
<div>
<Link href="/login">{common.header.actions.signIn}</Link>
</div>
</div>
</div>
</header>
);
}

View File

@@ -0,0 +1,24 @@
import Link from "next/link";
import { getLocale } from "@/libs/locales";
export default function Hero() {
const { common } = getLocale('en');
return (
<section>
{/* TODO: Style this hero section when implementing in your project */}
<div>
<h1>{common.hero.title}</h1>
<p>{common.hero.description}</p>
<div>
<Link href="/dashboard">
<button>{common.hero.actions.getStarted}</button>
</Link>
<Link href="/blog">
<button>{common.hero.actions.learnMore}</button>
</Link>
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,47 @@
export default function Pricing() {
const plans = [
{
name: "Open Source",
price: "Free",
features: [
"All boilerplate code",
"Docker configurations",
"Basic ML setup",
"Community support"
]
},
{
name: "Pro",
price: "$49/month",
features: [
"Everything in Open Source",
"Advanced configurations",
"Priority support",
"Custom integrations"
]
}
];
return (
<section>
{/* TODO: Style this pricing section when implementing in your project */}
<div>
<h2>Pricing</h2>
<div>
{plans.map((plan, index) => (
<div key={index}>
<h3>{plan.name}</h3>
<p>{plan.price}</p>
<ul>
{plan.features.map((feature, featureIndex) => (
<li key={featureIndex}>{feature}</li>
))}
</ul>
<button>Choose Plan</button>
</div>
))}
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,25 @@
import { getLocale } from "@/libs/locales";
export default function Testimonials1() {
const { common } = getLocale('en');
return (
<section>
{/* TODO: Style this testimonials section when implementing in your project */}
<div>
<h2>{common.testimonials.title}</h2>
<div>
{common.testimonials.items.map((testimonial, index) => (
<div key={index}>
<p>{`"${testimonial.content}"`}</p>
<div>
<p>{testimonial.name}</p>
<p>{testimonial.role}</p>
</div>
</div>
))}
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,32 @@
import commonEn from '@/locales/en/common.json';
// TODO: Add more languages as needed
const locales = {
en: {
common: commonEn
}
};
export function getLocale(locale: string = 'en') {
return locales[locale as keyof typeof locales] || locales.en;
}
export function t(key: string, locale: string = 'en') {
const translations = getLocale(locale);
const keys = key.split('.');
let value: unknown = translations;
for (const k of keys) {
if (typeof value !== 'object' || value === null || !(k in value)) {
return key;
}
value = (value as Record<string, unknown>)[k];
}
if (typeof value === 'string' || typeof value === 'number') {
return String(value);
}
return key;
}

View File

@@ -0,0 +1,85 @@
{
"header": {
"brand": "UltiPlate",
"nav": {
"home": "Home",
"dashboard": "Dashboard",
"blog": "Blog"
},
"actions": {
"signIn": "Sign In"
}
},
"footer": {
"brand": "UltiPlate",
"description": "Ultimate boilerplate for any project",
"legal": {
"title": "Legal",
"privacyPolicy": "Privacy Policy",
"termsOfService": "Terms of Service"
},
"company": {
"title": "Company",
"blog": "Blog",
"dashboard": "Dashboard"
},
"copyright": "© 2024 UltiPlate. All rights reserved."
},
"hero": {
"title": "Welcome to UltiPlate",
"description": "The ultimate boilerplate for any project - from web apps to ML projects, all deployable with Docker.",
"actions": {
"getStarted": "Get Started",
"learnMore": "Learn More"
}
},
"features": {
"title": "Features",
"items": [
{
"title": "Docker Ready",
"description": "Everything containerized and ready to deploy"
},
{
"title": "ML Support",
"description": "Built-in machine learning project structure"
},
{
"title": "Multiple Backends",
"description": "FastAPI, Flask, and more backend options"
},
{
"title": "Centralized Logging",
"description": "Loki-based logging across all services"
},
{
"title": "Database Ready",
"description": "Redis, MinIO, and database integrations"
},
{
"title": "Web Apps",
"description": "Next.js and Streamlit app templates"
}
]
},
"testimonials": {
"title": "What People Say",
"items": [
{
"name": "Alex Developer",
"role": "Full Stack Developer",
"content": "UltiPlate saved me weeks of setup time. Everything just works out of the box."
},
{
"name": "Sarah ML Engineer",
"role": "ML Engineer",
"content": "The ML project structure is perfect. From notebooks to production in minutes."
},
{
"name": "Mike DevOps",
"role": "DevOps Engineer",
"content": "Docker integration is seamless. Deploy anywhere with confidence."
}
]
}
}

View File

@@ -0,0 +1,8 @@
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!
)
}

View File

@@ -0,0 +1,66 @@
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
export async function updateSession(request: NextRequest) {
let supabaseResponse = NextResponse.next({
request,
})
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll()
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value))
supabaseResponse = NextResponse.next({
request,
})
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options)
)
},
},
}
)
// Do not run code between createServerClient and
// supabase.auth.getUser(). A simple mistake could make it very hard to debug
// issues with users being randomly logged out.
// IMPORTANT: DO NOT REMOVE auth.getUser()
const {
data: { user },
} = await supabase.auth.getUser()
if (
!user &&
!request.nextUrl.pathname.startsWith('/login') &&
!request.nextUrl.pathname.startsWith('/auth') &&
!request.nextUrl.pathname.startsWith('/error')
) {
// no user, potentially respond by redirecting the user to the login page
const url = request.nextUrl.clone()
url.pathname = '/login'
return NextResponse.redirect(url)
}
// IMPORTANT: You *must* return the supabaseResponse object as it is.
// If you're creating a new response object with NextResponse.next() make sure to:
// 1. Pass the request in it, like so:
// const myNewResponse = NextResponse.next({ request })
// 2. Copy over the cookies, like so:
// myNewResponse.cookies.setAll(supabaseResponse.cookies.getAll())
// 3. Change the myNewResponse object to fit your needs, but avoid changing
// the cookies!
// 4. Finally:
// return myNewResponse
// If this is not done, you may be causing the browser and server to go out
// of sync and terminate the user's session prematurely!
return supabaseResponse
}

View File

@@ -0,0 +1,29 @@
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
export async function createClient() {
const cookieStore = await cookies()
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll()
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
} catch {
// The `setAll` method was called from a Server Component.
// This can be ignored if you have middleware refreshing
// user sessions.
}
},
},
}
)
}