diff --git a/apps/envited.ascs.digital/app/api/auth/[...nextauth]/route.ts b/apps/envited.ascs.digital/app/api/auth/[...nextauth]/route.ts index bb0000d9..1c11a98d 100644 --- a/apps/envited.ascs.digital/app/api/auth/[...nextauth]/route.ts +++ b/apps/envited.ascs.digital/app/api/auth/[...nextauth]/route.ts @@ -1,61 +1,6 @@ import NextAuth from 'next-auth' -import type { NextAuthOptions } from 'next-auth' -import CredentialsProvider from 'next-auth/providers/credentials' -export const authOptions: NextAuthOptions = { - providers: [ - CredentialsProvider({ - // The name to display on the sign in form (e.g. 'Sign in with...') - name: 'Sign in with Your Credentials', - // The credentials is used to generate a suitable form on the sign in page. - // You can specify whatever fields you are expecting to be submitted. - // e.g. domain, username, password, 2FA token, etc. - // You can pass any HTML attribute to the tag through the object. - credentials: { - pkh: { label: 'Address', type: 'text', placeholder: 'tz...' }, - }, - async authorize(credentials) { - if (!credentials) { - return { - id: '', - pkh: '', - memberId: '', - role: '', - } - } - const { pkh } = credentials - - return { - id: '', - pkh, - memberId: '', - role: '', - } - }, - }), - ], - session: { - strategy: 'jwt', - }, - callbacks: { - async jwt({ token, user }) { - if (user) { - token.user = user - } - - return token - }, - async session({ session, token }: { session: any; token: any }) { - session.user.pkh = token.user.pkh - session.user.role = token.user.role - session.user.memberId = token.user.memberId - session.user.id = token.sub - session.user.email = undefined - session.user.image = undefined - return session - }, - }, -} +import { authOptions } from '../../../../common/auth/auth' const handler = NextAuth(authOptions) diff --git a/apps/envited.ascs.digital/app/dashboard/page.tsx b/apps/envited.ascs.digital/app/dashboard/page.tsx index a4762e88..da6665f9 100644 --- a/apps/envited.ascs.digital/app/dashboard/page.tsx +++ b/apps/envited.ascs.digital/app/dashboard/page.tsx @@ -1,13 +1,21 @@ -import { getServerSession } from '../../common/session' +import { getServerSession } from '../../common/auth' +import { Dashboard } from '../../modules/Dashboard' import { Header } from '../../modules/Header' export default async function Index() { const session = await getServerSession() + return ( <>
-
{session ? JSON.stringify(session) : 'No session'}
+
+ {session ? ( + + ) : ( +
Not logged in
+ )} +
) diff --git a/apps/envited.ascs.digital/common/auth/auth.test.ts b/apps/envited.ascs.digital/common/auth/auth.test.ts new file mode 100644 index 00000000..f6deaaa7 --- /dev/null +++ b/apps/envited.ascs.digital/common/auth/auth.test.ts @@ -0,0 +1,19 @@ +import { _signIn } from './auth' + +describe('common/auth/auth', () => { + describe('signIn', () => { + it('should call the sign in method with the expected parameters', async () => { + // when ... we want to sign a user in + // then ... it should call the sign in method with the expected parameters + const NASignIn = jest.fn().mockResolvedValue('SIGNED_IN') + const pkh = 'PKH' + + const session = await _signIn(NASignIn)({ pkh }) + expect(NASignIn).toHaveBeenCalledWith('credentials', { + pkh, + callbackUrl: '/dashboard', + }) + expect(session).toEqual('SIGNED_IN') + }) + }) +}) diff --git a/apps/envited.ascs.digital/common/auth/auth.ts b/apps/envited.ascs.digital/common/auth/auth.ts new file mode 100644 index 00000000..22bea3dc --- /dev/null +++ b/apps/envited.ascs.digital/common/auth/auth.ts @@ -0,0 +1,86 @@ +import type { NextAuthOptions } from 'next-auth' +import CredentialsProvider from 'next-auth/providers/credentials' +import { signIn as NASignIn, signOut as NASignOut } from 'next-auth/react' +import { match } from 'ts-pattern' + +import { Role } from '../types' + +export const authOptions: NextAuthOptions = { + pages: { + error: '/', + signIn: '/', + }, + providers: [ + CredentialsProvider({ + // The name to display on the sign in form (e.g. 'Sign in with...') + name: 'Sign in with Your Credentials', + // The credentials is used to generate a suitable form on the sign in page. + // You can specify whatever fields you are expecting to be submitted. + // e.g. domain, username, password, 2FA token, etc. + // You can pass any HTML attribute to the tag through the object. + credentials: { + pkh: { label: 'Address', type: 'text', placeholder: 'tz...' }, + }, + + async authorize(credentials) { + if (!credentials) { + return { + id: '', + pkh: '', + role: '', + } + } + const { pkh } = credentials + + return match(pkh) + .with('tz1USER', () => ({ + id: '1', + pkh: 'tz1USER', + role: Role.user, + })) + .with('tz1PRINCIPAL', () => ({ + id: '1', + pkh: 'tz1PRINCIPAL', + role: Role.principal, + })) + .with('tz1NO_USER', () => null) + .otherwise(() => null) + }, + }), + ], + session: { + strategy: 'jwt', + }, + callbacks: { + async jwt({ token, user }) { + if (user) { + token.user = user + } + + return token + }, + async session({ session, token }: { session: any; token: any }) { + session.user.pkh = token.user.pkh + session.user.role = token.user.role + session.user.id = token.sub + session.user.email = undefined + session.user.image = undefined + return session + }, + }, +} + +export const _signIn = + (NASignIn: any) => + ({ pkh }: { pkh: string }) => + NASignIn('credentials', { + pkh, + callbackUrl: '/dashboard', + }) + +export const signIn = _signIn(NASignIn) + +export const signOut = () => + NASignOut({ + callbackUrl: '/', + }) diff --git a/apps/envited.ascs.digital/common/session/index.ts b/apps/envited.ascs.digital/common/auth/index.ts similarity index 52% rename from apps/envited.ascs.digital/common/session/index.ts rename to apps/envited.ascs.digital/common/auth/index.ts index 205e1a2b..9598a7e0 100644 --- a/apps/envited.ascs.digital/common/session/index.ts +++ b/apps/envited.ascs.digital/common/auth/index.ts @@ -1 +1,2 @@ export { getServerSession } from './session' +export { signIn, signOut } from './auth' diff --git a/apps/envited.ascs.digital/common/session/session.test.ts b/apps/envited.ascs.digital/common/auth/session.test.ts similarity index 82% rename from apps/envited.ascs.digital/common/session/session.test.ts rename to apps/envited.ascs.digital/common/auth/session.test.ts index 0b683c6a..237fef36 100644 --- a/apps/envited.ascs.digital/common/session/session.test.ts +++ b/apps/envited.ascs.digital/common/auth/session.test.ts @@ -1,8 +1,8 @@ import { _getServerSession } from './session' -describe('common/session', () => { +describe('common/auth/session', () => { describe('getServerSession', () => { - it('should should fetch a server session with the correct parameters', async () => { + it('should fetch a server session with the correct parameters', async () => { // when ... we want to get the current session server side // then ... it should call the getServerSession function with the correct parameters const authOptions = 'AUTH_OPTIONS' as any diff --git a/apps/envited.ascs.digital/common/session/session.ts b/apps/envited.ascs.digital/common/auth/session.ts similarity index 81% rename from apps/envited.ascs.digital/common/session/session.ts rename to apps/envited.ascs.digital/common/auth/session.ts index 0bc18cb9..870850e7 100644 --- a/apps/envited.ascs.digital/common/session/session.ts +++ b/apps/envited.ascs.digital/common/auth/session.ts @@ -1,6 +1,6 @@ import { getServerSession as NAGetServerSession, NextAuthOptions } from 'next-auth' -import { authOptions } from '../../app/api/auth/[...nextauth]/route' +import { authOptions } from './auth' export const _getServerSession = (NAGetServerSession: any) => (authOptions: NextAuthOptions) => () => NAGetServerSession(authOptions) diff --git a/apps/envited.ascs.digital/common/database/database.test.ts b/apps/envited.ascs.digital/common/database/database.test.ts index dca5883e..a7f6e53f 100644 --- a/apps/envited.ascs.digital/common/database/database.test.ts +++ b/apps/envited.ascs.digital/common/database/database.test.ts @@ -5,7 +5,7 @@ describe('common/database', () => { it('should should setup a connection with a local database', async () => { // when ... we want to make a connection with a local db // then ... we should get a connection as expected - process.env.ENV = 'local' + process.env.ENV = 'development' process.env.POSTGRES_DATABASE_NAME = 'DB_NAME' process.env.POSTGRES_DATABASE_USER = 'DB_USER' process.env.POSTGRES_DATABASE_PASSWORD = 'DB_PASSWORD' diff --git a/apps/envited.ascs.digital/common/types/index.ts b/apps/envited.ascs.digital/common/types/index.ts index 3a82a366..71fb5fcc 100644 --- a/apps/envited.ascs.digital/common/types/index.ts +++ b/apps/envited.ascs.digital/common/types/index.ts @@ -1,10 +1,2 @@ -export { - Language, - Columns, - Size, - ColorScheme, -} from './types' -export type { - Action, - Obj, -} from './types' +export { Language, Columns, Size, ColorScheme, Role } from './types' +export type { Action, Obj } from './types' diff --git a/apps/envited.ascs.digital/modules/Dashboard/Dashboard.tsx b/apps/envited.ascs.digital/modules/Dashboard/Dashboard.tsx new file mode 100644 index 00000000..d3a175ce --- /dev/null +++ b/apps/envited.ascs.digital/modules/Dashboard/Dashboard.tsx @@ -0,0 +1,30 @@ +'use client' + +import { Button } from '@envited-marketplace/design-system' +import React, { FC } from 'react' + +import { signOut } from '../../common/auth' +import { Role } from '../../common/types' + +interface DashboardProps { + id: string + address: string + role: Role +} + +export const Dashboard: FC = ({ id, address, role }) => { + return ( +
+

You are logged in:

+
+
ID
+
{id}
+
Address
+
{address}
+
Role
+
{role}
+
+ +
+ ) +} diff --git a/apps/envited.ascs.digital/modules/Dashboard/index.ts b/apps/envited.ascs.digital/modules/Dashboard/index.ts new file mode 100644 index 00000000..5e2f6260 --- /dev/null +++ b/apps/envited.ascs.digital/modules/Dashboard/index.ts @@ -0,0 +1 @@ +export { Dashboard } from './Dashboard' diff --git a/apps/envited.ascs.digital/modules/Header/Header.test.tsx b/apps/envited.ascs.digital/modules/Header/Header.test.tsx index bee5833f..0ec2bfb2 100644 --- a/apps/envited.ascs.digital/modules/Header/Header.test.tsx +++ b/apps/envited.ascs.digital/modules/Header/Header.test.tsx @@ -1,10 +1,13 @@ +import '@testing-library/jest-dom' import { render } from '@testing-library/react' import { Header } from './Header' describe('Header', () => { it('should render successfully', () => { - const { baseElement } = render(
) - expect(baseElement).toBeTruthy() + // when ... rendering component + // then ... should render as expected + const { getByText } = render(
) + expect(getByText('Connect')).toBeInTheDocument() }) }) diff --git a/apps/envited.ascs.digital/modules/HeroHeader/HeroHeader.tsx b/apps/envited.ascs.digital/modules/HeroHeader/HeroHeader.tsx index 67bd9c6b..0f07278d 100644 --- a/apps/envited.ascs.digital/modules/HeroHeader/HeroHeader.tsx +++ b/apps/envited.ascs.digital/modules/HeroHeader/HeroHeader.tsx @@ -1,42 +1,37 @@ 'use client' -import { Button, Card, Grid, GridRow, Heading, HeadingWithTooltip, Tooltip } from '@envited-marketplace/design-system' +import { Button, Grid, GridRow } from '@envited-marketplace/design-system' +import { useSearchParams } from 'next/navigation' import React, { FC } from 'react' +import { signIn } from '../../common/auth' import { useTranslation } from '../../common/i18n' export const HeroHeader: FC = () => { const { t } = useTranslation('HeroHeader') + const searchParams = useSearchParams() return ( -
+
+ {searchParams.has('error') ? ( +
Could not sign you in
+ ) : null} - - - Hello - Hello} tooltip={Test} /> - - - Hello - Hello} tooltip={Test} /> - - - Hello - Hello} tooltip={Test} /> - - - Hello - Hello} tooltip={Test} /> - + + + +
-
- -
+

{t('[Heading] why')}

{t('[Description] why')}

diff --git a/apps/envited.ascs.digital/modules/HeroHeader/HeroHeader.ui.test.tsx b/apps/envited.ascs.digital/modules/HeroHeader/HeroHeader.ui.test.tsx index 020a5333..a82b69ac 100644 --- a/apps/envited.ascs.digital/modules/HeroHeader/HeroHeader.ui.test.tsx +++ b/apps/envited.ascs.digital/modules/HeroHeader/HeroHeader.ui.test.tsx @@ -16,6 +16,12 @@ jest.mock('next/navigation', () => ({ }, })) +jest.mock('next/navigation', () => ({ + useSearchParams: () => ({ + has: jest.fn(), + }), +})) + describe('modules/HeroHeader', () => { describe('render', () => { it('should return as expected', async () => { diff --git a/apps/envited.ascs.digital/modules/ThemeToggle/ThemeToggle.ui.test.tsx b/apps/envited.ascs.digital/modules/ThemeToggle/ThemeToggle.ui.test.tsx index f59d6b14..1b42d5f5 100644 --- a/apps/envited.ascs.digital/modules/ThemeToggle/ThemeToggle.ui.test.tsx +++ b/apps/envited.ascs.digital/modules/ThemeToggle/ThemeToggle.ui.test.tsx @@ -1,5 +1,6 @@ +import '@testing-library/jest-dom' +import { render } from '@testing-library/react' import React from 'react' -import TestRenderer from 'react-test-renderer' import ThemeToggle from './ThemeToggle' @@ -7,11 +8,10 @@ describe('modules/ThemeToggle', () => { describe('render', () => { it('should render as expected', async () => { // when ... rendering component - const component = TestRenderer.create() - // then ... should render with expected element type - const tree = component.toJSON() as any - expect(tree.children[0].type).toEqual('svg') + + const { getByRole } = render() + expect(getByRole('button')).toBeInTheDocument() }) }) }) diff --git a/package-lock.json b/package-lock.json index c1aab2c2..438d6035 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,6 +61,7 @@ "@types/ramda": "^0.29.9", "@types/react": "18.2.33", "@types/react-dom": "18.2.14", + "@types/react-test-renderer": "^18.0.7", "@typescript-eslint/eslint-plugin": "^6.13.2", "@typescript-eslint/parser": "^6.13.2", "autoprefixer": "^10.4.16", @@ -15406,6 +15407,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-test-renderer": { + "version": "18.0.7", + "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-18.0.7.tgz", + "integrity": "sha512-1+ANPOWc6rB3IkSnElhjv6VLlKg2dSv/OWClUyZimbLsQyBn8Js9Vtdsi3UICJ2rIQ3k2la06dkB+C92QfhKmg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", diff --git a/package.json b/package.json index f6f1adf0..e5b7bf5b 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "@types/ramda": "^0.29.9", "@types/react": "18.2.33", "@types/react-dom": "18.2.14", + "@types/react-test-renderer": "^18.0.7", "@typescript-eslint/eslint-plugin": "^6.13.2", "@typescript-eslint/parser": "^6.13.2", "autoprefixer": "^10.4.16",