diff --git a/jsconfig.json b/jsconfig.json index 09c5122333e02399345c229bffab8a3a53f55686..5c41b70be483a0dcd9612aef15fd3d7eb149a495 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -5,5 +5,8 @@ "target": "es2019", "jsx": "react" }, - "include": ["./src/**/*"] + "include": [ + "./src/**/*", + "./typings/**/*", + ] } diff --git a/package-lock.json b/package-lock.json index 6006e7d2911779da961aea3e9cfd1e4d7c230c63..ec1b2b02e7a152b88e3568aed8c7f71236839346 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index f64eb0b0bee29276ae94d1c434688c4fba301774..45a1787108f036d8611f765fb210c214da6aedc5 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/App.jsx b/src/App.jsx index 481d080992531f00bee5d2bafe4a63a836ad6540..a2e7648d1891d535e0e300b8148168de18524f14 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -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. diff --git a/src/components/Chip.jsx b/src/components/Chip.jsx new file mode 100644 index 0000000000000000000000000000000000000000..dd4603bf47980eeb6c49a5036c19690aaacfdb5d --- /dev/null +++ b/src/components/Chip.jsx @@ -0,0 +1,17 @@ +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; diff --git a/src/components/annoucements/Announcement.jsx b/src/components/annoucements/Announcement.jsx new file mode 100644 index 0000000000000000000000000000000000000000..03e0c3956f4c7be18f7403eeb67eb98193f816d9 --- /dev/null +++ b/src/components/annoucements/Announcement.jsx @@ -0,0 +1,68 @@ +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; diff --git a/src/components/annoucements/AnnouncementList.jsx b/src/components/annoucements/AnnouncementList.jsx new file mode 100644 index 0000000000000000000000000000000000000000..7d4dbd20d33ee9e1bdcb1618401f90d8a3cdeef0 --- /dev/null +++ b/src/components/annoucements/AnnouncementList.jsx @@ -0,0 +1,23 @@ +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; diff --git a/src/components/posts/Post.jsx b/src/components/posts/Post.jsx new file mode 100644 index 0000000000000000000000000000000000000000..a85ca959b633030210a0962f32dd71c89e3c46bd --- /dev/null +++ b/src/components/posts/Post.jsx @@ -0,0 +1,140 @@ +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; diff --git a/src/components/posts/PostList.jsx b/src/components/posts/PostList.jsx new file mode 100644 index 0000000000000000000000000000000000000000..8a2ee972e6b0fb2be2523f0828c59a64c5b5fdf9 --- /dev/null +++ b/src/components/posts/PostList.jsx @@ -0,0 +1,27 @@ +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; diff --git a/src/containers/AnnoucementsContainer.jsx b/src/containers/AnnoucementsContainer.jsx new file mode 100644 index 0000000000000000000000000000000000000000..4c592d1e7193a01fcddad433029d86eee7c726ad --- /dev/null +++ b/src/containers/AnnoucementsContainer.jsx @@ -0,0 +1,61 @@ +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; diff --git a/src/containers/PostsContainer.jsx b/src/containers/PostsContainer.jsx new file mode 100644 index 0000000000000000000000000000000000000000..e7dd22cf6a93ca727196d680cd002938c3db8f88 --- /dev/null +++ b/src/containers/PostsContainer.jsx @@ -0,0 +1,216 @@ +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; diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index ab2a061dd74c42268bd57d83a10d3f4022041f9e..48c1b810db89a09893ea049933e39166995af831 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -1,9 +1,154 @@ 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> </> ); }; diff --git a/src/stores.js b/src/stores.js index 0590a6309a9ddc538adea96edf1f881a007c43d1..cda15d259c0460aa45970bd53b9f877473dc9df8 100644 --- a/src/stores.js +++ b/src/stores.js @@ -1,10 +1,23 @@ 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); diff --git a/typings/cf2021.d.ts b/typings/cf2021.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..4dd762b6e080350c0db9f41e2c6fd1ce2a83d20e --- /dev/null +++ b/typings/cf2021.d.ts @@ -0,0 +1,87 @@ +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[]; + } +}