right before packages, env, db, and auth refactor

removing env validation, moving db and auth to each app
This commit is contained in:
Maze Winther 2025-11-26 09:42:44 +01:00
parent 04c09b439b
commit 811491ba9c
13 changed files with 346 additions and 31 deletions

View File

@ -14,6 +14,7 @@
"dependencies": {
"@hookform/resolvers": "^3.9.1",
"@opencut/env": "workspace:*",
"@opencut/ui": "workspace:*",
"@radix-ui/react-separator": "^1.1.7",
"@upstash/ratelimit": "^2.0.6",
"@upstash/redis": "^1.35.4",

View File

@ -1,6 +1,3 @@
import { Hero } from "@/components/landing/hero";
import { Header } from "@/components/header";
import { Footer } from "@/components/footer";
import type { Metadata } from "next";
import { SITE_URL } from "@/constants/site-constants";
@ -13,9 +10,7 @@ export const metadata: Metadata = {
export default async function Home() {
return (
<div>
<Header />
<Hero />
<Footer />
<h1>Hello World</h1>
</div>
);
}

View File

@ -0,0 +1,113 @@
import Link from "next/link";
import { RiDiscordFill, RiTwitterXLine } from "react-icons/ri";
import { FaGithub } from "react-icons/fa6";
import Image from "next/image";
import { DEFAULT_LOGO_URL, SOCIAL_LINKS } from "@/constants/site-constants";
import { capitalizeFirstLetter } from "@/lib/utils";
type Category = "resources" | "company";
interface Link {
label: string;
href: string;
}
type CategoryLinks = Record<Category, Link[]>;
const links: CategoryLinks = {
resources: [
{ label: "Roadmap", href: "/roadmap" },
{ label: "Blog", href: "/blog" },
{ label: "Privacy", href: "/privacy" },
{ label: "Terms of use", href: "/terms" },
],
company: [
{ label: "Contributors", href: "/contributors" },
{ label: "Sponsors", href: "/sponsors" },
{ label: "Branding", href: "/branding" },
{ label: "About", href: `${SOCIAL_LINKS.github}/blob/main/README.md` },
],
};
export function Footer() {
return (
<footer className="bg-background border-t">
<div className="mx-auto max-w-5xl px-8 py-10">
<div className="mb-8 grid grid-cols-1 gap-12 md:grid-cols-2">
{/* Brand Section */}
<div className="max-w-sm md:col-span-1">
<div className="mb-4 flex items-center justify-start gap-2">
<Image
src={DEFAULT_LOGO_URL}
alt="OpenCut"
width={24}
height={24}
className="invert dark:invert-0"
/>
<span className="text-lg font-bold">OpenCut</span>
</div>
<p className="text-muted-foreground mb-5 text-sm md:text-left">
The open source video editor that gets the job done. Simple,
powerful, and works on any platform.
</p>
<div className="flex justify-start gap-3">
<Link
href={SOCIAL_LINKS.github}
className="text-muted-foreground hover:text-foreground transition-colors"
target="_blank"
rel="noopener noreferrer"
>
<FaGithub className="h-5 w-5" />
</Link>
<Link
href={SOCIAL_LINKS.x}
className="text-muted-foreground hover:text-foreground transition-colors"
target="_blank"
rel="noopener noreferrer"
>
<RiTwitterXLine className="h-5 w-5" />
</Link>
<Link
href={SOCIAL_LINKS.discord}
className="text-muted-foreground hover:text-foreground transition-colors"
target="_blank"
rel="noopener noreferrer"
>
<RiDiscordFill className="h-5 w-5" />
</Link>
</div>
</div>
<div className="flex items-start justify-start gap-12 py-2">
{(Object.keys(links) as Category[]).map((category) => (
<div key={category} className="flex flex-col gap-2">
<h3 className="text-foreground font-semibold">{capitalizeFirstLetter({ string: category })}</h3>
<ul className="space-y-2 text-sm">
{links[category].map((link) => (
<li key={link.href}>
<Link
href={link.href}
className="text-muted-foreground hover:text-foreground transition-colors"
target={link.href.startsWith("http") ? "_blank" : undefined}
rel={link.href.startsWith("http") ? "noopener noreferrer" : undefined}
>
{link.label}
</Link>
</li>
))}
</ul>
</div>
))}
</div>
</div>
{/* Bottom Section */}
<div className="flex flex-col items-start justify-between gap-4 pt-2 md:flex-row">
<div className="text-muted-foreground flex items-center gap-4 text-sm">
<span>© 2025 OpenCut, All Rights Reserved</span>
</div>
</div>
</div>
</footer>
);
}

View File

@ -0,0 +1,130 @@
"use client";
import { useState } from "react";
import Link from "next/link";
import { motion } from "motion/react";
import { Button } from "./ui/button";
import { ArrowRight } from "lucide-react";
import Image from "next/image";
import { ThemeToggle } from "./theme-toggle";
import { GithubIcon, MenuIcon } from "@opencut/ui/icons";
import { cn } from "@/lib/utils";
import { DEFAULT_LOGO_URL, SOCIAL_LINKS } from "@/constants/site-constants";
export function Header() {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const links = [
{
label: "Contributors",
href: "/contributors",
},
{
label: "Sponsors",
href: "/sponsors",
},
{
label: "Blog",
href: "/blog",
},
];
return (
<header className="bg-background shadow-background/85 sticky top-0 z-10 shadow-[0_30px_35px_15px_rgba(0,0,0,1)]">
<div className="relative flex w-full items-center justify-between px-6 pt-4">
<div className="relative z-10 flex items-center gap-6">
<Link href="/" className="flex items-center gap-3">
<Image
src={DEFAULT_LOGO_URL}
alt="OpenCut Logo"
className="invert dark:invert-0"
width={32}
height={32}
/>
</Link>
<nav className="hidden items-center gap-4 md:flex">
{links.map((link) => (
<Link key={link.href} href={link.href}>
<Button variant="text" className="p-0 text-sm">
{link.label}
</Button>
</Link>
))}
</nav>
</div>
<div className="relative z-10">
<div className="flex items-center gap-3 md:hidden">
<Button
variant="text"
size="icon"
className="flex items-center justify-center p-0"
onClick={() => setIsMenuOpen(!isMenuOpen)}
>
<MenuIcon size={30} />
</Button>
</div>
<div className="hidden items-center gap-3 md:flex">
<Link href={SOCIAL_LINKS.github}>
<Button className="bg-background text-sm" variant="outline">
<GithubIcon className="h-4 w-4" />
31k+
</Button>
</Link>
<Link href="/projects">
<Button className="text-sm">
Projects
<ArrowRight className="h-4 w-4" />
</Button>
</Link>
<ThemeToggle />
</div>
</div>
<div
className={cn(
"bg-background/20 pointer-events-none fixed inset-0 opacity-0 backdrop-blur-3xl",
"transition-opacity duration-150",
isMenuOpen && "pointer-events-auto opacity-100",
)}
onClick={() => setIsMenuOpen(false)}
>
<div className="relative h-full">
<nav className="flex flex-col gap-3 px-6 pt-[5rem]">
{links.map((link, index) => (
<motion.div
key={link.href}
initial={{ scale: 0.98, opacity: 0 }}
animate={{
scale: isMenuOpen ? 1 : 0.98,
opacity: isMenuOpen ? 1 : 0,
}}
transition={{
duration: 0.4,
delay: isMenuOpen ? index * 0.1 : 0,
ease: [0.25, 0.46, 0.45, 0.94],
}}
>
<Link
href={link.href}
className="text-2xl font-semibold"
onClick={() => setIsMenuOpen(false)}
>
{link.label}
</Link>
</motion.div>
))}
</nav>
<ThemeToggle
className="absolute bottom-8 right-8 size-10"
iconClassName="!size-[1.2rem]"
onToggle={(e) => {
e.preventDefault();
e.stopPropagation();
}}
/>
</div>
</div>
</div>
</header>
);
}

View File

@ -24,6 +24,7 @@
"@opencut/auth": "workspace:*",
"@opencut/db": "workspace:*",
"@opencut/env": "workspace:*",
"@opencut/ui": "workspace:*",
"@radix-ui/react-separator": "^1.1.7",
"@upstash/ratelimit": "^2.0.6",
"@upstash/redis": "^1.35.4",

View File

@ -4,7 +4,7 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { ExternalLink } from "lucide-react";
import Link from "next/link";
import { GithubIcon } from "@/components/icons";
import { GithubIcon } from "@opencut/ui/icons";
import { EXTERNAL_TOOLS, SOCIAL_LINKS } from "@/constants/site-constants";
import { GitHubContributeSection } from "@/components/gitHub-contribute-section";
import { BasePage } from "../base-page";
@ -75,7 +75,7 @@ export default async function ContributorsPage() {
title="Contributors"
description="Meet the amazing people who contribute to OpenCut, the free and open-source video editor."
>
<div className="flex items-center justify-center gap-8 text-sm -mt-4">
<div className="-mt-4 flex items-center justify-center gap-8 text-sm">
<StatItem value={contributors.length} label="contributors" />
<StatItem value={totalContributions} label="contributions" />
</div>

View File

@ -18,23 +18,18 @@
"dependencies": {
"@hookform/resolvers": "^3.9.1",
"@opencut/env": "workspace:*",
"@opencut/ui": "workspace:*",
"@radix-ui/react-separator": "^1.1.7",
"@upstash/ratelimit": "^2.0.6",
"@upstash/redis": "^1.35.4",
"@vercel/analytics": "^1.4.1",
"aws4fetch": "^1.0.20",
"better-auth": "^1.2.7",
"botid": "^1.4.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"dayjs": "^1.11.13",
"dotenv": "^16.5.0",
"drizzle-orm": "^0.44.2",
"embla-carousel-react": "^8.5.1",
"eventemitter3": "^5.0.1",
"feed": "^5.1.0",
"framer-motion": "^11.13.1",
"input-otp": "^1.4.1",
"lucide-react": "^0.468.0",
"mediabunny": "^1.9.3",
@ -45,7 +40,6 @@
"pg": "^8.16.2",
"radix-ui": "^1.4.2",
"react": "^18.2.0",
"react-country-flag": "^3.1.0",
"react-day-picker": "^8.10.1",
"react-dom": "^18.2.0",
"react-hook-form": "^7.54.0",
@ -62,10 +56,7 @@
"sonner": "^1.7.1",
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7",
"unified": "^11.0.5",
"use-deep-compare-effect": "^1.8.1",
"vaul": "^1.1.1",
"wavesurfer.js": "^7.9.8",
"zod": "^3.25.67",
"zustand": "^5.0.2",
},
@ -99,6 +90,7 @@
"@opencut/auth": "workspace:*",
"@opencut/db": "workspace:*",
"@opencut/env": "workspace:*",
"@opencut/ui": "workspace:*",
"@radix-ui/react-separator": "^1.1.7",
"@upstash/ratelimit": "^2.0.6",
"@upstash/redis": "^1.35.4",
@ -115,7 +107,6 @@
"embla-carousel-react": "^8.5.1",
"eventemitter3": "^5.0.1",
"feed": "^5.1.0",
"framer-motion": "^11.13.1",
"input-otp": "^1.4.1",
"lucide-react": "^0.468.0",
"mediabunny": "^1.9.3",
@ -210,6 +201,18 @@
"typescript": "^5.8.3",
},
},
"packages/ui": {
"name": "@opencut/ui",
"version": "0.0.0",
"dependencies": {
"@types/react": "^19.2.7",
"react": "^19.2.0",
},
"devDependencies": {
"@types/bun": "latest",
"typescript": "^5.8.3",
},
},
},
"trustedDependencies": [
"@tailwindcss/oxide",
@ -411,6 +414,8 @@
"@opencut/tools": ["@opencut/tools@workspace:apps/tools"],
"@opencut/ui": ["@opencut/ui@workspace:packages/ui"],
"@opencut/web": ["@opencut/web@workspace:apps/web"],
"@peculiar/asn1-android": ["@peculiar/asn1-android@2.4.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.4.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "sha512-YFueREq97CLslZZBI8dKzis7jMfEHSLxM+nr0Zdx1POiXFLjqqwoY5s0F1UimdBiEw/iKlHey2m56MRDv7Jtyg=="],
@ -779,7 +784,7 @@
"feed": ["feed@5.1.0", "", { "dependencies": { "xml-js": "^1.6.11" } }, "sha512-qGNhgYygnefSkAHHrNHqC7p3R8J0/xQDS/cYUud8er/qD9EFGWyCdUDfULHTJQN1d3H3WprzVwMc9MfB4J50Wg=="],
"framer-motion": ["framer-motion@11.18.2", "", { "dependencies": { "motion-dom": "^11.18.1", "motion-utils": "^11.18.1", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w=="],
"framer-motion": ["framer-motion@12.23.6", "", { "dependencies": { "motion-dom": "^12.23.6", "motion-utils": "^12.23.6", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-dsJ389QImVE3lQvM8Mnk99/j8tiZDM/7706PCqvkQ8sSCnpmWxsgX+g0lj7r5OBVL0U36pIecCTBoIWcM2RuKw=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
@ -955,9 +960,9 @@
"motion": ["motion@12.23.6", "", { "dependencies": { "framer-motion": "^12.23.6", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-6U55IW5i6Vut2ryKEhrZKg55490k9d6qdGXZoNSf98oQgDj5D7bqTnVJotQ6UW3AS6QfbW6KSLa7/e1gy+a07g=="],
"motion-dom": ["motion-dom@11.18.1", "", { "dependencies": { "motion-utils": "^11.18.1" } }, "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw=="],
"motion-dom": ["motion-dom@12.23.6", "", { "dependencies": { "motion-utils": "^12.23.6" } }, "sha512-G2w6Nw7ZOVSzcQmsdLc0doMe64O/Sbuc2bVAbgMz6oP/6/pRStKRiVRV4bQfHp5AHYAKEGhEdVHTM+R3FDgi5w=="],
"motion-utils": ["motion-utils@11.18.1", "", {}, "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA=="],
"motion-utils": ["motion-utils@12.23.6", "", {}, "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
@ -1217,6 +1222,10 @@
"@opencut/tools/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"@opencut/ui/@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="],
"@opencut/ui/react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
"@opencut/web/@types/node": ["@types/node@24.2.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ=="],
"@opencut/web/next": ["next@15.5.3", "", { "dependencies": { "@next/env": "15.5.3", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.3", "@next/swc-darwin-x64": "15.5.3", "@next/swc-linux-arm64-gnu": "15.5.3", "@next/swc-linux-arm64-musl": "15.5.3", "@next/swc-linux-x64-gnu": "15.5.3", "@next/swc-linux-x64-musl": "15.5.3", "@next/swc-win32-arm64-msvc": "15.5.3", "@next/swc-win32-x64-msvc": "15.5.3", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-r/liNAx16SQj4D+XH/oI1dlpv9tdKJ6cONYPwwcCC46f2NjpaRWY+EKCzULfgQYV6YKXjHBchff2IZBSlZmJNw=="],
@ -1241,8 +1250,6 @@
"better-auth/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"motion/framer-motion": ["framer-motion@12.23.6", "", { "dependencies": { "motion-dom": "^12.23.6", "motion-utils": "^12.23.6", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-dsJ389QImVE3lQvM8Mnk99/j8tiZDM/7706PCqvkQ8sSCnpmWxsgX+g0lj7r5OBVL0U36pIecCTBoIWcM2RuKw=="],
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
@ -1321,6 +1328,8 @@
"@opencut/tools/next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
"@opencut/ui/@types/react/csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"@opencut/web/@types/node/undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="],
"@opencut/web/next/@next/env": ["@next/env@15.5.3", "", {}, "sha512-RSEDTRqyihYXygx/OJXwvVupfr9m04+0vH8vyy0HfZ7keRto6VX9BbEk0J2PUk0VGy6YhklJUSrgForov5F9pw=="],
@ -1347,10 +1356,6 @@
"@types/bun/bun-types/@types/node": ["@types/node@24.2.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ=="],
"motion/framer-motion/motion-dom": ["motion-dom@12.23.6", "", { "dependencies": { "motion-utils": "^12.23.6" } }, "sha512-G2w6Nw7ZOVSzcQmsdLc0doMe64O/Sbuc2bVAbgMz6oP/6/pRStKRiVRV4bQfHp5AHYAKEGhEdVHTM+R3FDgi5w=="],
"motion/framer-motion/motion-utils": ["motion-utils@12.23.6", "", {}, "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ=="],
"next/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"@opencut/tools/next/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],

30
packages/env/src/tools.ts vendored Normal file
View File

@ -0,0 +1,30 @@
import { z } from "zod";
const toolsEnvSchema = z.object({
// Node
NODE_ENV: z.enum(["development", "production", "test"]),
ANALYZE: z.string().optional(),
NEXT_RUNTIME: z.enum(["nodejs", "edge"]).optional(),
// Public
NEXT_PUBLIC_SITE_URL: z.url().default("http://localhost:3000"),
NEXT_PUBLIC_MARBLE_API_URL: z.url(),
// Server
DATABASE_URL: z
.string()
.startsWith("postgres://")
.or(z.string().startsWith("postgresql://")),
BETTER_AUTH_SECRET: z.string(),
UPSTASH_REDIS_REST_URL: z.url(),
UPSTASH_REDIS_REST_TOKEN: z.string(),
CLOUDFLARE_ACCOUNT_ID: z.string(),
R2_ACCESS_KEY_ID: z.string(),
R2_SECRET_ACCESS_KEY: z.string(),
R2_BUCKET_NAME: z.string(),
});
export type ToolsEnv = z.infer<typeof toolsEnvSchema>;
export const toolsEnv = toolsEnvSchema.parse(process.env);

View File

@ -1,6 +1,6 @@
import { z } from "zod";
const envSchema = z.object({
const webEnvSchema = z.object({
// Node
NODE_ENV: z.enum(["development", "production", "test"]),
ANALYZE: z.string().optional(),
@ -29,6 +29,6 @@ const envSchema = z.object({
MODAL_TRANSCRIPTION_URL: z.url(),
});
export type Env = z.infer<typeof envSchema>;
export type WebEnv = z.infer<typeof webEnvSchema>;
export const env = envSchema.parse(process.env);
export const webEnv = webEnvSchema.parse(process.env);

19
packages/ui/package.json Normal file
View File

@ -0,0 +1,19 @@
{
"name": "@opencut/ui",
"version": "0.0.0",
"description": "UI package for OpenCut",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts",
"./icons": "./src/icons/index.tsx"
},
"devDependencies": {
"@types/bun": "latest",
"typescript": "^5.8.3"
},
"dependencies": {
"@types/react": "^19.2.7",
"react": "^19.2.0"
}
}

1
packages/ui/src/index.ts Normal file
View File

@ -0,0 +1 @@
export * from "./icons";

20
packages/ui/tsconfig.json Normal file
View File

@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}