diff --git a/.github/workflows/ci.yml b/.github/workflows/e2e.yml similarity index 56% rename from .github/workflows/ci.yml rename to .github/workflows/e2e.yml index b2e11284..94916b09 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/e2e.yml @@ -1,38 +1,10 @@ -name: LGT Jest & Playwright Tests +name: E2E Tests on: pull_request: types: [opened, synchronize, reopened] jobs: - jest-test: - runs-on: ubuntu-latest - env: - NEXT_PUBLIC_APPWRITE_API_URL: ${{ secrets.NEXT_PUBLIC_APPWRITE_API_URL }} - NEXT_PUBLIC_APPWRITE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_APPWRITE_PROJECT_ID }} - NEXT_PUBLIC_APPWRITE_DATABASE_ID: ${{ secrets.NEXT_PUBLIC_APPWRITE_DATABASE_ID }} - APPWRITE_API_KEY: ${{ secrets.APPWRITE_API_KEY }} - strategy: - matrix: - node-version: ['20.x'] - - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - with: - version: 9 - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: 'pnpm' - - - name: Install dependencies - run: pnpm install - - name: tests - run: pnpm test - test-e2e-setup: name: Setup E2E Tests runs-on: ubuntu-latest @@ -49,9 +21,9 @@ jobs: - name: Get URL run: echo "https://${{ steps.vercel_preview_url.outputs.preview_url }}" - test_e2e: + test-e2e: needs: test-e2e-setup - name: Playwright Tests + name: E2E Tests timeout-minutes: 5 runs-on: ubuntu-latest env: @@ -64,8 +36,8 @@ jobs: with: node-version: lts/* - run: npm install -g pnpm && pnpm install - - run: pnpm exec playwright install --with-deps - - run: pnpm exec playwright test + - run: pnpm exec playwright install --with-deps + - run: pnpm exec playwright test - name: Upload Artifacts uses: actions/upload-artifact@v4 if: always() diff --git a/.github/workflows/jest.yml b/.github/workflows/jest.yml new file mode 100644 index 00000000..0ba88e14 --- /dev/null +++ b/.github/workflows/jest.yml @@ -0,0 +1,34 @@ +name: Jest Unit Tests + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + jest-test: + runs-on: ubuntu-latest + env: + NEXT_PUBLIC_APPWRITE_API_URL: ${{ secrets.NEXT_PUBLIC_APPWRITE_API_URL }} + NEXT_PUBLIC_APPWRITE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_APPWRITE_PROJECT_ID }} + NEXT_PUBLIC_APPWRITE_DATABASE_ID: ${{ secrets.NEXT_PUBLIC_APPWRITE_DATABASE_ID }} + APPWRITE_API_KEY: ${{ secrets.APPWRITE_API_KEY }} + strategy: + matrix: + node-version: ['20.x'] + + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 9 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install + - name: tests + run: pnpm test diff --git a/api/apiFunctions.interface.ts b/api/apiFunctions.interface.ts index 9de608db..f384597a 100644 --- a/api/apiFunctions.interface.ts +++ b/api/apiFunctions.interface.ts @@ -1,7 +1,7 @@ // Copyright (c) Gridiron Survivor. // Licensed under the MIT License. -import { IEntry } from '@/app/league/[leagueId]/entry/Entries.interface'; +import { IEntry } from '@/app/(main)/league/[leagueId]/entry/Entries.interface'; export interface IAccountData { email: string; diff --git a/api/apiFunctions.ts b/api/apiFunctions.ts index 938ce9ec..2cd7a402 100644 --- a/api/apiFunctions.ts +++ b/api/apiFunctions.ts @@ -16,7 +16,7 @@ import { Query } from 'appwrite'; import { IEntry, IEntryProps, -} from '@/app/league/[leagueId]/entry/Entries.interface'; +} from '@/app/(main)/league/[leagueId]/entry/Entries.interface'; /** * Register a new account diff --git a/app/(admin)/admin/leagues/page.tsx b/app/(admin)/admin/leagues/page.tsx new file mode 100644 index 00000000..e2765a5e --- /dev/null +++ b/app/(admin)/admin/leagues/page.tsx @@ -0,0 +1,14 @@ +// Copyright (c) Gridiron Survivor. +// Licensed under the MIT License. + +import { JSX } from 'react'; + +/** + * Renders the admin page. + * @returns {JSX.Element} - The rendered login page. + */ +const AdminLeagues = (): JSX.Element => { + return
Admin Leagues
; +}; + +export default AdminLeagues; diff --git a/app/(admin)/admin/notifications/page.tsx b/app/(admin)/admin/notifications/page.tsx new file mode 100644 index 00000000..62c2bbe6 --- /dev/null +++ b/app/(admin)/admin/notifications/page.tsx @@ -0,0 +1,14 @@ +// Copyright (c) Gridiron Survivor. +// Licensed under the MIT License. + +import { JSX } from 'react'; + +/** + * Renders the admin page. + * @returns {JSX.Element} - The rendered login page. + */ +const AdminNotifications = (): JSX.Element => { + return
Admin Notifications
; +}; + +export default AdminNotifications; diff --git a/app/(admin)/admin/page.tsx b/app/(admin)/admin/page.tsx new file mode 100644 index 00000000..8b7420df --- /dev/null +++ b/app/(admin)/admin/page.tsx @@ -0,0 +1,14 @@ +// Copyright (c) Gridiron Survivor. +// Licensed under the MIT License. + +import { JSX } from 'react'; + +/** + * Renders the admin page. + * @returns {JSX.Element} - The rendered login page. + */ +const AdminHome = (): JSX.Element => { + return
Admin Home
; +}; + +export default AdminHome; diff --git a/app/(admin)/admin/players/page.tsx b/app/(admin)/admin/players/page.tsx new file mode 100644 index 00000000..a6e97f10 --- /dev/null +++ b/app/(admin)/admin/players/page.tsx @@ -0,0 +1,14 @@ +// Copyright (c) Gridiron Survivor. +// Licensed under the MIT License. + +import { JSX } from 'react'; + +/** + * Renders the admin page. + * @returns {JSX.Element} - The rendered login page. + */ +const AdminPlayers = (): JSX.Element => { + return
Admin Players
; +}; + +export default AdminPlayers; diff --git a/app/(admin)/layout.tsx b/app/(admin)/layout.tsx new file mode 100644 index 00000000..803fafa4 --- /dev/null +++ b/app/(admin)/layout.tsx @@ -0,0 +1,46 @@ +// Copyright (c) Gridiron Survivor. +// Licensed under the MIT License. + +import React, { JSX } from 'react'; +import { GeistSans } from 'geist/font/sans'; +import '../globals.css'; +import { AuthContextProvider } from '@/context/AuthContextProvider'; +import ErrorBoundary from '../error'; +import { Toaster } from 'react-hot-toast'; + +const defaultUrl = process.env.VERCEL_URL + ? `https://${process.env.VERCEL_URL}` + : 'http://localhost:3000'; + +export const metadata = { + metadataBase: new URL(defaultUrl), + title: 'GridIron Survivor', + description: 'Fantasy Football Survivor Pool', +}; + +/** + * The root layout for the application. + * @param props - The props + * @param props.children - The children + * @returns The rendered root layout. + */ +const RootLayout = ({ + children, +}: { + children: React.ReactNode; +}): JSX.Element => { + return ( + + + + +
{children}
+ +
+
+ + + ); +}; + +export default RootLayout; diff --git a/app/layout.tsx b/app/(main)/layout.tsx similarity index 95% rename from app/layout.tsx rename to app/(main)/layout.tsx index a655b61c..6823b012 100644 --- a/app/layout.tsx +++ b/app/(main)/layout.tsx @@ -3,10 +3,10 @@ import React, { JSX } from 'react'; import { GeistSans } from 'geist/font/sans'; -import './globals.css'; +import '../globals.css'; import Nav from '@/components/Nav/Nav'; import { AuthContextProvider } from '@/context/AuthContextProvider'; -import ErrorBoundary from './error'; +import ErrorBoundary from '../error'; import { Toaster } from 'react-hot-toast'; const defaultUrl = process.env.VERCEL_URL diff --git a/app/league/[leagueId]/entry/Entries.interface.ts b/app/(main)/league/[leagueId]/entry/Entries.interface.ts similarity index 100% rename from app/league/[leagueId]/entry/Entries.interface.ts rename to app/(main)/league/[leagueId]/entry/Entries.interface.ts diff --git a/app/league/[leagueId]/entry/[entryId]/week/Week.interface.ts b/app/(main)/league/[leagueId]/entry/[entryId]/week/Week.interface.ts similarity index 100% rename from app/league/[leagueId]/entry/[entryId]/week/Week.interface.ts rename to app/(main)/league/[leagueId]/entry/[entryId]/week/Week.interface.ts diff --git a/app/league/[leagueId]/entry/[entryId]/week/Week.tsx b/app/(main)/league/[leagueId]/entry/[entryId]/week/Week.tsx similarity index 100% rename from app/league/[leagueId]/entry/[entryId]/week/Week.tsx rename to app/(main)/league/[leagueId]/entry/[entryId]/week/Week.tsx diff --git a/app/league/[leagueId]/entry/[entryId]/week/WeekTeams.interface.ts b/app/(main)/league/[leagueId]/entry/[entryId]/week/WeekTeams.interface.ts similarity index 100% rename from app/league/[leagueId]/entry/[entryId]/week/WeekTeams.interface.ts rename to app/(main)/league/[leagueId]/entry/[entryId]/week/WeekTeams.interface.ts diff --git a/app/league/[leagueId]/entry/[entryId]/week/WeekTeams.tsx b/app/(main)/league/[leagueId]/entry/[entryId]/week/WeekTeams.tsx similarity index 100% rename from app/league/[leagueId]/entry/[entryId]/week/WeekTeams.tsx rename to app/(main)/league/[leagueId]/entry/[entryId]/week/WeekTeams.tsx diff --git a/app/league/[leagueId]/entry/[entryId]/week/[weekId]/page.tsx b/app/(main)/league/[leagueId]/entry/[entryId]/week/[weekId]/page.tsx similarity index 100% rename from app/league/[leagueId]/entry/[entryId]/week/[weekId]/page.tsx rename to app/(main)/league/[leagueId]/entry/[entryId]/week/[weekId]/page.tsx diff --git a/app/league/[leagueId]/entry/all/page.tsx b/app/(main)/league/[leagueId]/entry/all/page.tsx similarity index 100% rename from app/league/[leagueId]/entry/all/page.tsx rename to app/(main)/league/[leagueId]/entry/all/page.tsx diff --git a/app/league/all/page.test.tsx b/app/(main)/league/all/page.test.tsx similarity index 100% rename from app/league/all/page.test.tsx rename to app/(main)/league/all/page.test.tsx diff --git a/app/league/all/page.tsx b/app/(main)/league/all/page.tsx similarity index 100% rename from app/league/all/page.tsx rename to app/(main)/league/all/page.tsx diff --git a/app/login/page.spec.ts b/app/(main)/login/page.spec.ts similarity index 100% rename from app/login/page.spec.ts rename to app/(main)/login/page.spec.ts diff --git a/app/login/page.test.tsx b/app/(main)/login/page.test.tsx similarity index 97% rename from app/login/page.test.tsx rename to app/(main)/login/page.test.tsx index 6c70a485..9f69d59c 100644 --- a/app/login/page.test.tsx +++ b/app/(main)/login/page.test.tsx @@ -24,7 +24,7 @@ jest.mock('next/navigation', () => ({ }, })); -jest.mock('../../context/AuthContextProvider', () => ({ +jest.mock('../../../context/AuthContextProvider', () => ({ useAuthContext() { return { ...mockUseAuthContext, diff --git a/app/login/page.tsx b/app/(main)/login/page.tsx similarity index 99% rename from app/login/page.tsx rename to app/(main)/login/page.tsx index c776bf2a..03fc3811 100644 --- a/app/login/page.tsx +++ b/app/(main)/login/page.tsx @@ -18,7 +18,7 @@ import { FormField, FormItem, FormMessage, -} from '../../components/Form/Form'; +} from '../../../components/Form/Form'; const LoginUserSchema = z.object({ email: z diff --git a/app/page.tsx b/app/(main)/page.tsx similarity index 100% rename from app/page.tsx rename to app/(main)/page.tsx diff --git a/app/register/Register.tsx b/app/(main)/register/Register.tsx similarity index 99% rename from app/register/Register.tsx rename to app/(main)/register/Register.tsx index 7de7b6d4..76a5ca8c 100644 --- a/app/register/Register.tsx +++ b/app/(main)/register/Register.tsx @@ -21,7 +21,7 @@ import { FormField, FormItem, FormMessage, -} from '../../components/Form/Form'; +} from '../../../components/Form/Form'; import { toast } from 'react-hot-toast'; import Alert from '@/components/AlertNotification/AlertNotification'; import { AlertVariants } from '@/components/AlertNotification/Alerts.enum'; diff --git a/app/register/page.test.tsx b/app/(main)/register/page.test.tsx similarity index 97% rename from app/register/page.test.tsx rename to app/(main)/register/page.test.tsx index c00ee135..aa96fa48 100644 --- a/app/register/page.test.tsx +++ b/app/(main)/register/page.test.tsx @@ -8,7 +8,7 @@ import { toast } from 'react-hot-toast'; const mockLogin = jest.fn(); const mockPush = jest.fn(); -jest.mock('../../api/apiFunctions', () => ({ +jest.mock('../../../api/apiFunctions', () => ({ registerAccount: jest.fn(), })); @@ -37,7 +37,7 @@ jest.mock('next/navigation', () => ({ }, })); -jest.mock('../../context/AuthContextProvider', () => ({ +jest.mock('../../../context/AuthContextProvider', () => ({ useAuthContext() { return mockUseAuthContext; }, diff --git a/app/register/page.tsx b/app/(main)/register/page.tsx similarity index 100% rename from app/register/page.tsx rename to app/(main)/register/page.tsx diff --git a/app/schedule/2024/week1.json b/app/(main)/schedule/2024/week1.json similarity index 100% rename from app/schedule/2024/week1.json rename to app/(main)/schedule/2024/week1.json diff --git a/app/schedule/2024/week10.json b/app/(main)/schedule/2024/week10.json similarity index 100% rename from app/schedule/2024/week10.json rename to app/(main)/schedule/2024/week10.json diff --git a/app/schedule/2024/week11.json b/app/(main)/schedule/2024/week11.json similarity index 100% rename from app/schedule/2024/week11.json rename to app/(main)/schedule/2024/week11.json diff --git a/app/schedule/2024/week12.json b/app/(main)/schedule/2024/week12.json similarity index 100% rename from app/schedule/2024/week12.json rename to app/(main)/schedule/2024/week12.json diff --git a/app/schedule/2024/week13.json b/app/(main)/schedule/2024/week13.json similarity index 100% rename from app/schedule/2024/week13.json rename to app/(main)/schedule/2024/week13.json diff --git a/app/schedule/2024/week14.json b/app/(main)/schedule/2024/week14.json similarity index 100% rename from app/schedule/2024/week14.json rename to app/(main)/schedule/2024/week14.json diff --git a/app/schedule/2024/week15.json b/app/(main)/schedule/2024/week15.json similarity index 100% rename from app/schedule/2024/week15.json rename to app/(main)/schedule/2024/week15.json diff --git a/app/schedule/2024/week16.json b/app/(main)/schedule/2024/week16.json similarity index 100% rename from app/schedule/2024/week16.json rename to app/(main)/schedule/2024/week16.json diff --git a/app/schedule/2024/week17.json b/app/(main)/schedule/2024/week17.json similarity index 100% rename from app/schedule/2024/week17.json rename to app/(main)/schedule/2024/week17.json diff --git a/app/schedule/2024/week18.json b/app/(main)/schedule/2024/week18.json similarity index 100% rename from app/schedule/2024/week18.json rename to app/(main)/schedule/2024/week18.json diff --git a/app/schedule/2024/week2.json b/app/(main)/schedule/2024/week2.json similarity index 100% rename from app/schedule/2024/week2.json rename to app/(main)/schedule/2024/week2.json diff --git a/app/schedule/2024/week3.json b/app/(main)/schedule/2024/week3.json similarity index 100% rename from app/schedule/2024/week3.json rename to app/(main)/schedule/2024/week3.json diff --git a/app/schedule/2024/week4.json b/app/(main)/schedule/2024/week4.json similarity index 100% rename from app/schedule/2024/week4.json rename to app/(main)/schedule/2024/week4.json diff --git a/app/schedule/2024/week5.json b/app/(main)/schedule/2024/week5.json similarity index 100% rename from app/schedule/2024/week5.json rename to app/(main)/schedule/2024/week5.json diff --git a/app/schedule/2024/week6.json b/app/(main)/schedule/2024/week6.json similarity index 100% rename from app/schedule/2024/week6.json rename to app/(main)/schedule/2024/week6.json diff --git a/app/schedule/2024/week7.json b/app/(main)/schedule/2024/week7.json similarity index 100% rename from app/schedule/2024/week7.json rename to app/(main)/schedule/2024/week7.json diff --git a/app/schedule/2024/week8.json b/app/(main)/schedule/2024/week8.json similarity index 100% rename from app/schedule/2024/week8.json rename to app/(main)/schedule/2024/week8.json diff --git a/app/schedule/2024/week9.json b/app/(main)/schedule/2024/week9.json similarity index 100% rename from app/schedule/2024/week9.json rename to app/(main)/schedule/2024/week9.json diff --git a/components/Alert/Alert.tsx b/components/Alert/Alert.tsx index 24ff467f..4f88d315 100644 --- a/components/Alert/Alert.tsx +++ b/components/Alert/Alert.tsx @@ -27,10 +27,11 @@ const alertVariants = cva( const Alert = React.forwardRef< HTMLDivElement, React.HTMLAttributes & VariantProps ->(({ variant, children }) => ( +>(({ variant, children }, ref) => (
{children}
)); Alert.displayName = 'Alert'; diff --git a/components/LeagueCard/LeagueCard.tsx b/components/LeagueCard/LeagueCard.tsx index 1cf92ce9..f3e0ce75 100644 --- a/components/LeagueCard/LeagueCard.tsx +++ b/components/LeagueCard/LeagueCard.tsx @@ -18,13 +18,14 @@ const LeagueCard = React.forwardRef( survivors, title, totalPlayers, - }) => ( + }, ref) => ( League Logo} + * @returns {Promise} - The user data or undefined if the user is not signed in */ - const getUser = useCallback(async () => { + const getUser = async (): Promise => { if (!isSessionInLocalStorage()) { router.push('/login'); return; @@ -100,7 +100,7 @@ export const AuthContextProvider = ({ resetUser(); setIsSignedIn(false); } - }, [user]); + }; /** * Helper function to validate session data in local storage diff --git a/functions/userAuth.js b/functions/userAuth.js index 9c58b53d..0b672b44 100644 --- a/functions/userAuth.js +++ b/functions/userAuth.js @@ -1,29 +1,65 @@ -const { ID } = require("appwrite"); -const sdk = require("node-appwrite") +// Copyright (c) Gridiron Survivor. +// Licensed under the MIT License. -const user = async ({ req, res, log, error }) => { +const { ID } = require('appwrite'); +const sdk = require('node-appwrite'); - // Init SDK - const client = new sdk.Client(); +/** + * creates a new user record in the User collection + * @param param - The parameters + * @param param.req - The request object + * @param param.res - The response object + * @returns {void} + */ +const user = async ({ req, res }) => { + // Init SDK + const client = new sdk.Client(); + const databases = new sdk.Databases(client); - const databases = new sdk.Databases(client); + client + .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint + .setProject(process.env.PROJECT_ID) // Your project ID + .setKey(process.env.X_Appwrite_Key); // Your secret API key - client - .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint - .setProject(process.env.PROJECT_ID) // Your project ID - .setKey(process.env.X_Appwrite_Key) // Your secret API key + // create a new user + if (req.method === 'POST' && req.body.email && req.body['$id']) { + await databases.createDocument( + process.env.DATABASE_ID, + process.env.COLLECTION_USERS_ID, + ID.unique(), + { + email: req.body.email, + name: req.body.name, + labels: req.body.labels, + userId: req.body['$id'], + leagues: ['66311a210039f0532044'], + }, + ); - // create a new user - if (req.method === "POST" && req.body.email && req.body.email && req.body['$id']) { - await databases.createDocument(process.env.DATABASE_ID, process.env.COLLECTION_USERS_ID, ID.unique(), { - "email": req.body.email, - "name": req.body.name, - "labels": req.body.labels, - "userId": req.body['$id'] - }); - return res.json({ msg: "User was created successfully!" }); - } + // get the list of participants and survivors from the league + const league = await databases.getDocument( + process.env.DATABASE_ID, + '6626a937b6302f6a4d28', + '66311a210039f0532044', + ); + // add the new user id to the list of participants and survivors + const updatedParticipants = [...league.participants, req.body['$id']]; + const updatedSurvivors = [...league.survivors, req.body['$id']]; + + // update the league with the new users id into the list of participants and survivors + await databases.updateDocument( + process.env.DATABASE_ID, + '6626a937b6302f6a4d28', + '66311a210039f0532044', + { + participants: updatedParticipants, + survivors: updatedSurvivors, + }, + ); + + return res.json({ msg: 'User was created successfully!' }); + } }; -module.exports = user; \ No newline at end of file +module.exports = user; diff --git a/utils/utils.interface.ts b/utils/utils.interface.ts index b8e3f763..78045232 100644 --- a/utils/utils.interface.ts +++ b/utils/utils.interface.ts @@ -8,7 +8,7 @@ import { IUser, IWeeklyPicks, } from '@/api/apiFunctions.interface'; -import { IEntry } from '@/app/league/[leagueId]/entry/Entries.interface'; +import { IEntry } from '@/app/(main)/league/[leagueId]/entry/Entries.interface'; export interface IGetGameData { userId: IUser['id']; diff --git a/utils/utils.ts b/utils/utils.ts index e77d4ae9..3110957b 100644 --- a/utils/utils.ts +++ b/utils/utils.ts @@ -14,7 +14,7 @@ import { IGetUserPick, } from './utils.interface'; import { ILeague } from '@/api/apiFunctions.interface'; -import { IEntry } from '@/app/league/[leagueId]/entry/Entries.interface'; +import { IEntry } from '@/app/(main)/league/[leagueId]/entry/Entries.interface'; /** * Combine class names @@ -143,4 +143,4 @@ export const getUserLeagues = async ( */ export const getUserEntries = async (userId: IUser['id'], leagueId: ILeague['leagueId']): Promise => { return await getCurrentUserEntries(userId, leagueId); -} \ No newline at end of file +}