diff --git a/apps/playground/eslint.config.js b/apps/playground/eslint.config.js new file mode 100644 index 00000000..fe3ef4a9 --- /dev/null +++ b/apps/playground/eslint.config.js @@ -0,0 +1,14 @@ +import baseConfig, { restrictEnvAccess } from "@swy/eslint-config/base"; +import nextjsConfig from "@swy/eslint-config/nextjs"; +import reactConfig from "@swy/eslint-config/react"; + +/** @type {import('typescript-eslint').Config} */ +export default [ + { + ignores: [".next/**"], + }, + ...baseConfig, + ...reactConfig, + ...nextjsConfig, + ...restrictEnvAccess, +]; diff --git a/apps/playground/next-env.d.ts b/apps/playground/next-env.d.ts new file mode 100644 index 00000000..40c3d680 --- /dev/null +++ b/apps/playground/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/apps/playground/next.config.js b/apps/playground/next.config.js new file mode 100644 index 00000000..3f4065ce --- /dev/null +++ b/apps/playground/next.config.js @@ -0,0 +1,57 @@ +import { fileURLToPath } from "url"; +import { createJiti } from "jiti"; + +// Import env files to validate at build time. Use jiti so we can load .ts files in here. +await createJiti(fileURLToPath(import.meta.url))("./src/env"); + +/** @type {import("next").NextConfig} */ +const config = { + reactStrictMode: true, + + /** Enables hot reloading for local packages without a build step */ + transpilePackages: [ + "@swy/liveblocks", + "@swy/notion", + "@swy/ui", + "@swy/validators", + "lucide-react", + ], + experimental: { + optimizePackageImports: ["lucide-react"], + }, + + /** We already do linting and typechecking as separate tasks in CI */ + eslint: { ignoreDuringBuilds: true }, + typescript: { ignoreBuildErrors: true }, + /** */ + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "img.clerk.com", + }, + { + protocol: "https", + hostname: "images.unsplash.com", + }, + { + protocol: "https", + hostname: "files.edgestore.dev", + }, + { + protocol: "https", + hostname: "www.notion.so", + }, + { + protocol: "https", + hostname: "upload.wikimedia.org", + }, + { + protocol: "https", + hostname: "avatars.githubusercontent.com", + }, + ], + }, +}; + +export default config; diff --git a/apps/playground/package.json b/apps/playground/package.json new file mode 100644 index 00000000..180d0e13 --- /dev/null +++ b/apps/playground/package.json @@ -0,0 +1,49 @@ +{ + "name": "@swy/playground", + "version": "1.4.0", + "private": true, + "type": "module", + "scripts": { + "build": "pnpm with-env next build", + "clean": "git clean -xdf .next .turbo node_modules", + "dev": "pnpm with-env next dev -p 3002", + "format": "prettier --check . --ignore-path ../../.gitignore", + "lint": "eslint", + "start": "pnpm with-env next start", + "typecheck": "tsc --noEmit", + "with-env": "dotenv -e ../../.env --" + }, + "dependencies": { + "@swy/i18n": "workspace:*", + "@swy/liveblocks": "workspace:*", + "@swy/notion": "workspace:*", + "@swy/ui": "workspace:*", + "@swy/validators": "workspace:*", + "@t3-oss/env-nextjs": "catalog:env", + "lucide-react": "catalog:ui", + "next": "catalog:next14", + "react": "catalog:react18", + "react-dom": "catalog:react18", + "react-textarea-autosize": "^8.5.3", + "sonner": "catalog:ui", + "swr": "^2.2.5", + "yjs": "^13.6.15", + "zod": "catalog:" + }, + "devDependencies": { + "@swy/eslint-config": "workspace:*", + "@swy/prettier-config": "workspace:*", + "@swy/tailwind-config": "workspace:*", + "@swy/tsconfig": "workspace:*", + "@types/node": "catalog:node20", + "@types/react": "catalog:react18", + "@types/react-dom": "catalog:react18", + "dotenv-cli": "catalog:env", + "eslint": "catalog:", + "jiti": "^2.4.0", + "prettier": "catalog:", + "tailwindcss": "catalog:", + "typescript": "catalog:" + }, + "prettier": "@swy/prettier-config" +} diff --git a/apps/playground/postcss.config.cjs b/apps/playground/postcss.config.cjs new file mode 100644 index 00000000..ee5f90b3 --- /dev/null +++ b/apps/playground/postcss.config.cjs @@ -0,0 +1,5 @@ +module.exports = { + plugins: { + tailwindcss: {}, + }, +}; diff --git a/apps/playground/public/documents-dark.png b/apps/playground/public/documents-dark.png new file mode 100644 index 00000000..db6193c0 Binary files /dev/null and b/apps/playground/public/documents-dark.png differ diff --git a/apps/playground/public/documents.png b/apps/playground/public/documents.png new file mode 100644 index 00000000..4e4518e1 Binary files /dev/null and b/apps/playground/public/documents.png differ diff --git a/apps/playground/public/empty-dark.png b/apps/playground/public/empty-dark.png new file mode 100644 index 00000000..6349df5c Binary files /dev/null and b/apps/playground/public/empty-dark.png differ diff --git a/apps/playground/public/empty.png b/apps/playground/public/empty.png new file mode 100644 index 00000000..3875ded0 Binary files /dev/null and b/apps/playground/public/empty.png differ diff --git a/apps/playground/public/error-dark.png b/apps/playground/public/error-dark.png new file mode 100644 index 00000000..134a35ff Binary files /dev/null and b/apps/playground/public/error-dark.png differ diff --git a/apps/playground/public/error.png b/apps/playground/public/error.png new file mode 100644 index 00000000..50f4495f Binary files /dev/null and b/apps/playground/public/error.png differ diff --git a/apps/playground/public/favicon.ico b/apps/playground/public/favicon.ico new file mode 100644 index 00000000..f0058b40 Binary files /dev/null and b/apps/playground/public/favicon.ico differ diff --git a/apps/playground/public/next.svg b/apps/playground/public/next.svg new file mode 100644 index 00000000..5174b28c --- /dev/null +++ b/apps/playground/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/playground/public/notion-dark.svg b/apps/playground/public/notion-dark.svg new file mode 100644 index 00000000..b3fa4afc --- /dev/null +++ b/apps/playground/public/notion-dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/playground/public/notion.svg b/apps/playground/public/notion.svg new file mode 100644 index 00000000..bf6442f7 --- /dev/null +++ b/apps/playground/public/notion.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/playground/public/reading-dark.png b/apps/playground/public/reading-dark.png new file mode 100644 index 00000000..c3871589 Binary files /dev/null and b/apps/playground/public/reading-dark.png differ diff --git a/apps/playground/public/reading.png b/apps/playground/public/reading.png new file mode 100644 index 00000000..826b7e9a Binary files /dev/null and b/apps/playground/public/reading.png differ diff --git a/apps/playground/public/t3-icon.svg b/apps/playground/public/t3-icon.svg new file mode 100644 index 00000000..e377165f --- /dev/null +++ b/apps/playground/public/t3-icon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/apps/playground/public/vercel.svg b/apps/playground/public/vercel.svg new file mode 100644 index 00000000..d2f84222 --- /dev/null +++ b/apps/playground/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/playground/src/app/(marketing)/_components/footer.tsx b/apps/playground/src/app/(marketing)/_components/footer.tsx new file mode 100644 index 00000000..a7b2101d --- /dev/null +++ b/apps/playground/src/app/(marketing)/_components/footer.tsx @@ -0,0 +1,19 @@ +import { Button } from "@swy/ui/shadcn"; + +import Logo from "./logo"; + +export default function Footer() { + return ( +
+ +
+ + +
+
+ ); +} diff --git a/apps/playground/src/app/(marketing)/_components/heading.tsx b/apps/playground/src/app/(marketing)/_components/heading.tsx new file mode 100644 index 00000000..9a99175d --- /dev/null +++ b/apps/playground/src/app/(marketing)/_components/heading.tsx @@ -0,0 +1,28 @@ +"use client"; + +import Link from "next/link"; +import { ArrowRight } from "lucide-react"; + +import { Button } from "@swy/ui/shadcn"; + +const Heading = () => { + return ( +
+

+ Your Ideas , Documents, & Plans. Unified. Welcome to{" "} + Steeeee WorXpace +

+

+ Steeeee WorXpace is the connected workspace where
+ better, faster work happens. +

+ + + +
+ ); +}; + +export default Heading; diff --git a/apps/playground/src/app/(marketing)/_components/heroes.tsx b/apps/playground/src/app/(marketing)/_components/heroes.tsx new file mode 100644 index 00000000..2d918374 --- /dev/null +++ b/apps/playground/src/app/(marketing)/_components/heroes.tsx @@ -0,0 +1,38 @@ +import Image from "next/image"; + +export default function Heroes() { + return ( +
+
+
+ Documents + Documents +
+
+ Reading + Reading +
+
+
+ ); +} diff --git a/apps/playground/src/app/(marketing)/_components/index.ts b/apps/playground/src/app/(marketing)/_components/index.ts new file mode 100644 index 00000000..57bfa331 --- /dev/null +++ b/apps/playground/src/app/(marketing)/_components/index.ts @@ -0,0 +1,4 @@ +export { default as Heading } from "./heading"; +export { default as Heroes } from "./heroes"; +export { default as Footer } from "./footer"; +export { default as Navbar } from "./navbar"; diff --git a/apps/playground/src/app/(marketing)/_components/logo.tsx b/apps/playground/src/app/(marketing)/_components/logo.tsx new file mode 100644 index 00000000..e2cd9d0c --- /dev/null +++ b/apps/playground/src/app/(marketing)/_components/logo.tsx @@ -0,0 +1,31 @@ +import { Poppins } from "next/font/google"; +import Image from "next/image"; + +import { cn } from "@swy/ui/lib"; + +const font = Poppins({ + subsets: ["latin"], + weight: ["400", "600"], +}); + +export default function Logo() { + return ( +
+ Logo + Logo +

WorXpace

+
+ ); +} diff --git a/apps/playground/src/app/(marketing)/_components/navbar.tsx b/apps/playground/src/app/(marketing)/_components/navbar.tsx new file mode 100644 index 00000000..cbab21dc --- /dev/null +++ b/apps/playground/src/app/(marketing)/_components/navbar.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { useScrollTop } from "@swy/ui/hooks"; +import { cn } from "@swy/ui/lib"; +import { ThemeToggle } from "@swy/ui/shadcn"; + +import Logo from "./logo"; + +export default function Navbar() { + const scrolled = useScrollTop(); + + return ( +
+ +
+ +
+
+ ); +} diff --git a/apps/playground/src/app/(marketing)/layout.tsx b/apps/playground/src/app/(marketing)/layout.tsx new file mode 100644 index 00000000..e65a7f49 --- /dev/null +++ b/apps/playground/src/app/(marketing)/layout.tsx @@ -0,0 +1,12 @@ +import { PropsWithChildren } from "react"; + +import { Navbar } from "./_components"; + +export default function MarketingLayout({ children }: PropsWithChildren) { + return ( +
+ +
{children}
+
+ ); +} diff --git a/apps/playground/src/app/(marketing)/page.tsx b/apps/playground/src/app/(marketing)/page.tsx new file mode 100644 index 00000000..878fae34 --- /dev/null +++ b/apps/playground/src/app/(marketing)/page.tsx @@ -0,0 +1,15 @@ +import { Footer, Heading, Heroes } from "./_components"; + +const Marketing = () => { + return ( +
+
+ + +
+
+
+ ); +}; + +export default Marketing; diff --git a/apps/playground/src/app/(platform)/(auth)/sign-in/_components/sign-in-button.tsx b/apps/playground/src/app/(platform)/(auth)/sign-in/_components/sign-in-button.tsx new file mode 100644 index 00000000..e0c02b66 --- /dev/null +++ b/apps/playground/src/app/(platform)/(auth)/sign-in/_components/sign-in-button.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import Image from "next/image"; + +import { Button } from "@swy/ui/shadcn"; + +interface SignInButtonProps { + name: string; + avatarUrl: string; +} + +export const SignInButton: React.FC = ({ + name, + avatarUrl, +}) => { + return ( + + ); +}; diff --git a/apps/playground/src/app/(platform)/(auth)/sign-in/page.tsx b/apps/playground/src/app/(platform)/(auth)/sign-in/page.tsx new file mode 100644 index 00000000..cc7cbd71 --- /dev/null +++ b/apps/playground/src/app/(platform)/(auth)/sign-in/page.tsx @@ -0,0 +1,25 @@ +import { IconBlock } from "@swy/ui/shared"; + +import { accounts } from "~/db/accounts"; +import { SignInButton } from "./_components/sign-in-button"; + +export default function Page() { + return ( +
+
+ +

+ Sign in with ... +

+
+ {Object.values(accounts).map(({ name, avatarUrl }) => ( + + ))} +
+
+
+ ); +} diff --git a/apps/playground/src/app/(platform)/layout.tsx b/apps/playground/src/app/(platform)/layout.tsx new file mode 100644 index 00000000..12c23dc3 --- /dev/null +++ b/apps/playground/src/app/(platform)/layout.tsx @@ -0,0 +1,19 @@ +"use client"; + +import React, { useEffect } from "react"; + +import { mockDB } from "~/db"; +import { useMockDB } from "~/hooks"; + +export default function Layout({ children }: React.PropsWithChildren) { + const { update } = useMockDB(); + + useEffect(() => { + update(mockDB); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( +
{children}
+ ); +} diff --git a/apps/playground/src/app/globals.css b/apps/playground/src/app/globals.css new file mode 100644 index 00000000..caa1c9ff --- /dev/null +++ b/apps/playground/src/app/globals.css @@ -0,0 +1,98 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + /* text color */ + --primary: 55 53 47; + /* background color */ + --bg-main: 255 255 255; + --bg-input: 242 241 238; + --bg-sidebar: 247 247 245; + --bg-modal: 255 255 255; + --bg-popover: 255 255 255; + --bg-tooltip: 15 15 15; + + /* Geneated by Shadcn */ + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + + /* --popover: 0 0% 100%; */ + /* --popover-foreground: 222.2 84% 4.9%; */ + + /* --primary: 222.2 47.4% 11.2%; */ + /* --primary-foreground: 210 40% 98%; */ + + /* --secondary: 210 40% 96.1%; */ + /* --secondary-foreground: 222.2 47.4% 11.2%; */ + + /* --muted: 210 40% 96.1%; */ + /* --muted-foreground: 215.4 16.3% 46.9%; */ + + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + + /* --border: 214.3 31.8% 91.4%; */ + /* --input: 214.3 31.8% 91.4%; */ + --ring: 222.2 84% 4.9%; + + --radius: 0.5rem; + } + + .dark { + /* text color */ + --primary: 255 255 255; + /* background color */ + --bg-main: 25 25 25; + --bg-input: 255 255 255; + --bg-sidebar: 32 32 32; + --bg-modal: 32 32 32; + --bg-popover: 37 37 37; + --bg-tooltip: 47 47 47; + + /* Geneated by Shadcn */ + --background: 0 0% 12.5%; + --foreground: 210 40% 98%; + + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + + /* --popover: 222.2 84% 4.9%; */ + /* --popover-foreground: 210 40% 98%; */ + + /* --primary: 210 40% 98%; */ + /* --primary-foreground: 222.2 47.4% 11.2%; */ + + /* --secondary: 217.2 32.6% 17.5%; */ + /* --secondary-foreground: 210 40% 98%; */ + + /* --muted: 217.2 32.6% 17.5%; */ + /* --muted-foreground: 215 20.2% 65.1%; */ + + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + + /* --border: 217.2 32.6% 17.5%; */ + /* --input: 217.2 32.6% 17.5%; */ + --ring: 212.7 26.8% 83.9%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-main text-primary dark:text-primary/80; + } +} diff --git a/apps/playground/src/app/layout.tsx b/apps/playground/src/app/layout.tsx new file mode 100644 index 00000000..7b4e5958 --- /dev/null +++ b/apps/playground/src/app/layout.tsx @@ -0,0 +1,63 @@ +import type { Metadata, Viewport } from "next"; +import { Inter } from "next/font/google"; + +import { ThemeProvider } from "@swy/ui/shadcn"; + +import { env } from "~/env"; + +import "~/app/globals.css"; + +const inter = Inter({ subsets: ["latin", "latin-ext"] }); + +export const metadata: Metadata = { + metadataBase: new URL( + env.VERCEL_ENV === "production" + ? "https://playground.steeeee0223.vercel.app" + : `http://localhost:${env.PORT}`, + ), + title: "Steeeee WorXpace", + description: "The all-in-1 workspace where better, faster work happens", + icons: { + icon: [ + { + media: "(prefers-color-scheme: light)", + url: "/notion.svg", + href: "/notion.svg", + }, + { + media: "(prefers-color-scheme: dark)", + url: "/notion-dark.svg", + href: "/notion-dark.svg", + }, + ], + }, +}; + +export const viewport: Viewport = { + themeColor: [ + { media: "(prefers-color-scheme: light)", color: "white" }, + { media: "(prefers-color-scheme: dark)", color: "black" }, + ], +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + + {children} + + + + ); +} diff --git a/apps/playground/src/components/index.ts b/apps/playground/src/components/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/apps/playground/src/db/accounts.ts b/apps/playground/src/db/accounts.ts new file mode 100644 index 00000000..93c279fe --- /dev/null +++ b/apps/playground/src/db/accounts.ts @@ -0,0 +1,47 @@ +import { AccountModel } from "./types"; + +export enum _USER { + U1 = "a97f4e50-0b72-44f4-a95a-aba319534af5", + U2 = "22bd2a9e-bbee-4f3f-9c78-6a430a2c29b2", + U3 = "83e2f249-f02c-4971-b8fc-cda5cf71dd77", +} + +export const accounts: Record = { + [_USER.U1]: { + id: _USER.U1, + name: "Steve Yu", + clerkId: "user_2", + email: "steve-yu@example.com", + avatarUrl: + "https://upload.wikimedia.org/wikipedia/commons/thumb/2/2d/Go_gopher_favicon.svg/1200px-Go_gopher_favicon.svg.png", + preferredName: "Steve", + updatedAt: Date.UTC(2024, 1, 1), + }, + [_USER.U2]: { + id: _USER.U2, + name: "Chien Pong", + clerkId: "user_3", + email: "pong123@example.com", + avatarUrl: "https://avatars.githubusercontent.com/u/91364382", + preferredName: "Pong", + updatedAt: Date.UTC(2024, 1, 1), + }, + [_USER.U3]: { + id: _USER.U3, + name: "Chia Ming", + clerkId: "user_4", + email: "chiaming@example.com", + avatarUrl: "https://avatars.githubusercontent.com/u/15240773", + preferredName: "Mark", + updatedAt: Date.UTC(2024, 1, 1), + }, + // "45d7e133-d354-47c2-881b-441b7f95a327": { + // id: "45d7e133-d354-47c2-881b-441b7f95a327", + // name: "John Wick", + // clerkId: "user_1", + // email: "john-wick@example.com", + // avatarUrl: "", + // preferredName: "John Wick", + // updatedAt: Date.UTC(2024, 1, 1), + // }, +}; diff --git a/apps/playground/src/db/index.ts b/apps/playground/src/db/index.ts new file mode 100644 index 00000000..3bbf07b2 --- /dev/null +++ b/apps/playground/src/db/index.ts @@ -0,0 +1,16 @@ +import { accounts } from "./accounts"; +import { memberships } from "./memberships"; +import type { AccountModel, MembershipModel, WorkspaceModel } from "./types"; +import { workspaces } from "./workspaces"; + +export interface MockDB { + accounts: Record; + workspaces: Record; + memberships: MembershipModel[]; +} + +export const mockDB: MockDB = { + accounts, + workspaces, + memberships, +}; diff --git a/apps/playground/src/db/memberships.ts b/apps/playground/src/db/memberships.ts new file mode 100644 index 00000000..c7a9b6a7 --- /dev/null +++ b/apps/playground/src/db/memberships.ts @@ -0,0 +1,71 @@ +import { Role } from "@swy/validators"; + +import { _USER } from "./accounts"; +import type { MembershipModel } from "./types"; +import { _WORKSPACE } from "./workspaces"; + +export const memberships: MembershipModel[] = [ + { + id: "mem-1", + role: Role.OWNER, + accountId: _USER.U1, + workspaceId: _WORKSPACE.W1, + joinedAt: Date.UTC(2024, 1, 1), + }, + { + id: "mem-2", + role: Role.MEMBER, + accountId: _USER.U2, + workspaceId: _WORKSPACE.W1, + joinedAt: Date.UTC(2024, 1, 1), + }, + { + id: "mem-3", + role: Role.GUEST, + accountId: _USER.U3, + workspaceId: _WORKSPACE.W1, + joinedAt: Date.UTC(2024, 1, 1), + }, + { + id: "mem-4", + role: Role.OWNER, + accountId: _USER.U3, + workspaceId: _WORKSPACE.W2, + joinedAt: Date.UTC(2024, 1, 1), + }, + { + id: "mem-5", + role: Role.MEMBER, + accountId: _USER.U1, + workspaceId: _WORKSPACE.W2, + joinedAt: Date.UTC(2024, 1, 1), + }, + { + id: "mem-6", + role: Role.OWNER, + accountId: _USER.U1, + workspaceId: _WORKSPACE.W3, + joinedAt: Date.UTC(2024, 1, 1), + }, + { + id: "mem-7", + role: Role.OWNER, + accountId: _USER.U2, + workspaceId: _WORKSPACE.W4, + joinedAt: Date.UTC(2024, 1, 1), + }, + { + id: "mem-8", + role: Role.OWNER, + accountId: _USER.U2, + workspaceId: _WORKSPACE.W5, + joinedAt: Date.UTC(2024, 1, 1), + }, + { + id: "mem-9", + role: Role.OWNER, + accountId: _USER.U3, + workspaceId: _WORKSPACE.W5, + joinedAt: Date.UTC(2024, 1, 1), + }, +]; diff --git a/apps/playground/src/db/types.ts b/apps/playground/src/db/types.ts new file mode 100644 index 00000000..48acf6eb --- /dev/null +++ b/apps/playground/src/db/types.ts @@ -0,0 +1,32 @@ +import type { IconInfo } from "@swy/ui/shared"; +import { Plan, Role } from "@swy/validators"; + +export interface AccountModel { + id: string; // default uuid + name: string; + email: string; + avatarUrl: string; + clerkId: string; + preferredName: string; + hasPassword?: boolean; + updatedAt: number; // ts in 'ms' +} + +export interface WorkspaceModel { + id: string; // (uuid) + name: string; + icon?: IconInfo | null; + domain: string; + plan: Plan; + inviteToken: string; // (uuid) default: same as `id` + createdBy: string; // (uuid) account.id + lastEditedAt: number; // ts in 'ms' +} + +export interface MembershipModel { + id: string; // (uuid) + role: Role; + accountId: string; // (uuid) account.id + workspaceId: string; // (uuid) workspace.id + joinedAt: number; // ts in 'ms' +} diff --git a/apps/playground/src/db/workspaces.ts b/apps/playground/src/db/workspaces.ts new file mode 100644 index 00000000..1e9d5115 --- /dev/null +++ b/apps/playground/src/db/workspaces.ts @@ -0,0 +1,65 @@ +import { Plan } from "@swy/validators"; + +import { _USER } from "./accounts"; +import type { WorkspaceModel } from "./types"; + +export enum _WORKSPACE { + W1 = "f12d4c5b-2d3b-4d2b-aef3-8f7319c5d481", + W2 = "b3c2a6e8-9231-4a9b-a89e-7d36d3f35ec2", + W3 = "a2c4e8d7-4b7e-45d9-bb3d-1f9e6fbe7d4f", + W4 = "c1d7e9b6-7a3d-4b2e-a8b3-9f7e6c5d3a2f", + W5 = "d9b3f6a2-5c4e-7b2d-8a3f-1f9e6c7d4b2e", +} + +export const workspaces: Record = { + [_WORKSPACE.W1]: { + id: _WORKSPACE.W1, + name: "Alpha Workspace", + icon: { type: "emoji", emoji: "🚀" }, + domain: "alphaworkspace.com", + plan: Plan.FREE, + inviteToken: _WORKSPACE.W1, + createdBy: _USER.U1, + lastEditedAt: Date.UTC(2024, 1, 31), + }, + [_WORKSPACE.W2]: { + id: _WORKSPACE.W2, + name: "Beta Labs", + icon: { type: "lucide", name: "goal", color: "#D44C47" }, + domain: "betalabs.org", + plan: Plan.EDUCATION, + inviteToken: _WORKSPACE.W2, + createdBy: _USER.U3, + lastEditedAt: Date.UTC(2024, 2, 26), + }, + [_WORKSPACE.W3]: { + id: _WORKSPACE.W3, + name: "Gamma Studio", + icon: { type: "emoji", emoji: "🎨" }, + domain: "gammastudio.net", + plan: Plan.PLUS, + inviteToken: _WORKSPACE.W3, + createdBy: _USER.U1, + lastEditedAt: Date.UTC(2024, 3, 14), + }, + [_WORKSPACE.W4]: { + id: _WORKSPACE.W4, + name: "Delta Group", + icon: { type: "text", text: "D" }, + domain: "deltagroup.com", + plan: Plan.BUSINESS, + inviteToken: _WORKSPACE.W4, + createdBy: _USER.U2, + lastEditedAt: Date.UTC(2024, 4, 29), + }, + [_WORKSPACE.W5]: { + id: _WORKSPACE.W5, + name: "Epsilon Enterprises", + icon: null, + domain: "epsilonent.com", + plan: Plan.ENTERPRISE, + inviteToken: _WORKSPACE.W5, + createdBy: _USER.U2, + lastEditedAt: Date.UTC(2024, 7, 17), + }, +}; diff --git a/apps/playground/src/env.ts b/apps/playground/src/env.ts new file mode 100644 index 00000000..7639a296 --- /dev/null +++ b/apps/playground/src/env.ts @@ -0,0 +1,44 @@ +import { createEnv } from "@t3-oss/env-nextjs"; +import { vercel } from "@t3-oss/env-nextjs/presets"; +import { z } from "zod"; + +export const env = createEnv({ + extends: [vercel()], + shared: { + NODE_ENV: z + .enum(["development", "production", "test"]) + .default("development"), + PORT: z.coerce.number().default(3002), + }, + /** + * Specify your server-side environment variables schema here. + * This way you can ensure the app isn't built with invalid env vars. + */ + server: { + // LIVEBLOCKS + LIVEBLOCKS_SECRET_KEY: z.string(), + }, + /** + * Specify your client-side environment variables schema here. + * For them to be exposed to the client, prefix them with `NEXT_PUBLIC_`. + */ + client: { + // UNSPLASH + NEXT_PUBLIC_UNSPLASH_ACCESS_KEY: z.string(), + }, + /** + * Destructure all variables from `process.env` to make sure they aren't tree-shaken away. + */ + runtimeEnv: { + NODE_ENV: process.env.NODE_ENV, + // NEXT + PORT: process.env.PORT, + // LIVEBLOCKS + LIVEBLOCKS_SECRET_KEY: process.env.LIVEBLOCKS_SECRET_KEY, + // UNSPLASH + NEXT_PUBLIC_UNSPLASH_ACCESS_KEY: + process.env.NEXT_PUBLIC_UNSPLASH_ACCESS_KEY, + }, + skipValidation: + !!process.env.CI || process.env.npm_lifecycle_event === "lint", +}); diff --git a/apps/playground/src/hooks/index.ts b/apps/playground/src/hooks/index.ts new file mode 100644 index 00000000..2183ad53 --- /dev/null +++ b/apps/playground/src/hooks/index.ts @@ -0,0 +1 @@ +export * from "./useMockDB"; diff --git a/apps/playground/src/hooks/useMockDB.ts b/apps/playground/src/hooks/useMockDB.ts new file mode 100644 index 00000000..2bf080db --- /dev/null +++ b/apps/playground/src/hooks/useMockDB.ts @@ -0,0 +1,21 @@ +"use client"; + +import { useLocalStorage } from "usehooks-ts"; + +import type { MockDB } from "~/db"; + +const initial: MockDB = { + accounts: {}, + workspaces: {}, + memberships: [], +}; + +export const useMockDB = () => { + const [value, update] = useLocalStorage("mock:db", initial); + + return { + ...value, + update, + reset: () => update(initial), + }; +}; diff --git a/apps/playground/src/liveblocks.config.ts b/apps/playground/src/liveblocks.config.ts new file mode 100644 index 00000000..185ca73b --- /dev/null +++ b/apps/playground/src/liveblocks.config.ts @@ -0,0 +1,50 @@ +// Define Liveblocks types for your application +// https://liveblocks.io/docs/api-reference/liveblocks-react#Typing-your-data +declare global { + interface Liveblocks { + // Each user's Presence, for useMyPresence, useOthers, etc. + Presence: { + // Example, real-time cursor coordinates + cursor: { x: number; y: number } | null; + }; + + // The Storage tree for the room, for useMutation, useStorage, etc. + Storage: { + // Example, a conflict-free list + // animals: LiveList; + }; + + // Custom user info set when authenticating with a secret key + UserMeta: { + id: string; + info: { + // Example properties, for useSelf, useUser, useOthers, etc. + name: string; + email: string; + avatarUrl: string; + }; + }; + + // Custom events, for useBroadcastEvent, useEventListener + RoomEvent: {}; + // Example has two events, using a union + // | { type: "PLAY" } + // | { type: "REACTION"; emoji: "🔥" }; + + // Custom metadata set on threads, for useThreads, useCreateThread, etc. + ThreadMetadata: { + // Example, attaching coordinates to a thread + // x: number; + // y: number; + }; + + // Custom room info set with resolveRoomsInfo, for useRoomInfo + RoomInfo: { + // Example, rooms with a title and url + // title: string; + // url: string; + }; + } +} + +export {}; diff --git a/apps/playground/tailwind.config.ts b/apps/playground/tailwind.config.ts new file mode 100644 index 00000000..2e814470 --- /dev/null +++ b/apps/playground/tailwind.config.ts @@ -0,0 +1,17 @@ +import type { Config } from "tailwindcss"; + +import baseConfig from "@swy/tailwind-config"; + +export default { + // We need to append the path to the UI package to the content array so that + // those classes are included correctly. + content: [ + ...baseConfig.content, + "../../packages/notion/src/**/*.{ts,tsx}", + "../../packages/ui/src/**/*.{ts,tsx}", + ], + presets: [baseConfig], + theme: { + extend: {}, + }, +} satisfies Config; diff --git a/apps/playground/tsconfig.json b/apps/playground/tsconfig.json new file mode 100644 index 00000000..0739dc1d --- /dev/null +++ b/apps/playground/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "@swy/tsconfig/base.json", + "compilerOptions": { + "module": "esnext", + "lib": ["ES2022", "dom", "dom.iterable"], + "jsx": "preserve", + "baseUrl": ".", + "types": ["../../packages/i18n/src/types/i18next.d.ts"], + "paths": { "~/*": ["./src/*"] }, + "plugins": [{ "name": "next" }] + }, + "include": [".", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/apps/storybook/src/stories/shared/icon-menu.stories.tsx b/apps/storybook/src/stories/shared/icon-menu.stories.tsx index b2bb7ccc..559a66fe 100644 --- a/apps/storybook/src/stories/shared/icon-menu.stories.tsx +++ b/apps/storybook/src/stories/shared/icon-menu.stories.tsx @@ -12,14 +12,11 @@ export default meta; type Story = StoryObj; -const defaultIcon: IconInfo = { type: "text", text: "S" } +const defaultIcon: IconInfo = { type: "text", text: "S" }; const Template: Story["render"] = () => { const [icon, setIcon] = useState(defaultIcon); return ( - setIcon(defaultIcon)} - > + setIcon(defaultIcon)}> ); diff --git a/packages/ui/src/components/shared/icon-menu/emoji-picker/emoji-picker-content.tsx b/packages/ui/src/components/shared/icon-menu/emoji-picker/emoji-picker-content.tsx index fdd2a634..26c00bc9 100644 --- a/packages/ui/src/components/shared/icon-menu/emoji-picker/emoji-picker-content.tsx +++ b/packages/ui/src/components/shared/icon-menu/emoji-picker/emoji-picker-content.tsx @@ -116,7 +116,7 @@ export function EmojiPickerContent({
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9ab43041..b4027ee8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -185,6 +185,94 @@ importers: specifier: 'catalog:' version: 5.6.3 + apps/playground: + dependencies: + '@swy/i18n': + specifier: workspace:* + version: link:../../packages/i18n + '@swy/liveblocks': + specifier: workspace:* + version: link:../../packages/liveblocks + '@swy/notion': + specifier: workspace:* + version: link:../../packages/notion + '@swy/ui': + specifier: workspace:* + version: link:../../packages/ui + '@swy/validators': + specifier: workspace:* + version: link:../../packages/validators + '@t3-oss/env-nextjs': + specifier: catalog:env + version: 0.11.1(typescript@5.6.3)(zod@3.23.8) + lucide-react: + specifier: catalog:ui + version: 0.456.0(react@18.3.1) + next: + specifier: catalog:next14 + version: 14.2.15(@babel/core@7.25.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: + specifier: catalog:react18 + version: 18.3.1 + react-dom: + specifier: catalog:react18 + version: 18.3.1(react@18.3.1) + react-textarea-autosize: + specifier: ^8.5.3 + version: 8.5.4(@types/react@18.3.11)(react@18.3.1) + sonner: + specifier: catalog:ui + version: 1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + swr: + specifier: ^2.2.5 + version: 2.2.5(react@18.3.1) + yjs: + specifier: ^13.6.15 + version: 13.6.20 + zod: + specifier: 'catalog:' + version: 3.23.8 + devDependencies: + '@swy/eslint-config': + specifier: workspace:* + version: link:../../tooling/eslint + '@swy/prettier-config': + specifier: workspace:* + version: link:../../tooling/prettier + '@swy/tailwind-config': + specifier: workspace:* + version: link:../../tooling/tailwind + '@swy/tsconfig': + specifier: workspace:* + version: link:../../tooling/typescript + '@types/node': + specifier: catalog:node20 + version: 20.16.13 + '@types/react': + specifier: catalog:react18 + version: 18.3.11 + '@types/react-dom': + specifier: catalog:react18 + version: 18.3.1 + dotenv-cli: + specifier: catalog:env + version: 7.4.2 + eslint: + specifier: 'catalog:' + version: 9.13.0(jiti@2.4.0) + jiti: + specifier: ^2.4.0 + version: 2.4.0 + prettier: + specifier: 'catalog:' + version: 3.3.3 + tailwindcss: + specifier: 'catalog:' + version: 3.4.14(ts-node@10.9.2(@types/node@20.16.13)(typescript@5.6.3)) + typescript: + specifier: 'catalog:' + version: 5.6.3 + apps/storybook: dependencies: '@swy/liveblocks':