+
diff --git a/bun.lock b/bun.lock
index 2cb81c94..d82ab694 100644
--- a/bun.lock
+++ b/bun.lock
@@ -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=="],
diff --git a/packages/env/src/tools.ts b/packages/env/src/tools.ts
new file mode 100644
index 00000000..a854ac4c
--- /dev/null
+++ b/packages/env/src/tools.ts
@@ -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
;
+
+export const toolsEnv = toolsEnvSchema.parse(process.env);
diff --git a/packages/env/src/index.ts b/packages/env/src/web.ts
similarity index 85%
rename from packages/env/src/index.ts
rename to packages/env/src/web.ts
index b5d81acb..8d6535a6 100644
--- a/packages/env/src/index.ts
+++ b/packages/env/src/web.ts
@@ -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;
+export type WebEnv = z.infer;
-export const env = envSchema.parse(process.env);
+export const webEnv = webEnvSchema.parse(process.env);
diff --git a/packages/ui/package.json b/packages/ui/package.json
new file mode 100644
index 00000000..892fc590
--- /dev/null
+++ b/packages/ui/package.json
@@ -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"
+ }
+}
diff --git a/apps/web/src/components/icons.tsx b/packages/ui/src/icons/index.tsx
similarity index 100%
rename from apps/web/src/components/icons.tsx
rename to packages/ui/src/icons/index.tsx
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
new file mode 100644
index 00000000..5e4d7c83
--- /dev/null
+++ b/packages/ui/src/index.ts
@@ -0,0 +1 @@
+export * from "./icons";
\ No newline at end of file
diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json
new file mode 100644
index 00000000..d179b7d1
--- /dev/null
+++ b/packages/ui/tsconfig.json
@@ -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"]
+}