diff --git a/.eslintrc.cjs b/.eslintrc.cjs index ab220d1..eb4eaed 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,43 +1,42 @@ /** @type {import("eslint").Linter.Config} */ const config = { - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": true + parser: "@typescript-eslint/parser", + ignorePatterns: ["prettier.config.mjs"], + parserOptions: { + project: true, }, - "plugins": [ - "@typescript-eslint" - ], - "extends": [ + plugins: ["@typescript-eslint"], + extends: [ "next/core-web-vitals", "plugin:@typescript-eslint/recommended-type-checked", "plugin:@typescript-eslint/stylistic-type-checked", - "plugin:storybook/recommended" + "plugin:storybook/recommended", ], - "rules": { + rules: { "@typescript-eslint/array-type": "off", "@typescript-eslint/consistent-type-definitions": "off", "@typescript-eslint/consistent-type-imports": [ "warn", { - "prefer": "type-imports", - "fixStyle": "inline-type-imports" - } + prefer: "type-imports", + fixStyle: "inline-type-imports", + }, ], "@typescript-eslint/no-unused-vars": [ "warn", { - "argsIgnorePattern": "^_" - } + argsIgnorePattern: "^_", + }, ], "@typescript-eslint/require-await": "off", "@typescript-eslint/no-misused-promises": [ "error", { - "checksVoidReturn": { - "attributes": false - } - } - ] - } -} -module.exports = config; \ No newline at end of file + checksVoidReturn: { + attributes: false, + }, + }, + ], + }, +}; +module.exports = config; diff --git a/package.json b/package.json index cfcbd42..bbc452d 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-slot": "^1.0.2", "@sentry/nextjs": "^7.105.0", "@t3-oss/env-nextjs": "^0.9.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 193152a..59928ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ dependencies: '@radix-ui/react-dropdown-menu': specifier: ^2.0.6 version: 2.0.6(@types/react-dom@18.2.22)(@types/react@18.2.69)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-label': + specifier: ^2.0.2 + version: 2.0.2(@types/react-dom@18.2.22)(@types/react@18.2.69)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-slot': specifier: ^1.0.2 version: 1.0.2(@types/react@18.2.69)(react@18.2.0) @@ -34,16 +37,16 @@ dependencies: version: 5.28.6(react@18.2.0) '@trpc/client': specifier: next - version: 11.0.0-next.322(@trpc/server@11.0.0-next.322) + version: 11.0.0-next.324(@trpc/server@11.0.0-next.324) '@trpc/next': specifier: next - version: 11.0.0-next.322(@tanstack/react-query@5.28.6)(@trpc/client@11.0.0-next.322)(@trpc/react-query@11.0.0-next.322)(@trpc/server@11.0.0-next.322)(next@14.1.4)(react-dom@18.2.0)(react@18.2.0) + version: 11.0.0-next.324(@tanstack/react-query@5.28.6)(@trpc/client@11.0.0-next.324)(@trpc/react-query@11.0.0-next.324)(@trpc/server@11.0.0-next.324)(next@14.1.4)(react-dom@18.2.0)(react@18.2.0) '@trpc/react-query': specifier: next - version: 11.0.0-next.322(@tanstack/react-query@5.28.6)(@trpc/client@11.0.0-next.322)(@trpc/server@11.0.0-next.322)(react-dom@18.2.0)(react@18.2.0) + version: 11.0.0-next.324(@tanstack/react-query@5.28.6)(@trpc/client@11.0.0-next.324)(@trpc/server@11.0.0-next.324)(react-dom@18.2.0)(react@18.2.0) '@trpc/server': specifier: next - version: 11.0.0-next.322 + version: 11.0.0-next.324 class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -2592,6 +2595,27 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-label@2.0.2(@types/react-dom@18.2.22)(@types/react@18.2.69)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-N5ehvlM7qoTLx7nWPodsPYPgMzA5WM8zZChQg8nyFJKnDO5WHdba1vv5/H6IO5LtJMfD2Q3wh1qHFGNtK0w3bQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.22)(@types/react@18.2.69)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.2.69 + '@types/react-dom': 18.2.22 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-menu@2.0.6(@types/react-dom@18.2.22)(@types/react@18.2.69)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA==} peerDependencies: @@ -4067,21 +4091,21 @@ packages: '@testing-library/dom': 9.3.4 dev: true - /@trpc/client@11.0.0-next.322(@trpc/server@11.0.0-next.322): - resolution: {integrity: sha512-3Ao8lZSOGR5OFlECfBddgLap1TcwFvq46JgxP/ZNaxOoTHrUn+MBI2AGRbXFeUcgcodBGYDr/LUQgpPWNvxbCg==} + /@trpc/client@11.0.0-next.324(@trpc/server@11.0.0-next.324): + resolution: {integrity: sha512-jvB764wVe+ngNVLZiLeCHKliDwouGdU6c38tAhYrZxd4L0H+tmuhwoBM5lBnf1EvmOroNWtel7SZSVrlBHkwiA==} peerDependencies: - '@trpc/server': 11.0.0-next.322+13be792bf + '@trpc/server': 11.0.0-next.324+d8186919f dependencies: - '@trpc/server': 11.0.0-next.322 + '@trpc/server': 11.0.0-next.324 dev: false - /@trpc/next@11.0.0-next.322(@tanstack/react-query@5.28.6)(@trpc/client@11.0.0-next.322)(@trpc/react-query@11.0.0-next.322)(@trpc/server@11.0.0-next.322)(next@14.1.4)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-ONqZF4a9R0cQ9YPG7kGr29nqaJQR8HMOStshmHnZGp8OagdqT/NwGmSN3f4UIhaqS5XXwf0wM6p/NJowHr9yBg==} + /@trpc/next@11.0.0-next.324(@tanstack/react-query@5.28.6)(@trpc/client@11.0.0-next.324)(@trpc/react-query@11.0.0-next.324)(@trpc/server@11.0.0-next.324)(next@14.1.4)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-k8nNmjezKX1fz4W4hwhkscdHhnsgGX+lEyFKgKwpX5BZ9N9bIKhQ13+0KCzN38f4+24D68F3d3++9QAQUNXN9w==} peerDependencies: '@tanstack/react-query': ^5.25.0 - '@trpc/client': 11.0.0-next.322+13be792bf - '@trpc/react-query': 11.0.0-next.322+13be792bf - '@trpc/server': 11.0.0-next.322+13be792bf + '@trpc/client': 11.0.0-next.324+d8186919f + '@trpc/react-query': 11.0.0-next.324+d8186919f + '@trpc/server': 11.0.0-next.324+d8186919f next: '*' react: '>=16.8.0' react-dom: '>=16.8.0' @@ -4092,32 +4116,32 @@ packages: optional: true dependencies: '@tanstack/react-query': 5.28.6(react@18.2.0) - '@trpc/client': 11.0.0-next.322(@trpc/server@11.0.0-next.322) - '@trpc/react-query': 11.0.0-next.322(@tanstack/react-query@5.28.6)(@trpc/client@11.0.0-next.322)(@trpc/server@11.0.0-next.322)(react-dom@18.2.0)(react@18.2.0) - '@trpc/server': 11.0.0-next.322 + '@trpc/client': 11.0.0-next.324(@trpc/server@11.0.0-next.324) + '@trpc/react-query': 11.0.0-next.324(@tanstack/react-query@5.28.6)(@trpc/client@11.0.0-next.324)(@trpc/server@11.0.0-next.324)(react-dom@18.2.0)(react@18.2.0) + '@trpc/server': 11.0.0-next.324 next: 14.1.4(@babel/core@7.24.3)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false - /@trpc/react-query@11.0.0-next.322(@tanstack/react-query@5.28.6)(@trpc/client@11.0.0-next.322)(@trpc/server@11.0.0-next.322)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-jLVuehJa9KMdmfpXwB9QTRud03/yb2F/1ERuixvPHWL9hMDHcub8VAQltykE+Nj4gnB/ppmzinVeia5wYW93Ng==} + /@trpc/react-query@11.0.0-next.324(@tanstack/react-query@5.28.6)(@trpc/client@11.0.0-next.324)(@trpc/server@11.0.0-next.324)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-SpnsTbK+RVkv2UA1CZ4apTDAJ0OmJokR5YBtTlYKXHGjKLWud5o4AxrBvMM5jA0Rv6263NZ7GQolgBUsfH3PBg==} peerDependencies: '@tanstack/react-query': ^5.25.0 - '@trpc/client': 11.0.0-next.322+13be792bf - '@trpc/server': 11.0.0-next.322+13be792bf + '@trpc/client': 11.0.0-next.324+d8186919f + '@trpc/server': 11.0.0-next.324+d8186919f react: '>=18.2.0' react-dom: '>=18.2.0' dependencies: '@tanstack/react-query': 5.28.6(react@18.2.0) - '@trpc/client': 11.0.0-next.322(@trpc/server@11.0.0-next.322) - '@trpc/server': 11.0.0-next.322 + '@trpc/client': 11.0.0-next.324(@trpc/server@11.0.0-next.324) + '@trpc/server': 11.0.0-next.324 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) dev: false - /@trpc/server@11.0.0-next.322: - resolution: {integrity: sha512-5oQdKlVTvCtsY3eM7f0D5iliH9g+Dti9VOnuysar1aiRwA6ENQ8mK2JU1yRG111jWgLTz1dfX1x0TDeenoyw0Q==} + /@trpc/server@11.0.0-next.324: + resolution: {integrity: sha512-ccnyc8eFicM45Ej8Bmj6uFKWBH32kEVLS4Tvj+fbNyhFaAS8I3RQhnXEaj48vDOZ5qQL5vcH37dxbmLBtRUjfA==} dev: false /@tsconfig/node10@1.0.10: diff --git a/prettier.config.js b/prettier.config.mjs similarity index 100% rename from prettier.config.js rename to prettier.config.mjs diff --git a/src/app/global-error.jsx b/src/app/global-error.tsx similarity index 72% rename from src/app/global-error.jsx rename to src/app/global-error.tsx index 2e6130a..118079e 100644 --- a/src/app/global-error.jsx +++ b/src/app/global-error.tsx @@ -4,7 +4,7 @@ import * as Sentry from "@sentry/nextjs"; import Error from "next/error"; import { useEffect } from "react"; -export default function GlobalError({ error }) { +export default function GlobalError({ error }: { error: Error }) { useEffect(() => { Sentry.captureException(error); }, [error]); @@ -12,7 +12,7 @@ export default function GlobalError({ error }) { return ( - + ); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 6a8bc93..78a7a25 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,8 +1,11 @@ +'use client' + import "~/styles/globals.css"; import { Inter } from "next/font/google"; import { TRPCReactProvider } from "~/trpc/react"; +import { SessionProvider } from 'next-auth/react'; import { Layout } from "~/components/patterns/layout"; const inter = Inter({ @@ -10,11 +13,11 @@ const inter = Inter({ variable: "--font-sans", }); -export const metadata = { - title: "Create T3 App", - description: "Generated by create-t3-app", - icons: [{ rel: "icon", url: "/favicon.ico" }], -}; +// export const metadata = { +// title: "Create T3 App", +// description: "Generated by create-t3-app", +// icons: [{ rel: "icon", url: "/favicon.ico" }], +// }; export default function RootLayout({ children, @@ -25,7 +28,9 @@ export default function RootLayout({ + {children} + diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx new file mode 100644 index 0000000..454fc8b --- /dev/null +++ b/src/app/login/page.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { LoginScreen } from '~/components/patterns/login-screen'; + +const LoginPage = () => { + return ( +
+ +
+ ); +}; + +export default LoginPage; \ No newline at end of file diff --git a/src/components/patterns/layout.tsx b/src/components/patterns/layout.tsx index e51e36c..f07f64e 100644 --- a/src/components/patterns/layout.tsx +++ b/src/components/patterns/layout.tsx @@ -1,5 +1,5 @@ import Link from "next/link"; -import { CircleUser, Menu, Package2, Search } from "lucide-react"; +import { Menu, Package2, Search } from "lucide-react"; import { Button } from "~/components/ui/button"; @@ -13,8 +13,10 @@ import { } from "~/components/ui/dropdown-menu"; import { Input } from "~/components/ui/input"; import { Sheet, SheetContent, SheetTrigger } from "~/components/ui/sheet"; +import LoginLogoutButton from "./login-logout-button"; +import UserButton from "./user-button"; -export function Layout({ children }: { children: React.ReactNode }) { +export async function Layout({ children }: { children: React.ReactNode }) { return (
@@ -121,7 +123,7 @@ export function Layout({ children }: { children: React.ReactNode }) { @@ -130,8 +132,8 @@ export function Layout({ children }: { children: React.ReactNode }) { Settings Support + - Logout
diff --git a/src/components/patterns/login-logout-button.tsx b/src/components/patterns/login-logout-button.tsx new file mode 100644 index 0000000..1ca4d70 --- /dev/null +++ b/src/components/patterns/login-logout-button.tsx @@ -0,0 +1,24 @@ +"use client"; + +import { DropdownMenuItem } from "~/components/ui/dropdown-menu"; + +import { signOut, useSession } from "next-auth/react"; +import { useRouter } from "next/navigation"; + +const LoginLogoutButton = () => { + const session = useSession(); + const isAuthenticated = + session.status !== "loading" && session.status === "authenticated"; + + const router = useRouter(); + + return ( + (isAuthenticated ? signOut() : router.push("/login"))} + > + {isAuthenticated ? "Logout" : "Login"} + + ); +}; + +export default LoginLogoutButton; diff --git a/src/components/patterns/login-screen.tsx b/src/components/patterns/login-screen.tsx new file mode 100644 index 0000000..478444a --- /dev/null +++ b/src/components/patterns/login-screen.tsx @@ -0,0 +1,41 @@ +"use client"; + +import { signIn } from "next-auth/react"; +import Image from "next/image"; + +import { Button } from "~/components/ui/button"; + +export function LoginScreen() { + return ( +
+
+
+
+

Login

+

+ Authenticate with a common provider +

+
+
+ +
+
+
+
+ Image +
+
+ ); +} diff --git a/src/components/patterns/user-button.tsx b/src/components/patterns/user-button.tsx new file mode 100644 index 0000000..127bc0d --- /dev/null +++ b/src/components/patterns/user-button.tsx @@ -0,0 +1,25 @@ +import { CircleUser } from "lucide-react"; +import { useSession } from "next-auth/react"; +import React from "react"; +import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar"; + +const UserButton = () => { + const session = useSession(); + const isAuthenticated = + session.status !== "loading" && session.status === "authenticated"; + + return ( +
+ {isAuthenticated ? ( + + + CN + + ) : ( + + )} +
+ ); +}; + +export default UserButton; diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx new file mode 100644 index 0000000..8f40738 --- /dev/null +++ b/src/components/ui/label.tsx @@ -0,0 +1,26 @@ +"use client" + +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "~/lib/utils" + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" +) + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)) +Label.displayName = LabelPrimitive.Root.displayName + +export { Label } diff --git a/src/stories/LoginScreen.stories.ts b/src/stories/LoginScreen.stories.ts new file mode 100644 index 0000000..2527f69 --- /dev/null +++ b/src/stories/LoginScreen.stories.ts @@ -0,0 +1,18 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { LoginScreen } from "~/components/patterns/login-screen"; + +const meta = { + title: "Example/Login Screen", + component: LoginScreen, + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout + layout: "fullscreen", + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const LoginScreenStory: Story = {}; + +// More on interaction testing: https://storybook.js.org/docs/writing-tests/interaction-testin