Skip to content

Commit

Permalink
Merge pull request #65 from plum-in-a-compote/create-notifications-page
Browse files Browse the repository at this point in the history
Create notifications page
  • Loading branch information
wisnie authored Oct 23, 2022
2 parents ea38a87 + 35ee289 commit 93684c9
Show file tree
Hide file tree
Showing 14 changed files with 331 additions and 6 deletions.
43 changes: 43 additions & 0 deletions components/composited/Notification/NotificationItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import Link from 'next/link';
import { Fragment } from 'react';
import { Badge } from '../../generic/Badge/Badge';
import { LinkButton } from '../../generic/LinkButton/LinkButton';
import { Text } from '../../generic/Text/Text';
import {
getNotificationTitle,
getNotificationDescription,
NotificationType,
} from './getNotificationContents';

type NotificationItemProps = {
notificationType: NotificationType;
communityName: string;
isNew?: boolean;
};

export const NotificationItem = ({
notificationType,
communityName,
isNew = false,
}: NotificationItemProps) => {
const title = getNotificationTitle(notificationType);
const [descPart1, descPart2] = getNotificationDescription(notificationType);

return (
<Fragment>
<div className="flex items-center mb-2 lg:mb-3">
<span className="pr-3 text-xs text-gray-800 leading-4 font-semibold sm:text-sm sm:leading-4 lg:leading-5 lg:text-lg">
{title}
</span>
{isNew && <Badge textContent="Nowe" color="amber" />}
</div>
<div>
<Text className="sm:text-sm" as="span" content={descPart1} />
<Link href={`/communities/${communityName}`}>
<LinkButton textContent={communityName} />
</Link>
<Text className="sm:text-sm" as="span" content={descPart2} />
</div>
</Fragment>
);
};
31 changes: 31 additions & 0 deletions components/composited/Notification/getNotificationContents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export type NotificationType =
| 'applicationAccepted'
| 'newApplication'
| 'newProject'
| 'projectVotingComplete';

export const getNotificationTitle = (notificationName: NotificationType) => {
switch (notificationName) {
case 'applicationAccepted':
return 'Akceptacja podania';
case 'newApplication':
return 'Nowe podanie';
case 'newProject':
return 'Nowy projekt';
case 'projectVotingComplete':
return 'Zakończenie głosowania';
}
};

export const getNotificationDescription = (notificationName: NotificationType) => {
switch (notificationName) {
case 'applicationAccepted':
return ['Twoje podanie do grupy ', ' zostało pomyślnie rozpatrzone.'];
case 'newApplication':
return ['Pojawiło się nowe podanie do twojej grupy ', '.'];
case 'newProject':
return ['Utworzono nowy projekt zagospodarowania budżetu w grupie ', '.'];
case 'projectVotingComplete':
return ['Projekt w grupie ', ' oczekuje rozpatrzenia.'];
}
};
22 changes: 22 additions & 0 deletions components/composited/NotificationsPage/NotificationList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Notification } from '../../../validators/Notification';
import { NotificationItem } from '../Notification/NotificationItem';

type NotificationListProps = {
notifications: Notification[];
};

export const NotificationList = ({ notifications }: NotificationListProps) => {
return (
<ul className="list-none flex flex-col gap-6 sm:col-end-2 sm:gap-8 lg:gap-12">
{notifications.map((notification) => (
<li key={notification.id}>
<NotificationItem
notificationType={notification.notificationType}
communityName={notification.communityName}
isNew={true}
/>
</li>
))}
</ul>
);
};
73 changes: 73 additions & 0 deletions components/composited/NotificationsPage/NotificationsPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Fragment, MouseEventHandler } from 'react';
import { Heading } from '../../generic/Heading/Heading';
import { LinkButton } from '../../generic/LinkButton/LinkButton';
import { WarningMessage } from '../../generic/WarningMessage/WarningMessage';
import { PreferencesForm } from '../PreferencesForm/PreferencesForm';
import { NotificationList } from './NotificationList';

export const NotificationsPage = () => {
// Placeholder function
const handleLoadMore: MouseEventHandler<HTMLButtonElement> = () => 1;

return (
<Fragment>
<section className="mb-16 sm:grid sm:grid-cols-2">
<div>
<Heading
className="mb-4"
as="h1"
variant="base"
content="Powiadomienia"
displayDecorationBorder={true}
/>
<WarningMessage
className="mb-3 sm:mb-8"
title="Wsparcie wkrótce!"
description="Ta strona nie jest obecnie funkcjonalna, a przedstawione dane są poglądowe."
/>
</div>
{/* Map over props to render notifications */}
{/* MOCKED DATA */}
<NotificationList
notifications={[
{
id: 1,
title: 'Test',
communityName: 'Moja szkola',
notificationType: 'applicationAccepted',
},
{
id: 2,
title: 'Testtest',
communityName: 'Twoja szkola',
notificationType: 'newApplication',
},
{
id: 3,
title: 'Testtesttest',
communityName: 'Moja szkola jeden',
notificationType: 'newProject',
},
]}
/>
<LinkButton
className="mt-4 sm:col-end-2 sm:mt-10 lg:mt-20"
textContent="Wczytaj więcej..."
underline={true}
onClick={handleLoadMore}
/>
</section>
<section>
<Heading className="mb-6 text-gray-700" as="h1" variant="smBold" content="Personalizacja" />
<PreferencesForm
preferences={{
applicationAccepted: true,
newApplication: false,
newProject: true,
projectVotingComplete: false,
}}
/>
</section>
</Fragment>
);
};
43 changes: 43 additions & 0 deletions components/composited/PreferencesForm/PreferencesForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Checkbox } from '../../generic/Checkbox/Checkbox';

type UserPreferences = {
newProject: boolean;
applicationAccepted: boolean;
newApplication: boolean;
projectVotingComplete: boolean;
};

type PreferencesFormProps = {
preferences: UserPreferences;
};

export const PreferencesForm = ({ preferences }: PreferencesFormProps) => {
return (
<form className="flex flex-col gap-6">
<Checkbox
defaultChecked={preferences.newProject}
name="newProject"
label="Nowe projekty"
description="Otrzymuj powiadomienia o nowych propozycjach zagospodarowania budżetu w społecznościach, do których należysz."
/>
<Checkbox
defaultChecked={preferences.applicationAccepted}
name="applicationAccepted"
label="Akceptacje próśb o dołączenie"
description="Otrzymuj powiadomienia, gdy Twoje podanie do społeczności zostanie zaakceptowane."
/>
<Checkbox
defaultChecked={preferences.newApplication}
name="newApplication"
label="Nowe prośby o dołączenie"
description="Otrzymuj powiadomienia o nowych prośbach o dołączenie do społeczności, których jesteś administratorem."
/>
<Checkbox
defaultChecked={preferences.projectVotingComplete}
name="projectVotingComplete"
label="Zakończone głosowania"
description="Otrzymuj powiadomienia o zakończeniu głosowań w społecznościach, do których należysz."
/>
</form>
);
};
1 change: 0 additions & 1 deletion components/composited/SignInPage/SignInPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export const SignInPage = () => {
className="mb-8 sm:col-end-2"
content="Konto jest potrzebne, aby utworzyć lokalną społeczność lub do niej dołączyć."
/>
{/* Display all errors in single place */}
{login.isError && (
<ErrorMessage
className="mb-6 sm:col-end-2"
Expand Down
6 changes: 4 additions & 2 deletions components/generic/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
type CheckBoxProps = {
name: string;
label: string;
defaultChecked?: boolean;
description?: string;
};

export const Checkbox = ({ name, label, description }: CheckBoxProps) => {
export const Checkbox = ({ name, label, description, defaultChecked = false }: CheckBoxProps) => {
return (
<label className="flex flex-col gap-1.5">
<div className="flex gap-3">
<input
className="w-4 h-4 border border-gray-200 rounded-full focus:ring-blue-300 checked:border-4 checked:border-blue-700 checked:bg-transparent checked:focus:border-blue-700 checked:focus:bg-transparent"
className="w-4 h-4 border border-gray-200 rounded-full focus:ring-blue-300 checked:border-4 checked:border-blue-700 checked:bg-transparent checked:focus:border-blue-700 checked:hover:border-blue-700 checked:hover:bg-transparent checked:focus:bg-transparent"
type="checkbox"
defaultChecked={defaultChecked}
name={name}
/>
<span className="text-xs leading-4 font-semibold text-gray-700">{label}</span>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import Link from 'next/link';
import { AccountIcon } from '../Icons/AccountIcon';
export const AccountLink = () => {
import { NotificationIcon } from '../Icons/NotificationIcon';
export const AccountLinks = () => {
return (
<div className="flex gap-3">
<Link href="/notifications">
<a
className="transition group block p-0.5 rounded-full hover:bg-blue-100 focus:bg-blue-100"
aria-label="Przejź do powiadomień."
>
<NotificationIcon className="transition group-hover:fill-blue-800 group-focus:fill-blue-800" />
</a>
</Link>
<Link href="/account">
<a
className="transition group block p-0.5 rounded-full hover:bg-blue-100 focus:bg-blue-100"
Expand Down
4 changes: 2 additions & 2 deletions components/generic/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { clsx as cx } from 'clsx';
import { Container } from '../Container/Container';
import { MenuIcon } from '../Icons/MenuIcon';
import { HeaderLink } from './HeaderLink';
import { AccountLink } from './AccountLink';
import { AccountLinks } from './AccountLinks';
import { Logo } from './Logo';

export const Header = () => {
Expand All @@ -31,7 +31,7 @@ export const Header = () => {
>
<MenuIcon />
</button>
<AccountLink />
<AccountLinks />
</div>

<div className="sm:flex sm:gap-10 lg:gap-12">
Expand Down
29 changes: 29 additions & 0 deletions components/generic/Icons/NotificationIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { clsx as cx } from 'clsx';
import { DEFAULT_HEIGHT, DEFAULT_WIDTH } from './constants';

type NotificationIconProps = {
width?: number;
height?: number;
className?: string;
};

export const NotificationIcon = ({
width = DEFAULT_WIDTH,
height = DEFAULT_HEIGHT,
className,
}: NotificationIconProps) => {
return (
<svg
className={cx('fill-gray-600', className)}
xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
viewBox="0 0 24 24"
aria-hidden={true}
focusable={false}
>
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M19.29 17.29L18 16v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-1.29 1.29c-.63.63-.19 1.71.7 1.71h13.17c.9 0 1.34-1.08.71-1.71zM16 17H8v-6c0-2.48 1.51-4.5 4-4.5s4 2.02 4 4.5v6zm-4 5c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2z" />
</svg>
);
};
29 changes: 29 additions & 0 deletions components/generic/LinkButton/LinkButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { clsx as cx } from 'clsx';
import { MouseEventHandler } from 'react';

type LinkButtonProps = {
textContent: string;
className?: string;
underline?: boolean;
onClick?: MouseEventHandler<HTMLButtonElement>;
};

export const LinkButton = ({
textContent,
className = '',
underline = false,
onClick,
}: LinkButtonProps) => {
return (
<button
className={cx(
'w-fit text-xs text-blue-700 leading-4 hover:underline sm:leading-5 sm:text-sm lg:text-base lg:leading-6 focus:text-blue-800 focus:outline-none focus:ring focus:ring-blue-300',
className,
underline && 'underline',
)}
onClick={onClick}
>
{textContent}
</button>
);
};
20 changes: 20 additions & 0 deletions pages/notifications.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { NextPage } from 'next';
import Head from 'next/head';
import { NotificationsPage } from '../components/composited/NotificationsPage/NotificationsPage';
import { Auth } from '../components/generic/Auth/Auth';
import { MainLayout } from '../components/generic/MainLayout/MainLayout';

const Notifications: NextPage = () => {
return (
// <Auth>
<MainLayout>
<Head>
<title>Powiadomienia - My Local Circle</title>
</Head>
<NotificationsPage />
</MainLayout>
// </Auth>
);
};

export default Notifications;
15 changes: 15 additions & 0 deletions validators/Notification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { z } from 'zod';

export const NotificationSch = z.object({
id: z.number(),
title: z.string(),
communityName: z.string(),
notificationType: z.enum([
'applicationAccepted',
'newApplication',
'newProject',
'projectVotingComplete',
]),
});

export type Notification = z.infer<typeof NotificationSch>;
10 changes: 10 additions & 0 deletions validators/Preferences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { z } from 'zod';

export const PreferencesSch = z.object({
applicationAccepted: z.boolean(),
newApplication: z.boolean(),
newProject: z.boolean(),
projectVotingComplete: z.boolean(),
});

export type Preferences = z.infer<typeof PreferencesSch>;

0 comments on commit 93684c9

Please sign in to comment.