From eb37fb307a553a47e1c13ad77eba40f7217be1cb Mon Sep 17 00:00:00 2001 From: Jesse Winton Date: Wed, 6 Dec 2023 23:16:58 -0500 Subject: [PATCH 01/69] 2.0 migration --- .husky/pre-commit | 4 - LICENSE | 21 - README.md | 80 +- components/author-with-date.tsx | 40 - components/button-link.tsx | 50 - components/button.tsx | 73 - components/dialog.tsx | 101 - components/icon-button.tsx | 35 - components/icons.tsx | 459 - components/layout.tsx | 110 - components/like-button.tsx | 138 - components/menu.tsx | 120 - components/post-summary-skeleton.tsx | 38 - components/post-summary.tsx | 142 - components/search-dialog.tsx | 207 - components/textarea.tsx | 35 - doc/dependency_decisions.yml | 87 - doc/github_setup.md | 17 - doc/okta_setup.md | 20 - doc/pscale-actions-setup.md | 10 - doc/slack_setup.md | 14 - env/browser.ts | 8 - env/server.ts | 71 - lib/auth.ts | 116 - lib/classnames.ts | 3 - lib/cloudinary.ts | 33 - lib/editor.ts | 82 - lib/prisma.ts | 9 - lib/trpc.ts | 23 - lib/types.ts | 10 - next.config.js | 19 +- package-lock.json | 10204 ---------------- package.json | 132 +- pages/_app.tsx | 100 - pages/_document.tsx | 37 - pages/api/auth/[...nextauth].ts | 4 - pages/api/sign-cloudinary.ts | 57 - pages/api/trpc/[trpc].ts | 17 - pages/index.tsx | 157 - pages/new.tsx | 55 - pages/post/[id]/edit.tsx | 100 - pages/post/[id]/index.tsx | 780 -- pages/profile/[userId].tsx | 571 - pages/sign-in.tsx | 70 - pnpm-lock.yaml | 4644 +++++++ postcss.config.js => postcss.config.cjs | 6 +- prettier.config.js | 6 + prisma/schema.prisma | 168 +- server/context.ts | 21 - server/create-protected-router.ts | 20 - server/create-router.ts | 6 - server/routers/_app.ts | 13 - server/routers/user.ts | 78 - .../sign-in/_components/sign-in-buttons.tsx | 24 + src/app/(auth)/sign-in/page.tsx | 34 + src/app/(default)/layout.tsx | 15 + .../(default)/new/_components}/post-form.tsx | 43 +- src/app/(default)/new/page.tsx | 27 + src/app/(default)/page.tsx | 48 + .../post/[id]/_components/edit-post-form.tsx | 106 + .../post/[id]/_components/post-action.tsx | 284 + src/app/(default)/post/[id]/edit/page.tsx | 55 + src/app/(default)/post/[id]/page.tsx | 110 + .../_components/edit-profile-action.tsx | 126 + src/app/(default)/profile/[userId]/page.tsx | 120 + src/app/_components/actions.tsx | 24 + src/app/_components/alert-dialog.tsx | 97 + src/app/_components/author-with-date.tsx | 39 + .../app/_components}/avatar.tsx | 22 +- .../app/_components}/banner.tsx | 6 +- src/app/_components/button.tsx | 79 + src/app/_components/comment.tsx | 298 + src/app/_components/dialog.tsx | 77 + .../app/_components}/footer.tsx | 6 +- src/app/_components/header.tsx | 17 + .../app/_components}/html-view.tsx | 2 +- src/app/_components/like-button.tsx | 102 + src/app/_components/liked-by.tsx | 65 + .../app/_components}/markdown-editor.tsx | 291 +- src/app/_components/menu.tsx | 76 + .../app/_components}/pagination.tsx | 46 +- src/app/_components/post-summary.tsx | 84 + src/app/_components/profile-menu.tsx | 83 + src/app/_components/search-dialog.tsx | 98 + src/app/_components/suggestion-list.tsx | 51 + .../app/_components}/text-field.tsx | 14 +- src/app/_hooks/use-dialog-store.ts | 18 + .../app/_hooks/use-leave-confirm.ts | 14 +- src/app/_hooks/use-search-store.ts | 11 + src/app/_providers/session.tsx | 3 + src/app/_providers/theme.tsx | 12 + src/app/_providers/toaster.tsx | 3 + src/app/_svg/bold-icon.tsx | 27 + src/app/_svg/chevron-left-icon.tsx | 19 + src/app/_svg/chevron-right-icon.tsx | 19 + src/app/_svg/dot-pattern.tsx | 32 + src/app/_svg/dots-icon.tsx | 17 + src/app/_svg/edit-icon.tsx | 20 + src/app/_svg/eye-closed-icon.tsx | 29 + src/app/_svg/eye-icon.tsx | 19 + src/app/_svg/github-logo.tsx | 17 + src/app/_svg/heart-filled-icon.tsx | 19 + src/app/_svg/heart-icon.tsx | 18 + src/app/_svg/italic-icon.tsx | 20 + src/app/_svg/link-icon.tsx | 27 + src/app/_svg/list-icon.tsx | 20 + src/app/_svg/logo.tsx | 24 + src/app/_svg/markdown-icon.tsx | 29 + src/app/_svg/message-icon.tsx | 17 + src/app/_svg/search-icon.tsx | 20 + src/app/_svg/spinner.tsx | 24 + src/app/_svg/trash-icon.tsx | 20 + src/app/_svg/x-icon.tsx | 20 + src/app/api/auth/[...nextauth]/route.ts | 7 + .../avatar.ts => src/app/api/avatar/route.tsx | 70 +- src/app/api/sign-cloudinary/route.ts | 35 + src/app/api/trpc/[trpc]/route.ts | 34 + src/app/layout.tsx | 49 + src/env.js | 101 + src/server/api/root.ts | 18 + {server => src/server/api}/routers/comment.ts | 67 +- {server => src/server/api}/routers/post.ts | 204 +- src/server/api/routers/user.ts | 65 + src/server/api/trpc.ts | 108 + src/server/auth.ts | 100 + src/server/cloudinary.ts | 92 + src/server/db.ts | 16 + {lib => src/server}/slack.ts | 18 +- lib/text.ts => src/server/summary.ts | 21 +- {styles => src/styles}/globals.css | 0 src/svg/bold-icon.svg | 6 + src/svg/chevron-left-icon.svg | 4 + src/svg/chevron-right-icon.svg | 4 + src/svg/dot-pattern.svg | 8 + src/svg/dots-icon.svg | 4 + src/svg/edit-icon.svg | 4 + src/svg/eye-closed-icon.svg | 7 + src/svg/eye-icon.svg | 6 + src/svg/github-logo.svg | 5 + src/svg/heart-filled-icon.svg | 5 + src/svg/heart-icon.svg | 5 + src/svg/italic-icon.svg | 4 + src/svg/link-icon.svg | 6 + src/svg/list-icon.svg | 4 + src/svg/logo.svg | 15 + src/svg/markdown-icon.svg | 15 + src/svg/message-icon.svg | 5 + src/svg/search-icon.svg | 4 + src/svg/spinner.svg | 5 + src/svg/trash-icon.svg | 5 + src/svg/x-icon.svg | 4 + src/trpc/react.tsx | 48 + src/trpc/server.ts | 65 + src/trpc/shared.ts | 30 + src/utils/core.ts | 6 + src/utils/suggestion.ts | 36 + src/utils/text.ts | 20 + svgr.config.cjs | 25 + tailwind.config.js => tailwind.config.ts | 48 +- tsconfig.json | 44 +- 160 files changed, 8907 insertions(+), 15053 deletions(-) delete mode 100755 .husky/pre-commit delete mode 100644 LICENSE delete mode 100644 components/author-with-date.tsx delete mode 100644 components/button-link.tsx delete mode 100644 components/button.tsx delete mode 100644 components/dialog.tsx delete mode 100644 components/icon-button.tsx delete mode 100644 components/icons.tsx delete mode 100644 components/layout.tsx delete mode 100644 components/like-button.tsx delete mode 100644 components/menu.tsx delete mode 100644 components/post-summary-skeleton.tsx delete mode 100644 components/post-summary.tsx delete mode 100644 components/search-dialog.tsx delete mode 100644 components/textarea.tsx delete mode 100644 doc/dependency_decisions.yml delete mode 100644 doc/github_setup.md delete mode 100644 doc/okta_setup.md delete mode 100644 doc/pscale-actions-setup.md delete mode 100644 doc/slack_setup.md delete mode 100644 env/browser.ts delete mode 100644 env/server.ts delete mode 100644 lib/auth.ts delete mode 100644 lib/classnames.ts delete mode 100644 lib/cloudinary.ts delete mode 100644 lib/editor.ts delete mode 100644 lib/prisma.ts delete mode 100644 lib/trpc.ts delete mode 100644 lib/types.ts delete mode 100644 package-lock.json delete mode 100644 pages/_app.tsx delete mode 100644 pages/_document.tsx delete mode 100644 pages/api/auth/[...nextauth].ts delete mode 100644 pages/api/sign-cloudinary.ts delete mode 100644 pages/api/trpc/[trpc].ts delete mode 100644 pages/index.tsx delete mode 100644 pages/new.tsx delete mode 100644 pages/post/[id]/edit.tsx delete mode 100644 pages/post/[id]/index.tsx delete mode 100644 pages/profile/[userId].tsx delete mode 100644 pages/sign-in.tsx create mode 100644 pnpm-lock.yaml rename postcss.config.js => postcss.config.cjs (57%) create mode 100644 prettier.config.js delete mode 100644 server/context.ts delete mode 100644 server/create-protected-router.ts delete mode 100644 server/create-router.ts delete mode 100644 server/routers/_app.ts delete mode 100644 server/routers/user.ts create mode 100644 src/app/(auth)/sign-in/_components/sign-in-buttons.tsx create mode 100644 src/app/(auth)/sign-in/page.tsx create mode 100644 src/app/(default)/layout.tsx rename {components => src/app/(default)/new/_components}/post-form.tsx (69%) create mode 100644 src/app/(default)/new/page.tsx create mode 100644 src/app/(default)/page.tsx create mode 100644 src/app/(default)/post/[id]/_components/edit-post-form.tsx create mode 100644 src/app/(default)/post/[id]/_components/post-action.tsx create mode 100644 src/app/(default)/post/[id]/edit/page.tsx create mode 100644 src/app/(default)/post/[id]/page.tsx create mode 100644 src/app/(default)/profile/[userId]/_components/edit-profile-action.tsx create mode 100644 src/app/(default)/profile/[userId]/page.tsx create mode 100644 src/app/_components/actions.tsx create mode 100644 src/app/_components/alert-dialog.tsx create mode 100644 src/app/_components/author-with-date.tsx rename {components => src/app/_components}/avatar.tsx (73%) rename {components => src/app/_components}/banner.tsx (69%) create mode 100644 src/app/_components/button.tsx create mode 100644 src/app/_components/comment.tsx create mode 100644 src/app/_components/dialog.tsx rename {components => src/app/_components}/footer.tsx (87%) create mode 100644 src/app/_components/header.tsx rename {components => src/app/_components}/html-view.tsx (85%) create mode 100644 src/app/_components/like-button.tsx create mode 100644 src/app/_components/liked-by.tsx rename {components => src/app/_components}/markdown-editor.tsx (63%) create mode 100644 src/app/_components/menu.tsx rename {components => src/app/_components}/pagination.tsx (53%) create mode 100644 src/app/_components/post-summary.tsx create mode 100644 src/app/_components/profile-menu.tsx create mode 100644 src/app/_components/search-dialog.tsx create mode 100644 src/app/_components/suggestion-list.tsx rename {components => src/app/_components}/text-field.tsx (70%) create mode 100644 src/app/_hooks/use-dialog-store.ts rename lib/form.ts => src/app/_hooks/use-leave-confirm.ts (72%) create mode 100644 src/app/_hooks/use-search-store.ts create mode 100644 src/app/_providers/session.tsx create mode 100644 src/app/_providers/theme.tsx create mode 100644 src/app/_providers/toaster.tsx create mode 100644 src/app/_svg/bold-icon.tsx create mode 100644 src/app/_svg/chevron-left-icon.tsx create mode 100644 src/app/_svg/chevron-right-icon.tsx create mode 100644 src/app/_svg/dot-pattern.tsx create mode 100644 src/app/_svg/dots-icon.tsx create mode 100644 src/app/_svg/edit-icon.tsx create mode 100644 src/app/_svg/eye-closed-icon.tsx create mode 100644 src/app/_svg/eye-icon.tsx create mode 100644 src/app/_svg/github-logo.tsx create mode 100644 src/app/_svg/heart-filled-icon.tsx create mode 100644 src/app/_svg/heart-icon.tsx create mode 100644 src/app/_svg/italic-icon.tsx create mode 100644 src/app/_svg/link-icon.tsx create mode 100644 src/app/_svg/list-icon.tsx create mode 100644 src/app/_svg/logo.tsx create mode 100644 src/app/_svg/markdown-icon.tsx create mode 100644 src/app/_svg/message-icon.tsx create mode 100644 src/app/_svg/search-icon.tsx create mode 100644 src/app/_svg/spinner.tsx create mode 100644 src/app/_svg/trash-icon.tsx create mode 100644 src/app/_svg/x-icon.tsx create mode 100644 src/app/api/auth/[...nextauth]/route.ts rename pages/api/avatar.ts => src/app/api/avatar/route.tsx (52%) create mode 100644 src/app/api/sign-cloudinary/route.ts create mode 100644 src/app/api/trpc/[trpc]/route.ts create mode 100644 src/app/layout.tsx create mode 100644 src/env.js create mode 100644 src/server/api/root.ts rename {server => src/server/api}/routers/comment.ts (56%) rename {server => src/server/api}/routers/post.ts (59%) create mode 100644 src/server/api/routers/user.ts create mode 100644 src/server/api/trpc.ts create mode 100644 src/server/auth.ts create mode 100644 src/server/cloudinary.ts create mode 100644 src/server/db.ts rename {lib => src/server}/slack.ts (74%) rename lib/text.ts => src/server/summary.ts (54%) rename {styles => src/styles}/globals.css (100%) create mode 100644 src/svg/bold-icon.svg create mode 100644 src/svg/chevron-left-icon.svg create mode 100644 src/svg/chevron-right-icon.svg create mode 100644 src/svg/dot-pattern.svg create mode 100644 src/svg/dots-icon.svg create mode 100644 src/svg/edit-icon.svg create mode 100644 src/svg/eye-closed-icon.svg create mode 100644 src/svg/eye-icon.svg create mode 100644 src/svg/github-logo.svg create mode 100644 src/svg/heart-filled-icon.svg create mode 100644 src/svg/heart-icon.svg create mode 100644 src/svg/italic-icon.svg create mode 100644 src/svg/link-icon.svg create mode 100644 src/svg/list-icon.svg create mode 100644 src/svg/logo.svg create mode 100644 src/svg/markdown-icon.svg create mode 100644 src/svg/message-icon.svg create mode 100644 src/svg/search-icon.svg create mode 100644 src/svg/spinner.svg create mode 100644 src/svg/trash-icon.svg create mode 100644 src/svg/x-icon.svg create mode 100644 src/trpc/react.tsx create mode 100644 src/trpc/server.ts create mode 100644 src/trpc/shared.ts create mode 100644 src/utils/core.ts create mode 100644 src/utils/suggestion.ts create mode 100644 src/utils/text.ts create mode 100644 svgr.config.cjs rename tailwind.config.js => tailwind.config.ts (85%) diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100755 index 35d6918..0000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -npx pretty-quick --staged diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 82dc6e3..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 PlanetScale - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index a64047a..fba19ed 100644 --- a/README.md +++ b/README.md @@ -1,74 +1,28 @@ - +# Create T3 App -Beam is a simple tool that allows members to write posts to share across your organization. Think of it like a lightweight internal blog. Features include a simple **Markdown-based** editor with preview, **image drag and drop**, comments and likes, **search**, a clean responsive layout with **dark mode support**, and an admin role for hiding posts. +This is a [T3 Stack](https://create.t3.gg/) project bootstrapped with `create-t3-app`. - +## What's next? How do I make an app with this? -## Setup +We try to keep this project as simple as possible, so you can start with just the scaffolding we set up for you, and add additional things later when they become necessary. -### Install dependencies +If you are not familiar with the different technologies used in this project, please refer to the respective docs. If you still are in the wind, please join our [Discord](https://t3.gg/discord) and ask for help. -```bash -npm install -``` +- [Next.js](https://nextjs.org) +- [NextAuth.js](https://next-auth.js.org) +- [Prisma](https://prisma.io) +- [Tailwind CSS](https://tailwindcss.com) +- [tRPC](https://trpc.io) -### Create a database +## Learn More -- [Create a PlanetScale database](https://planetscale.com/docs/tutorials/planetscale-quick-start-guide#create-a-database) -- Create a [connection string](https://planetscale.com/docs/concepts/connection-strings#creating-a-password) to connect to your database. Choose **Prisma** for the format -- **Alternatively**, your PlanetScale database and connection string can be generated using the [pscale CLI](https://github.com/planetscale/cli) or GitHub Actions. [View instructions](doc/pscale-actions-setup.md). -- Set up the environment variables: +To learn more about the [T3 Stack](https://create.t3.gg/), take a look at the following resources: -```bash -cp .env.example .env -``` +- [Documentation](https://create.t3.gg/) +- [Learn the T3 Stack](https://create.t3.gg/en/faq#what-learning-resources-are-currently-available) — Check out these awesome tutorials -- Open `.env` and set the `DATABASE_URL` variable with the connection string from PlanetScale -- Create the database schema: +You can check out the [create-t3-app GitHub repository](https://github.com/t3-oss/create-t3-app) — your feedback and contributions are welcome! -```bash -npx prisma db push -``` +## How do I deploy this? -### Configure authentication - -GitHub and Okta authentication settings are available as defaults, but thanks to NextAuth.js, you can configure your Beam instance with most other common authentication providers. - -- [Configuring GitHub authentication](doc/github_setup.md) -- [Configuring Okta authentication](doc/okta_setup.md) - -Beam uses [NextAuth.js](https://next-auth.js.org/), so if you prefer to use one of the [many providers](https://next-auth.js.org/providers/) it supports, you can customize your own installation. Simply update the [`lib/auth.ts`](/lib/auth.ts#L11) file to add your own provider. - -### Enable image uploads (optional) - -To enable image uploads, set the environment variable `NEXT_PUBLIC_ENABLE_IMAGE_UPLOAD` to `true`. - -Beam uses Cloudinary for storing uploaded images. You can [sign up for a free account](https://cloudinary.com/users/register/free). - -- On your Cloudinary dashboard, look for these values under your account settings: **Cloud Name**, **API Key**, **API Secret**. -- Update `.env` with the following variables: - - `CLOUDINARY_CLOUD_NAME`: **Cloud Name** - - `CLOUDINARY_API_KEY`: **API Key** - - `CLOUDINARY_API_SECRET`: **API Secret** - -### Configure Slack notifications (optional) - -If you'd like to have new Beam posts published to a Slack channel, follow [these instructions](doc/slack_setup.md). - -## Running the app locally - -```bash -npm run dev -``` - -Open [http://localhost:3000](http://localhost:3000) in your browser. - -### Authenticating with GitHub - -## Deploying to Vercel - -One-click deploy: - -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fplanetscale%2Fbeam) - -⚠️ Remember to update your callback URLs after deploying. +Follow our deployment guides for [Vercel](https://create.t3.gg/en/deployment/vercel), [Netlify](https://create.t3.gg/en/deployment/netlify) and [Docker](https://create.t3.gg/en/deployment/docker) for more information. diff --git a/components/author-with-date.tsx b/components/author-with-date.tsx deleted file mode 100644 index 8091fcf..0000000 --- a/components/author-with-date.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Avatar } from '@/components/avatar' -import type { Author } from '@/lib/types' -import formatDistanceToNow from 'date-fns/formatDistanceToNow' -import Link from 'next/link' - -type AuthorWithDateProps = { - author: Author - date: Date -} - -export function AuthorWithDate({ author, date }: AuthorWithDateProps) { - return ( -
- - - - - - - - - - -
-
- - - {author.name} - - -
- -

- {' '} - ago -

-
-
- ) -} diff --git a/components/button-link.tsx b/components/button-link.tsx deleted file mode 100644 index 4611ecb..0000000 --- a/components/button-link.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { buttonClasses, ButtonVariant } from '@/components/button' -import Link, { LinkProps } from 'next/link' -import * as React from 'react' - -type ButtonLinkProps = { - variant?: ButtonVariant - responsive?: boolean -} & Omit, 'href'> & - LinkProps - -export const ButtonLink = React.forwardRef( - ( - { - href, - as, - replace, - scroll, - shallow, - passHref, - prefetch, - locale, - className, - variant = 'primary', - responsive, - ...rest - }, - forwardedRef - ) => { - return ( - - - - ) - } -) - -ButtonLink.displayName = 'ButtonLink' diff --git a/components/button.tsx b/components/button.tsx deleted file mode 100644 index 9d86ece..0000000 --- a/components/button.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { SpinnerIcon } from '@/components/icons' -import { classNames } from '@/lib/classnames' -import * as React from 'react' - -export type ButtonVariant = 'primary' | 'secondary' - -type ButtonProps = { - variant?: ButtonVariant - responsive?: boolean - isLoading?: boolean - loadingChildren?: React.ReactNode -} & React.ComponentPropsWithoutRef<'button'> - -export function buttonClasses({ - className, - variant = 'primary', - responsive, - isLoading, - disabled, -}: ButtonProps) { - return classNames( - 'inline-flex items-center justify-center font-semibold transition-colors rounded-full focus-ring', - responsive - ? 'px-3 h-8 text-xs sm:px-4 sm:text-sm sm:h-button' - : 'px-4 text-sm h-button', - variant === 'primary' && - 'text-secondary-inverse bg-secondary-inverse hover:text-primary-inverse hover:bg-primary-inverse', - variant === 'secondary' && - 'border text-primary border-secondary bg-primary hover:bg-secondary', - (disabled || isLoading) && 'opacity-50 cursor-default', - className - ) -} - -export const Button = React.forwardRef( - ( - { - className, - variant = 'primary', - responsive, - type = 'button', - isLoading = false, - loadingChildren, - disabled, - children, - ...rest - }, - forwardedRef - ) => { - return ( - - ) - } -) - -Button.displayName = 'Button' diff --git a/components/dialog.tsx b/components/dialog.tsx deleted file mode 100644 index 1fa9337..0000000 --- a/components/dialog.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { XIcon } from '@/components/icons' -import { Dialog as HeadlessDialog, Transition } from '@headlessui/react' -import * as React from 'react' - -type DialogProps = { - isOpen: boolean - onClose: () => void - children: React.ReactNode - initialFocus?: React.MutableRefObject -} - -export function Dialog({ - isOpen, - onClose, - children, - initialFocus, -}: DialogProps) { - return ( - - -
- - - - - -
- {children} -
-
-
-
-
- ) -} - -export function DialogContent({ children }: { children: React.ReactNode }) { - return
{children}
-} - -export function DialogActions({ children }: { children: React.ReactNode }) { - return
{children}
-} - -export function DialogTitle({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ) -} - -export function DialogDescription({ - children, - className, -}: { - children: React.ReactNode - className?: string -}) { - return ( - - {children} - - ) -} - -export function DialogCloseButton({ onClick }: { onClick: () => void }) { - return ( -
- -
- ) -} diff --git a/components/icon-button.tsx b/components/icon-button.tsx deleted file mode 100644 index c1f6cc2..0000000 --- a/components/icon-button.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { ButtonVariant } from '@/components/button' -import { classNames } from '@/lib/classnames' -import * as React from 'react' - -export type IconButtonOwnProps = { - variant?: ButtonVariant -} - -type IconButtonProps = IconButtonOwnProps & - React.ComponentPropsWithoutRef<'button'> - -export const IconButton = React.forwardRef( - ( - { className, variant = 'primary', type = 'button', ...rest }, - forwardedRef - ) => { - return ( -
- - -

- {likedBy - .slice(0, MAX_LIKED_BY_SHOWN) - .map((item) => - item.user.id === session!.user.id ? 'You' : item.user.name - ) - .join(', ')} - {likeCount > MAX_LIKED_BY_SHOWN && - ` and ${likeCount - MAX_LIKED_BY_SHOWN} more`} -

- -
- - ) -} diff --git a/components/menu.tsx b/components/menu.tsx deleted file mode 100644 index 954e1f7..0000000 --- a/components/menu.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { classNames } from '@/lib/classnames' -import { Menu as HeadlessMenu, Transition } from '@headlessui/react' -import Link, { LinkProps } from 'next/link' -import * as React from 'react' - -export function Menu({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ) -} - -export const MenuButton = HeadlessMenu.Button - -export function MenuItems({ - children, - className, -}: { - children: React.ReactNode - className?: string -}) { - return ( - - - {children} - - - ) -} - -export function MenuItemsContent({ children }: { children: React.ReactNode }) { - return
{children}
-} - -function NextLink({ - href, - children, - ...rest -}: Omit, 'href'> & LinkProps) { - return ( - - {children} - - ) -} - -function menuItemClasses({ - active, - className, -}: { - active: boolean - className?: string -}) { - return classNames( - active && 'bg-secondary', - 'block w-full text-left px-4 py-2 text-sm text-primary transition-colors', - className - ) -} - -export function MenuItemLink({ - className, - href, - children, -}: { - className?: string - href: string - children: React.ReactNode -}) { - return ( - - {({ active }) => ( - - {children} - - )} - - ) -} - -export function MenuItemButton({ - className, - children, - onClick, -}: { - className?: string - children: React.ReactNode - onClick: () => void -}) { - return ( - - {({ active }) => ( - - )} - - ) -} diff --git a/components/post-summary-skeleton.tsx b/components/post-summary-skeleton.tsx deleted file mode 100644 index 820a700..0000000 --- a/components/post-summary-skeleton.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { classNames } from '@/lib/classnames' - -type PostSummarySkeletonProps = { - hideAuthor?: boolean -} - -export function PostSummarySkeleton({ hideAuthor }: PostSummarySkeletonProps) { - return ( -
-
-
-
- {!hideAuthor && ( -
- )} -
-
- {!hideAuthor && ( -
- )} -
-
-
-
-
-
-
-
-
-
-
- ) -} diff --git a/components/post-summary.tsx b/components/post-summary.tsx deleted file mode 100644 index 81950c0..0000000 --- a/components/post-summary.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import { AuthorWithDate } from '@/components/author-with-date' -import { Banner } from '@/components/banner' -import { HtmlView } from '@/components/html-view' -import { - ChevronRightIcon, - HeartFilledIcon, - HeartIcon, - MessageIcon, -} from '@/components/icons' -import { MAX_LIKED_BY_SHOWN } from '@/components/like-button' -import { classNames } from '@/lib/classnames' -import { InferQueryOutput } from '@/lib/trpc' -import * as Tooltip from '@radix-ui/react-tooltip' -import formatDistanceToNow from 'date-fns/formatDistanceToNow' -import { useSession } from 'next-auth/react' -import { summarize } from '@/lib/text' -import Link from 'next/link' -import * as React from 'react' - -export type PostSummaryProps = { - post: InferQueryOutput<'post.feed'>['posts'][number] - hideAuthor?: boolean - onLike: () => void - onUnlike: () => void -} - -export function PostSummary({ - post, - hideAuthor = false, - onLike, - onUnlike, -}: PostSummaryProps) { - const { summary, hasMore } = React.useMemo( - () => summarize(post.contentHtml), - [post.contentHtml] - ) - - const { data: session } = useSession() - - const isLikedByCurrentUser = Boolean( - post.likedBy.find((item) => item.user.id === session!.user.id) - ) - const likeCount = post.likedBy.length - - return ( -
- {post.hidden && ( - - This post has been hidden and is only visible to administrators. - - )} -
- - -

- {post.title} -

-
- - -
- {hideAuthor ? ( -

- {' '} - ago -

- ) : ( - - )} -
- - - -
- {hasMore && ( - - - Continue reading - - - )} -
- - { - event.preventDefault() - }} - onMouseDown={(event) => { - event.preventDefault() - }} - > -
- {isLikedByCurrentUser ? ( - - ) : ( - - )} - - {likeCount} - -
-
- -

- {post.likedBy - .slice(0, MAX_LIKED_BY_SHOWN) - .map((item) => - item.user.id === session!.user.id ? 'You' : item.user.name - ) - .join(', ')} - {likeCount > MAX_LIKED_BY_SHOWN && - ` and ${likeCount - MAX_LIKED_BY_SHOWN} more`} -

- -
-
- -
- - - {post._count.comments} - -
-
-
-
-
- ) -} diff --git a/components/search-dialog.tsx b/components/search-dialog.tsx deleted file mode 100644 index 5b6638e..0000000 --- a/components/search-dialog.tsx +++ /dev/null @@ -1,207 +0,0 @@ -import { SearchIcon, SpinnerIcon } from '@/components/icons' -import { classNames } from '@/lib/classnames' -import { InferQueryOutput, trpc } from '@/lib/trpc' -import { Dialog, Transition } from '@headlessui/react' -import Link from 'next/link' -import { useRouter } from 'next/router' -import * as React from 'react' -import { useDebounce } from 'use-debounce' -import { ItemOptions, useItemList } from 'use-item-list' - -type SearchDialogProps = { - isOpen: boolean - onClose: () => void -} - -function SearchResult({ - useItem, - result, -}: { - useItem: ({ ref, text, value, disabled }: ItemOptions) => { - id: string - index: number - highlight: () => void - select: () => void - selected: any - useHighlighted: () => Boolean - } - result: InferQueryOutput<'post.search'>[number] -}) { - const ref = React.useRef(null) - const { id, index, highlight, select, useHighlighted } = useItem({ - ref, - value: result, - }) - const highlighted = useHighlighted() - - return ( -
  • - - - {result.title} - - -
  • - ) -} - -function SearchField({ onSelect }: { onSelect: () => void }) { - const [value, setValue] = React.useState('') - const [debouncedValue] = useDebounce(value, 1000) - const router = useRouter() - - const feedQuery = trpc.useQuery( - [ - 'post.search', - { - query: debouncedValue, - }, - ], - { - enabled: debouncedValue.trim().length > 0, - } - ) - - const { moveHighlightedItem, selectHighlightedItem, useItem } = useItemList({ - onSelect: (item) => { - router.push(`/post/${item.value.id}`) - onSelect() - }, - }) - - React.useEffect(() => { - function handleKeydownEvent(event: KeyboardEvent) { - const { code } = event - - if (code === 'ArrowUp' || code === 'ArrowDown' || code === 'Enter') { - event.preventDefault() - } - - if (code === 'ArrowUp') { - moveHighlightedItem(-1) - } - - if (code === 'ArrowDown') { - moveHighlightedItem(1) - } - - if (code === 'Enter') { - selectHighlightedItem() - } - } - - document.addEventListener('keydown', handleKeydownEvent) - return () => { - document.removeEventListener('keydown', handleKeydownEvent) - } - }, [moveHighlightedItem, selectHighlightedItem, router]) - - return ( -
    -
    -
    - -
    -
    -
    - { - setValue(event.target.value) - }} - /> -
    - {feedQuery.data && - (feedQuery.data.length > 0 ? ( -
      - {feedQuery.data.map((result) => ( - - ))} -
    - ) : ( -
    - No results. Try something else -
    - ))} - {feedQuery.isError && ( -
    - Error: {feedQuery.error.message} -
    - )} -
    - ) -} - -export function SearchDialog({ isOpen, onClose }: SearchDialogProps) { - return ( - - -
    - - - - - -
    - {isOpen ? ( - - ) : ( -
    - )} -
    - -
    -
    -
    - ) -} diff --git a/components/textarea.tsx b/components/textarea.tsx deleted file mode 100644 index bdf3086..0000000 --- a/components/textarea.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { classNames } from '@/lib/classnames' -import * as React from 'react' - -export type TextareaOwnProps = { - label?: string -} - -type TextareaProps = TextareaOwnProps & - React.ComponentPropsWithoutRef<'textarea'> - -export const Textarea = React.forwardRef( - ({ label, id, name, className, ...rest }, forwardedRef) => { - return ( -
    - {label && ( - - )} -