├── .npmrc ├── packages ├── ui │ ├── src │ │ ├── hooks │ │ │ └── .gitkeep │ │ ├── lib │ │ │ └── utils.ts │ │ └── components │ │ │ ├── skeleton.tsx │ │ │ ├── label.tsx │ │ │ ├── sonner.tsx │ │ │ ├── textarea.tsx │ │ │ ├── collapsible.tsx │ │ │ ├── input.tsx │ │ │ ├── switch.tsx │ │ │ ├── avatar.tsx │ │ │ ├── badge.tsx │ │ │ ├── alert.tsx │ │ │ ├── tabs.tsx │ │ │ ├── button.tsx │ │ │ └── card.tsx │ ├── postcss.config.mjs │ ├── components.json │ ├── tsconfig.json │ └── package.json ├── db │ ├── .gitignore │ ├── src │ │ ├── schema │ │ │ ├── index.ts │ │ │ ├── note.ts │ │ │ ├── message.ts │ │ │ └── user.ts │ │ └── index.ts │ ├── migrations │ │ ├── meta │ │ │ └── _journal.json │ │ └── 0000_fresh_shard.sql │ ├── drizzle.config.ts │ ├── tsconfig.json │ └── package.json └── encryption │ ├── tsconfig.json │ ├── src │ ├── generate-aes-key.ts │ └── index.ts │ └── package.json ├── apps └── www │ ├── public │ ├── ads.txt │ ├── vercel.svg │ ├── window.svg │ ├── file.svg │ ├── globe.svg │ └── next.svg │ ├── app │ ├── icon.png │ ├── favicon.ico │ ├── apple-icon.png │ ├── twitter-image.png │ ├── opengraph-image.png │ ├── globals.css │ ├── inbox │ │ ├── layout.tsx │ │ ├── components │ │ │ ├── current-user-card.tsx │ │ │ ├── received │ │ │ │ ├── received-message-card-skeleton.tsx │ │ │ │ ├── received-card.tsx │ │ │ │ ├── received-card-menu.tsx │ │ │ │ └── reply-dialog.tsx │ │ │ ├── sent │ │ │ │ ├── sent-message-card-skeleton.tsx │ │ │ │ └── sent-card.tsx │ │ │ └── inbox-tabs.tsx │ │ └── page.tsx │ ├── login │ │ ├── layout.tsx │ │ ├── components │ │ │ └── login-form.tsx │ │ └── page.tsx │ ├── notes │ │ ├── layout.tsx │ │ ├── components │ │ │ ├── note-card-skeleton.tsx │ │ │ └── note-form.tsx │ │ └── page.tsx │ ├── social │ │ ├── layout.tsx │ │ ├── page.tsx │ │ └── components │ │ │ └── social-card.tsx │ ├── register │ │ ├── layout.tsx │ │ ├── components │ │ │ └── register-form.tsx │ │ └── page.tsx │ ├── settings │ │ ├── layout.tsx │ │ ├── components │ │ │ ├── sign-out-button.tsx │ │ │ ├── danger-button.tsx │ │ │ ├── settings-skeleton.tsx │ │ │ ├── danger-settings.tsx │ │ │ ├── settings-tabs.tsx │ │ │ ├── password-form.tsx │ │ │ └── account-settings.tsx │ │ └── page.tsx │ ├── to │ │ └── [username] │ │ │ ├── layout.tsx │ │ │ ├── components │ │ │ ├── chat-form-skeleton.tsx │ │ │ └── chat-form.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ ├── robots.ts │ ├── terms │ │ └── page.tsx │ ├── privacy │ │ └── page.tsx │ ├── user │ │ └── [username] │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ ├── auth │ │ └── google │ │ │ └── route.ts │ ├── providers.tsx │ ├── sitemap.ts │ ├── page.tsx │ ├── not-found.tsx │ ├── api │ │ ├── users │ │ │ └── [username] │ │ │ │ └── route.ts │ │ ├── notes │ │ │ └── route.ts │ │ └── messages │ │ │ └── route.ts │ ├── error.tsx │ ├── global-error.tsx │ ├── actions │ │ └── note.ts │ └── layout.tsx │ ├── types │ ├── index.ts │ ├── lucide-react.d.ts │ └── user.ts │ ├── postcss.config.mjs │ ├── mdx-components.tsx │ ├── lib │ ├── oauth.ts │ ├── schema.ts │ ├── avatar.ts │ ├── get-query-client.ts │ ├── utils.ts │ └── session.ts │ ├── components │ ├── loading-icon.tsx │ ├── hover-prefetch-link.tsx │ ├── footer.tsx │ ├── share-button.tsx │ ├── animated-shiny-text.tsx │ ├── grid-pattern.tsx │ ├── menu.tsx │ ├── copy-link.tsx │ ├── theme-toggle.tsx │ ├── ad-container.tsx │ ├── skeleton │ │ ├── user-card-skeleton.tsx │ │ └── chat-list-skeleton.tsx │ ├── unauthenticated-dialog.tsx │ ├── chat-list.tsx │ ├── browser-warning.tsx │ ├── share-link-dialog.tsx │ ├── demo.tsx │ ├── navbar.tsx │ └── user-card.tsx │ ├── components.json │ ├── hooks │ ├── use-media-query.tsx │ ├── use-dynamic-textarea.ts │ └── form.tsx │ ├── .gitignore │ ├── next.config.ts │ ├── tsconfig.json │ ├── README.md │ ├── proxy.ts │ ├── package.json │ └── markdown │ ├── privacy.mdx │ └── terms.mdx ├── .vscode └── settings.json ├── pnpm-workspace.yaml ├── lefthook.yml ├── .gitignore ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── ci.yml ├── turbo.json ├── biome.json ├── package.json └── SECURITY.md /.npmrc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ui/src/hooks/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/db/.gitignore: -------------------------------------------------------------------------------- 1 | local.db* 2 | -------------------------------------------------------------------------------- /apps/www/public/ads.txt: -------------------------------------------------------------------------------- 1 | google.com, pub-4274133898976040, DIRECT, f08c47fec0942fa0 2 | -------------------------------------------------------------------------------- /apps/www/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omsimos/umamin/HEAD/apps/www/app/icon.png -------------------------------------------------------------------------------- /apps/www/types/index.ts: -------------------------------------------------------------------------------- 1 | export type Cursor = { 2 | id: string; 3 | date: Date; 4 | }; 5 | -------------------------------------------------------------------------------- /apps/www/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omsimos/umamin/HEAD/apps/www/app/favicon.ico -------------------------------------------------------------------------------- /apps/www/app/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omsimos/umamin/HEAD/apps/www/app/apple-icon.png -------------------------------------------------------------------------------- /apps/www/app/twitter-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omsimos/umamin/HEAD/apps/www/app/twitter-image.png -------------------------------------------------------------------------------- /apps/www/app/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omsimos/umamin/HEAD/apps/www/app/opengraph-image.png -------------------------------------------------------------------------------- /packages/db/src/schema/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./message"; 2 | export * from "./note"; 3 | export * from "./user"; 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.workingDirectories": [ 3 | { 4 | "mode": "auto" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "apps/*" 3 | - "packages/*" 4 | 5 | onlyBuiltDependencies: 6 | - lefthook 7 | -------------------------------------------------------------------------------- /apps/www/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["@tailwindcss/postcss"], 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /apps/www/app/globals.css: -------------------------------------------------------------------------------- 1 | /* File added for LSP support */ 2 | @import "@umamin/ui/globals.css"; 3 | @plugin "@tailwindcss/typography"; 4 | -------------------------------------------------------------------------------- /apps/www/types/lucide-react.d.ts: -------------------------------------------------------------------------------- 1 | declare module "lucide-react" { 2 | export * from "lucide-react/dist/lucide-react.suffixed"; 3 | } 4 | -------------------------------------------------------------------------------- /apps/www/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/ui/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { "@tailwindcss/postcss": {} }, 4 | }; 5 | 6 | export default config; 7 | -------------------------------------------------------------------------------- /apps/www/app/inbox/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from "react"; 2 | 3 | export default function Layout({ children }: { children: React.ReactNode }) { 4 | return {children}; 5 | } 6 | -------------------------------------------------------------------------------- /apps/www/app/login/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from "react"; 2 | 3 | export default function Layout({ children }: { children: React.ReactNode }) { 4 | return {children}; 5 | } 6 | -------------------------------------------------------------------------------- /apps/www/app/notes/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from "react"; 2 | 3 | export default function Layout({ children }: { children: React.ReactNode }) { 4 | return {children}; 5 | } 6 | -------------------------------------------------------------------------------- /apps/www/app/social/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from "react"; 2 | 3 | export default function Layout({ children }: { children: React.ReactNode }) { 4 | return {children}; 5 | } 6 | -------------------------------------------------------------------------------- /apps/www/app/register/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from "react"; 2 | 3 | export default function Layout({ children }: { children: React.ReactNode }) { 4 | return {children}; 5 | } 6 | -------------------------------------------------------------------------------- /apps/www/app/settings/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from "react"; 2 | 3 | export default function Layout({ children }: { children: React.ReactNode }) { 4 | return {children}; 5 | } 6 | -------------------------------------------------------------------------------- /apps/www/app/to/[username]/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from "react"; 2 | 3 | export default function Layout({ children }: { children: React.ReactNode }) { 4 | return {children}; 5 | } 6 | -------------------------------------------------------------------------------- /apps/www/mdx-components.tsx: -------------------------------------------------------------------------------- 1 | import type { MDXComponents } from "mdx/types"; 2 | 3 | const components: MDXComponents = {}; 4 | 5 | export function useMDXComponents(): MDXComponents { 6 | return components; 7 | } 8 | -------------------------------------------------------------------------------- /packages/ui/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /apps/www/lib/oauth.ts: -------------------------------------------------------------------------------- 1 | import { Google } from "arctic"; 2 | 3 | export const google = new Google( 4 | process.env.GOOGLE_CLIENT_ID ?? "", 5 | process.env.GOOGLE_CLIENT_SECRET ?? "", 6 | process.env.GOOGLE_REDIRECT_URI ?? "", 7 | ); 8 | -------------------------------------------------------------------------------- /lefthook.yml: -------------------------------------------------------------------------------- 1 | pre-commit: 2 | commands: 3 | check: 4 | glob: "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}" 5 | run: pnpm exec biome check --write --no-errors-on-unmatched --files-ignore-unknown=true --colors=off {staged_files} 6 | stage_fixed: true 7 | -------------------------------------------------------------------------------- /packages/db/migrations/meta/_journal.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "7", 3 | "dialect": "sqlite", 4 | "entries": [ 5 | { 6 | "idx": 0, 7 | "version": "6", 8 | "when": 1719486511741, 9 | "tag": "0000_fresh_shard", 10 | "breakpoints": true 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/db/drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "drizzle-kit"; 2 | 3 | export default defineConfig({ 4 | schema: "./src/schema", 5 | out: "./migrations", 6 | dialect: "turso", 7 | dbCredentials: { 8 | url: process.env.TURSO_CONNECTION_URL ?? "", 9 | authToken: process.env.TURSO_AUTH_TOKEN ?? "", 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /apps/www/app/inbox/components/current-user-card.tsx: -------------------------------------------------------------------------------- 1 | import { UserCard } from "@/components/user-card"; 2 | import { getSession } from "@/lib/auth"; 3 | 4 | export async function CurrentUserCard() { 5 | const { user } = await getSession(); 6 | 7 | if (!user) { 8 | return null; 9 | } 10 | 11 | return ; 12 | } 13 | -------------------------------------------------------------------------------- /packages/ui/src/components/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@umamin/ui/lib/utils"; 2 | 3 | function Skeleton({ className, ...props }: React.ComponentProps<"div">) { 4 | return ( 5 |
10 | ); 11 | } 12 | 13 | export { Skeleton }; 14 | -------------------------------------------------------------------------------- /apps/www/public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/www/public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/www/app/robots.ts: -------------------------------------------------------------------------------- 1 | import type { MetadataRoute } from "next"; 2 | 3 | export default function robots(): MetadataRoute.Robots { 4 | const base = "https://www.umamin.link"; 5 | return { 6 | rules: [ 7 | { 8 | userAgent: "*", 9 | allow: "/", 10 | disallow: ["/api/*", "/auth/*", "/inbox/*", "/settings/*"], 11 | }, 12 | ], 13 | sitemap: `${base}/sitemap.xml`, 14 | host: base, 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /apps/www/components/loading-icon.tsx: -------------------------------------------------------------------------------- 1 | import { Loader2Icon, type LucideIcon } from "lucide-react"; 2 | 3 | type Props = { 4 | loading: boolean; 5 | icon?: LucideIcon; 6 | }; 7 | export function LoadingIcon({ loading, icon }: Props) { 8 | const Icon = icon; 9 | return ( 10 | <> 11 | {loading ? ( 12 | 13 | ) : ( 14 | Icon && 15 | )} 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /packages/db/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "esModuleInterop": true, 5 | "skipLibCheck": true, 6 | "target": "es2022", 7 | "allowJs": true, 8 | "resolveJsonModule": true, 9 | "moduleDetection": "force", 10 | "isolatedModules": true, 11 | "strict": true, 12 | "noUncheckedIndexedAccess": true, 13 | "module": "NodeNext" 14 | }, 15 | "include": ["src"], 16 | "exclude": ["node_modules"] 17 | } 18 | -------------------------------------------------------------------------------- /packages/encryption/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "esModuleInterop": true, 5 | "skipLibCheck": true, 6 | "target": "es2022", 7 | "allowJs": true, 8 | "resolveJsonModule": true, 9 | "moduleDetection": "force", 10 | "isolatedModules": true, 11 | "strict": true, 12 | "noUncheckedIndexedAccess": true, 13 | "module": "NodeNext" 14 | }, 15 | "include": ["src"], 16 | "exclude": ["node_modules"] 17 | } 18 | -------------------------------------------------------------------------------- /apps/www/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "../../packages/ui/src/styles/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true 11 | }, 12 | "iconLibrary": "lucide", 13 | "aliases": { 14 | "components": "@/components", 15 | "hooks": "@/hooks", 16 | "lib": "@/lib", 17 | "utils": "@umamin/ui/lib/utils", 18 | "ui": "@umamin/ui/components" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/ui/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "src/styles/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true 11 | }, 12 | "iconLibrary": "lucide", 13 | "aliases": { 14 | "components": "@umamin/ui/components", 15 | "utils": "@umamin/ui/lib/utils", 16 | "hooks": "@umamin/ui/hooks", 17 | "lib": "@umamin/ui/lib", 18 | "ui": "@umamin/ui/components" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apps/www/hooks/use-media-query.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export function useMediaQuery(query: string) { 4 | const [value, setValue] = useState(false); 5 | 6 | useEffect(() => { 7 | function onChange(event: MediaQueryListEvent) { 8 | setValue(event.matches); 9 | } 10 | 11 | const result = matchMedia(query); 12 | result.addEventListener("change", onChange); 13 | setValue(result.matches); 14 | 15 | return () => result.removeEventListener("change", onChange); 16 | }, [query]); 17 | 18 | return value; 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # Dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # Local env files 9 | .env 10 | .env.local 11 | .env.development.local 12 | .env.test.local 13 | .env.production.local 14 | 15 | # Testing 16 | coverage 17 | 18 | # Turbo 19 | .turbo 20 | 21 | # Vercel 22 | .vercel 23 | 24 | # Build Outputs 25 | .next/ 26 | out/ 27 | build 28 | dist 29 | 30 | 31 | # Debug 32 | npm-debug.log* 33 | yarn-debug.log* 34 | yarn-error.log* 35 | 36 | # Misc 37 | .DS_Store 38 | *.pem 39 | -------------------------------------------------------------------------------- /packages/db/src/index.ts: -------------------------------------------------------------------------------- 1 | import { drizzle } from "drizzle-orm/libsql"; 2 | import * as schema from "./schema"; 3 | 4 | export const db = drizzle({ 5 | connection: { 6 | url: process.env.TURSO_CONNECTION_URL ?? "", 7 | authToken: process.env.TURSO_AUTH_TOKEN ?? "", 8 | }, 9 | // cache: 10 | // process.env.NODE_ENV === "production" 11 | // ? upstashCache({ 12 | // url: process.env.UPSTASH_REDIS_REST_URL ?? "", 13 | // token: process.env.UPSTASH_REDIS_REST_TOKEN ?? "", 14 | // global: true, 15 | // }) 16 | // : undefined, 17 | schema, 18 | }); 19 | -------------------------------------------------------------------------------- /apps/www/app/terms/page.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import TermsOfService from "@/markdown/terms.mdx"; 3 | 4 | export const metadata: Metadata = { 5 | title: "Umamin — Terms of Service", 6 | description: 7 | "Understand the terms and conditions for using Umamin, an open-source platform for sending and receiving encrypted anonymous messages.", 8 | }; 9 | 10 | export default function Page() { 11 | return ( 12 |
13 | 14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /apps/www/components/hover-prefetch-link.tsx: -------------------------------------------------------------------------------- 1 | import Link, { type LinkProps } from "next/link"; 2 | import { useState } from "react"; 3 | 4 | type Props = { 5 | children: React.ReactNode; 6 | className?: string; 7 | } & LinkProps; 8 | 9 | export function HoverPrefetchLink({ children, className, ...rest }: Props) { 10 | const [active, setActive] = useState(false); 11 | 12 | return ( 13 | setActive(true)} 18 | > 19 | {children} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /apps/www/app/privacy/page.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import Privacy from "@/markdown/privacy.mdx"; 3 | 4 | export const metadata: Metadata = { 5 | title: "Umamin — Privacy Policy", 6 | description: 7 | "Learn how Umamin, an open-source platform for sending and receiving encrypted anonymous messages, collects, uses, and protects your personal information.", 8 | }; 9 | 10 | export default function Page() { 11 | return ( 12 |
13 | 14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /packages/encryption/src/generate-aes-key.ts: -------------------------------------------------------------------------------- 1 | const { subtle } = globalThis.crypto; 2 | 3 | async function main() { 4 | try { 5 | const key = await subtle.generateKey( 6 | { name: "AES-GCM", length: 256 }, 7 | true, 8 | ["encrypt", "decrypt"], 9 | ); 10 | const rawKey = await subtle.exportKey("raw", key); 11 | const base64Key = Buffer.from(new Uint8Array(rawKey)).toString("base64"); 12 | console.log("Generated AES-256-GCM key (base64):"); 13 | console.log(base64Key); 14 | } catch (err) { 15 | console.error("Failed to generate key:", err); 16 | process.exit(1); 17 | } 18 | } 19 | 20 | main(); 21 | -------------------------------------------------------------------------------- /packages/encryption/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@umamin/encryption", 3 | "version": "0.0.0", 4 | "private": true, 5 | "exports": { 6 | ".": { 7 | "types": "./dist/index.d.mts", 8 | "default": "./dist/index.mjs" 9 | } 10 | }, 11 | "scripts": { 12 | "dev": "tsdown --watch ./src", 13 | "build": "tsdown", 14 | "generate": "tsx ./src/generate-aes-key.ts", 15 | "clean": "rm -rf ./node_modules .turbo dist", 16 | "check-types": "tsc --noEmit" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^22.19.3", 20 | "tsdown": "^0.18.0", 21 | "tsx": "^4.21.0", 22 | "typescript": "^5.9.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apps/www/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /packages/ui/src/components/label.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as LabelPrimitive from "@radix-ui/react-label"; 4 | import { cn } from "@umamin/ui/lib/utils"; 5 | 6 | function Label({ 7 | className, 8 | ...props 9 | }: React.ComponentProps) { 10 | return ( 11 | 19 | ); 20 | } 21 | 22 | export { Label }; 23 | -------------------------------------------------------------------------------- /apps/www/next.config.ts: -------------------------------------------------------------------------------- 1 | import createMDX from "@next/mdx"; 2 | 3 | import type { NextConfig } from "next"; 4 | 5 | const nextConfig: NextConfig = { 6 | reactCompiler: true, 7 | cacheComponents: true, 8 | experimental: { 9 | turbopackFileSystemCacheForDev: true, 10 | }, 11 | images: { 12 | remotePatterns: [new URL("https://lh3.googleusercontent.com/a/**")], 13 | }, 14 | compiler: { 15 | removeConsole: process.env.NODE_ENV === "production", 16 | }, 17 | transpilePackages: ["@umamin/db", "@umamin/encryption", "@umamin/ui"], 18 | }; 19 | 20 | const withMDX = createMDX({ 21 | options: { 22 | remarkPlugins: ["remark-gfm"], 23 | }, 24 | }); 25 | 26 | export default withMDX(nextConfig); 27 | -------------------------------------------------------------------------------- /packages/ui/src/components/sonner.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useTheme } from "next-themes"; 4 | import { Toaster as Sonner, type ToasterProps } from "sonner"; 5 | 6 | const Toaster = ({ ...props }: ToasterProps) => { 7 | const { theme = "system" } = useTheme(); 8 | 9 | return ( 10 | 22 | ); 23 | }; 24 | 25 | export { Toaster }; 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "" 5 | labels: "feature request" 6 | assignees: "" 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /packages/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "jsx": "react-jsx", 5 | "declaration": true, 6 | "declarationMap": true, 7 | "esModuleInterop": true, 8 | "incremental": false, 9 | "isolatedModules": true, 10 | "lib": ["es2022", "DOM", "DOM.Iterable"], 11 | "module": "NodeNext", 12 | "moduleDetection": "force", 13 | "moduleResolution": "NodeNext", 14 | "noUncheckedIndexedAccess": true, 15 | "resolveJsonModule": true, 16 | "skipLibCheck": true, 17 | "strict": true, 18 | "target": "ES2022", 19 | "paths": { 20 | "@umamin/ui/*": ["./src/*"] 21 | } 22 | }, 23 | "include": ["."], 24 | "exclude": ["node_modules", "dist"] 25 | } 26 | -------------------------------------------------------------------------------- /apps/www/app/settings/components/sign-out-button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useQueryClient } from "@tanstack/react-query"; 4 | import { Button } from "@umamin/ui/components/button"; 5 | import { Loader2Icon, LogOutIcon } from "lucide-react"; 6 | import { useFormStatus } from "react-dom"; 7 | 8 | export function SignOutButton() { 9 | const { pending } = useFormStatus(); 10 | const queryClient = useQueryClient(); 11 | 12 | return ( 13 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /apps/www/app/user/[username]/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@umamin/ui/components/button"; 2 | import { MessageSquareMoreIcon, UserPlusIcon } from "lucide-react"; 3 | import { UserCardSkeleton } from "@/components/skeleton/user-card-skeleton"; 4 | 5 | export default function Loading() { 6 | return ( 7 |
8 | 9 | 10 |
11 | 15 | 16 | 20 |
21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /apps/www/components/footer.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | export function Footer() { 4 | return ( 5 |
6 |
7 | developed by{" "} 8 | 13 | @josh.xfi 14 | {" "} 15 | and{" "} 16 | 21 | contributors 22 | 23 |
24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /apps/www/app/settings/components/danger-button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useQueryClient } from "@tanstack/react-query"; 4 | import { Button } from "@umamin/ui/components/button"; 5 | import { Loader2Icon } from "lucide-react"; 6 | import { useFormStatus } from "react-dom"; 7 | 8 | export function DeleteButton({ confirmText }: { confirmText: string }) { 9 | const { pending } = useFormStatus(); 10 | const queryClient = useQueryClient(); 11 | 12 | return ( 13 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /apps/www/hooks/use-dynamic-textarea.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useLayoutEffect, useRef } from "react"; 2 | 3 | function updateTextAreaSize(textArea?: HTMLTextAreaElement | null) { 4 | if (!textArea) return; 5 | textArea.style.height = "3rem"; 6 | textArea.style.height = `${textArea.scrollHeight}px`; 7 | } 8 | 9 | export function useDynamicTextarea(content: string) { 10 | const textAreaRef = useRef(null); 11 | 12 | const inputRef = useCallback((textArea: HTMLTextAreaElement | null) => { 13 | textAreaRef.current = textArea; 14 | updateTextAreaSize(textArea); 15 | }, []); 16 | 17 | // biome-ignore lint/correctness/useExhaustiveDependencies: updates height only when content changes 18 | useLayoutEffect(() => { 19 | updateTextAreaSize(textAreaRef.current); 20 | }, [content]); 21 | 22 | return inputRef; 23 | } 24 | -------------------------------------------------------------------------------- /packages/ui/src/components/textarea.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@umamin/ui/lib/utils"; 2 | import type * as React from "react"; 3 | 4 | function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { 5 | return ( 6 |