Skip to content
Snippets Groups Projects
Commit 8a647822 authored by xaralis's avatar xaralis
Browse files

feat: add rendering components for announcements and posts

parent 54ccbb5f
Branches
No related tags found
No related merge requests found
......@@ -5,5 +5,8 @@
"target": "es2019",
"jsx": "react"
},
"include": ["./src/**/*"]
"include": [
"./src/**/*",
"./typings/**/*",
]
}
......@@ -4674,6 +4674,11 @@
}
}
},
"date-fns": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.1.tgz",
"integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ=="
},
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
......
......@@ -6,6 +6,7 @@
"@react-keycloak/web": "^2.1.4",
"@sentry/react": "^5.23.0",
"classnames": "^2.2.6",
"date-fns": "^2.16.1",
"keycloak-js": "^10.0.2",
"pullstate": "^1.20.4",
"react": "^16.13.1",
......
......@@ -35,7 +35,19 @@ const onKeycloakEvent = (event) => {
}
};
const LoadingComponent = <p className="block mt-4 font-bold">Načítání ...</p>;
const LoadingComponent = (
<div className="h-screen w-screen flex justify-center items-center">
<div className="text-center">
<img
src={`${process.env.REACT_APP_STYLEGUIDE_URL}/images/logo-round-black.svg`}
className="w-20 mb-2 inline-block"
alt="Pirátská strana"
/>
<h1 className="head-heavy-base mb-2">Celostátní fórum 2021</h1>
<p>Načítám aplikaci ...</p>
</div>
</div>
);
const BaseApp = () => {
const loadGroupMappings = useCallback(async () => {
......@@ -88,8 +100,10 @@ const AuthenticatedApp = () => {
const ErrorBoundaryFallback = () => (
<div className="h-screen w-screen flex justify-center items-center">
<div className="text-center">
<h1 className="text-5xl mb-4">V aplikaci došlo k chybě :(</h1>
<p className="text-lg">
<h1 className="head-alt-xl text-red-600 mb-4">
V aplikaci došlo k chybě :(
</h1>
<p className="text-lg leading-normal">
Naši vývojáři o tom již byli informování a opraví to co nejdříve.
<br />
Omlouváme se za tuto nepříjemnost.
......
import React from "react";
import classNames from "classnames";
const Chip = ({ className, color = "grey-125", condensed, children }) => {
const chipClass = classNames(
"chip",
{
"chip--condensed": !!condensed,
},
`chip--${color}`,
className
);
return <span className={chipClass}>{children}</span>;
};
export default Chip;
import React from "react";
import classNames from "classnames";
import { format } from "date-fns";
import Chip from "components/Chip";
const Announcement = ({
className,
datetime,
type,
content,
link,
relatedPostId,
isSeen,
}) => {
const wrapperClassName = classNames(
"bg-opacity-50 border-l-2 px-4 py-2 lg:px-8 lg:py-3",
{
"bg-grey-50": isSeen,
"bg-yellow-100": !isSeen,
"border-orange-300": type === "rejected-procedure-proposal",
"border-blue-300": type === "suggested-procedure-proposal",
"border-green-400": type === "accepted-procedure-proposal",
"border-red-600": type === "voting",
"border-cyan-500": type === "announcement",
"border-black": type === "user-ban",
},
className
);
const chipColor = {
"rejected-procedure-proposal": "orange-300",
"suggested-procedure-proposal": "blue-300",
"accepted-procedure-proposal": "green-400",
voting: "red-600",
announcement: "cyan-500",
"user-bank": "black",
}[type];
const chipLabel = {
"rejected-procedure-proposal": "Zamítnutý návrh postupu",
"suggested-procedure-proposal": "Přijatelný návrh postupu",
"accepted-procedure-proposal": "Schválený návrh postupu",
voting: "Rozhodující hlasování",
announcement: "Oznámení předsedajícího",
"user-ban": "Zablokovaný účastník jednání",
}[type];
const linkLabel =
type === "voting" ? "Hlasovat v heliosu" : "Zobrazit související příspěvek";
return (
<div className={wrapperClassName}>
<div className="flex items-center justify-between mb-2">
<div className="space-x-2 flex items-center">
<div className="font-bold text-sm">{format(datetime, "H:mm")}</div>
<Chip color={chipColor} condensed>
{chipLabel}
</Chip>
{link && <a href={link}>{linkLabel + "»"}</a>}
</div>
</div>
<span className="leading-tight text-sm lg:text-base">{content}</span>
</div>
);
};
export default Announcement;
import React from "react";
import classNames from "classnames";
import Announcement from "./Announcement";
const AnnouncementList = ({ items, className }) => {
return (
<div className={classNames("space-y-px", className)}>
{items.map((item) => (
<Announcement
key={item.id}
datetime={item.datetime}
type={item.type}
content={item.content}
link={item.link}
isSeen={item.isSeen}
/>
))}
</div>
);
};
export default AnnouncementList;
import React from "react";
import classNames from "classnames";
import { format } from "date-fns";
import Chip from "components/Chip";
const Post = ({
className,
datetime,
author,
type,
ranking,
content,
isSeen,
isArchived,
state,
originalContent,
}) => {
const wrapperClassName = classNames(
"flex items-start p-4 lg:p-2 lg:py-4 lg:-mx-2",
{
"bg-yellow-100 bg-opacity-50": !isSeen,
"opacity-25 hover:opacity-100 transition-opacity duration-200": isArchived,
},
className
);
const labels = [];
if (type === "post") {
labels.push(
<Chip
key="type__post"
condensed
color="grey-125"
className="text-grey-300"
>
Příspěvek
</Chip>
);
}
if (type === "procedure-proposal") {
labels.push(
<Chip key="type__procedure-proposal" condensed color="cyan-200">
Návrh postupu
</Chip>
);
labels.push(
{
pending: (
<Chip key="state__pending" condensed color="grey-500">
Čeká na zpracování
</Chip>
),
announced: (
<Chip key="state__announced" condensed color="blue-300">
Vyhlášený
</Chip>
),
accepted: (
<Chip key="state__accepted" condensed color="green-400">
Schválený
</Chip>
),
rejected: (
<Chip key="state__rejected" condensed color="red-600">
Zamítnutý
</Chip>
),
"rejected-by-chairman": (
<Chip key="state__rejected-by-chairmen" condensed color="red-600">
Zamítnutý předsedajícím
</Chip>
),
}[state]
);
}
if (isArchived) {
labels.push(
<Chip
key="isArchived"
condensed
color="grey-125"
className="text-grey-300"
>
Archivovaný
</Chip>
);
}
return (
<div className={wrapperClassName}>
<img
src="http://placeimg.com/100/100/people"
className="w-8 h-8 lg:w-14 lg:h-14 rounded mr-3"
alt={author.name}
/>
<div className="flex-1">
<div className="mb-1">
<div className="flex justify-between items-start xl:items-center">
<div className="flex flex-col xl:flex-row xl:items-center">
<div className="flex flex-col xl:flex-row xl:items-center">
<span className="font-bold">{author.name}</span>
<div className="mt-1 lg:mt-0 lg:ml-2">
<span className="text-grey-200 text-sm">{author.group}</span>
<span className="text-grey-200 ml-1 text-sm">
@ {format(datetime, "H:mm")}
</span>
</div>
</div>
<div className="flex flex-row flex-wrap lg:flex-no-wrap lg:items-center mt-1 xl:mt-0 xl:ml-2 space-x-2">
{labels}
</div>
</div>
<div className="flex items-center space-x-4">
<div className="space-x-2 text-sm flex items-center">
<button className="text-blue-300 flex items-center space-x-1">
<span className="font-bold">{ranking.likes}</span>
<i className="ico--thumbs-up"></i>
</button>
<button className="text-red-600 flex items-center space-x-1">
<i className="ico--thumbs-down transform -scale-x-1"></i>
<span className="font-bold">{ranking.dislikes}</span>
</button>
</div>
</div>
</div>
</div>
<p className="text-sm lg:text-base text-black leading-normal">
{content}
</p>
</div>
</div>
);
};
export default Post;
import React from "react";
import classNames from "classnames";
import Post from "./Post";
const PostList = ({ className, items }) => {
return (
<div className={classNames("space-y-px", className)}>
{items.map((item) => (
<Post
key={item.id}
datetime={item.datetime}
author={item.author}
type={item.type}
state={item.state}
content={item.content}
originalContent={item.originalContent}
ranking={item.ranking}
isSeen={item.isSeen}
isArchived={item.isArchived}
/>
))}
</div>
);
};
export default PostList;
import React from "react";
import AnnouncementList from "components/annoucements/AnnouncementList";
const AnnoucementsContainer = () => {
/** @type {CF2021.Announcement[]} */
const items = [
{
id: "1",
content:
"Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
datetime: new Date(),
isSeen: false,
type: "rejected-procedure-proposal",
},
{
id: "2",
content:
"Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
datetime: new Date(),
isSeen: false,
type: "accepted-procedure-proposal",
},
{
id: "3",
content:
"Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
datetime: new Date(),
isSeen: true,
type: "suggested-procedure-proposal",
},
{
id: "4",
content:
"Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
datetime: new Date(),
isSeen: true,
type: "voting",
},
{
id: "5",
content:
"Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
datetime: new Date(),
isSeen: true,
type: "announcement",
},
{
id: "6",
content:
"Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
datetime: new Date(),
isSeen: true,
type: "user-ban",
},
];
return <AnnouncementList items={items} />;
};
export default AnnoucementsContainer;
import React from "react";
import PostList from "components/posts/PostList";
const PostsContainer = ({ className }) => {
/** @type {CF2021.Post[]} */
const items = [
{
id: "1",
content:
"Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
datetime: new Date(),
author: {
name: "John Doe",
group: "cf",
},
ranking: {
likes: 5,
dislikes: 1,
score: 4,
},
isSeen: false,
isArchived: false,
type: "post",
},
{
id: "2",
content:
"Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
datetime: new Date(),
author: {
name: "John Doe",
group: "cf",
},
ranking: {
likes: 5,
dislikes: 1,
score: 4,
},
isSeen: false,
isArchived: false,
type: "post",
},
{
id: "3",
content:
"Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
datetime: new Date(),
author: {
name: "John Doe",
group: "cf",
},
ranking: {
likes: 5,
dislikes: 1,
score: 4,
},
isSeen: true,
isArchived: false,
type: "post",
},
{
id: "4",
content:
"Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
datetime: new Date(),
author: {
name: "John Doe",
group: "KS Pardubický kraj",
},
ranking: {
likes: 5,
dislikes: 1,
score: 4,
},
isSeen: true,
isArchived: false,
type: "post",
},
{
id: "5",
content:
"Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
datetime: new Date(),
author: {
name: "John Doe",
group: "KS Pardubický kraj",
},
ranking: {
likes: 5,
dislikes: 1,
score: 4,
},
isSeen: true,
isArchived: false,
type: "post",
},
{
id: "6",
content:
"Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
datetime: new Date(),
author: {
name: "John Doe",
group: "KS Pardubický kraj",
},
ranking: {
likes: 5,
dislikes: 1,
score: 4,
},
isSeen: true,
isArchived: true,
type: "post",
},
{
id: "7",
content:
"Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
datetime: new Date(),
author: {
name: "John Doe",
group: "KS Pardubický kraj",
},
ranking: {
likes: 5,
dislikes: 1,
score: 4,
},
isSeen: true,
isArchived: true,
type: "procedure-proposal",
state: "pending",
},
{
id: "8",
content:
"Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
datetime: new Date(),
author: {
name: "John Doe",
group: "KS Pardubický kraj",
},
ranking: {
likes: 5,
dislikes: 1,
score: 4,
},
isSeen: true,
isArchived: false,
type: "procedure-proposal",
state: "announced",
},
{
id: "9",
content:
"Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
datetime: new Date(),
author: {
name: "John Doe",
group: "KS Pardubický kraj",
},
ranking: {
likes: 5,
dislikes: 1,
score: 4,
},
isSeen: true,
isArchived: false,
type: "procedure-proposal",
state: "accepted",
},
{
id: "10",
content:
"Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
datetime: new Date(),
author: {
name: "John Doe",
group: "KS Pardubický kraj",
},
ranking: {
likes: 5,
dislikes: 1,
score: 4,
},
isSeen: true,
isArchived: false,
type: "procedure-proposal",
state: "rejected",
},
{
id: "11",
content:
"Shizz fo shizzle mah nizzle fo rizzle, mah home g-dizzle, gravida vizzle, arcu. Pellentesque crunk tortizzle. Sed erizzle. Black izzle sheezy telliv.",
datetime: new Date(),
author: {
name: "John Doe",
group: "KS Pardubický kraj",
},
ranking: {
likes: 5,
dislikes: 1,
score: 4,
},
isSeen: true,
isArchived: true,
type: "procedure-proposal",
state: "rejected-by-chairman",
},
];
return <PostList items={items} className={className} />;
};
export default PostsContainer;
import React from "react";
import AnnouncementsContainer from "containers/AnnoucementsContainer";
import PostsContainer from "containers/PostsContainer";
const Home = () => {
return (
<>
<p>Homepage</p>
<article className="container container--wide pt-8 lg:py-24 cf2021">
<section className="cf2021__video space-y-8">
<div className="flex items-center justify-between mb-4 lg:mb-8">
<h1 className="head-alt-md lg:head-alt-lg mb-0">
Bod č. 1: Programové priority Pirátské strany pro sněmovní volby
2021
</h1>
</div>
<iframe
width="100%"
height="500"
src="https://www.youtube.com/embed/73jJLspL8o8"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen=""
title="Video stream"
></iframe>
</section>
<section className="cf2021__notifications">
<div className="lg:card lg:elevation-10">
<div className="lg:card__body pb-2 lg:py-6">
<h2 className="head-heavy-sm">Oznámení</h2>
</div>
<AnnouncementsContainer className="container-padding--zero lg:container-padding--auto" />
<div className="lg:card__body pt-4 lg:py-6">
<div className="form-field ">
<div className="form-field__wrapper form-field__wrapper--shadowed">
<textarea
className="text-input form-field__control "
value=""
rows="3"
cols="40"
placeholder="Vyplňte text oznámení"
readOnly
></textarea>
</div>
</div>
<button className="btn btn--black btn--hoveractive text-sm mt-2">
<div className="btn__body ">Přidat oznámení</div>
</button>
</div>
</div>
</section>
<section className="cf2021__posts">
<div className="flex flex-col xl:flex-row xl:justify-between xl:items-center mb-4">
<h2 className="head-heavy-sm whitespace-no-wrap">
Příspěvky v rozpravě
</h2>
<div className="flex flex-col space-y-2 xl:space-y-0 xl:space-x-8 xl:flex-row xl:items-center">
<div className="-mx-1">
<span className="chip chip--grey-125 chip--select chip--hoveractive text-xs ml-1 mt-2 xl:mt-0">
<select>
<option value="">Jen nezpracované</option>
<option value="">Vše</option>
<option value="">Jen aktivní</option>
<option value="">Jen archivované</option>
</select>
<span className="chip__icon ico--chevron-down"></span>
</span>
<span className="chip chip--grey-125 chip--select chip--hoveractive text-xs ml-1 mt-2 xl:mt-0">
<select>
<option value="">Podle času</option>
<option value="">Podle podpory</option>
</select>
<span className="chip__icon ico--chevron-down"></span>
</span>
<span className="chip chip--grey-125 chip--select chip--hoveractive text-xs ml-1 mt-2 xl:mt-0">
<select>
<option value="">Návrhy i příspěvky</option>
<option value="">Jen návrhy</option>
<option value="">Jen příspěvky</option>
</select>
<span className="chip__icon ico--chevron-down"></span>
</span>
</div>
<div>
<span className="chip chip--grey-125 chip--hoveractive text-xs">
<span className="ico--chevron-left"></span>
</span>
<span className="chip chip--grey-125 chip--hoveractive ml-1">
<span className="ico--chevron-right"></span>
</span>
</div>
</div>
</div>
<PostsContainer className="container-padding--zero lg:container-padding--auto" />
<div className="my-8 space-y-4">
<div className="form-field ">
<div className="form-field__wrapper form-field__wrapper--shadowed">
<textarea
className="text-input form-field__control "
value=""
rows="5"
cols="40"
placeholder="Vyplňte text vašeho příspěvku"
readOnly
></textarea>
</div>
</div>
<div className="space-x-4">
<button className="btn btn--icon ">
<div className="btn__body-wrap">
<div className="btn__body ">Přidat příspěvek</div>
<div className="btn__icon dropdown-button">
<i className="ico--chevron-down"></i>
<ul className="dropdown-button__choices bg-white text-black whitespace-no-wrap">
<li className="dropdown-button__choice hover:bg-grey-125">
<span className="block px-4 py-3" href="#">
Navrhnout postup
</span>
</li>
</ul>
</div>
</div>
</button>
<span className="text-sm text-grey-200 hidden lg:inline">
Pro pokročilejší formátování můžete používat{" "}
<a
href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet"
className="underline"
target="_blank"
rel="noreferrer noopener"
>
Markdown
</a>
.
</span>
</div>
</div>
</section>
</article>
</>
);
};
......
import { Store } from "pullstate";
export const AuthStore = new Store({
/** @type {CF2021.AuthStorePayload} */
const authStoreInitial = {
isAuthenticated: false,
user: {
name: null,
groups: null,
},
groupMappings: null,
});
groupMappings: [],
};
export const AuthStore = new Store(authStoreInitial);
/** @type {CF2021.AnnouncementStorePayload} */
const announcementStoreInitial = {
items: [],
};
export const AnnouncementStore = new Store(announcementStoreInitial);
/** @type {CF2021.PostStorePayload} */
const postStoreInitial = {
items: [],
};
export const PostStore = new Store(postStoreInitial);
declare namespace CF2021 {
interface GroupMapping {
id: number;
code: string;
name: string;
}
export interface AnonymousAuthStorePayload {
isAuthenticated: false;
groupMappings: GroupMapping[];
}
export interface UserAuthStorePayload extends AnonymousAuthStorePayload {
isAuthenticated: true;
user: {
name: string;
groups: string[];
};
}
export type AuthStorePayload =
| AnonymousAuthStorePayload
| UserAuthStorePayload;
export type AnnouncementType =
| "rejected-procedure-proposal"
| "accepted-procedure-proposal"
| "suggested-procedure-proposal"
| "voting"
| "announcement"
| "user-ban";
export interface Announcement {
id: string;
datetime: Date;
type: AnnouncementType;
content: string;
link?: string;
relatedPostId: string;
isSeen: boolean;
}
export interface AnnouncementStorePayload {
items: Announcement[];
}
export type PostType = "post" | "procedure-proposal";
export interface AbstractPost {
id: string;
datetime: Date;
author: {
name: string;
group: string;
};
type: PostType;
content: string;
ranking: {
score: number;
likes: number;
dislikes: number;
};
isArchived: boolean;
isSeen: boolean;
}
export interface DiscussionPost extends AbstractPost {
type: "post";
}
export interface ProposalPost extends AbstractPost {
type: "procedure-proposal";
state:
| "pending"
| "announced"
| "accepted"
| "rejected"
| "rejected-by-chairman";
originalContent?: string;
}
export type Post = ProposalPost | DiscussionPost;
export interface PostStorePayload {
items: Post[];
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment