From 0beb74195e216f30685998a9b715895aabc41891 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Sun, 25 Aug 2024 13:19:08 +0200 Subject: [PATCH 01/16] feat: reorganise locale files --- next.config.js | 2 +- src/app/[locale]/(default)/news/(header)/layout.tsx | 2 +- src/app/[locale]/layout.tsx | 2 +- src/app/not-found.tsx | 2 +- src/components/layout/Footer.tsx | 2 +- src/components/layout/LogoLink.tsx | 2 +- src/components/layout/Nav.tsx | 2 +- src/components/news/ArticleCard.tsx | 2 +- src/components/news/ArticleItem.tsx | 2 +- src/components/settings/LocaleMenu.tsx | 4 ++-- src/lib/{ => locale}/config.ts | 2 +- src/{ => lib/locale}/i18n.ts | 3 ++- src/lib/{ => locale}/navigation.ts | 2 +- src/middleware.ts | 7 ++++++- 14 files changed, 21 insertions(+), 15 deletions(-) rename src/lib/{ => locale}/config.ts (93%) rename src/{ => lib/locale}/i18n.ts (67%) rename src/lib/{ => locale}/navigation.ts (75%) diff --git a/next.config.js b/next.config.js index bb92a5b..26cdcc5 100644 --- a/next.config.js +++ b/next.config.js @@ -5,7 +5,7 @@ import nextIntl from 'next-intl/plugin'; * for Docker builds. */ await import('./src/env.js'); -const withNextIntl = nextIntl('./src/i18n.ts'); +const withNextIntl = nextIntl('./src/lib/locale/i18n.ts'); /** @type {import("next").NextConfig} */ const config = {}; diff --git a/src/app/[locale]/(default)/news/(header)/layout.tsx b/src/app/[locale]/(default)/news/(header)/layout.tsx index eca5470..3b11d0b 100644 --- a/src/app/[locale]/(default)/news/(header)/layout.tsx +++ b/src/app/[locale]/(default)/news/(header)/layout.tsx @@ -2,7 +2,7 @@ import { SquarePen } from 'lucide-react'; import { useTranslations } from 'next-intl'; import { unstable_setRequestLocale } from 'next-intl/server'; -import { Link } from '@/lib/navigation'; +import { Link } from '@/lib/locale/navigation'; import { Button } from '@/components/ui/Button'; diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 24bfeeb..0cc4abc 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -2,7 +2,7 @@ import { getTranslations, unstable_setRequestLocale } from 'next-intl/server'; import { Inter, Montserrat } from 'next/font/google'; import { notFound } from 'next/navigation'; -import { locales } from '@/lib/config'; +import { locales } from '@/lib/locale/config'; import { cx } from '@/lib/utils'; import { RootProviders } from '@/components/providers/RootProviders'; diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index 9d1c1de..0175f74 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -2,7 +2,7 @@ import { redirect, usePathname } from 'next/navigation'; -import { defaultLocale } from '@/lib/config'; +import { defaultLocale } from '@/lib/locale/config'; export default function NotFound() { const pathname = usePathname(); diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index b2dd899..f506cef 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -2,7 +2,7 @@ import { Bug, Mail } from 'lucide-react'; import { useTranslations } from 'next-intl'; import ExternalLink from 'next/link'; -import { Link } from '@/lib/navigation'; +import { Link } from '@/lib/locale/navigation'; import { Facebook } from '@/components/assets/icons/Facebook'; import { Github } from '@/components/assets/icons/Github'; diff --git a/src/components/layout/LogoLink.tsx b/src/components/layout/LogoLink.tsx index 58c0c24..0448fd9 100644 --- a/src/components/layout/LogoLink.tsx +++ b/src/components/layout/LogoLink.tsx @@ -1,6 +1,6 @@ import { useTranslations } from 'next-intl'; -import { Link } from '@/lib/navigation'; +import { Link } from '@/lib/locale/navigation'; import { cx } from '@/lib/utils'; import { Logo } from '@/components/assets/Logo'; diff --git a/src/components/layout/Nav.tsx b/src/components/layout/Nav.tsx index 4654c96..568373b 100644 --- a/src/components/layout/Nav.tsx +++ b/src/components/layout/Nav.tsx @@ -1,4 +1,4 @@ -import { Link } from '@/lib/navigation'; +import { Link } from '@/lib/locale/navigation'; import { Button } from '@/components/ui/Button'; diff --git a/src/components/news/ArticleCard.tsx b/src/components/news/ArticleCard.tsx index f64ed5f..957b646 100644 --- a/src/components/news/ArticleCard.tsx +++ b/src/components/news/ArticleCard.tsx @@ -1,6 +1,6 @@ import Image from 'next/image'; -import { Link } from '@/lib/navigation'; +import { Link } from '@/lib/locale/navigation'; import { cx } from '@/lib/utils'; import { InternalBadge } from '@/components/news/InternalBadge'; diff --git a/src/components/news/ArticleItem.tsx b/src/components/news/ArticleItem.tsx index a18ea51..314fcdc 100644 --- a/src/components/news/ArticleItem.tsx +++ b/src/components/news/ArticleItem.tsx @@ -1,6 +1,6 @@ import Image from 'next/image'; -import { Link } from '@/lib/navigation'; +import { Link } from '@/lib/locale/navigation'; import { cx } from '@/lib/utils'; import { InternalBadge } from '@/components/news/InternalBadge'; diff --git a/src/components/settings/LocaleMenu.tsx b/src/components/settings/LocaleMenu.tsx index 5ff377e..a793b6f 100644 --- a/src/components/settings/LocaleMenu.tsx +++ b/src/components/settings/LocaleMenu.tsx @@ -4,8 +4,8 @@ import { Globe2 } from 'lucide-react'; import { useParams } from 'next/navigation'; import * as React from 'react'; -import { flagIcons, locales } from '@/lib/config'; -import { usePathname, useRouter } from '@/lib/navigation'; +import { flagIcons, locales } from '@/lib/locale/config'; +import { usePathname, useRouter } from '@/lib/locale/navigation'; import { Button } from '@/components/ui/Button'; import { diff --git a/src/lib/config.ts b/src/lib/locale/config.ts similarity index 93% rename from src/lib/config.ts rename to src/lib/locale/config.ts index b839efa..e2338de 100644 --- a/src/lib/config.ts +++ b/src/lib/locale/config.ts @@ -1,6 +1,6 @@ import GBflagIcon from 'country-flag-icons/react/1x1/GB'; import NOflagIcon from 'country-flag-icons/react/1x1/NO'; -import type { Pathnames } from 'next-intl/navigation'; +import type { Pathnames } from 'next-intl/routing'; const flagIcons = { en: GBflagIcon, no: NOflagIcon }; diff --git a/src/i18n.ts b/src/lib/locale/i18n.ts similarity index 67% rename from src/i18n.ts rename to src/lib/locale/i18n.ts index 1fc89f6..4ee897b 100644 --- a/src/i18n.ts +++ b/src/lib/locale/i18n.ts @@ -2,5 +2,6 @@ import { getRequestConfig } from 'next-intl/server'; export default getRequestConfig(async ({ locale }) => ({ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - messages: (await import(`../messages/${locale}.json`)).default as Messages, + messages: (await import(`../../../messages/${locale}.json`)) + .default as Messages, })); diff --git a/src/lib/navigation.ts b/src/lib/locale/navigation.ts similarity index 75% rename from src/lib/navigation.ts rename to src/lib/locale/navigation.ts index 0c676df..4b15167 100644 --- a/src/lib/navigation.ts +++ b/src/lib/locale/navigation.ts @@ -1,6 +1,6 @@ import { createLocalizedPathnamesNavigation } from 'next-intl/navigation'; -import { localePrefix, locales, pathnames } from '@/lib/config'; +import { localePrefix, locales, pathnames } from '@/lib/locale/config'; export const { Link, redirect, usePathname, useRouter, getPathname } = createLocalizedPathnamesNavigation({ locales, localePrefix, pathnames }); diff --git a/src/middleware.ts b/src/middleware.ts index 82d9d62..6ccec91 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,6 +1,11 @@ import createMiddleware from 'next-intl/middleware'; -import { defaultLocale, localePrefix, locales, pathnames } from '@/lib/config'; +import { + defaultLocale, + localePrefix, + locales, + pathnames, +} from '@/lib/locale/config'; export default createMiddleware({ defaultLocale, From c12537d74ee5fa6059ae81b41a9c9dbef59be0d9 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Sun, 25 Aug 2024 13:27:05 +0200 Subject: [PATCH 02/16] docs: add more docs to readme --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3d62a9c..f82e907 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,10 @@ Here is a list of documentation to help you get started: - [Class Variance Authority](https://beta.cva.style/) - Tool for creating style variants in our UI components - [Shadcn/ui](https://ui.shadcn.com/docs) - Reusable UI components - [Radix UI Primitives](https://www.radix-ui.com/primitives/docs/overview/introduction) - Primitives library that Shadcn/ui is built on, great documentation if you need to access the underlying components +- [Aceternity/ui](https://ui.aceternity.com/components) - More fancy components - [Lucide](https://lucide.dev/icons/) - Icons library +- [Drizzle](https://orm.drizzle.team/docs/overview) - ORM for interacting with the database +- [TRPC](https://trpc.io/docs) - Tool for creating API endpoints as functions ### Other resources @@ -24,8 +27,8 @@ Here is a list of documentation to help you get started: - When you want to link to a new internal page use the `` component from `@/lib/navigation` instead of the normal anchortag ``. This will ensure that the page is loaded with the correct locale. If you want to link to external resources or other media, use the built-in `` component from Next.js. Remember to add `prefetch={false}` to the `` component if the page is not visited often. - If you need to use both `` components from `@/lib/navigation` and Next.js, make sure to import the Next.js `` component as `ExternalLink` to avoid naming conflicts. -- Remember to surround Links with the `Button` ui component. This will provide some basic styling and accessibility features for keyboard navigation even if it is not supposed to look like a button. -- For interationalization use the `useTranslations` hook from `next-intl`. For client components you can pass the translations as props. +- Remember to surround Links with the `Button` UI component. This will provide some basic styling and accessibility features for keyboard navigation even if it is not supposed to look like a button. +- For internationalization use the `useTranslations` hook from `next-intl`. For client components you can pass the translations as props. ## Development setup @@ -49,7 +52,7 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the ## Build -When you build the project, you prerender all the Server Side Generated (SSG) pages. This makes the site load faster and perform better and behave like it will when it is deployed. When serving the built project it will not hot reload when you make changes to the code like it does in development mode. +When you build the project, you pre-render all the Server Side Generated (SSG) pages. This makes the site load faster and perform better and behave like it will when it is deployed. When serving the built project it will not hot reload when you make changes to the code like it does in development mode. You can build the project with the following command: From 381319c15e6978f77571873e0d63997ba0ed1cc1 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Sun, 25 Aug 2024 17:36:13 +0200 Subject: [PATCH 03/16] build: work on db setup --- src/env.js | 10 ++++++++++ src/server/db/index.ts | 20 +++++++++++++++++++ src/server/db/schema.ts | 1 + src/server/db/schema/users.ts | 36 +++++++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+) create mode 100644 src/server/db/index.ts create mode 100644 src/server/db/schema.ts create mode 100644 src/server/db/schema/users.ts diff --git a/src/env.js b/src/env.js index 8c66c42..5a4fa73 100644 --- a/src/env.js +++ b/src/env.js @@ -8,6 +8,11 @@ export const env = createEnv({ */ server: { NODE_ENV: z.enum(['development', 'test', 'production']), + DATABASE_HOST: z.string(), + DATABASE_PORT: z.string(), + DATABASE_USER: z.string(), + DATABASE_PASSWORD: z.string(), + DATABASE_NAME: z.string(), }, /** @@ -25,6 +30,11 @@ export const env = createEnv({ */ runtimeEnv: { NODE_ENV: process.env.NODE_ENV, + DATABASE_HOST: process.env.DATABASE_HOST, + DATABASE_PORT: process.env.DATABASE_PORT, + DATABASE_USER: process.env.DATABASE_USER, + DATABASE_PASSWORD: process.env.DATABASE_PASSWORD, + DATABASE_NAME: process.env.DATABASE_NAME, // NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR, }, /** diff --git a/src/server/db/index.ts b/src/server/db/index.ts new file mode 100644 index 0000000..a6fc94c --- /dev/null +++ b/src/server/db/index.ts @@ -0,0 +1,20 @@ +import { drizzle } from 'drizzle-orm/postgres-js'; +import postgres from 'postgres'; + +import { env } from '@/env'; +import * as schema from './schema'; + +/** + * Cache the database connection in development. This avoids creating a new connection on every HMR + * update. + */ +const globalForDb = globalThis as unknown as { + connection: postgres.Sql | undefined; +}; + +const connectionString = `postgresql://${env.DATABASE_USER}:${env.DATABASE_PASSWORD}@${env.DATABASE_HOST}:${env.DATABASE_PORT}/${env.DATABASE_NAME}`; + +const connection = globalForDb.connection ?? postgres(connectionString); +if (env.NODE_ENV !== 'production') globalForDb.connection = connection; + +export const db = drizzle(connection, { schema }); diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts new file mode 100644 index 0000000..5af2b93 --- /dev/null +++ b/src/server/db/schema.ts @@ -0,0 +1 @@ +export * from './schema/users'; diff --git a/src/server/db/schema/users.ts b/src/server/db/schema/users.ts new file mode 100644 index 0000000..948da6c --- /dev/null +++ b/src/server/db/schema/users.ts @@ -0,0 +1,36 @@ +// Example model schema from the Drizzle docs +// https://orm.drizzle.team/docs/sql-schema-declaration + +import { sql } from 'drizzle-orm'; +import { + index, + pgTableCreator, + serial, + timestamp, + varchar, +} from 'drizzle-orm/pg-core'; + +/** + * This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same + * database instance for multiple projects. + * + * @see https://orm.drizzle.team/docs/goodies#multi-project-schema + */ +export const createTable = pgTableCreator((name) => `test-t3_${name}`); + +export const posts = createTable( + 'post', + { + id: serial('id').primaryKey(), + name: varchar('name', { length: 256 }), + createdAt: timestamp('created_at', { withTimezone: true }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true }).$onUpdate( + () => new Date(), + ), + }, + (example) => ({ + nameIndex: index('name_idx').on(example.name), + }), +); From 9689ab6943f7d40c91ee2287608dad558615a063 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Sun, 25 Aug 2024 17:36:33 +0200 Subject: [PATCH 04/16] build: config files --- .dockerignore | 5 ++++- .env.example | 6 ++++++ .gitignore | 5 ++++- Dockerfile | 4 ++-- bun.lockb | Bin 153407 -> 145053 bytes docker-compose.yml | 29 +++++++++++++++++++++++++++++ drizzle.config.ts | 13 +++++++++++++ package.json | 12 ++++++++---- 8 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 docker-compose.yml create mode 100644 drizzle.config.ts diff --git a/.dockerignore b/.dockerignore index b909791..cfabe27 100644 --- a/.dockerignore +++ b/.dockerignore @@ -32,4 +32,7 @@ Dockerfile* .gitignore # docs -README.md \ No newline at end of file +README.md + +# data +/data diff --git a/.env.example b/.env.example index adfe836..ff498a8 100644 --- a/.env.example +++ b/.env.example @@ -12,3 +12,9 @@ # Example: # SERVERVAR="foo" # NEXT_PUBLIC_CLIENTVAR="bar" +NODE_ENV="development" +DATABASE_HOST="localhost" +DATABASE_PORT="5432" +DATABASE_USER="user" +DATABASE_PASSWORD="password" +DATABASE_NAME="database" diff --git a/.gitignore b/.gitignore index 4752b1d..c55f229 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,7 @@ next-env.d.ts # site map public/sitemap.xml -public/robots.txt \ No newline at end of file +public/robots.txt + +# data +/data diff --git a/Dockerfile b/Dockerfile index deda379..b0beeff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ WORKDIR /app COPY package.json bun.lockb ./ -RUN bun install --production +RUN bun install --production --frozen-lockfile COPY . . @@ -13,4 +13,4 @@ RUN bun run build EXPOSE 3000 -ENTRYPOINT [ "bun", "run", "start" ] \ No newline at end of file +ENTRYPOINT [ "bun", "run", "start" ] diff --git a/bun.lockb b/bun.lockb index 7b8d3643f325ad84c504f693d41380c1f01e7e18..2ea993d61e36cd3305991c4aed325b531b020b78 100755 GIT binary patch delta 25017 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 P<1+H#ZM8bf&Q|{inc7Yq delta 29889 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 Date: Sun, 25 Aug 2024 23:52:55 +0200 Subject: [PATCH 05/16] chore: change from KiD to Nexus logo --- src/components/assets/sponsors/IDILogo.tsx | 2 - src/components/assets/sponsors/KiDLogo.tsx | 82 -------- src/components/assets/sponsors/NexusLogo.tsx | 187 +++++++++++++++++++ src/components/layout/Footer.tsx | 4 +- 4 files changed, 189 insertions(+), 86 deletions(-) delete mode 100644 src/components/assets/sponsors/KiDLogo.tsx create mode 100644 src/components/assets/sponsors/NexusLogo.tsx diff --git a/src/components/assets/sponsors/IDILogo.tsx b/src/components/assets/sponsors/IDILogo.tsx index e6ac5b0..9f2e736 100644 --- a/src/components/assets/sponsors/IDILogo.tsx +++ b/src/components/assets/sponsors/IDILogo.tsx @@ -20,12 +20,10 @@ function IDILogo({ > diff --git a/src/components/assets/sponsors/KiDLogo.tsx b/src/components/assets/sponsors/KiDLogo.tsx deleted file mode 100644 index 1ce2cb6..0000000 --- a/src/components/assets/sponsors/KiDLogo.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { cx } from '@/lib/utils'; - -function KiDLogo({ - className, - title, - ...rest -}: { - className?: string; - title: string; -}) { - return ( - - - - - - - - - - - - - - - - - - ); -} - -export { KiDLogo }; diff --git a/src/components/assets/sponsors/NexusLogo.tsx b/src/components/assets/sponsors/NexusLogo.tsx new file mode 100644 index 0000000..90945b6 --- /dev/null +++ b/src/components/assets/sponsors/NexusLogo.tsx @@ -0,0 +1,187 @@ +import { cx } from '@/lib/utils'; + +function NexusLogo({ + className, + title, + ...rest +}: { + className?: string; + title: string; +}) { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} + +export { NexusLogo }; diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index f506cef..7ef176d 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -9,7 +9,7 @@ import { Github } from '@/components/assets/icons/Github'; import { Instagram } from '@/components/assets/icons/Instagram'; import { Slack } from '@/components/assets/icons/Slack'; import { IDILogo } from '@/components/assets/sponsors/IDILogo'; -import { KiDLogo } from '@/components/assets/sponsors/KiDLogo'; +import { NexusLogo } from '@/components/assets/sponsors/NexusLogo'; import { LogoLink } from '@/components/layout/LogoLink'; import { Nav } from '@/components/layout/Nav'; import { Button } from '@/components/ui/Button'; @@ -198,7 +198,7 @@ function Footer() { target='_blank' rel='noopener noreferrer' > - + From b1121a7c1871a80323b4eb1ac02e6762583bf7b7 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Mon, 26 Aug 2024 00:29:44 +0200 Subject: [PATCH 06/16] docs: add more sources --- README.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f82e907..3a60841 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,27 @@ Here is a list of documentation to help you get started: +### Frontend + - [React](https://react.dev/reference/react) - Library for building user interfaces - [Next.js](https://nextjs.org/docs) - Framework for routing and server-side rendering - [Next-intl](https://next-intl-docs.vercel.app/) - Internationalization library +- [nuqs](https://nuqs.47ng.com/docs/installation) - Easy to use query params +- [Tanstack Form](https://tanstack.com/form/latest/docs/overview) - When we need to handle form validation (shadcn/ui uses react-hook-form. but I think this is better, we will figure it out) +- [Tanstack Query](https://tanstack.com/query/latest/docs/framework/react/overview) - TRPC wraps Tanstack Query which is how we fetch data from the backend + +#### Styling + - [Tailwind CSS](https://tailwindcss.com/docs) - Styling library - [Class Variance Authority](https://beta.cva.style/) - Tool for creating style variants in our UI components -- [Shadcn/ui](https://ui.shadcn.com/docs) - Reusable UI components - - [Radix UI Primitives](https://www.radix-ui.com/primitives/docs/overview/introduction) - Primitives library that Shadcn/ui is built on, great documentation if you need to access the underlying components -- [Aceternity/ui](https://ui.aceternity.com/components) - More fancy components +- [shadcn/ui](https://ui.shadcn.com/docs) - Reusable UI components + - [Radix UI Primitives](https://www.radix-ui.com/primitives/docs/overview/introduction) - Primitives library that shadcn/ui is built on, great documentation if you need to access the underlying components +- [Aceternity/ui](https://ui.aceternity.com/components) - More fancy components that can be used (matches shadcn/ui) +- [tsparticles](https://github.com/tsparticles/react) - Cool particles library we can use as backgrounds - [Lucide](https://lucide.dev/icons/) - Icons library + +### Backend + - [Drizzle](https://orm.drizzle.team/docs/overview) - ORM for interacting with the database - [TRPC](https://trpc.io/docs) - Tool for creating API endpoints as functions From 7a0a8f399b90231532c564c298177248a2c10f46 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 29 Aug 2024 05:50:21 +0200 Subject: [PATCH 07/16] docs: add blockNote to docs --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3a60841..c57d0ad 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Here is a list of documentation to help you get started: - [Next.js](https://nextjs.org/docs) - Framework for routing and server-side rendering - [Next-intl](https://next-intl-docs.vercel.app/) - Internationalization library - [nuqs](https://nuqs.47ng.com/docs/installation) - Easy to use query params +- [BlockNote](https://www.blocknotejs.org/docs) - Tool for markdown textboxes - [Tanstack Form](https://tanstack.com/form/latest/docs/overview) - When we need to handle form validation (shadcn/ui uses react-hook-form. but I think this is better, we will figure it out) - [Tanstack Query](https://tanstack.com/query/latest/docs/framework/react/overview) - TRPC wraps Tanstack Query which is how we fetch data from the backend From e8f618b4a8d7189bc57532c05be6ee14ef8e6510 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Thu, 29 Aug 2024 23:31:11 +0200 Subject: [PATCH 08/16] feat: setup s3, fiexed i18n and started auth --- .env.example | 13 +++- Dockerfile | 1 + README.md | 1 + bun.lockb | Bin 145053 -> 211506 bytes docker-compose.yml | 20 +++---- drizzle.config.ts | 7 +-- next-sitemap.config.js | 4 +- package.json | 9 ++- src/app/[locale]/layout.tsx | 6 +- src/app/not-found.tsx | 5 +- src/components/settings/LocaleMenu.tsx | 18 +++--- src/env.js | 13 +++- src/lib/locale/config.ts | 35 ----------- src/lib/locale/i18n.ts | 15 +++-- src/lib/locale/index.ts | 33 +++++++++++ src/lib/locale/navigation.ts | 5 +- src/middleware.ts | 15 +---- src/server/auth/index.ts | 41 +++++++++++++ src/server/auth/user.ts | 30 ++++++++++ src/server/db/index.ts | 8 ++- src/server/db/schema.ts | 1 + src/server/db/schema/office.ts | 6 ++ src/server/db/schema/users.ts | 79 +++++++++++++++++-------- src/server/s3/index.ts | 32 ++++++++++ 24 files changed, 275 insertions(+), 122 deletions(-) delete mode 100644 src/lib/locale/config.ts create mode 100644 src/lib/locale/index.ts create mode 100644 src/server/auth/index.ts create mode 100644 src/server/auth/user.ts create mode 100644 src/server/db/schema/office.ts create mode 100644 src/server/s3/index.ts diff --git a/.env.example b/.env.example index ff498a8..87023ca 100644 --- a/.env.example +++ b/.env.example @@ -9,12 +9,19 @@ # When adding additional environment variables, the schema in "/src/env.js" # should be updated accordingly. -# Example: -# SERVERVAR="foo" -# NEXT_PUBLIC_CLIENTVAR="bar" NODE_ENV="development" +SITE_URL="http://localhost:3000" + +# Database DATABASE_HOST="localhost" DATABASE_PORT="5432" DATABASE_USER="user" DATABASE_PASSWORD="password" DATABASE_NAME="database" + +# Storage +STORAGE_HOST="localhost" +STORAGE_PORT="9000" +STORAGE_USER="user" +STORAGE_PASSWORD="password" +STORAGE_NAME="images" diff --git a/Dockerfile b/Dockerfile index b0beeff..45c64fa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ RUN bun install --production --frozen-lockfile COPY . . +ENV SKIP_ENV_VALIDATION=true ENV NODE_ENV=production RUN bun run build diff --git a/README.md b/README.md index c57d0ad..0d4461a 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Here is a list of documentation to help you get started: - [Drizzle](https://orm.drizzle.team/docs/overview) - ORM for interacting with the database - [TRPC](https://trpc.io/docs) - Tool for creating API endpoints as functions +- [Lucia](https://lucia-auth.com) - Authentication library ### Other resources diff --git a/bun.lockb b/bun.lockb index 2ea993d61e36cd3305991c4aed325b531b020b78..747835e41d95da1d433e33972333db8a34b137c7 100755 GIT binary patch delta 71032 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 delta 26580 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 K%{9$DLjDgn+dDY` diff --git a/docker-compose.yml b/docker-compose.yml index a8a9e59..8f5893a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,13 +17,13 @@ services: - ./data/db:/var/lib/postgresql/data ports: - "5432:5432" - # s3: - # image: bitnami/minio:2024 - # environment: - # MINIO_ROOT_USER: ${STORAGE_USER} - # MINIO_ROOT_PASSWORD: ${STORAGE_PASSWORD} - # MINIO_DEFAULT_BUCKETS: ${STORAGE_NAME} - # volumes: - # - ./data/s3:/bitnami/minio/data - # ports: - # - "9000:9000" + s3: + image: bitnami/minio:2024 + environment: + MINIO_ROOT_USER: ${STORAGE_USER} + MINIO_ROOT_PASSWORD: ${STORAGE_PASSWORD} + MINIO_DEFAULT_BUCKETS: ${STORAGE_NAME} + volumes: + - ./data/s3:/bitnami/minio/data + ports: + - "9000:9000" diff --git a/drizzle.config.ts b/drizzle.config.ts index d039a79..c39240d 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,13 +1,10 @@ -import { defineConfig } from 'drizzle-kit'; - import { env } from '@/env'; - -const connectionString = `postgresql://${env.DATABASE_USER}:${env.DATABASE_PASSWORD}@${env.DATABASE_HOST}:${env.DATABASE_PORT}/${env.DATABASE_NAME}`; +import { defineConfig } from 'drizzle-kit'; export default defineConfig({ schema: './src/server/db/schema/*.ts', dialect: 'postgresql', dbCredentials: { - url: connectionString, + url: `postgresql://${env.DATABASE_USER}:${env.DATABASE_PASSWORD}@${env.DATABASE_HOST}:${env.DATABASE_PORT}/${env.DATABASE_NAME}`, }, }); diff --git a/next-sitemap.config.js b/next-sitemap.config.js index 0894518..e2b4c5b 100644 --- a/next-sitemap.config.js +++ b/next-sitemap.config.js @@ -1,6 +1,8 @@ +import { env } from '@/env'; + /** @type {import('next-sitemap').IConfig} */ const config = { - siteUrl: process.env.SITE_URL ?? 'https://localhost:3000', + siteUrl: env.SITE_URL, generateRobotsTxt: true, generateIndexSitemap: false, }; diff --git a/package.json b/package.json index 744d77f..36f1e97 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,16 @@ "dev": "next dev --turbo", "lint": "biome check --write --unsafe", "start": "next start", + "db:start": "docker-compose up db", "db:generate": "drizzle-kit generate", "db:migrate": "drizzle-kit migrate", "db:push": "drizzle-kit push", - "db:studio": "drizzle-kit studio" + "db:studio": "drizzle-kit studio", + "s3:start": "docker-compose up s3" }, "dependencies": { + "@aws-sdk/client-s3": "^3.637.0", + "@lucia-auth/adapter-drizzle": "^1.1.0", "@radix-ui/react-avatar": "^1.1.0", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1", @@ -32,9 +36,10 @@ "country-flag-icons": "^1.5.12", "cva": "^1.0.0-beta.1", "drizzle-orm": "^0.33.0", + "lucia": "^3.2.0", "lucide-react": "^0.396.0", "next": "^14.2.4", - "next-intl": "^3.15.2", + "next-intl": "^3.18.1", "next-sitemap": "^4.2.3", "next-themes": "^0.3.0", "nuqs": "^1.17.4", diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 0cc4abc..0b30d93 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -1,8 +1,7 @@ import { getTranslations, unstable_setRequestLocale } from 'next-intl/server'; import { Inter, Montserrat } from 'next/font/google'; -import { notFound } from 'next/navigation'; -import { locales } from '@/lib/locale/config'; +import { routing } from '@/lib/locale'; import { cx } from '@/lib/utils'; import { RootProviders } from '@/components/providers/RootProviders'; @@ -25,7 +24,7 @@ const montserrat = Montserrat({ }); export function generateStaticParams() { - return locales.map((locale) => ({ locale })); + return routing.locales.map((locale) => ({ locale })); } export async function generateMetadata({ @@ -75,7 +74,6 @@ export default function LocaleLayout({ children, params: { locale }, }: LocaleLayoutProps) { - if (!locales.includes(locale)) notFound(); unstable_setRequestLocale(locale); return ( - {locales.map((locale) => { - const FlagIcon = flagIcons[locale as keyof typeof flagIcons]; + {routing.locales.map((locale) => { + const FlagIcon = localeIcons[locale as keyof typeof localeIcons]; return ( ; - -export { flagIcons, locales, defaultLocale, localePrefix, pathnames }; diff --git a/src/lib/locale/i18n.ts b/src/lib/locale/i18n.ts index 4ee897b..5754d72 100644 --- a/src/lib/locale/i18n.ts +++ b/src/lib/locale/i18n.ts @@ -1,7 +1,12 @@ +import { routing } from '@/lib/locale'; import { getRequestConfig } from 'next-intl/server'; +import { notFound } from 'next/navigation'; -export default getRequestConfig(async ({ locale }) => ({ - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - messages: (await import(`../../../messages/${locale}.json`)) - .default as Messages, -})); +export default getRequestConfig(async ({ locale }) => { + // @ts-ignore + if (!routing.locales.includes(locale)) notFound(); + return { + messages: (await import(`../../../messages/${locale}.json`)) + .default as Messages, + }; +}); diff --git a/src/lib/locale/index.ts b/src/lib/locale/index.ts new file mode 100644 index 0000000..20bda14 --- /dev/null +++ b/src/lib/locale/index.ts @@ -0,0 +1,33 @@ +import { GB, NO } from 'country-flag-icons/react/1x1'; +import { defineRouting } from 'next-intl/routing'; + +export const localeIcons = { en: GB, no: NO }; + +export const routing = defineRouting({ + locales: ['en', 'no'], + defaultLocale: 'no', + localePrefix: 'as-needed', + pathnames: { + '/': '/', + '/events': { + en: '/events', + no: '/arrangementer', + }, + '/news': { + en: '/news', + no: '/nyheter', + }, + '/news/new': { + en: '/news/new', + no: '/nyheter/ny', + }, + '/news/[article]': { + en: '/news/[article]', + no: '/nyheter/[article]', + }, + '/about': { + en: '/about', + no: '/om-oss', + }, + }, +}); diff --git a/src/lib/locale/navigation.ts b/src/lib/locale/navigation.ts index 4b15167..43e9e40 100644 --- a/src/lib/locale/navigation.ts +++ b/src/lib/locale/navigation.ts @@ -1,6 +1,5 @@ +import { routing } from '@/lib/locale'; import { createLocalizedPathnamesNavigation } from 'next-intl/navigation'; -import { localePrefix, locales, pathnames } from '@/lib/locale/config'; - export const { Link, redirect, usePathname, useRouter, getPathname } = - createLocalizedPathnamesNavigation({ locales, localePrefix, pathnames }); + createLocalizedPathnamesNavigation(routing); diff --git a/src/middleware.ts b/src/middleware.ts index 6ccec91..ef0e5b2 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,18 +1,7 @@ +import { routing } from '@/lib/locale'; import createMiddleware from 'next-intl/middleware'; -import { - defaultLocale, - localePrefix, - locales, - pathnames, -} from '@/lib/locale/config'; - -export default createMiddleware({ - defaultLocale, - localePrefix, - locales, - pathnames, -}); +export default createMiddleware(routing); export const config = { matcher: ['/', '/(en|no)/:path*', '/((?!api|_next|.*\\..*).*)'], diff --git a/src/server/auth/index.ts b/src/server/auth/index.ts new file mode 100644 index 0000000..80b4736 --- /dev/null +++ b/src/server/auth/index.ts @@ -0,0 +1,41 @@ +import { env } from '@/env'; +import { db } from '@/server/db'; +import { sessions, users } from '@/server/db/schema'; +import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle'; +import { Lucia } from 'lucia'; + +const adapter = new DrizzlePostgreSQLAdapter(db, sessions, users); + +export const auth = new Lucia(adapter, { + sessionCookie: { + // this sets cookies with super long expiration + // since Next.js doesn't allow Lucia to extend cookie expiration when rendering pages + expires: false, + attributes: { + // set to `true` when using HTTPS + secure: env.NODE_ENV === 'production', + }, + }, + getUserAttributes: (attributes) => { + return { + // attributes has the type of DatabaseUserAttributes + username: attributes.username, + }; + }, +}); + +export * from './user'; + +// IMPORTANT! +declare module 'lucia' { + // Has to be an interface + interface Register { + Lucia: typeof auth; + UserId: number; + DatabaseUserAttributes: DatabaseUserAttributes; + } +} + +type DatabaseUserAttributes = { + username: string; +}; diff --git a/src/server/auth/user.ts b/src/server/auth/user.ts new file mode 100644 index 0000000..3a813bc --- /dev/null +++ b/src/server/auth/user.ts @@ -0,0 +1,30 @@ +import { auth } from '@/server/auth'; +import { cookies } from 'next/headers'; +import { cache } from 'react'; + +export const getUser = cache(async () => { + const sessionId = cookies().get(auth.sessionCookieName)?.value ?? null; + if (!sessionId) return null; + const { user, session } = await auth.validateSession(sessionId); + try { + if (session?.fresh) { + const sessionCookie = auth.createSessionCookie(session.id); + cookies().set( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes, + ); + } + if (!session) { + const sessionCookie = auth.createBlankSessionCookie(); + cookies().set( + sessionCookie.name, + sessionCookie.value, + sessionCookie.attributes, + ); + } + } catch { + // Next.js throws error when attempting to set cookies when rendering page + } + return user; +}); diff --git a/src/server/db/index.ts b/src/server/db/index.ts index a6fc94c..d963f1a 100644 --- a/src/server/db/index.ts +++ b/src/server/db/index.ts @@ -12,9 +12,11 @@ const globalForDb = globalThis as unknown as { connection: postgres.Sql | undefined; }; -const connectionString = `postgresql://${env.DATABASE_USER}:${env.DATABASE_PASSWORD}@${env.DATABASE_HOST}:${env.DATABASE_PORT}/${env.DATABASE_NAME}`; - -const connection = globalForDb.connection ?? postgres(connectionString); +const connection = + globalForDb.connection ?? + postgres( + `postgresql://${env.DATABASE_USER}:${env.DATABASE_PASSWORD}@${env.DATABASE_HOST}:${env.DATABASE_PORT}/${env.DATABASE_NAME}`, + ); if (env.NODE_ENV !== 'production') globalForDb.connection = connection; export const db = drizzle(connection, { schema }); diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts index 5af2b93..a8aff43 100644 --- a/src/server/db/schema.ts +++ b/src/server/db/schema.ts @@ -1 +1,2 @@ export * from './schema/users'; +export * from './schema/office'; diff --git a/src/server/db/schema/office.ts b/src/server/db/schema/office.ts new file mode 100644 index 0000000..f56a0da --- /dev/null +++ b/src/server/db/schema/office.ts @@ -0,0 +1,6 @@ +import { pgTable, serial, timestamp } from 'drizzle-orm/pg-core'; + +export const coffee = pgTable('coffee', { + id: serial('id').primaryKey(), + createdAt: timestamp('created_at').defaultNow().notNull(), +}); diff --git a/src/server/db/schema/users.ts b/src/server/db/schema/users.ts index 948da6c..4df3b48 100644 --- a/src/server/db/schema/users.ts +++ b/src/server/db/schema/users.ts @@ -1,36 +1,67 @@ -// Example model schema from the Drizzle docs -// https://orm.drizzle.team/docs/sql-schema-declaration - -import { sql } from 'drizzle-orm'; +import { relations } from 'drizzle-orm'; import { - index, - pgTableCreator, + integer, + pgTable, + primaryKey, serial, + text, timestamp, varchar, } from 'drizzle-orm/pg-core'; -/** - * This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same - * database instance for multiple projects. - * - * @see https://orm.drizzle.team/docs/goodies#multi-project-schema - */ -export const createTable = pgTableCreator((name) => `test-t3_${name}`); +export const users = pgTable('users', { + id: serial('id').primaryKey(), + username: varchar('username', { length: 8 }).unique().notNull(), + passwordHash: text('password_hash').notNull(), + name: varchar('name', { length: 256 }).notNull(), +}); + +export const usersRelations = relations(users, ({ many }) => ({ + usersHasSkills: many(usersHasSkills), +})); + +export const sessions = pgTable('session', { + id: text('id').primaryKey(), + userId: integer('user_id') + .references(() => users.id) + .notNull(), + expiresAt: timestamp('expires_at', { + withTimezone: true, + mode: 'date', + }).notNull(), +}); + +export const skills = pgTable('skills', { + id: serial('id').primaryKey(), + identifier: varchar('identifier', { length: 256 }).unique().notNull(), +}); + +export const skillsRelations = relations(skills, ({ many }) => ({ + usersHasSkills: many(usersHasSkills), +})); -export const posts = createTable( - 'post', +export const usersHasSkills = pgTable( + 'users_has_skills', { - id: serial('id').primaryKey(), - name: varchar('name', { length: 256 }), - createdAt: timestamp('created_at', { withTimezone: true }) - .default(sql`CURRENT_TIMESTAMP`) + userId: integer('user_id') + .references(() => users.id) + .notNull(), + skillId: integer('skill_id') + .references(() => skills.id) .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true }).$onUpdate( - () => new Date(), - ), }, - (example) => ({ - nameIndex: index('name_idx').on(example.name), + (t) => ({ + pk: primaryKey({ columns: [t.userId, t.skillId] }), }), ); + +export const usersHasSkillsRelations = relations(usersHasSkills, ({ one }) => ({ + group: one(skills, { + fields: [usersHasSkills.skillId], + references: [skills.id], + }), + user: one(users, { + fields: [usersHasSkills.userId], + references: [users.id], + }), +})); diff --git a/src/server/s3/index.ts b/src/server/s3/index.ts new file mode 100644 index 0000000..82113d1 --- /dev/null +++ b/src/server/s3/index.ts @@ -0,0 +1,32 @@ +import { env } from '@/env'; +import { + DeleteObjectCommand, + PutObjectCommand, + S3Client, +} from '@aws-sdk/client-s3'; + +const endpoint = `http://${env.STORAGE_HOST}:${env.STORAGE_PORT}`; + +// Cache the S3 client in development. This avoids creating a new client on every HMR update. +const globalForS3 = globalThis as unknown as { + s3: S3Client | undefined; +}; + +const s3 = + globalForS3.s3 ?? + new S3Client({ + credentials: { + accessKeyId: env.STORAGE_USER, + secretAccessKey: env.STORAGE_PASSWORD, + }, + endpoint: endpoint, + forcePathStyle: true, + region: '', // Required but not used with self-hosted storage + }); + +if (env.NODE_ENV !== 'production') globalForS3.s3 = s3; + +const buckets = env.STORAGE_NAME.split(','); +const imageBucket = buckets[0] as string; + +export { s3, endpoint, imageBucket, PutObjectCommand, DeleteObjectCommand }; From 282789c0191f136a064693d754b581ec8e111585 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Fri, 30 Aug 2024 00:04:13 +0200 Subject: [PATCH 09/16] fix: icon names and imports --- src/app/[locale]/(default)/layout.tsx | 3 +- .../(default)/news/(header)/layout.tsx | 4 +- src/app/[locale]/(default)/storage/layout.tsx | 8 ++-- src/app/[locale]/(default)/storage/page.tsx | 12 +++--- src/app/[locale]/layout.tsx | 8 ++-- .../icons/{Facebook.tsx => FacebookIcon.tsx} | 4 +- .../icons/{Github.tsx => GitHubIcon.tsx} | 4 +- .../{Instagram.tsx => InstagramIcon.tsx} | 4 +- .../assets/icons/{Slack.tsx => SlackIcon.tsx} | 4 +- src/components/assets/icons/index.tsx | 4 ++ src/components/assets/sponsors/index.tsx | 2 + src/components/layout/Footer.tsx | 37 +++++++++--------- src/components/layout/Header.tsx | 3 +- src/components/layout/LogoLink.tsx | 8 ++-- src/components/layout/MobileSheet.tsx | 7 ++-- src/components/layout/Nav.tsx | 3 +- src/components/layout/PaginationCarousel.tsx | 7 +--- .../layout/PaginationCarouselSkeleton.tsx | 4 +- src/components/news/ArticleCard.tsx | 9 ++--- src/components/news/ArticleItem.tsx | 8 ++-- src/components/news/CardGrid.tsx | 4 +- src/components/news/CardGridSkeleton.tsx | 6 +-- src/components/news/InternalBadge.tsx | 10 ++--- src/components/news/ItemGrid.tsx | 3 +- src/components/news/ItemGridSkeleton.tsx | 3 +- src/components/profile/AvatarIcon.tsx | 3 +- src/components/settings/DarkModeMenu.tsx | 11 +++--- src/components/settings/ProfileMenu.tsx | 7 ++-- src/components/ui/Avatar.tsx | 3 +- src/components/ui/Badge.tsx | 3 +- src/components/ui/Button.tsx | 3 +- src/components/ui/Card.tsx | 3 +- src/components/ui/Combobox.tsx | 12 +++--- src/components/ui/Command.tsx | 10 ++--- src/components/ui/Dialog.tsx | 7 ++-- src/components/ui/DropdownMenu.tsx | 8 ++-- src/components/ui/Pagination.tsx | 18 +++++---- src/components/ui/Popover.tsx | 3 +- src/components/ui/SearchBar.tsx | 7 ++-- src/components/ui/Select.tsx | 13 +++---- src/components/ui/Separator.tsx | 3 +- src/components/ui/Sheet.tsx | 7 ++-- src/components/ui/Tooltip.tsx | 3 +- src/lib/config.ts | 39 ------------------- src/server/db/schema.ts | 2 - src/server/db/schema/index.ts | 2 + 46 files changed, 128 insertions(+), 208 deletions(-) rename src/components/assets/icons/{Facebook.tsx => FacebookIcon.tsx} (81%) rename src/components/assets/icons/{Github.tsx => GitHubIcon.tsx} (87%) rename src/components/assets/icons/{Instagram.tsx => InstagramIcon.tsx} (84%) rename src/components/assets/icons/{Slack.tsx => SlackIcon.tsx} (89%) create mode 100644 src/components/assets/icons/index.tsx create mode 100644 src/components/assets/sponsors/index.tsx delete mode 100644 src/lib/config.ts delete mode 100644 src/server/db/schema.ts create mode 100644 src/server/db/schema/index.ts diff --git a/src/app/[locale]/(default)/layout.tsx b/src/app/[locale]/(default)/layout.tsx index ac6416d..c01ad96 100644 --- a/src/app/[locale]/(default)/layout.tsx +++ b/src/app/[locale]/(default)/layout.tsx @@ -1,8 +1,7 @@ -import { unstable_setRequestLocale } from 'next-intl/server'; - import { Footer } from '@/components/layout/Footer'; import { Header } from '@/components/layout/Header'; import { Main } from '@/components/layout/Main'; +import { unstable_setRequestLocale } from 'next-intl/server'; type DefaultLayoutProps = { children: React.ReactNode; diff --git a/src/app/[locale]/(default)/news/(header)/layout.tsx b/src/app/[locale]/(default)/news/(header)/layout.tsx index 3b11d0b..feebab9 100644 --- a/src/app/[locale]/(default)/news/(header)/layout.tsx +++ b/src/app/[locale]/(default)/news/(header)/layout.tsx @@ -1,4 +1,4 @@ -import { SquarePen } from 'lucide-react'; +import { SquarePenIcon } from 'lucide-react'; import { useTranslations } from 'next-intl'; import { unstable_setRequestLocale } from 'next-intl/server'; @@ -23,7 +23,7 @@ export default function NewsHeaderLayout({

{t('title')}

diff --git a/src/app/[locale]/(default)/storage/layout.tsx b/src/app/[locale]/(default)/storage/layout.tsx index c941e8f..0a81eae 100644 --- a/src/app/[locale]/(default)/storage/layout.tsx +++ b/src/app/[locale]/(default)/storage/layout.tsx @@ -1,14 +1,12 @@ -import { useTranslations } from 'next-intl'; - import { Button } from '@/components/ui/Button'; - import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '@/components/ui/Tooltip'; -import { ShoppingCart } from 'lucide-react'; +import { ShoppingCartIcon } from 'lucide-react'; +import { useTranslations } from 'next-intl'; export default function StorageLayout({ children, @@ -25,7 +23,7 @@ export default function StorageLayout({ diff --git a/src/app/[locale]/(default)/storage/page.tsx b/src/app/[locale]/(default)/storage/page.tsx index 26128df..a8bcba8 100644 --- a/src/app/[locale]/(default)/storage/page.tsx +++ b/src/app/[locale]/(default)/storage/page.tsx @@ -1,14 +1,7 @@ -import { items } from '@/mock-data/items'; -import { useTranslations } from 'next-intl'; -import { getTranslations, unstable_setRequestLocale } from 'next-intl/server'; -import Image from 'next/image'; -import { createSearchParamsCache, parseAsInteger } from 'nuqs/parsers'; - import { PaginationCarousel } from '@/components/layout/PaginationCarousel'; import { Button } from '@/components/ui/Button'; import { Card, - CardContent, CardDescription, CardFooter, CardHeader, @@ -23,6 +16,11 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/Select'; +import { items } from '@/mock-data/items'; +import { useTranslations } from 'next-intl'; +import { getTranslations, unstable_setRequestLocale } from 'next-intl/server'; +import Image from 'next/image'; +import { createSearchParamsCache, parseAsInteger } from 'nuqs/parsers'; export async function generateMetadata({ params: { locale }, diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 0b30d93..fdfa2c5 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -1,10 +1,8 @@ -import { getTranslations, unstable_setRequestLocale } from 'next-intl/server'; -import { Inter, Montserrat } from 'next/font/google'; - +import { RootProviders } from '@/components/providers/RootProviders'; import { routing } from '@/lib/locale'; import { cx } from '@/lib/utils'; - -import { RootProviders } from '@/components/providers/RootProviders'; +import { getTranslations, unstable_setRequestLocale } from 'next-intl/server'; +import { Inter, Montserrat } from 'next/font/google'; type LocaleLayoutProps = { children: React.ReactNode; diff --git a/src/components/assets/icons/Facebook.tsx b/src/components/assets/icons/FacebookIcon.tsx similarity index 81% rename from src/components/assets/icons/Facebook.tsx rename to src/components/assets/icons/FacebookIcon.tsx index dd534e3..5b7aa7b 100644 --- a/src/components/assets/icons/Facebook.tsx +++ b/src/components/assets/icons/FacebookIcon.tsx @@ -1,4 +1,4 @@ -function Facebook({ className, ...rest }: { className?: string }) { +function FacebookIcon({ className, ...rest }: { className?: string }) { return ( - + @@ -67,7 +66,7 @@ function Footer() { target='_blank' rel='noopener noreferrer' > - + @@ -80,7 +79,7 @@ function Footer() { target='_blank' rel='noopener noreferrer' > - + @@ -93,7 +92,7 @@ function Footer() { target='_blank' rel='noopener noreferrer' > - + @@ -106,7 +105,7 @@ function Footer() { target='_blank' rel='noopener noreferrer' > - + @@ -130,7 +129,7 @@ function Footer() { {t('signIn')}
- {t('haveYouFoundA')} ? + {t('haveYouFoundA')} ?
{t.rich('utilitiesDescription', { code: (children) => ( @@ -142,7 +141,7 @@ function Footer() { href='mailto:hackerspace-dev@idi.ntnu.no' aria-label={t('sendAnEmail')} > - + ), diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index adb8fec..c81eba8 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -1,11 +1,10 @@ -import { useTranslations } from 'next-intl'; - import { LogoLink } from '@/components/layout/LogoLink'; import { MobileSheet } from '@/components/layout/MobileSheet'; import { Nav } from '@/components/layout/Nav'; import { DarkModeMenu } from '@/components/settings/DarkModeMenu'; import { LocaleMenu } from '@/components/settings/LocaleMenu'; import { ProfileMenu } from '@/components/settings/ProfileMenu'; +import { useTranslations } from 'next-intl'; function Header() { const t = useTranslations('layout'); diff --git a/src/components/layout/LogoLink.tsx b/src/components/layout/LogoLink.tsx index 0448fd9..23018e8 100644 --- a/src/components/layout/LogoLink.tsx +++ b/src/components/layout/LogoLink.tsx @@ -1,10 +1,8 @@ -import { useTranslations } from 'next-intl'; - -import { Link } from '@/lib/locale/navigation'; -import { cx } from '@/lib/utils'; - import { Logo } from '@/components/assets/Logo'; import { Button } from '@/components/ui/Button'; +import { Link } from '@/lib/locale/navigation'; +import { cx } from '@/lib/utils'; +import { useTranslations } from 'next-intl'; function LogoLink({ className, diff --git a/src/components/layout/MobileSheet.tsx b/src/components/layout/MobileSheet.tsx index 2f0dc3c..12eddbd 100644 --- a/src/components/layout/MobileSheet.tsx +++ b/src/components/layout/MobileSheet.tsx @@ -1,8 +1,5 @@ 'use client'; -import { Menu } from 'lucide-react'; -import * as React from 'react'; - import { LogoLink } from '@/components/layout/LogoLink'; import { Nav } from '@/components/layout/Nav'; import { Button } from '@/components/ui/Button'; @@ -13,6 +10,8 @@ import { SheetTitle, SheetTrigger, } from '@/components/ui/Sheet'; +import { MenuIcon } from 'lucide-react'; +import * as React from 'react'; type MobileSheetProps = { className?: string; @@ -36,7 +35,7 @@ function MobileSheet({ className, t }: MobileSheetProps) { size='icon' aria-label={t.navigationMenu} > - + diff --git a/src/components/layout/Nav.tsx b/src/components/layout/Nav.tsx index 568373b..3164282 100644 --- a/src/components/layout/Nav.tsx +++ b/src/components/layout/Nav.tsx @@ -1,6 +1,5 @@ -import { Link } from '@/lib/locale/navigation'; - import { Button } from '@/components/ui/Button'; +import { Link } from '@/lib/locale/navigation'; type NavProps = { className?: string; diff --git a/src/components/layout/PaginationCarousel.tsx b/src/components/layout/PaginationCarousel.tsx index 7ff6326..bdc222c 100644 --- a/src/components/layout/PaginationCarousel.tsx +++ b/src/components/layout/PaginationCarousel.tsx @@ -1,9 +1,5 @@ 'use client'; -import { parseAsInteger, useQueryState } from 'nuqs'; - -import { cx } from '@/lib/utils'; - import { Pagination, PaginationContent, @@ -13,7 +9,8 @@ import { PaginationNext, PaginationPrevious, } from '@/components/ui/Pagination'; - +import { cx } from '@/lib/utils'; +import { parseAsInteger, useQueryState } from 'nuqs'; type PaginationCarouselProps = { className?: string; totalPages: number; diff --git a/src/components/layout/PaginationCarouselSkeleton.tsx b/src/components/layout/PaginationCarouselSkeleton.tsx index 3021e21..9e55181 100644 --- a/src/components/layout/PaginationCarouselSkeleton.tsx +++ b/src/components/layout/PaginationCarouselSkeleton.tsx @@ -1,5 +1,3 @@ -import { useTranslations } from 'next-intl'; - import { Pagination, PaginationContent, @@ -8,7 +6,7 @@ import { PaginationNext, PaginationPrevious, } from '@/components/ui/Pagination'; - +import { useTranslations } from 'next-intl'; type PaginationCarouselSkeletonProps = { className?: string; }; diff --git a/src/components/news/ArticleCard.tsx b/src/components/news/ArticleCard.tsx index 957b646..041f2ee 100644 --- a/src/components/news/ArticleCard.tsx +++ b/src/components/news/ArticleCard.tsx @@ -1,8 +1,3 @@ -import Image from 'next/image'; - -import { Link } from '@/lib/locale/navigation'; -import { cx } from '@/lib/utils'; - import { InternalBadge } from '@/components/news/InternalBadge'; import { Button } from '@/components/ui/Button'; import { @@ -11,7 +6,9 @@ import { CardHeader, CardTitle, } from '@/components/ui/Card'; - +import { Link } from '@/lib/locale/navigation'; +import { cx } from '@/lib/utils'; +import Image from 'next/image'; type ArticleCardProps = { className?: string; id: number; diff --git a/src/components/news/ArticleItem.tsx b/src/components/news/ArticleItem.tsx index 314fcdc..b1c774a 100644 --- a/src/components/news/ArticleItem.tsx +++ b/src/components/news/ArticleItem.tsx @@ -1,10 +1,8 @@ -import Image from 'next/image'; - -import { Link } from '@/lib/locale/navigation'; -import { cx } from '@/lib/utils'; - import { InternalBadge } from '@/components/news/InternalBadge'; import { Button } from '@/components/ui/Button'; +import { Link } from '@/lib/locale/navigation'; +import { cx } from '@/lib/utils'; +import Image from 'next/image'; type ArticleItemProps = { className?: string; diff --git a/src/components/news/CardGrid.tsx b/src/components/news/CardGrid.tsx index dc4b9db..46156cd 100644 --- a/src/components/news/CardGrid.tsx +++ b/src/components/news/CardGrid.tsx @@ -1,7 +1,5 @@ -import { cx } from '@/lib/utils'; - import { ArticleCard } from '@/components/news/ArticleCard'; - +import { cx } from '@/lib/utils'; type CardGridProps = { topArticles: { id: number; diff --git a/src/components/news/CardGridSkeleton.tsx b/src/components/news/CardGridSkeleton.tsx index 699fe5a..09a1392 100644 --- a/src/components/news/CardGridSkeleton.tsx +++ b/src/components/news/CardGridSkeleton.tsx @@ -1,8 +1,6 @@ -import * as React from 'react'; - -import { cx } from '@/lib/utils'; - import { Skeleton } from '@/components/ui/Skeleton'; +import { cx } from '@/lib/utils'; +import * as React from 'react'; function CardGridSkeleton() { return ( diff --git a/src/components/news/InternalBadge.tsx b/src/components/news/InternalBadge.tsx index 1eac30b..e2f0932 100644 --- a/src/components/news/InternalBadge.tsx +++ b/src/components/news/InternalBadge.tsx @@ -1,8 +1,3 @@ -import { ShieldAlert } from 'lucide-react'; -import { useTranslations } from 'next-intl'; - -import { cx } from '@/lib/utils'; - import { Button } from '@/components/ui/Button'; import { Tooltip, @@ -10,6 +5,9 @@ import { TooltipProvider, TooltipTrigger, } from '@/components/ui/Tooltip'; +import { cx } from '@/lib/utils'; +import { ShieldAlertIcon } from 'lucide-react'; +import { useTranslations } from 'next-intl'; type InternalBadgeProps = { className?: string; @@ -33,7 +31,7 @@ function InternalBadge({ className, internal }: InternalBadgeProps) { size='xs-icon' > - + diff --git a/src/components/news/ItemGrid.tsx b/src/components/news/ItemGrid.tsx index 2a8a359..6a610f6 100644 --- a/src/components/news/ItemGrid.tsx +++ b/src/components/news/ItemGrid.tsx @@ -1,6 +1,5 @@ -import { articleMockData as articleData } from '@/mock-data/article'; - import { ArticleItem } from '@/components/news/ArticleItem'; +import { articleMockData as articleData } from '@/mock-data/article'; type ItemGridProps = { page: number; diff --git a/src/components/news/ItemGridSkeleton.tsx b/src/components/news/ItemGridSkeleton.tsx index 03567e1..51f4699 100644 --- a/src/components/news/ItemGridSkeleton.tsx +++ b/src/components/news/ItemGridSkeleton.tsx @@ -1,6 +1,5 @@ -import * as React from 'react'; - import { ArticleItemSkeleton } from '@/components/news/ArticleItemSkeleton'; +import * as React from 'react'; function ItemGridSkeleton() { return ( diff --git a/src/components/profile/AvatarIcon.tsx b/src/components/profile/AvatarIcon.tsx index 8925682..03a8e0c 100644 --- a/src/components/profile/AvatarIcon.tsx +++ b/src/components/profile/AvatarIcon.tsx @@ -1,6 +1,5 @@ -import Image from 'next/image'; - import { Avatar, AvatarFallback } from '@/components/ui/Avatar'; +import Image from 'next/image'; type AvatarIconProps = { className?: string; diff --git a/src/components/settings/DarkModeMenu.tsx b/src/components/settings/DarkModeMenu.tsx index ba1212b..09ea3ed 100644 --- a/src/components/settings/DarkModeMenu.tsx +++ b/src/components/settings/DarkModeMenu.tsx @@ -1,9 +1,5 @@ 'use client'; -import { Moon, Sun } from 'lucide-react'; -import { useTheme } from 'next-themes'; -import * as React from 'react'; - import { Button } from '@/components/ui/Button'; import { DropdownMenu, @@ -11,6 +7,9 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/DropdownMenu'; +import { MoonIcon, SunIcon } from 'lucide-react'; +import { useTheme } from 'next-themes'; +import * as React from 'react'; type DarkModeMenuProps = { t: { @@ -28,8 +27,8 @@ function DarkModeMenu({ t }: DarkModeMenuProps) { diff --git a/src/components/settings/ProfileMenu.tsx b/src/components/settings/ProfileMenu.tsx index 2a092ed..446f65e 100644 --- a/src/components/settings/ProfileMenu.tsx +++ b/src/components/settings/ProfileMenu.tsx @@ -1,8 +1,5 @@ 'use client'; -import { User } from 'lucide-react'; -import * as React from 'react'; - import { Button } from '@/components/ui/Button'; import { DropdownMenu, @@ -10,6 +7,8 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/DropdownMenu'; +import { UserIcon } from 'lucide-react'; +import * as React from 'react'; function ProfileMenu({ t }: { t: { profile: string; signIn: string } }) { // TODO: User Icon Color should only have the primary color when logged in @@ -17,7 +16,7 @@ function ProfileMenu({ t }: { t: { profile: string; signIn: string } }) { diff --git a/src/components/ui/Avatar.tsx b/src/components/ui/Avatar.tsx index 6aa1374..a9166d5 100644 --- a/src/components/ui/Avatar.tsx +++ b/src/components/ui/Avatar.tsx @@ -1,10 +1,9 @@ 'use client'; +import { cx } from '@/lib/utils'; import * as AvatarPrimitive from '@radix-ui/react-avatar'; import * as React from 'react'; -import { cx } from '@/lib/utils'; - const Avatar = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef diff --git a/src/components/ui/Badge.tsx b/src/components/ui/Badge.tsx index 1a4e2d4..c84e389 100644 --- a/src/components/ui/Badge.tsx +++ b/src/components/ui/Badge.tsx @@ -1,6 +1,5 @@ -import type * as React from 'react'; - import { type VariantProps, cva, cx } from '@/lib/utils'; +import type * as React from 'react'; const badgeVariants = cva({ base: 'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx index 46e2c08..0a34580 100644 --- a/src/components/ui/Button.tsx +++ b/src/components/ui/Button.tsx @@ -1,8 +1,7 @@ +import { type VariantProps, cva } from '@/lib/utils'; import { Slot } from '@radix-ui/react-slot'; import * as React from 'react'; -import { type VariantProps, cva } from '@/lib/utils'; - const buttonVariants = cva({ base: 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', variants: { diff --git a/src/components/ui/Card.tsx b/src/components/ui/Card.tsx index 0682009..0479465 100644 --- a/src/components/ui/Card.tsx +++ b/src/components/ui/Card.tsx @@ -1,6 +1,5 @@ -import * as React from 'react'; - import { cx } from '@/lib/utils'; +import * as React from 'react'; const Card = React.forwardRef< HTMLDivElement, diff --git a/src/components/ui/Combobox.tsx b/src/components/ui/Combobox.tsx index 9fe99fe..57a4b93 100644 --- a/src/components/ui/Combobox.tsx +++ b/src/components/ui/Combobox.tsx @@ -1,10 +1,5 @@ 'use client'; -import { Check, ChevronsUpDown } from 'lucide-react'; -import * as React from 'react'; - -import { cx } from '@/lib/utils'; - import { Button } from '@/components/ui/Button'; import { Command, @@ -19,6 +14,9 @@ import { PopoverContent, PopoverTrigger, } from '@/components/ui/Popover'; +import { cx } from '@/lib/utils'; +import { CheckIcon, ChevronsUpDownIcon } from 'lucide-react'; +import * as React from 'react'; type ComboboxProps = { choices: { @@ -53,7 +51,7 @@ function Combobox({ {value ? choices.find((choice) => choice.value === value)?.label : defaultDescription} - + @@ -71,7 +69,7 @@ function Combobox({ setOpen(false); }} > - , React.ComponentPropsWithoutRef @@ -43,7 +41,7 @@ const CommandInput = React.forwardRef< React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => (
- + {children} - + Close diff --git a/src/components/ui/DropdownMenu.tsx b/src/components/ui/DropdownMenu.tsx index de391cc..c7daf18 100644 --- a/src/components/ui/DropdownMenu.tsx +++ b/src/components/ui/DropdownMenu.tsx @@ -1,7 +1,7 @@ 'use client'; import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; -import { Check, ChevronRight, Circle } from 'lucide-react'; +import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react'; import * as React from 'react'; import { cx } from '@/lib/utils'; @@ -34,7 +34,7 @@ const DropdownMenuSubTrigger = React.forwardRef< {...props} > {children} - + )); DropdownMenuSubTrigger.displayName = @@ -107,7 +107,7 @@ const DropdownMenuCheckboxItem = React.forwardRef< > - + {children} @@ -130,7 +130,7 @@ const DropdownMenuRadioItem = React.forwardRef< > - + {children} diff --git a/src/components/ui/Pagination.tsx b/src/components/ui/Pagination.tsx index ffbcc6f..64cd132 100644 --- a/src/components/ui/Pagination.tsx +++ b/src/components/ui/Pagination.tsx @@ -1,9 +1,11 @@ -import { ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react'; -import * as React from 'react'; - -import { cx } from '@/lib/utils'; - import { type ButtonProps, buttonVariants } from '@/components/ui/Button'; +import { cx } from '@/lib/utils'; +import { + ChevronLeftIcon, + ChevronRightIcon, + MoreHorizontalIcon, +} from 'lucide-react'; +import * as React from 'react'; const Pagination = ({ className, ...props }: React.ComponentProps<'nav'>) => (