From 9c16e90b15654e05d2d2bd5d86bd885b040271d7 Mon Sep 17 00:00:00 2001 From: Quentin Aubert Date: Wed, 23 Aug 2023 11:22:17 +0200 Subject: [PATCH] #538 - wip - remove modals and display new env. values in UI --- .../frontend/api/ApiDocumentation.tsx | 33 +++-- .../src/components/frontend/api/ApiHome.tsx | 2 +- .../components/frontend/api/ApiPricing.tsx | 135 ++++++++++-------- .../frontend/fastMode/FastApiCard.tsx | 1 - .../javascript/src/components/utils/Option.ts | 30 ++-- 5 files changed, 110 insertions(+), 91 deletions(-) diff --git a/daikoku/javascript/src/components/frontend/api/ApiDocumentation.tsx b/daikoku/javascript/src/components/frontend/api/ApiDocumentation.tsx index f9efb652c..80b4674fe 100644 --- a/daikoku/javascript/src/components/frontend/api/ApiDocumentation.tsx +++ b/daikoku/javascript/src/components/frontend/api/ApiDocumentation.tsx @@ -1,20 +1,17 @@ /* eslint-disable react/display-name */ +import { useQuery, useQueryClient } from '@tanstack/react-query'; import asciidoctor from 'asciidoctor'; +import classNames from 'classnames'; import hljs from 'highlight.js'; import { useContext, useEffect, useState } from 'react'; -import { Link, useMatch, useMatches, useParams } from 'react-router-dom'; -import { useQuery, useQueryClient } from '@tanstack/react-query'; -import classNames from 'classnames'; -import {Option} from '../../utils'; import { I18nContext } from '../../../core'; -import * as Services from '../../../services'; import { converter } from '../../../services/showdown'; - -import { IApi, IDocPage, IDocumentation, IDocumentationPages, ResponseError, isError } from '../../../types'; +import { IDocPage, IDocumentation, IDocumentationPages, ResponseError, isError } from '../../../types'; import { Spinner } from '../../utils'; import 'highlight.js/styles/monokai.css'; +import { ParamKeyValuePair, useSearchParams } from 'react-router-dom'; const asciidoctorConverter = asciidoctor(); @@ -33,7 +30,7 @@ export const ApiDocumentationCartidge = (props: ApiDocumentationCartidgeProps) = {pages.map((page) => { return (
  • - props.goTo(page.id)}> + props.goTo(page.id)}> {page.title} {renderLinks(page.children, level + 1)} @@ -107,7 +104,10 @@ type ApiDocumentationProps = { export const ApiDocumentation = (props: ApiDocumentationProps) => { const { Translation } = useContext(I18nContext); - const [pageId, setPageId] = useState(props.documentation?.pages[0].id) + const [searchParams, setSearchParams] = useSearchParams(); + + const page = searchParams.get('page'); + const [pageId, setPageId] = useState(page || props.documentation?.pages[0].id); const flattenDoc = (pages?: IDocumentationPages): Array => { if (!pages) { @@ -117,14 +117,21 @@ export const ApiDocumentation = (props: ApiDocumentationProps) => { } } + useEffect(() => { + if (pageId) { + setSearchParams({page: pageId}) + } + }, [pageId]) + + const orderedPages = flattenDoc(props.documentation?.pages) const idx = orderedPages.findIndex(p => p === pageId) const next = orderedPages[idx + (pageId ? 1 : 2)]; const prev = orderedPages[idx - 1]; - return (<> - + return (
    +
    { )}
    - +
    - ); +
    ); } const TypeNotSupportedYet = () =>

    Content type not supported yet !

    ; diff --git a/daikoku/javascript/src/components/frontend/api/ApiHome.tsx b/daikoku/javascript/src/components/frontend/api/ApiHome.tsx index 5b435427c..160bac96b 100644 --- a/daikoku/javascript/src/components/frontend/api/ApiHome.tsx +++ b/daikoku/javascript/src/components/frontend/api/ApiHome.tsx @@ -138,7 +138,7 @@ export const ApiHome = ({ const navigate = useNavigate(); const defaultParams = useParams(); - const apiGroupMatch = useMatch('/:teamId/apigroups/:apiGroupId/apis/:apiId/:versionId/:tab'); + const apiGroupMatch = useMatch('/:teamId/apigroups/:apiGroupId/apis/:apiId/:versionId/:tab*'); const params = Option(apiGroupMatch) .map((match: any) => match.params) .getOrElse(defaultParams); diff --git a/daikoku/javascript/src/components/frontend/api/ApiPricing.tsx b/daikoku/javascript/src/components/frontend/api/ApiPricing.tsx index 206b3311a..35b33f906 100644 --- a/daikoku/javascript/src/components/frontend/api/ApiPricing.tsx +++ b/daikoku/javascript/src/components/frontend/api/ApiPricing.tsx @@ -1,31 +1,31 @@ import { getApolloContext } from '@apollo/client'; -import { constraints, format, type as formType } from '@maif/react-forms'; +import { useQuery } from '@tanstack/react-query'; +import classNames from 'classnames'; import difference from 'lodash/difference'; import find from 'lodash/find'; -import React, { useContext, useEffect } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; +import { Link, useMatch, useNavigate, useParams, useSearchParams } from 'react-router-dom'; import { ModalContext } from '../../../contexts'; import { I18nContext } from '../../../core'; import * as Services from '../../../services'; import { currencies } from '../../../services/currencies'; -import { IApi, IBaseUsagePlan, isError, isMiniFreeWithQuotas, IState, IStateContext, ISubscription, ISubscriptionDemand, ISubscriptionWithApiInfo, isValidationStepTeamAdmin, ITeamSimple, IUsagePlan, IUsagePlanFreeWithQuotas, IUsagePlanPayPerUse, IUsagePlanQuotasWithLimits, IUsagePlanQuotasWitoutLimit, IValidationStepTeamAdmin } from '../../../types'; -import { INotification } from '../../../types'; +import { + IApi, IBaseUsagePlan, isError, + isMiniFreeWithQuotas, IState, IStateContext, ISubscription, + ISubscriptionDemand, ISubscriptionWithApiInfo, isValidationStepTeamAdmin, + ITeamSimple, IUsagePlan +} from '../../../types'; import { access, - api, - apikey, Can, isPublish, isSubscriptionProcessIsAutomatic, manage, + apikey, Can, isPublish, isSubscriptionProcessIsAutomatic, Option, queryClient, renderPlanInfo, renderPricing, Spinner } from '../../utils'; -import { ActionWithTeamSelector } from '../../utils/ActionWithTeamSelector'; import { formatPlanType } from '../../utils/formatters'; -import classNames from 'classnames'; -import { Link, useNavigate } from 'react-router-dom'; -import { is } from 'cypress/types/bluebird'; -import { useQuery } from '@tanstack/react-query'; +import { ApiDocumentation } from './ApiDocumentation'; import { ApiRedoc } from './ApiRedoc'; import { ApiSwagger } from './ApiSwagger'; -import { ApiDocumentation } from './ApiDocumentation'; export const currency = (plan?: IBaseUsagePlan) => { if (!plan) { @@ -160,32 +160,6 @@ const ApiPricingCard = (props: ApiPricingCardProps) => { }) } - const openDocumentationModal = (title: string) => { - openCustomModal({ - title, - content: Services.getUsagePlanDocPage(props.api._id, props.plan._id, pageId)} /> - }) - } - - const openSwaggerModal = (title: string) => { - openCustomModal({ - title, - content: - }) - } - - const openTestModal = (title: string) => { - openCustomModal({ - title, - content: - }) - } - return (
    @@ -199,9 +173,9 @@ const ApiPricingCard = (props: ApiPricingCardProps) => {

    {tenant.display === 'environment' && (
    - - - + swagger + test + Documentation
    )}
    @@ -349,6 +323,11 @@ type ApiPricingProps = { export const ApiPricing = (props: ApiPricingProps) => { const usagePlansQuery = useQuery(['plans'], () => Services.getVisiblePlans(props.api._id, props.api.currentVersion)) + const match = useMatch('/:team/:api/:version/pricing/:env/:tab') + + const maybeTab = match?.params.tab + const maybeEnv = match?.params.env + useEffect(() => { queryClient.invalidateQueries(['plans']) }, [props.api]) @@ -364,28 +343,58 @@ export const ApiPricing = (props: ApiPricingProps) => { props.myTeams.some((team) => plan.authorizedTeams.includes(team._id)); }); - return ( -
    - {possibleUsagePlans - .sort((a, b) => (a.customName || a.type).localeCompare(b.customDescription || b.type)) - .map((plan) => - subs.api === props.api._id && subs.plan === plan._id - )} - inProgressDemands={props.inProgressDemands.filter( - (demand) => demand.api === props.api._id && demand.plan === plan._id - )} - askForApikeys={props.askForApikeys} - /> - )} -
    - ); + if (maybeEnv && maybeTab) { + const plan = usagePlansQuery.data.find(p => p.customName === maybeEnv)! + return ( +
    +
    + + + +
    {plan.customName}
    +
    +
    + swagger + test + Documentation +
    +
    + {maybeTab === 'swagger' && } + {maybeTab === 'documentation' && Services.getUsagePlanDocPage(props.api._id, plan._id, pageId)} />} + {maybeTab === 'testing' && } +
    +
    + ) + } else { + return ( +
    + {possibleUsagePlans + .sort((a, b) => (a.customName || a.type).localeCompare(b.customDescription || b.type)) + .map((plan) => + subs.api === props.api._id && subs.plan === plan._id + )} + inProgressDemands={props.inProgressDemands.filter( + (demand) => demand.api === props.api._id && demand.plan === plan._id + )} + askForApikeys={props.askForApikeys} + /> + )} +
    + ); + } + } else { return
    } diff --git a/daikoku/javascript/src/components/frontend/fastMode/FastApiCard.tsx b/daikoku/javascript/src/components/frontend/fastMode/FastApiCard.tsx index 5ca729ea6..adc68b62c 100644 --- a/daikoku/javascript/src/components/frontend/fastMode/FastApiCard.tsx +++ b/daikoku/javascript/src/components/frontend/fastMode/FastApiCard.tsx @@ -45,7 +45,6 @@ export const FastApiCard = (props: FastApiCardProps) => { : Services.askForApiKey(apiId, team._id, plan._id, motivation) const adminStep = plan.subscriptionProcess.find(s => isValidationStepTeamAdmin(s)) - console.debug({adminStep, plan}) if (adminStep && isValidationStepTeamAdmin(adminStep)) { openFormModal<{ motivation: string }>({ title: translate('motivations.modal.title'), diff --git a/daikoku/javascript/src/components/utils/Option.ts b/daikoku/javascript/src/components/utils/Option.ts index c6a8303d4..0bcc62cc1 100644 --- a/daikoku/javascript/src/components/utils/Option.ts +++ b/daikoku/javascript/src/components/utils/Option.ts @@ -1,18 +1,22 @@ -// export interface TOption { -// map(f: (a: A) => B): TOption; -// flatMap(f: (a: A) => TOption): TOption; -// fold(ifEmpty: B, f: (a: A) => B): B; -// orElse(ob: TOption): TOption; -// getOrElse(a: B): A; -// getOrNull(): A | undefined -// isDefined: boolean; -// exists(f: (a: A) => boolean): boolean; -// filter(f: (a: A) => boolean): TOption; -// } +interface ISome extends IOption {} +interface INone extends IOption {} -export const Option = (x) => (x === undefined || x === null ? None : Some(x)); +export interface IOption { + map(f: (a: A) => B): ISome | INone; + flatMap(f: (a: A) => IOption): IOption | INone; + fold(ifEmpty: B, f: (a: A) => B): B; + orElse(ob: IOption): IOption | IOption | INone; + getOrElse(b: B): A | B; + getOrNull(): A | undefined + isDefined: boolean; + exists(f: (a: A) => boolean): boolean; + filter(f: (a: A) => boolean): IOption | INone; +} -export const Some = (x) => ({ + +export const Option = (x: T) => (x === undefined || x === null ? None : Some(x)); + +export const Some = (x: T) => ({ map: (f) => Option(f(x)), flatMap: (f) => f(x), fold: (_ifEmpty, f) => f(x),