From 71ed96c64557799958d7d9b5bb0fba8185e56dff Mon Sep 17 00:00:00 2001 From: Romain Date: Fri, 2 Oct 2020 09:43:20 +0200 Subject: [PATCH] Adding roadmap to the platform. --- api/api/feature/config/routes.json | 52 ++++++++++++ api/api/feature/controllers/feature.js | 8 ++ api/api/feature/models/feature.js | 8 ++ api/api/feature/models/feature.settings.json | 29 +++++++ api/api/feature/services/feature.js | 8 ++ api/config/functions/bootstrap.js | 1 + components/Header.js | 5 ++ components/Modal.js | 18 +++-- components/cards/FeatureCard.js | 84 ++++++++++++++++++++ pages/roadmap.js | 83 +++++++++++++++++++ services/community.js | 26 ++++++ 11 files changed, 317 insertions(+), 5 deletions(-) create mode 100644 api/api/feature/config/routes.json create mode 100644 api/api/feature/controllers/feature.js create mode 100644 api/api/feature/models/feature.js create mode 100644 api/api/feature/models/feature.settings.json create mode 100644 api/api/feature/services/feature.js create mode 100644 components/cards/FeatureCard.js create mode 100644 pages/roadmap.js diff --git a/api/api/feature/config/routes.json b/api/api/feature/config/routes.json new file mode 100644 index 0000000..288ad4a --- /dev/null +++ b/api/api/feature/config/routes.json @@ -0,0 +1,52 @@ +{ + "routes": [ + { + "method": "GET", + "path": "/features", + "handler": "feature.find", + "config": { + "policies": [] + } + }, + { + "method": "GET", + "path": "/features/count", + "handler": "feature.count", + "config": { + "policies": [] + } + }, + { + "method": "GET", + "path": "/features/:id", + "handler": "feature.findOne", + "config": { + "policies": [] + } + }, + { + "method": "POST", + "path": "/features", + "handler": "feature.create", + "config": { + "policies": [] + } + }, + { + "method": "PUT", + "path": "/features/:id", + "handler": "feature.update", + "config": { + "policies": [] + } + }, + { + "method": "DELETE", + "path": "/features/:id", + "handler": "feature.delete", + "config": { + "policies": [] + } + } + ] +} diff --git a/api/api/feature/controllers/feature.js b/api/api/feature/controllers/feature.js new file mode 100644 index 0000000..a589b84 --- /dev/null +++ b/api/api/feature/controllers/feature.js @@ -0,0 +1,8 @@ +'use strict'; + +/** + * Read the documentation (https://strapi.io/documentation/v3.x/concepts/controllers.html#core-controllers) + * to customize this controller + */ + +module.exports = {}; diff --git a/api/api/feature/models/feature.js b/api/api/feature/models/feature.js new file mode 100644 index 0000000..319ea80 --- /dev/null +++ b/api/api/feature/models/feature.js @@ -0,0 +1,8 @@ +'use strict'; + +/** + * Read the documentation (https://strapi.io/documentation/v3.x/concepts/models.html#lifecycle-hooks) + * to customize this model + */ + +module.exports = {}; diff --git a/api/api/feature/models/feature.settings.json b/api/api/feature/models/feature.settings.json new file mode 100644 index 0000000..e159812 --- /dev/null +++ b/api/api/feature/models/feature.settings.json @@ -0,0 +1,29 @@ +{ + "kind": "collectionType", + "collectionName": "features", + "info": { + "name": "Feature", + "description": "A feature to be added to the roadmap" + }, + "options": { + "increments": true, + "timestamps": true + }, + "attributes": { + "name": { + "type": "string", + "description": "The name/title of the feature", + "required": true + }, + "stage": { + "type": "enumeration", + "enum": ["under_consideration", "planned", "in_development", "launched"], + "default": "under_consideration" + }, + "description": { + "type": "text", + "description": "Complete description of the wanted feature", + "required": true + } + } +} diff --git a/api/api/feature/services/feature.js b/api/api/feature/services/feature.js new file mode 100644 index 0000000..1f5330e --- /dev/null +++ b/api/api/feature/services/feature.js @@ -0,0 +1,8 @@ +'use strict'; + +/** + * Read the documentation (https://strapi.io/documentation/v3.x/concepts/services.html#core-services) + * to customize this service + */ + +module.exports = {}; diff --git a/api/config/functions/bootstrap.js b/api/config/functions/bootstrap.js index f289ab1..d211a02 100644 --- a/api/config/functions/bootstrap.js +++ b/api/config/functions/bootstrap.js @@ -86,6 +86,7 @@ module.exports = async () => { "library-media", "category", "faq", + "feature", "help-section", "library-section", "user", diff --git a/components/Header.js b/components/Header.js index 1a53694..7327c81 100644 --- a/components/Header.js +++ b/components/Header.js @@ -175,6 +175,11 @@ export default function Header() { slug: "library", id: 7, }, + { + label: "Roadmap", + slug: "roadmap", + id: 8, + }, ]; return ( diff --git a/components/Modal.js b/components/Modal.js index 97f98f7..ea7904b 100644 --- a/components/Modal.js +++ b/components/Modal.js @@ -3,6 +3,7 @@ import React from "react"; import ReactDOM from "react-dom"; import styled from "styled-components"; import Close from "../public/icons/close.svg"; +import Breaks from "../utils/breakpoints"; import Card from "./cards/Card"; import Icon from "./Icon"; @@ -28,24 +29,31 @@ const ModalContainer = styled(Card)` && { padding: 0; height: unset; - width: unset; + width: 740px; display: flex; flex-direction: column; - align-items: center; - justify-content: center; + + @media screen and (max-width: ${Breaks.large}) { + width: 680px; + min-height: 470px; + } + + @media screen and (max-width: ${Breaks.medium}) { + width: 100%; + height: 100%; + } } & p { - max-width: 512px; margin-bottom: 1rem; } `; const ModalHead = styled.div` display: flex; - align-items: center; + background-color: #252525; border-bottom: 1px solid ${(props) => props.theme.grey}; width: 100%; diff --git a/components/cards/FeatureCard.js b/components/cards/FeatureCard.js new file mode 100644 index 0000000..c33faaf --- /dev/null +++ b/components/cards/FeatureCard.js @@ -0,0 +1,84 @@ +import { format, parseISO } from "date-fns"; +import PropTypes from "prop-types"; +import React from "react"; +import styled from "styled-components"; +import useModal from "../../utils/useModal"; +import Modal from "../Modal"; +import Card from "./Card"; + +const Container = styled(Card)` + flex-flow: column; + + text-align: start; +`; + +const Name = styled.b` + width: 100%; + max-width: 100%; + + max-height: 1.5em; + + margin: 0 0 1em 0; + + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + +const Description = styled.p` + display: -webkit-box; + -webkit-line-clamp: 6; + -webkit-box-orient: vertical; + + height: 9em; + max-height: 9em; + + font-size: 15px; + overflow: hidden; +`; + +const FeatureMetaData = styled.div` + display: flex; + align-items: center; + + font-size: 14px; + line-height: 1.5; + text-transform: uppercase; + + color: ${(props) => props.theme.primary}; +`; + +export default function FeatureCard({ className, feature }) { + const { show, toggle } = useModal(); + const date = parseISO(feature.created_at); + + return ( + <> + + +

+ ID {feature.id} | Posted on {format(date, "MMMM dd, yyyy")} +

+
+ +

{feature.description}

+
+ + toggle()} className={className}> + {feature.name} + {feature.description} + + + ); +} + +FeatureCard.propTypes = { + /** + * Object containing the feature details + */ + feature: PropTypes.object.isRequired, + /** + * Styling class of the container. Used by styled-components. + */ + className: PropTypes.string, +}; diff --git a/pages/roadmap.js b/pages/roadmap.js new file mode 100644 index 0000000..ae5a5bf --- /dev/null +++ b/pages/roadmap.js @@ -0,0 +1,83 @@ +import React from "react"; +import styled from "styled-components"; +import FeatureCard from "../components/cards/FeatureCard"; +import ItemsGrid from "../components/grids/ItemsGrid"; +import MainLayout from "../components/layouts/MainLayout"; +import { getAllFeatures } from "../services/community"; +import { REVALIDATION_DELAY } from "../utils/settings"; +import useTabs from "../utils/useTabs"; + +const CardGrid = styled(ItemsGrid)` + && { + grid-template-rows: unset; + } +`; + +const TabsHolder = styled.div` + padding: 1rem 0 0 0; +`; + +export default function Roadmap({ features }) { + const { Tabs, currentTab } = useTabs([ + "Under Consideration", + "Planned", + "In Development", + "Launched", + ]); + + return ( + + + + + + + {currentTab === "Under Consideration" && ( + <> + {features.under_consideration.map((feature) => ( + + ))} + + )} + {currentTab === "Planned" && ( + <> + {features.planned.map((feature) => ( + + ))} + + )} + {currentTab === "In Development" && ( + <> + {features.in_development.map((feature) => ( + + ))} + + )} + {currentTab === "Launched" && ( + <> + {features.launched.map((feature) => ( + + ))} + + )} + + + ); +} + +export async function getStaticProps() { + const res = await getAllFeatures(); + + return { + props: { + features: res.data || [], + }, + revalidate: REVALIDATION_DELAY, + }; +} diff --git a/services/community.js b/services/community.js index f0e5b65..b0af884 100644 --- a/services/community.js +++ b/services/community.js @@ -747,6 +747,32 @@ export async function search({ return getQuery(graphQLQuery); } +/********************************************** + * FEATURES * + **********************************************/ + +const featureModel = `{ + id + created_at + stage + name + description + }`; + +/** + * Get all the features from the API + */ +export async function getAllFeatures() { + const graphQLQuery = `{ + under_consideration: features(where: { stage: "under_consideration" }) ${featureModel} + planned: features(where: { stage: "planned" }) ${featureModel} + in_development: features(where: { stage: "in_development" }) ${featureModel} + launched: features(where: { stage: "launched" }) ${featureModel} + }`; + + return getQuery(graphQLQuery); +} + /********************************************** * USERS GRAPHS * **********************************************/