From 90467920ee3985cff5e5809d1f66c1b63b608918 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20D=C3=ADaz=20Gonz=C3=A1lez?= Date: Fri, 17 Jan 2025 17:38:05 +0000 Subject: [PATCH] feat(web): allow displaying a product license Apart from displaying only available languages, still pending interface improvements and unit tests. --- web/src/api/software.ts | 17 ++++- web/src/components/product/LicenseDialog.tsx | 78 +++++++++++--------- web/src/queries/software.ts | 21 ++++++ web/src/types/software.ts | 16 ++++ 4 files changed, 97 insertions(+), 35 deletions(-) diff --git a/web/src/api/software.ts b/web/src/api/software.ts index 38a6f72bd9..568a0db011 100644 --- a/web/src/api/software.ts +++ b/web/src/api/software.ts @@ -1,5 +1,5 @@ /* - * Copyright (c) [2024] SUSE LLC + * Copyright (c) [2024-2025] SUSE LLC * * All Rights Reserved. * @@ -26,6 +26,8 @@ import { SoftwareConfig, RegistrationInfo, SoftwareProposal, + License, + LicenseContent, } from "~/types/software"; import { get, post, put } from "~/api/http"; @@ -44,6 +46,17 @@ const fetchProposal = (): Promise => get("/api/software/propos */ const fetchProducts = (): Promise => get("/api/software/products"); +/** + * Returns the list of available licenses + */ +const fetchLicenses = (): Promise => get("/api/software/licenses"); + +/** + * Returns the content for given license id + */ +const fetchLicense = (id: string, lang: string = "en"): Promise => + get(`/api/software/licenses/${id}?lang=${lang}`); + /** * Returns an object with the registration info */ @@ -72,6 +85,8 @@ export { fetchPatterns, fetchProposal, fetchProducts, + fetchLicenses, + fetchLicense, fetchRegistration, updateConfig, register, diff --git a/web/src/components/product/LicenseDialog.tsx b/web/src/components/product/LicenseDialog.tsx index c6d2933501..1a38c6cf29 100644 --- a/web/src/components/product/LicenseDialog.tsx +++ b/web/src/components/product/LicenseDialog.tsx @@ -20,11 +20,10 @@ * find current contact information at www.suse.com. */ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { Popup } from "~/components/core"; import { _ } from "~/i18n"; import { - Divider, MenuToggle, ModalProps, Select, @@ -35,51 +34,62 @@ import { } from "@patternfly/react-core"; import { Product } from "~/types/software"; import { sprintf } from "sprintf-js"; +import { fetchLicense } from "~/api/software"; +import { useInstallerL10n } from "~/context/installerL10n"; +import supportedLanguages from "~/languages.json"; function LicenseDialog({ onClose, product }: { onClose: ModalProps["onClose"]; product: Product }) { - const [locale, setLocale] = useState("en"); - const [localeSelectorOpen, setLocaleSelectorOpen] = useState(false); - const locales = ["en", "es", "de", "cz", "pt"]; + const { language: uiLanguage } = useInstallerL10n(); + const [language, setLanguage] = useState(uiLanguage); + const [license, setLicense] = useState(); + const [languageSelectorOpen, setLanguageSelectorOpen] = useState(false); const localesToggler = (toggleRef) => ( setLocaleSelectorOpen(!localeSelectorOpen)} - isExpanded={localeSelectorOpen} + onClick={() => setLanguageSelectorOpen(!languageSelectorOpen)} + isExpanded={languageSelectorOpen} > - {locale} + {supportedLanguages[language]} ); - const onLocaleSelection = (_, locale: string) => { - setLocale(locale); - setLocaleSelectorOpen(false); - }; + useEffect(() => { + language && fetchLicense(product.licenseId, language).then(({ body }) => setLicense(body)); + }, [language, product.licenseId]); - const eula = "Lorem ipsum"; + const onLocaleSelection = (_, lang: string) => { + setLanguage(lang); + setLanguageSelectorOpen(false); + }; return ( - + + + +

{sprintf(_("License for %s"), product.name)}

+
+ +
+ + } + > - - -

{sprintf(_("License for %s"), product.name)}

-
- -
- - {eula} +
{license}
{_("Close")} diff --git a/web/src/queries/software.ts b/web/src/queries/software.ts index 72afd692e7..2aea9510e0 100644 --- a/web/src/queries/software.ts +++ b/web/src/queries/software.ts @@ -24,12 +24,14 @@ import React from "react"; import { useMutation, useQueries, + useQuery, useQueryClient, useSuspenseQueries, useSuspenseQuery, } from "@tanstack/react-query"; import { useInstallerClient } from "~/context/installer"; import { + License, Pattern, PatternsSelection, Product, @@ -40,6 +42,7 @@ import { } from "~/types/software"; import { fetchConfig, + fetchLicenses, fetchPatterns, fetchProducts, fetchProposal, @@ -75,6 +78,15 @@ const productsQuery = () => ({ staleTime: Infinity, }); +/** + * Query to retrieve available licenses + */ +const licensesQuery = () => ({ + queryKey: ["software/licenses"], + queryFn: fetchLicenses, + staleTime: Infinity, +}); + /** * Query to retrieve selected product */ @@ -166,6 +178,14 @@ const useProduct = ( }; }; +/** + * Returns available products and selected one, if any + */ +const useLicenses = (): { licenses: License[]; isPending: boolean } => { + const { data: licenses, isPending } = useQuery(licensesQuery()); + return { licenses, isPending }; +}; + /** * Returns a list of patterns with their selectedBy property properly set based on current proposal. */ @@ -261,6 +281,7 @@ export { useConfigMutation, usePatterns, useProduct, + useLicenses, useProductChanges, useProposal, useProposalChanges, diff --git a/web/src/types/software.ts b/web/src/types/software.ts index adb749021d..5a0f2d0651 100644 --- a/web/src/types/software.ts +++ b/web/src/types/software.ts @@ -47,6 +47,20 @@ type Product = { licenseId?: string; }; +type License = { + /** License ID (e.g., "license.sle") */ + id: string; + /** Available locales */ + languages: string[]; +}; + +type LicenseContent = { + /** License ID (e.g., "license.sle") */ + id: string; + /** License body */ + body: string; +}; + type PatternsSelection = { [key: string]: SelectedBy }; type SoftwareProposal = { @@ -90,6 +104,8 @@ export type { Pattern, PatternsSelection, Product, + License, + LicenseContent, SoftwareConfig, RegistrationInfo, SoftwareProposal,