├── apps └── web │ ├── strip-attributes.d.ts │ ├── postcss.config.js │ ├── tailwind.config.js │ ├── .eslintrc.js │ ├── public │ ├── favicon.ico │ ├── static │ │ ├── dashboard.png │ │ └── animals │ │ │ ├── bird.png │ │ │ ├── rat.png │ │ │ ├── rabbit.png │ │ │ └── walrus.png │ └── fonts │ │ └── eudoxus-sans-var.woff2 │ ├── .env │ ├── prisma │ ├── migrations │ │ ├── migration_lock.toml │ │ ├── 20220802080439_remove_reaction_id │ │ │ └── migration.sql │ │ ├── 20220802073542_reaction_count │ │ │ └── migration.sql │ │ └── 20220802073129_init │ │ │ └── migration.sql │ └── schema.prisma │ ├── tsconfig.json │ ├── next-env.d.ts │ ├── pages │ ├── index.tsx │ ├── _document.tsx │ ├── api │ │ ├── trpc │ │ │ └── [trpc].ts │ │ └── auth │ │ │ └── [...nextauth].ts │ ├── _app.tsx │ ├── dashboard.tsx │ ├── view │ │ └── [id].tsx │ └── editor │ │ └── [id].tsx │ ├── lib │ ├── useSSRTheme.ts │ ├── store.ts │ ├── useIsMounted.ts │ ├── useScreen.ts │ ├── useHotkeys.ts │ ├── useOnClickOutside.ts │ ├── useHover.ts │ ├── useSSRMediaQuery.ts │ ├── browser.ts │ └── trpc.ts │ ├── server │ ├── createRouter.ts │ ├── prisma.ts │ ├── context.ts │ ├── env.js │ └── routers │ │ ├── _app.ts │ │ ├── reactions.ts │ │ ├── folders.ts │ │ └── drafts.ts │ ├── next-auth.d.ts │ ├── next.config.js │ ├── modules │ ├── layout │ │ ├── QuickFindPopover.tsx │ │ ├── SidebarItem │ │ │ ├── icons │ │ │ │ ├── asdf.tsx │ │ │ │ ├── FocusedExploreIcon.tsx │ │ │ │ ├── FocusedHomeIcon.tsx │ │ │ │ ├── FocusedArchiveIcon.tsx │ │ │ │ └── FocusedTrophyIcon.tsx │ │ │ └── index.tsx │ │ ├── SidebarControls.tsx │ │ ├── SampleSplitter.tsx │ │ ├── DashboardLayout.tsx │ │ ├── FloatingActions.tsx │ │ ├── UserDropdown.tsx │ │ ├── SettingsModal.tsx │ │ ├── Sidebar.tsx │ │ └── FileTree.tsx │ ├── landing-page │ │ ├── Statistic.tsx │ │ ├── ListCheck.tsx │ │ ├── Waitlist.tsx │ │ ├── Navbar.tsx │ │ ├── FeatureCard.tsx │ │ ├── HeroSection.tsx │ │ └── LandingPage.tsx │ ├── kbar │ │ ├── CommandPallette.tsx │ │ └── RenderResults.tsx │ ├── editor │ │ ├── FormikAutoSave.tsx │ │ ├── plugins │ │ │ ├── MathNode.tsx │ │ │ ├── TrailingNode.tsx │ │ │ ├── Placeholder.tsx │ │ │ ├── CommandsList.tsx │ │ │ ├── SlashCommands.tsx │ │ │ └── DraggableItems.tsx │ │ └── RichTextEditor.tsx │ └── authentication │ │ └── LoginModal.tsx │ ├── styles │ ├── lowlight.css │ └── globals.css │ ├── README.md │ └── package.json ├── packages ├── ui │ ├── postcss.config.js │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── index.tsx │ ├── src │ │ ├── ThemeIcon.tsx │ │ ├── IconButton.tsx │ │ ├── Avatar.tsx │ │ ├── ScrollArea.tsx │ │ ├── IconInput.tsx │ │ ├── Popover.tsx │ │ ├── Input.tsx │ │ ├── Menu.tsx │ │ ├── Modal.tsx │ │ └── Button.tsx │ └── package.json ├── tailwind-config │ ├── postcss.config.js │ ├── package.json │ └── tailwind.config.js ├── eslint-config-custom │ ├── index.js │ └── package.json └── tsconfig │ ├── package.json │ ├── react-library.json │ ├── base.json │ └── nextjs.json ├── .eslintrc.js ├── turbo.json ├── .gitignore ├── README.md ├── package.json └── patches └── @tiptap+react+2.0.0-beta.114.patch /apps/web/strip-attributes.d.ts: -------------------------------------------------------------------------------- 1 | declare module "strip-attributes"; 2 | -------------------------------------------------------------------------------- /apps/web/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require("tailwind-config/postcss.config"); 2 | -------------------------------------------------------------------------------- /apps/web/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require("tailwind-config/tailwind.config"); 2 | -------------------------------------------------------------------------------- /packages/ui/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require("tailwind-config/postcss.config"); 2 | -------------------------------------------------------------------------------- /apps/web/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ["custom"], 4 | }; 5 | -------------------------------------------------------------------------------- /packages/ui/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require("tailwind-config/tailwind.config"); 2 | -------------------------------------------------------------------------------- /apps/web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderinblack08/presage/HEAD/apps/web/public/favicon.ico -------------------------------------------------------------------------------- /apps/web/public/static/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderinblack08/presage/HEAD/apps/web/public/static/dashboard.png -------------------------------------------------------------------------------- /apps/web/public/static/animals/bird.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderinblack08/presage/HEAD/apps/web/public/static/animals/bird.png -------------------------------------------------------------------------------- /apps/web/public/static/animals/rat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderinblack08/presage/HEAD/apps/web/public/static/animals/rat.png -------------------------------------------------------------------------------- /apps/web/.env: -------------------------------------------------------------------------------- 1 | # DATABASE_URL="mysql://root@127.0.0.1:3309/presage" 2 | DATABASE_URL="postgres://postgres:postgres@localhost:5432/presage" 3 | -------------------------------------------------------------------------------- /apps/web/public/static/animals/rabbit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderinblack08/presage/HEAD/apps/web/public/static/animals/rabbit.png -------------------------------------------------------------------------------- /apps/web/public/static/animals/walrus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderinblack08/presage/HEAD/apps/web/public/static/animals/walrus.png -------------------------------------------------------------------------------- /apps/web/public/fonts/eudoxus-sans-var.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderinblack08/presage/HEAD/apps/web/public/fonts/eudoxus-sans-var.woff2 -------------------------------------------------------------------------------- /packages/tailwind-config/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /packages/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/nextjs.json", 3 | "include": ["."], 4 | "exclude": ["dist", "build", "node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /apps/web/prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /apps/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tsconfig/nextjs.json", 3 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "server/env.js"], 4 | "exclude": ["node_modules"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/eslint-config-custom/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["next", "prettier"], 3 | rules: { 4 | "@next/next/no-html-link-for-pages": "off", 5 | "react/jsx-key": "off", 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /apps/web/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /apps/web/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | import { LandingPage } from "../modules/landing-page/LandingPage"; 3 | 4 | const Home: NextPage = () => { 5 | return ; 6 | }; 7 | 8 | export default Home; 9 | -------------------------------------------------------------------------------- /packages/tsconfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tsconfig", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "index.js", 6 | "files": [ 7 | "base.json", 8 | "nextjs.json", 9 | "react-library.json" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | // This tells ESLint to load the config from the package `eslint-config-custom` 4 | extends: ["custom"], 5 | settings: { 6 | next: { 7 | rootDir: ["apps/*/"], 8 | }, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "pipeline": { 3 | "build": { 4 | "dependsOn": ["^build"], 5 | "outputs": ["dist/**", ".next/**"] 6 | }, 7 | "lint": { 8 | "outputs": [] 9 | }, 10 | "dev": { 11 | "cache": false 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/web/lib/useSSRTheme.ts: -------------------------------------------------------------------------------- 1 | import { useTheme } from "next-themes"; 2 | import { useIsMounted } from "./useIsMounted"; 3 | 4 | export const useSSRTheme = () => { 5 | const theme = useTheme(); 6 | const isMounted = useIsMounted(); 7 | 8 | return isMounted() ? theme : null; 9 | }; 10 | -------------------------------------------------------------------------------- /apps/web/server/createRouter.ts: -------------------------------------------------------------------------------- 1 | import * as trpc from "@trpc/server"; 2 | import { Context } from "./context"; 3 | 4 | /** 5 | * Helper function to create a router with context 6 | */ 7 | export function createRouter() { 8 | return trpc.router(); 9 | } 10 | -------------------------------------------------------------------------------- /apps/web/lib/store.ts: -------------------------------------------------------------------------------- 1 | import { atom } from "jotai"; 2 | 3 | export const collapseAtom = atom(false); 4 | export const currentFileAtom = atom({ 5 | draftId: "", 6 | stringPath: [] as string[], 7 | absolutePath: [] as string[], 8 | }); 9 | 10 | export const fileTreeAtom = atom(new Set()); 11 | -------------------------------------------------------------------------------- /packages/tsconfig/react-library.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "React Library", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "lib": ["ES2015"], 7 | "module": "ESNext", 8 | "target": "ES6", 9 | "jsx": "react-jsx" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/web/next-auth.d.ts: -------------------------------------------------------------------------------- 1 | import NextAuth, { DefaultSession } from "next-auth"; 2 | 3 | declare module "next-auth" { 4 | /** 5 | * Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context 6 | */ 7 | interface Session { 8 | user: { 9 | id: string; 10 | } & DefaultSession["user"]; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/tailwind-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tailwind-config", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "index.js", 6 | "files": [ 7 | "tailwind.config.js", 8 | "postcss.config.js" 9 | ], 10 | "devDependencies": { 11 | "@tailwindcss/forms": "^0.5.2", 12 | "@tailwindcss/typography": "^0.5.4" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/ui/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./src/Button"; 2 | export * from "./src/Avatar"; 3 | export * from "./src/IconInput"; 4 | export * from "./src/Input"; 5 | export * from "./src/Menu"; 6 | export * from "./src/Modal"; 7 | export * from "./src/ThemeIcon"; 8 | export * from "./src/ScrollArea"; 9 | export * from "./src/Popover"; 10 | export * from "./src/IconButton"; 11 | -------------------------------------------------------------------------------- /packages/eslint-config-custom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-config-custom", 3 | "version": "0.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "eslint-config-next": "^12.0.8", 8 | "eslint-config-prettier": "^8.3.0", 9 | "eslint-plugin-react": "7.28.0" 10 | }, 11 | "publishConfig": { 12 | "access": "public" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/web/lib/useIsMounted.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useRef } from "react"; 2 | 3 | export function useIsMounted() { 4 | const isMounted = useRef(false); 5 | 6 | useEffect(() => { 7 | isMounted.current = true; 8 | 9 | return () => { 10 | isMounted.current = false; 11 | }; 12 | }, []); 13 | 14 | return useCallback(() => isMounted.current, []); 15 | } 16 | -------------------------------------------------------------------------------- /apps/web/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import Document, { Html, Head, Main, NextScript } from "next/document"; 2 | 3 | class MyDocument extends Document { 4 | render() { 5 | return ( 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | ); 14 | } 15 | } 16 | 17 | export default MyDocument; 18 | -------------------------------------------------------------------------------- /apps/web/lib/useScreen.ts: -------------------------------------------------------------------------------- 1 | import { useSSRMediaQuery } from "./useSSRMediaQuery"; 2 | 3 | export const useScreen = () => { 4 | const isSmallerThanMobile = useSSRMediaQuery("(max-width: 640px)"); 5 | const isSmallerThanTablet = useSSRMediaQuery("(max-width: 768px)"); 6 | const isSmallerThanDesktop = useSSRMediaQuery("(max-width: 1024px)"); 7 | 8 | return { isSmallerThanMobile, isSmallerThanDesktop, isSmallerThanTablet }; 9 | }; 10 | -------------------------------------------------------------------------------- /apps/web/next.config.js: -------------------------------------------------------------------------------- 1 | const withTM = require("next-transpile-modules")(["ui"]); 2 | const { env } = require("./server/env"); 3 | 4 | /** 5 | * @type {import('next').NextConfig} 6 | */ 7 | const config = { 8 | reactStrictMode: true, 9 | publicRuntimeConfig: { 10 | NODE_ENV: env.NODE_ENV, 11 | }, 12 | experimental: { 13 | images: { allowFutureImage: true }, 14 | }, 15 | }; 16 | 17 | module.exports = withTM(config); 18 | -------------------------------------------------------------------------------- /packages/ui/src/ThemeIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface ThemeIconProps { 4 | className?: string; 5 | } 6 | 7 | export const ThemeIcon: React.FC = ({ 8 | className, 9 | children, 10 | }) => { 11 | return ( 12 |
15 | {children} 16 |
17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /.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 | # testing 9 | coverage 10 | 11 | # next.js 12 | .next/ 13 | out/ 14 | build 15 | 16 | # misc 17 | .DS_Store 18 | *.pem 19 | 20 | # debug 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | .pnpm-debug.log* 25 | 26 | # local env files 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | 32 | # turbo 33 | .turbo 34 | -------------------------------------------------------------------------------- /apps/web/modules/layout/QuickFindPopover.tsx: -------------------------------------------------------------------------------- 1 | import { IconSearch } from "@tabler/icons"; 2 | import React from "react"; 3 | import { Button } from "ui"; 4 | 5 | interface QuickFindPopoverProps {} 6 | 7 | export const QuickFindPopover: React.FC = ({}) => { 8 | return ( 9 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /apps/web/prisma/migrations/20220802080439_remove_reaction_id/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - The primary key for the `ReactionCount` table will be changed. If it partially fails, the table could be left without primary key constraint. 5 | - You are about to drop the column `id` on the `ReactionCount` table. All the data in the column will be lost. 6 | 7 | */ 8 | -- AlterTable 9 | ALTER TABLE "ReactionCount" DROP CONSTRAINT "ReactionCount_pkey", 10 | DROP COLUMN "id", 11 | ADD CONSTRAINT "ReactionCount_pkey" PRIMARY KEY ("draftId", "type"); 12 | -------------------------------------------------------------------------------- /packages/ui/src/IconButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from "react"; 2 | 3 | export type IconButtonProps = React.DetailedHTMLProps< 4 | React.ButtonHTMLAttributes, 5 | HTMLButtonElement 6 | > & {}; 7 | 8 | export const IconButton = forwardRef( 9 | ({ children }, ref) => { 10 | return ( 11 | 17 | ); 18 | } 19 | ); 20 | 21 | IconButton.displayName = "IconButton"; 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Presage 2 | 3 | A Medium alternative built for referral podcasts and blogs [joinpresage.com](https://joinpresage.vercel.app) 4 | 5 | ## Development 6 | 7 | All packages (web, api, etc.) are under `packages/`. When starting the api, run `yarn watch` and `yarn dev`. 8 | 9 | When contributing code, please checkout our [Figma file](https://www.figma.com/file/zPd9BYz6uGH4SxuANFGKvM/Presage). 10 | 11 | ## Contact & Other Details 12 | 13 | If you want to get in touch for investing, contributing, or joining our platform, please DM us on twitter [@joinpresage](https://twitter.com/joinpresage) or email use [help@joinpresage.com](mailto:help@joinpresage.com) 14 | -------------------------------------------------------------------------------- /packages/tsconfig/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "compilerOptions": { 5 | "downlevelIteration": true, 6 | "composite": false, 7 | "declaration": true, 8 | "declarationMap": true, 9 | "esModuleInterop": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "inlineSources": false, 12 | "isolatedModules": true, 13 | "moduleResolution": "node", 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "preserveWatchOutput": true, 17 | "skipLibCheck": true, 18 | "strict": true 19 | }, 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/ui/src/Avatar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface AvatarProps { 4 | size?: "sm" | "md" | "lg"; 5 | circle?: boolean; 6 | name: string; 7 | src: string; 8 | } 9 | 10 | const sizes = { 11 | sm: "w-8 h-8", 12 | md: "w-10 h-10", 13 | lg: "w-12 h-12", 14 | }; 15 | 16 | export const Avatar: React.FC = ({ 17 | circle = true, 18 | size = "md", 19 | name, 20 | src, 21 | }) => { 22 | return ( 23 | // eslint-disable-next-line @next/next/no-img-element 24 | {name} 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /packages/tsconfig/nextjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Next.js", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "target": "es5", 7 | "lib": ["dom", "dom.iterable", "esnext"], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "noEmit": true, 13 | "incremental": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "jsx": "preserve" 19 | }, 20 | "include": ["src", "next-env.d.ts"], 21 | "exclude": ["node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /apps/web/server/prisma.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Instantiates a single instance PrismaClient and save it on the global object. 3 | * @link https://www.prisma.io/docs/support/help-articles/nextjs-prisma-client-dev-practices 4 | */ 5 | import { env } from "./env"; 6 | import { PrismaClient } from "@prisma/client"; 7 | 8 | const prismaGlobal = global as typeof global & { 9 | prisma?: PrismaClient; 10 | }; 11 | 12 | export const prisma: PrismaClient = 13 | prismaGlobal.prisma || 14 | new PrismaClient({ 15 | log: 16 | env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"], 17 | }); 18 | 19 | if (env.NODE_ENV !== "production") { 20 | prismaGlobal.prisma = prisma; 21 | } 22 | -------------------------------------------------------------------------------- /apps/web/lib/useHotkeys.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import isHotkey from "is-hotkey"; 3 | 4 | export default function useHotkeys( 5 | hotkeys: { hotkey: string; callback: () => void }[] 6 | ) { 7 | useEffect(() => { 8 | const handleKeyboardShortcuts = (event: KeyboardEvent) => { 9 | for (const { hotkey, callback } of hotkeys) { 10 | if (isHotkey(hotkey, event)) { 11 | event.preventDefault(); 12 | callback(); 13 | } 14 | } 15 | }; 16 | document.addEventListener("keydown", handleKeyboardShortcuts); 17 | return () => 18 | document.removeEventListener("keydown", handleKeyboardShortcuts); 19 | }, [hotkeys]); 20 | } 21 | -------------------------------------------------------------------------------- /apps/web/server/context.ts: -------------------------------------------------------------------------------- 1 | import * as trpc from "@trpc/server"; 2 | import * as trpcNext from "@trpc/server/adapters/next"; 3 | import { unstable_getServerSession } from "next-auth"; 4 | import { authOptions } from "../pages/api/auth/[...nextauth]"; 5 | 6 | import { prisma } from "./prisma"; 7 | 8 | export const createContext = async ({ 9 | req, 10 | res, 11 | }: trpcNext.CreateNextContextOptions) => { 12 | // const session = await getSessionFromCookie({ req: req as NextApiRequest }); 13 | const session = await unstable_getServerSession(req, res, authOptions); 14 | 15 | return { 16 | req, 17 | res, 18 | session, 19 | prisma, 20 | }; 21 | }; 22 | 23 | export type Context = trpc.inferAsyncReturnType; 24 | -------------------------------------------------------------------------------- /apps/web/lib/useOnClickOutside.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | 3 | export default function useOnClickOutside( 4 | element: Element | null, 5 | handler?: () => void 6 | ) { 7 | useEffect(() => { 8 | if (!element || !handler) { 9 | return; 10 | } 11 | 12 | const listener = (event: Event) => { 13 | if (!element || element.contains(event.target as Node)) { 14 | return; 15 | } 16 | 17 | handler(); 18 | }; 19 | 20 | document.addEventListener("mousedown", listener); 21 | document.addEventListener("touchstart", listener); 22 | 23 | return () => { 24 | document.removeEventListener("mousedown", listener); 25 | document.removeEventListener("touchstart", listener); 26 | }; 27 | }, [element, handler]); 28 | } 29 | -------------------------------------------------------------------------------- /packages/ui/src/ScrollArea.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import * as RadixScrollArea from "@radix-ui/react-scroll-area"; 3 | 4 | interface ScrollAreaProps { 5 | className?: string; 6 | } 7 | 8 | export const ScrollArea: React.FC = ({ 9 | className, 10 | children, 11 | }) => { 12 | return ( 13 | 14 | 15 | {children} 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "repo", 3 | "version": "0.0.0", 4 | "private": true, 5 | "workspaces": [ 6 | "apps/*", 7 | "packages/*" 8 | ], 9 | "scripts": { 10 | "build": "turbo run build", 11 | "dev": "turbo run dev --parallel", 12 | "lint": "turbo run lint", 13 | "format": "prettier --write \"**/*.{ts,tsx,md}\"", 14 | "postinstall": "patch-package" 15 | }, 16 | "devDependencies": { 17 | "autoprefixer": "^10.4.7", 18 | "eslint-config-custom": "*", 19 | "patch-package": "^6.4.7", 20 | "postcss": "^8.4.14", 21 | "postinstall-postinstall": "^2.1.0", 22 | "prettier": "latest", 23 | "tailwindcss": "^3.1.6", 24 | "turbo": "latest" 25 | }, 26 | "engines": { 27 | "npm": ">=7.0.0", 28 | "node": ">=14.0.0" 29 | }, 30 | "dependencies": {}, 31 | "packageManager": "yarn@1.22.17" 32 | } 33 | -------------------------------------------------------------------------------- /packages/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui", 3 | "version": "0.0.0", 4 | "main": "./index.tsx", 5 | "types": "./index.tsx", 6 | "license": "MIT", 7 | "scripts": { 8 | "lint": "eslint *.ts*" 9 | }, 10 | "devDependencies": { 11 | "@types/react": "^17.0.37", 12 | "@types/react-dom": "^17.0.11", 13 | "eslint": "^7.32.0", 14 | "eslint-config-custom": "*", 15 | "react": "^17.0.2", 16 | "tailwind-config": "*", 17 | "tsconfig": "*", 18 | "typescript": "^4.5.2" 19 | }, 20 | "dependencies": { 21 | "@headlessui/react": "^1.6.6", 22 | "@radix-ui/react-dialog": "^0.1.7", 23 | "@radix-ui/react-dropdown-menu": "^0.1.6", 24 | "@radix-ui/react-polymorphic": "^0.0.14", 25 | "@radix-ui/react-popover": "^0.1.6", 26 | "@radix-ui/react-scroll-area": "^0.1.4", 27 | "@tabler/icons": "^1.74.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apps/web/modules/landing-page/Statistic.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface StatisticProps { 4 | statistic: string; 5 | description: string; 6 | } 7 | 8 | export const Statistic: React.FC = ({ 9 | statistic, 10 | description, 11 | }) => { 12 | return ( 13 |
14 |
15 | {statistic} 16 |
17 |
18 | {description.split(" ").map((word, index) => ( 19 |
23 | {word} 24 |
25 | ))} 26 |
27 |
28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /packages/ui/src/IconInput.tsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from "react"; 2 | import { Input } from "./Input"; 3 | 4 | export interface IconInputProps 5 | extends React.ComponentPropsWithoutRef<"input"> { 6 | icon?: React.ReactNode; 7 | inputClassName?: string; 8 | } 9 | 10 | export const IconInput = forwardRef( 11 | ({ className, inputClassName, icon, ...props }, ref) => { 12 | return ( 13 |
16 |
17 | {icon} 18 |
19 | 24 |
25 | ); 26 | } 27 | ); 28 | 29 | IconInput.displayName = "IconInput"; 30 | -------------------------------------------------------------------------------- /apps/web/modules/kbar/CommandPallette.tsx: -------------------------------------------------------------------------------- 1 | import { KBarPortal, KBarPositioner, KBarAnimator, KBarSearch } from "kbar"; 2 | import React from "react"; 3 | import { RenderResults } from "./RenderResults"; 4 | 5 | interface CommandPalletteProps {} 6 | 7 | export const CommandPallette: React.FC = ({}) => { 8 | return ( 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 |
17 |
18 |
19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /apps/web/server/env.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | /** 3 | * This file is included in `/next.config.js` which ensures the app isn't built with invalid env vars. 4 | * It has to be a `.js`-file to be imported there. 5 | */ 6 | const { z } = require("zod"); 7 | 8 | /*eslint sort-keys: "error"*/ 9 | const envSchema = z.object({ 10 | DATABASE_URL: z.string().url(), 11 | GITHUB_CLIENT_ID: z.string(), 12 | GITHUB_CLIENT_SECRET: z.string(), 13 | GOOGLE_CLIENT_ID: z.string(), 14 | GOOGLE_CLIENT_SECRET: z.string(), 15 | NEXTAUTH_URL: z.string(), 16 | NEXT_AUTH_SECRET: z.string(), 17 | NODE_ENV: z.enum(["development", "test", "production"]), 18 | }); 19 | 20 | const env = envSchema.safeParse(process.env); 21 | 22 | if (!env.success) { 23 | console.error( 24 | "❌ Invalid environment variables:", 25 | JSON.stringify(env.error.format(), null, 4) 26 | ); 27 | process.exit(1); 28 | } 29 | 30 | module.exports.env = env.data; 31 | -------------------------------------------------------------------------------- /apps/web/modules/layout/SidebarItem/icons/asdf.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface FocusedExploreIconProps {} 4 | 5 | export const FocusedExploreIcon: React.FC = ({}) => { 6 | return ( 7 | 14 | 22 | 29 | 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /apps/web/modules/layout/SidebarItem/icons/FocusedExploreIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface FocusedExploreIconProps {} 4 | 5 | export const FocusedExploreIcon: React.FC = ({}) => { 6 | return ( 7 | 14 | 22 | 29 | 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /apps/web/lib/useHover.ts: -------------------------------------------------------------------------------- 1 | import { MutableRefObject, useState, useRef, useEffect } from "react"; 2 | 3 | export function useHover(): [MutableRefObject, boolean] { 4 | const [value, setValue] = useState(false); 5 | const ref: any = useRef(null); 6 | const handleMouseOver = (): void => setValue(true); 7 | const handleMouseOut = (): void => setValue(false); 8 | useEffect( 9 | () => { 10 | const node: any = ref.current; 11 | if (node) { 12 | node.addEventListener("mouseover", handleMouseOver); 13 | node.addEventListener("mouseout", handleMouseOut); 14 | return () => { 15 | node.removeEventListener("mouseover", handleMouseOver); 16 | node.removeEventListener("mouseout", handleMouseOut); 17 | }; 18 | } 19 | }, 20 | // eslint-disable-next-line react-hooks/exhaustive-deps 21 | [ref.current] // Recall only if ref changes 22 | ); 23 | return [ref, value]; 24 | } 25 | -------------------------------------------------------------------------------- /apps/web/modules/landing-page/ListCheck.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface ListCheckProps {} 4 | 5 | export const ListCheck: React.FC = ({}) => { 6 | return ( 7 | 14 | 15 | 21 | 22 | 30 | 31 | 32 | 33 | 34 | 35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /apps/web/modules/kbar/RenderResults.tsx: -------------------------------------------------------------------------------- 1 | import { KBarResults, useMatches } from "kbar"; 2 | 3 | export function RenderResults() { 4 | const { results } = useMatches(); 5 | 6 | return ( 7 | ( 10 | 28 | )} 29 | /> 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /apps/web/modules/layout/SidebarControls.tsx: -------------------------------------------------------------------------------- 1 | import { useAtom } from "jotai"; 2 | import { useKBar } from "kbar"; 3 | import React from "react"; 4 | import { MdMenu, MdSearch } from "react-icons/md"; 5 | import { Button } from "ui"; 6 | import { collapseAtom } from "../../lib/store"; 7 | import { useIsMounted } from "../../lib/useIsMounted"; 8 | 9 | interface SidebarControlsProps {} 10 | 11 | export const SidebarControls: React.FC = ({}) => { 12 | const { query } = useKBar(); 13 | const isMounted = useIsMounted(); 14 | const [collapsed, setCollapsed] = useAtom(collapseAtom); 15 | 16 | return ( 17 |
18 |
31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /apps/web/modules/editor/FormikAutoSave.tsx: -------------------------------------------------------------------------------- 1 | import { useFormikContext } from "formik"; 2 | import React, { useEffect, useState } from "react"; 3 | import { useDebouncedCallback } from "use-debounce"; 4 | 5 | export const AutoSave: React.FC = () => { 6 | const formik = useFormikContext(); 7 | const [isLoading, setIsLoading] = useState(false); 8 | const debouncedSubmit = useDebouncedCallback( 9 | () => 10 | formik.submitForm().then(() => { 11 | setIsLoading(false); 12 | }), 13 | 1000 14 | ); 15 | 16 | useEffect(() => { 17 | if (formik.isValid && formik.dirty) { 18 | setIsLoading(true); 19 | debouncedSubmit(); 20 | } 21 | }, [debouncedSubmit, formik.dirty, formik.isValid, formik.values]); 22 | 23 | // return ( 24 | // 29 | // {isLoading ? "loading..." : "saved"} 30 | // 31 | // ); 32 | return null; 33 | }; 34 | -------------------------------------------------------------------------------- /apps/web/modules/layout/SampleSplitter.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from "react"; 2 | import { SplitterProps } from "react-resizable-layout"; 3 | 4 | export const cn = (...args: any[]) => args.filter(Boolean).join(" "); 5 | 6 | const SampleSplitter: React.FC = ({ 7 | dir, 8 | isDragging, 9 | ...props 10 | }) => { 11 | const [isFocused, setIsFocused] = useState(false); 12 | const splitterRef = useRef(null); 13 | 14 | return ( 15 |
setIsFocused(true)} 23 | onBlur={() => setIsFocused(false)} 24 | {...props} 25 | /> 26 | ); 27 | }; 28 | 29 | export default SampleSplitter; 30 | -------------------------------------------------------------------------------- /apps/web/pages/api/trpc/[trpc].ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file contains tRPC's HTTP response handler 3 | */ 4 | import * as trpcNext from "@trpc/server/adapters/next"; 5 | import { createContext } from "../../../server/context"; 6 | import { appRouter } from "../../../server/routers/_app"; 7 | // import { createContext } from "~/server/context"; 8 | // import { appRouter } from "~/server/routers/_app"; 9 | 10 | export default trpcNext.createNextApiHandler({ 11 | router: appRouter, 12 | /** 13 | * @link https://trpc.io/docs/context 14 | */ 15 | createContext, 16 | /** 17 | * @link https://trpc.io/docs/error-handling 18 | */ 19 | onError({ error }) { 20 | if (error.code === "INTERNAL_SERVER_ERROR") { 21 | // send to bug reporting 22 | console.error("Something went wrong", error); 23 | } 24 | }, 25 | /** 26 | * Enable query batching 27 | */ 28 | batching: { 29 | enabled: true, 30 | }, 31 | /** 32 | * @link https://trpc.io/docs/caching#api-response-caching 33 | */ 34 | // responseMeta() { 35 | // // ... 36 | // }, 37 | }); 38 | -------------------------------------------------------------------------------- /apps/web/styles/lowlight.css: -------------------------------------------------------------------------------- 1 | pre { 2 | @apply !p-6 !rounded-xl !text-white !bg-gray-900; 3 | } 4 | 5 | pre code { 6 | color: inherit !important; 7 | background: transparent !important; 8 | border: none !important; 9 | padding: 0 !important; 10 | } 11 | 12 | .hljs-comment, 13 | .hljs-quote { 14 | color: #8a8a8a; 15 | } 16 | 17 | .hljs-variable, 18 | .hljs-template-variable, 19 | .hljs-attribute, 20 | .hljs-tag, 21 | .hljs-name, 22 | .hljs-regexp, 23 | .hljs-link, 24 | .hljs-name, 25 | .hljs-selector-id, 26 | .hljs-selector-class { 27 | color: #f98181; 28 | } 29 | 30 | .hljs-number, 31 | .hljs-meta, 32 | .hljs-built_in, 33 | .hljs-builtin-name, 34 | .hljs-literal, 35 | .hljs-type, 36 | .hljs-params { 37 | color: #fbbc88; 38 | } 39 | 40 | .hljs-string, 41 | .hljs-symbol, 42 | .hljs-bullet { 43 | color: #b9f18d; 44 | } 45 | 46 | .hljs-title, 47 | .hljs-section { 48 | color: #faf594; 49 | } 50 | 51 | .hljs-keyword, 52 | .hljs-selector-tag { 53 | color: #70cff8; 54 | } 55 | 56 | .hljs-emphasis { 57 | font-style: italic; 58 | } 59 | 60 | .hljs-strong { 61 | font-weight: 700; 62 | } 63 | -------------------------------------------------------------------------------- /apps/web/modules/editor/plugins/MathNode.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { mergeAttributes, Node } from "@tiptap/core"; 3 | 4 | import { inputRules } from "prosemirror-inputrules"; 5 | 6 | import { 7 | makeInlineMathInputRule, 8 | mathPlugin, 9 | mathSelectPlugin, 10 | } from "@benrbray/prosemirror-math"; 11 | 12 | export const Math = Node.create({ 13 | name: "math_inline", 14 | group: "inline math", 15 | content: "text*", // important! 16 | inline: true, // important! 17 | atom: true, // important! 18 | code: true, 19 | 20 | parseHTML() { 21 | return [ 22 | { 23 | tag: "math-inline", // important! 24 | }, 25 | ]; 26 | }, 27 | 28 | renderHTML({ HTMLAttributes }) { 29 | return [ 30 | "math-inline", 31 | mergeAttributes({ class: "math-node" }, HTMLAttributes), 32 | 0, 33 | ]; 34 | }, 35 | 36 | addProseMirrorPlugins() { 37 | const inputRulePlugin = inputRules({ 38 | rules: [makeInlineMathInputRule(/\$\$(.+)\$\$/, this.type)], 39 | }); 40 | 41 | return [mathPlugin, inputRulePlugin, mathSelectPlugin]; 42 | }, 43 | }); 44 | -------------------------------------------------------------------------------- /apps/web/modules/authentication/LoginModal.tsx: -------------------------------------------------------------------------------- 1 | import { signIn } from "next-auth/react"; 2 | import React from "react"; 3 | import { AiOutlineGithub, AiOutlineGoogle } from "react-icons/ai"; 4 | import { Button, Modal } from "ui"; 5 | 6 | interface LoginModalProps {} 7 | 8 | export const LoginModal: React.FC = ({}) => { 9 | return ( 10 | Login} 12 | title="Login Providers" 13 | > 14 |
15 | 23 | 31 |
32 |
33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /apps/web/lib/useSSRMediaQuery.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export const useSSRMediaQuery = (mediaQuery: string) => { 4 | const [isVerified, setIsVerified] = useState(false); 5 | 6 | useEffect(() => { 7 | if (typeof window !== "undefined") { 8 | const mediaQueryList = window.matchMedia(mediaQuery); 9 | const documentChangeHandler = () => 10 | setIsVerified(!!mediaQueryList.matches); 11 | 12 | try { 13 | mediaQueryList.addEventListener("change", documentChangeHandler); 14 | } catch (e) { 15 | // Safari isn't supporting mediaQueryList.addEventListener 16 | console.error(e); 17 | mediaQueryList.addListener(documentChangeHandler); 18 | } 19 | 20 | documentChangeHandler(); 21 | return () => { 22 | try { 23 | mediaQueryList.removeEventListener("change", documentChangeHandler); 24 | } catch (e) { 25 | // Safari isn't supporting mediaQueryList.removeEventListener 26 | console.error(e); 27 | mediaQueryList.removeListener(documentChangeHandler); 28 | } 29 | }; 30 | } 31 | }, [mediaQuery]); 32 | 33 | return isVerified; 34 | }; 35 | -------------------------------------------------------------------------------- /apps/web/modules/layout/DashboardLayout.tsx: -------------------------------------------------------------------------------- 1 | import { useAtomValue } from "jotai"; 2 | import React from "react"; 3 | import { useResizable } from "react-resizable-layout"; 4 | import { collapseAtom } from "../../lib/store"; 5 | import SampleSplitter from "./SampleSplitter"; 6 | import { Sidebar } from "./Sidebar"; 7 | import { SidebarControls } from "./SidebarControls"; 8 | 9 | interface DashboardLayoutProps {} 10 | 11 | export const DashboardLayout: React.FC< 12 | React.PropsWithChildren 13 | > = ({ children }) => { 14 | const collapsed = useAtomValue(collapseAtom); 15 | const { position, isDragging, splitterProps } = useResizable({ 16 | axis: "x", 17 | initial: 280, 18 | min: 240, 19 | max: 440, 20 | }); 21 | 22 | return ( 23 |
24 |
29 | 30 |
31 | 32 | 33 |
{children}
34 |
35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /apps/web/modules/layout/SidebarItem/icons/FocusedHomeIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface FocusedHomeIconProps {} 4 | 5 | export const FocusedHomeIcon: React.FC = ({}) => { 6 | return ( 7 | 14 | 22 | 29 | 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /apps/web/modules/landing-page/Waitlist.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Input } from "ui"; 2 | import React, { useState } from "react"; 3 | import toast from "react-hot-toast"; 4 | 5 | interface WaitlistProps {} 6 | 7 | const notify = () => toast.success("Success! Thanks for joining the Waitlist."); 8 | 9 | export const Waitlist: React.FC = ({}) => { 10 | const [email, setEmail] = useState(""); 11 | const [loading, setLoading] = useState(false); 12 | 13 | return ( 14 |
{ 17 | e.preventDefault(); 18 | setLoading(true); 19 | await fetch("/api/waitlist", { 20 | method: "POST", 21 | body: JSON.stringify({ email }), 22 | }); 23 | setLoading(false); 24 | notify(); 25 | setEmail(""); 26 | }} 27 | > 28 | setEmail(e.target.value)} 32 | value={email} 33 | required 34 | /> 35 | 43 |
44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /packages/tailwind-config/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "../../packages/ui/src/**/*.{ts,tsx}", 5 | "./pages/**/*.{js,ts,jsx,tsx}", 6 | "./components/**/*.{js,ts,jsx,tsx}", 7 | "./modules/**/*.{js,ts,jsx,tsx}", 8 | "./editor/**/*.{js,ts,jsx,tsx}", 9 | ], 10 | darkMode: "class", 11 | theme: { 12 | extend: { 13 | colors: { gray: require("tailwindcss/colors").zinc }, 14 | fontFamily: { 15 | sans: ["Inter", ...require("tailwindcss/defaultTheme").fontFamily.sans], 16 | display: [ 17 | "Eudoxus Sans", 18 | ...require("tailwindcss/defaultTheme").fontFamily.sans, 19 | ], 20 | }, 21 | typography: { 22 | DEFAULT: { 23 | css: { 24 | "code::before": { 25 | content: '""', 26 | }, 27 | "code::after": { 28 | content: '""', 29 | }, 30 | "blockquote p:first-of-type::before": { 31 | content: '""', 32 | }, 33 | "blockquote p:last-of-type::after": { 34 | content: '""', 35 | }, 36 | }, 37 | }, 38 | }, 39 | }, 40 | }, 41 | plugins: [ 42 | require("@tailwindcss/typography"), 43 | require("@tailwindcss/forms")({ strategy: "class" }), 44 | ], 45 | }; 46 | -------------------------------------------------------------------------------- /apps/web/lib/browser.ts: -------------------------------------------------------------------------------- 1 | const nav = typeof navigator != "undefined" ? navigator : null; 2 | const doc = typeof document != "undefined" ? document : null; 3 | const agent = (nav && nav.userAgent) || ""; 4 | 5 | const ie_edge = /Edge\/(\d+)/.exec(agent); 6 | const ie_upto10 = /MSIE \d/.exec(agent); 7 | const ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(agent); 8 | 9 | export const ie = !!(ie_upto10 || ie_11up || ie_edge); 10 | export const ie_version = ie_upto10 11 | ? (document as any).documentMode 12 | : ie_11up 13 | ? +ie_11up[1] 14 | : ie_edge 15 | ? +ie_edge[1] 16 | : 0; 17 | export const gecko = !ie && /gecko\/(\d+)/i.test(agent); 18 | export const gecko_version = 19 | gecko && +(/Firefox\/(\d+)/.exec(agent) || [0, 0])[1]; 20 | 21 | const _chrome = !ie && /Chrome\/(\d+)/.exec(agent); 22 | export const chrome = !!_chrome; 23 | export const chrome_version = _chrome ? +_chrome[1] : 0; 24 | export const safari = !ie && !!nav && /Apple Computer/.test(nav.vendor); 25 | // Is true for both iOS and iPadOS for convenience 26 | export const ios = 27 | safari && (/Mobile\/\w+/.test(agent) || (!!nav && nav.maxTouchPoints > 2)); 28 | export const mac = ios || (nav ? /Mac/.test(nav.platform) : false); 29 | export const android = /Android \d/.test(agent); 30 | export const webkit = 31 | !!doc && "webkitFontSmoothing" in doc.documentElement.style; 32 | export const webkit_version = webkit 33 | ? +(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent) || [0, 0])[1] 34 | : 0; 35 | -------------------------------------------------------------------------------- /apps/web/README.md: -------------------------------------------------------------------------------- 1 | ## Getting Started 2 | 3 | First, run the development server: 4 | 5 | ```bash 6 | yarn dev 7 | ``` 8 | 9 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 10 | 11 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 12 | 13 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. 14 | 15 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 16 | 17 | ## Learn More 18 | 19 | To learn more about Next.js, take a look at the following resources: 20 | 21 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 22 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 23 | 24 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 25 | 26 | ## Deploy on Vercel 27 | 28 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_source=github.com&utm_medium=referral&utm_campaign=turborepo-readme) from the creators of Next.js. 29 | 30 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 31 | -------------------------------------------------------------------------------- /apps/web/modules/layout/FloatingActions.tsx: -------------------------------------------------------------------------------- 1 | import { IconSun, IconMoon } from "@tabler/icons"; 2 | import { useKBar } from "kbar"; 3 | import React from "react"; 4 | import { useSSRTheme } from "../../lib/useSSRTheme"; 5 | 6 | interface FloatingActionsProps {} 7 | 8 | export const FloatingActions: React.FC = ({}) => { 9 | const theme = useSSRTheme(); 10 | const { query } = useKBar(); 11 | 12 | return ( 13 |
14 |
15 | 30 | 35 |
36 |
37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /apps/web/server/routers/_app.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file contains the root router of your tRPC-backend 3 | */ 4 | import { createRouter } from "../createRouter"; 5 | import superjson from "superjson"; 6 | import { folderRouter } from "./folders"; 7 | import { TRPCError } from "@trpc/server"; 8 | import { draftsRouter } from "./drafts"; 9 | import { reactionsRouter } from "./reactions"; 10 | 11 | /** 12 | * Create your application's root router 13 | * If you want to use SSG, you need export this 14 | * @link https://trpc.io/docs/ssg 15 | * @link https://trpc.io/docs/router 16 | */ 17 | export const appRouter = createRouter() 18 | /** 19 | * Add data transformers 20 | * @link https://trpc.io/docs/data-transformers 21 | */ 22 | .transformer(superjson) 23 | .middleware(async ({ ctx, meta, next }) => { 24 | if (!ctx.session?.user && meta?.hasAuth) { 25 | throw new TRPCError({ code: "UNAUTHORIZED" }); 26 | } 27 | return next(); 28 | }) 29 | /** 30 | * Optionally do custom error (type safe!) formatting 31 | * @link https://trpc.io/docs/error-formatting 32 | */ 33 | // .formatError(({ shape, error }) => { }) 34 | /** 35 | * Add a health check endpoint to be called with `/api/trpc/healthz` 36 | */ 37 | .query("healthz", { 38 | async resolve() { 39 | return "yay!"; 40 | }, 41 | }) 42 | .merge("folders.", folderRouter) 43 | .merge("drafts.", draftsRouter) 44 | .merge("reactions.", reactionsRouter); 45 | 46 | export type AppRouter = typeof appRouter; 47 | -------------------------------------------------------------------------------- /apps/web/modules/layout/SidebarItem/icons/FocusedArchiveIcon.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface FocusedArchiveIconProps {} 4 | 5 | export const FocusedArchiveIcon: React.FC = ({}) => { 6 | return ( 7 | 14 | 22 | 26 | 33 | 40 | 41 | ); 42 | }; 43 | -------------------------------------------------------------------------------- /packages/ui/src/Popover.tsx: -------------------------------------------------------------------------------- 1 | import * as PopoverPrimitive from "@radix-ui/react-popover"; 2 | import { IconX } from "@tabler/icons"; 3 | import React from "react"; 4 | 5 | const cx = (...args: string[]) => args.join(" "); 6 | 7 | interface PopoverProps { 8 | trigger?: React.ReactNode; 9 | className?: string; 10 | } 11 | 12 | export const Popover: React.FC = ({ 13 | trigger, 14 | className, 15 | children, 16 | }) => { 17 | return ( 18 |
19 | 20 | {trigger} 21 | 31 | {children} 32 | 38 | 39 | 40 | 41 | 42 |
43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /apps/web/modules/landing-page/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import { useSession } from "next-auth/react"; 2 | import Image from "next/image"; 3 | import Link from "next/link"; 4 | import React from "react"; 5 | import { Button } from "ui"; 6 | import logo from "../../public/static/logo.svg"; 7 | import { LoginModal } from "../authentication/LoginModal"; 8 | 9 | interface NavbarProps {} 10 | 11 | export const Navbar: React.FC = ({}) => { 12 | const { data: session } = useSession(); 13 | 14 | return ( 15 | 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /apps/web/pages/api/auth/[...nextauth].ts: -------------------------------------------------------------------------------- 1 | import { PrismaAdapter } from "@next-auth/prisma-adapter"; 2 | import NextAuth, { NextAuthOptions } from "next-auth"; 3 | import GitHubProvider from "next-auth/providers/github"; 4 | import GoogleProvider from "next-auth/providers/google"; 5 | import { prisma } from "../../../server/prisma"; 6 | import { generateUsername } from "friendly-username-generator"; 7 | import { env } from "../../../server/env"; 8 | 9 | export const authOptions: NextAuthOptions = { 10 | // Configure one or more authentication providers 11 | adapter: { 12 | ...PrismaAdapter(prisma), 13 | createUser: (data) => { 14 | return prisma.user.create({ 15 | data: { ...data, username: generateUsername() }, 16 | }); 17 | }, 18 | }, 19 | providers: [ 20 | GitHubProvider({ 21 | clientId: env.GITHUB_CLIENT_ID, 22 | clientSecret: env.GITHUB_CLIENT_SECRET, 23 | }), 24 | GoogleProvider({ 25 | clientId: env.GOOGLE_CLIENT_ID, 26 | clientSecret: env.GOOGLE_CLIENT_SECRET, 27 | }), 28 | ], 29 | callbacks: { 30 | session: async ({ session, token }) => { 31 | if (session?.user) { 32 | session.user.id = token.uid as string; 33 | } 34 | return session; 35 | }, 36 | jwt: async ({ user, token }) => { 37 | if (user) { 38 | token.uid = user.id; 39 | } 40 | return token; 41 | }, 42 | }, 43 | session: { 44 | strategy: "jwt", 45 | }, 46 | pages: { 47 | signIn: "/", 48 | }, 49 | secret: env.NEXT_AUTH_SECRET, 50 | }; 51 | 52 | export default NextAuth(authOptions); 53 | -------------------------------------------------------------------------------- /packages/ui/src/Input.tsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from "react"; 2 | 3 | export interface InputProps 4 | extends Omit, "size"> { 5 | textarea?: boolean; 6 | size?: "sm" | "md"; 7 | label?: string; 8 | icon?: React.ReactNode; 9 | } 10 | 11 | export const Input = forwardRef( 12 | ({ textarea, className, size = "md", label, icon, ...props }, ref) => { 13 | const styles = `${ 14 | size === "md" ? "text-base px-4 py-2" : "text-sm px-3 py-2" 15 | } block bg-white dark:border-2 dark:border-gray-800 dark:bg-gray-900 rounded-xl border shadow-sm focus:outline-none placeholder-gray-400 dark:placeholder-gray-600 dark:text-white w-full transition focus-visible:ring focus-visible:ring-gray-300 ${ 16 | textarea && "resize-none h-32" 17 | } ${icon ? "pl-10" : ""} ${className}`; 18 | let output; 19 | if (textarea) { 20 | output = ( 21 |