diff --git a/app/dashboard/expenses/page.tsx b/app/[slug]/dashboard/expenses/page.tsx similarity index 100% rename from app/dashboard/expenses/page.tsx rename to app/[slug]/dashboard/expenses/page.tsx diff --git a/app/dashboard/guests/page.tsx b/app/[slug]/dashboard/guests/page.tsx similarity index 100% rename from app/dashboard/guests/page.tsx rename to app/[slug]/dashboard/guests/page.tsx diff --git a/app/dashboard/layout.tsx b/app/[slug]/dashboard/layout.tsx similarity index 100% rename from app/dashboard/layout.tsx rename to app/[slug]/dashboard/layout.tsx diff --git a/app/dashboard/page.tsx b/app/[slug]/dashboard/page.tsx similarity index 100% rename from app/dashboard/page.tsx rename to app/[slug]/dashboard/page.tsx diff --git a/app/dashboard/tables/page.tsx b/app/[slug]/dashboard/tables/page.tsx similarity index 100% rename from app/dashboard/tables/page.tsx rename to app/[slug]/dashboard/tables/page.tsx diff --git a/app/[slug]/page.tsx b/app/[slug]/page.tsx new file mode 100644 index 0000000..34e8adf --- /dev/null +++ b/app/[slug]/page.tsx @@ -0,0 +1,30 @@ +/* Copyright (C) 2024 Manuel Bustillo*/ + +'use client'; +import Link from 'next/link'; +import styles from '@/app/ui/home.module.css'; +import LoginForm from '@/app/ui/components/login-form'; + + + +export default async function Page({ params }: { params: Promise<{ slug: string }> }) { + + localStorage.setItem('slug', (await params).slug) + return ( +
+
+
+ + Already have an account? Sign in + + +
+ +
+ Don't have an account? Register now! +
+
+ +
+ ); +} diff --git a/app/api/authentication.tsx b/app/api/authentication.tsx new file mode 100644 index 0000000..6b57d06 --- /dev/null +++ b/app/api/authentication.tsx @@ -0,0 +1,36 @@ +/* Copyright (C) 2024 Manuel Bustillo*/ + +import { getCsrfToken, getSlug } from '@/app/lib/utils'; +import { User } from '@/app/lib/definitions'; + +export function login({ email, password, onLogin }: { email: string, password: string, onLogin: (user: User) => void }) { + console.log(email, password); + return fetch(`/api/${getSlug()}/users/sign_in`, { + method: 'POST', + body: JSON.stringify({ user: { email, password } }), + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': getCsrfToken(), + } + }) + .then((response) => response.json()) + .then((data: any) => { + console.log(data); + onLogin({ + id: data.id || '', + email: data.email || '', + }); + }) + .catch((error) => console.error(error)); +} + +export function logout({ onLogout }: { onLogout: () => void }) { + fetch(`/api/${getSlug()}/users/sign_out`, { + method: 'DELETE', + headers: { + 'X-CSRF-TOKEN': getCsrfToken(), + } + }).then(onLogout) + .catch((error) => console.error(error)); +} diff --git a/app/api/expenses.tsx b/app/api/expenses.tsx index f384413..a23cd16 100644 --- a/app/api/expenses.tsx +++ b/app/api/expenses.tsx @@ -1,10 +1,10 @@ /* Copyright (C) 2024 Manuel Bustillo*/ import { Expense } from '@/app/lib/definitions'; -import { getCsrfToken } from '@/app/lib/utils'; +import { getCsrfToken, getSlug } from '@/app/lib/utils'; export function loadExpenses(onLoad?: (expenses: Expense[]) => void) { - fetch("/api/default/expenses") + fetch(`/api/${getSlug()}/expenses`) .then((response) => response.json()) .then((data) => { onLoad && onLoad(data.map((record: any) => { @@ -21,7 +21,7 @@ export function loadExpenses(onLoad?: (expenses: Expense[]) => void) { } export function updateExpense(expense: Expense) { - fetch(`/api/default/expenses/${expense.id}`, + fetch(`/api/${getSlug()}/expenses/${expense.id}`, { method: 'PUT', body: JSON.stringify({ diff --git a/app/api/groups.tsx b/app/api/groups.tsx index cf83904..807c19f 100644 --- a/app/api/groups.tsx +++ b/app/api/groups.tsx @@ -1,9 +1,10 @@ /* Copyright (C) 2024 Manuel Bustillo*/ import { Group } from '@/app/lib/definitions'; +import { getSlug } from '../lib/utils'; export function loadGroups(onLoad?: (groups: Group[]) => void) { - fetch("/api/default/groups") + fetch(`/api/${getSlug()}/groups`) .then((response) => response.json()) .then((data) => { onLoad && onLoad(data.map((record: any) => { diff --git a/app/api/guests.tsx b/app/api/guests.tsx index 628e7e5..f9c1263 100644 --- a/app/api/guests.tsx +++ b/app/api/guests.tsx @@ -1,10 +1,10 @@ /* Copyright (C) 2024 Manuel Bustillo*/ import { Guest } from '@/app/lib/definitions'; -import { getCsrfToken } from '@/app/lib/utils'; +import { getCsrfToken, getSlug } from '@/app/lib/utils'; export function loadGuests(onLoad?: (guests: Guest[]) => void) { - fetch("/api/default/guests") + fetch(`/api/${getSlug()}/guests`) .then((response) => response.json()) .then((data) => { onLoad && onLoad(data.map((record: any) => { @@ -22,7 +22,7 @@ export function loadGuests(onLoad?: (guests: Guest[]) => void) { }; export function updateGuest(guest: Guest) { - return fetch(`/api/default/guests/${guest.id}`, + return fetch(`/api/${getSlug()}/guests/${guest.id}`, { method: 'PUT', body: JSON.stringify({ guest: { name: guest.name, status: guest.status } }), @@ -35,7 +35,7 @@ export function updateGuest(guest: Guest) { } export function createGuest(name: string, group_id: string, onCreate?: () => void) { - fetch("/api/default/guests", { + fetch(`/api/${getSlug()}/guests`, { method: 'POST', body: JSON.stringify({ name: name, group_id: group_id }), headers: { @@ -51,7 +51,7 @@ export function createGuest(name: string, group_id: string, onCreate?: () => voi } export function destroyGuest(guest: Guest, onDestroy?: () => void) { - fetch(`/api/default/guests/${guest.id}`, { + fetch(`/api/${getSlug()}/guests/${guest.id}`, { method: 'DELETE', headers: { 'X-CSRF-TOKEN': getCsrfToken(), diff --git a/app/api/tableSimulations.tsx b/app/api/tableSimulations.tsx index 2072661..5f6d2d9 100644 --- a/app/api/tableSimulations.tsx +++ b/app/api/tableSimulations.tsx @@ -1,9 +1,10 @@ /* Copyright (C) 2024 Manuel Bustillo*/ import { TableArrangement } from '@/app/lib/definitions'; +import { getSlug } from '../lib/utils'; export function loadTableSimulations(onLoad?: (tableSimulations: TableArrangement[]) => void) { - fetch('/api/default/tables_arrangements') + fetch(`/api/${getSlug()}/tables_arrangements`) .then((response) => response.json()) .then((data) => { onLoad && onLoad(data.map((record: any) => { diff --git a/app/lib/definitions.ts b/app/lib/definitions.ts index 5c008c9..5511a46 100644 --- a/app/lib/definitions.ts +++ b/app/lib/definitions.ts @@ -54,3 +54,8 @@ export type guestsTable = { amount: number; status: 'pending' | 'paid'; }; + +export type User = { + id: string; + email: string; +} \ No newline at end of file diff --git a/app/lib/utils.ts b/app/lib/utils.ts index 72f3baf..09359e4 100644 --- a/app/lib/utils.ts +++ b/app/lib/utils.ts @@ -6,3 +6,5 @@ export const getCsrfToken = () => { .find((row) => row.startsWith("csrf-token")) ?.split("=")[1] || 'unknown'; } + +export const getSlug = () => localStorage.getItem('slug') || 'default'; diff --git a/app/page.tsx b/app/page.tsx deleted file mode 100644 index 95ca011..0000000 --- a/app/page.tsx +++ /dev/null @@ -1,12 +0,0 @@ -/* Copyright (C) 2024 Manuel Bustillo*/ - -import Link from 'next/link'; -import styles from '@/app/ui/home.module.css'; - -export default function Page() { - return ( -
- -
- ); -} diff --git a/app/ui/components/login-form.tsx b/app/ui/components/login-form.tsx new file mode 100644 index 0000000..63ad65d --- /dev/null +++ b/app/ui/components/login-form.tsx @@ -0,0 +1,51 @@ +/* Copyright (C) 2024 Manuel Bustillo*/ + +'use client'; + +import { FloatLabel } from 'primereact/floatlabel'; +import { InputText } from 'primereact/inputtext'; +import { login } from '../../api/authentication'; +import { useState, useEffect } from 'react'; +import { classNames } from './button'; +import { useRouter } from 'next/navigation' +import { User } from '../../lib/definitions'; +import { getSlug } from '@/app/lib/utils'; + +export default function LoginForm() { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + + const router = useRouter(); + + const [currentUser, setCurrentUser] = useState(null); + + useEffect(() => { + localStorage.setItem('currentUser', JSON.stringify(currentUser)); + }, [currentUser]); + + return ( +
+ + setEmail(e.target.value)} /> + + + + setPassword(e.target.value)} /> + + + +
+ ) +} \ No newline at end of file diff --git a/app/ui/dashboard/nav-links.tsx b/app/ui/dashboard/nav-links.tsx index 7d88618..0b881dc 100644 --- a/app/ui/dashboard/nav-links.tsx +++ b/app/ui/dashboard/nav-links.tsx @@ -10,13 +10,14 @@ import { import Link from 'next/link'; import { usePathname } from 'next/navigation'; import clsx from 'clsx'; +import { getSlug } from '@/app/lib/utils'; // Map of links to display in the side navigation. // Depending on the size of the application, this would be stored in a database. const links = [ - { name: 'Guests', href: '/dashboard/guests', icon: UserGroupIcon }, - { name: 'Expenses', href: '/dashboard/expenses', icon: BanknotesIcon }, - { name: 'Table distributions', href: '/dashboard/tables', icon: RectangleGroupIcon }, + { name: 'Guests', href: `/${getSlug()}/dashboard/guests`, icon: UserGroupIcon }, + { name: 'Expenses', href: `/${getSlug()}/dashboard/expenses`, icon: BanknotesIcon }, + { name: 'Table distributions', href: `/${getSlug()}/dashboard/tables`, icon: RectangleGroupIcon }, ]; diff --git a/app/ui/dashboard/sidenav.tsx b/app/ui/dashboard/sidenav.tsx index a13a2e6..ec4e61c 100644 --- a/app/ui/dashboard/sidenav.tsx +++ b/app/ui/dashboard/sidenav.tsx @@ -1,30 +1,46 @@ /* Copyright (C) 2024 Manuel Bustillo*/ +'use client'; + import Link from 'next/link'; import NavLinks from '@/app/ui/dashboard/nav-links'; import { PowerIcon } from '@heroicons/react/24/outline'; import { gloriaHallelujah } from '@/app/ui/fonts'; +import { logout } from '@/app/api/authentication'; +import { useRouter } from 'next/navigation'; +import { getSlug } from '@/app/lib/utils'; export default function SideNav() { + const router = useRouter(); + return (
-
+

Wedding Planner

-
- -
+ Logged in as {JSON.parse(localStorage.getItem('currentUser') || '{}').email} +
); diff --git a/tests/guests.spec.ts b/tests/guests.spec.ts index 3a564d7..8bf139c 100644 --- a/tests/guests.spec.ts +++ b/tests/guests.spec.ts @@ -65,7 +65,7 @@ const mockGroupsAPI = ({ page }: { page: Page }) => { test('should display the list of guests', async ({ page }) => { await mockGuestsAPI({ page }); - await page.goto('/dashboard/guests'); + await page.goto('/default/dashboard/guests'); await expect(page.getByRole('tab', { name: 'Groups' })).toBeVisible(); await expect(page.getByRole('tab', { name: 'Guests' })).toBeVisible(); @@ -90,7 +90,7 @@ test('should display the list of guests', async ({ page }) => { test('should display the list of groups', async ({ page }) => { await mockGroupsAPI({ page }); - await page.goto('/dashboard/guests'); + await page.goto('/default/dashboard/guests'); await page.getByRole('tab', { name: 'Groups' }).click(); await expect(page.getByText('There are 2 elements in the list')).toBeVisible();