Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions frontend-web/webclient/app/DefaultObjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import {SidebarStateProps} from "./Applications/Redux/Reducer";
import {getUserThemePreference} from "./UtilityFunctions";
import {defaultAvatar} from "./AvataaarLib";
import {HookStore} from "./Utilities/ReduxHooks";
import {ProviderBrandingResponse} from "./UCloud/ProviderBrandingApi";
import {initProviderBranding} from "./ProviderBrandings/AutomaticProviderBranding";
import {BrandingResponse} from "./UCloud/BrandingApi";
import {initBranding} from "./Applications/Branding/AutomaticBranding";
import {SidebarTabId} from "./ui-components/SidebarComponents";
Expand All @@ -27,7 +25,6 @@ export interface LegacyReduxObject {
avatar: AvatarReduxObject;
project: ProjectRedux.State;
terminal: TerminalState;
providerBrandings: ProviderBrandingResponse;
branding: BrandingResponse
popinChild: PopInArgs;
sidebar: SidebarStateProps;
Expand Down Expand Up @@ -59,7 +56,6 @@ export function initObject(): ReduxObject {
avatar: initAvatar(),
project: ProjectRedux.initialState,
terminal: initTerminalState(),
providerBrandings: initProviderBranding(),
branding: initBranding(),
popinChild: {el: undefined},
sidebar: {favorites: [], theme: getThemeOrDefaultValue()}
Expand Down
73 changes: 36 additions & 37 deletions frontend-web/webclient/app/Grants/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ import {useLocation, useNavigate} from "react-router-dom";
import * as Grants from ".";
import {State} from ".";
import {ChangeOrganizationDetails, OptionalInfo, optionalInfoRequest, optionalInfoUpdate} from "@/UserSettings/ChangeUserDetails";
import {useSelector} from "react-redux";
import {sendFailureNotification, sendSuccessNotification} from "@/Notifications";
import {providerBrandingStore} from "@/ProviderBrandings/AutomaticProviderBranding";

// State model
// =====================================================================================================================
Expand Down Expand Up @@ -166,25 +166,25 @@ const defaultState: EditorState = {
type EditorAction =
| {type: "GrantLoaded", grant: Grants.Application, wallets: Accounting.WalletV2[]}
| {
type: "GrantGiverInitiatedLoaded",
wallets: Accounting.WalletV2[],
start: number,
end: number,
title: string,
projectId?: string,
piUsernameHint: string
}
type: "GrantGiverInitiatedLoaded",
wallets: Accounting.WalletV2[],
start: number,
end: number,
title: string,
projectId?: string,
piUsernameHint: string
}
| {type: "AllocatorsLoaded", allocators: Grants.GrantGiver[], recipientType?: Grants.Recipient["type"]}
| {type: "DurationUpdated", month?: number, year?: number, duration?: number}
| {type: "DurationWarning", durationWarning: string}
| {type: "AllocatorChecked", isChecked: boolean, allocatorId: string}
| {
type: "BalanceUpdated",
provider: string,
category: string,
allocator: string,
balance: number | null,
}
type: "BalanceUpdated",
provider: string,
category: string,
allocator: string,
balance: number | null,
}
| {type: "SetIsCreating", stateDuringCreate?: EditorState["stateDuringCreate"]}
| {type: "RecipientUpdated", isCreatingNewProject: boolean, reference?: string}
| {type: "ProjectsReloaded", projects: {id: string | null, title: string}[]}
Expand Down Expand Up @@ -1438,7 +1438,6 @@ export function Editor(): React.ReactNode {
const isForSubAllocator = getQueryParam(location.search, "subAllocator") == "true";
useProjectId(); // FIXME(Jonas): Is this some refresh-thing that breaks stuff if you remove it?

const providerBrandingData = useSelector((it: ReduxObject) => it.providerBrandings);
const [missingUserInfo, setMissingUserInfo] = React.useState(false);
React.useEffect(() => {
(async () => {
Expand Down Expand Up @@ -1937,8 +1936,8 @@ export function Editor(): React.ReactNode {
state.fullScreenLoading ? <>
<HexSpin size={64} />
</> : state.fullScreenError ? <>
{state.fullScreenError}
</> :
{state.fullScreenError}
</> :
<Box mx="auto" className={classes.join(" ")}>
<header className={"at-top"}>
<h3 className="title">Information about your project</h3>
Expand All @@ -1962,8 +1961,8 @@ export function Editor(): React.ReactNode {
{!state.locked && <>
{!isGrantGiverInitiated &&
<ConfirmationButton actionText={"Discard changes"} icon={"heroTrash"}
color={"errorMain"}
onAction={onDiscard} />
color={"errorMain"}
onAction={onDiscard} />
}

<Button onClick={validateThenUpdate} type={"button"} color={"successMain"}>
Expand All @@ -1976,8 +1975,8 @@ export function Editor(): React.ReactNode {

{!isClosed && state.stateDuringEdit.allowWithdrawal && state.locked && <>
<ConfirmationButton actionText={"Withdraw application"} icon={"heroTrash"}
color={"errorMain"}
onAction={onWithdraw} />
color={"errorMain"}
onAction={onWithdraw} />
</>}
</>}

Expand Down Expand Up @@ -2040,20 +2039,20 @@ export function Editor(): React.ReactNode {
<label>
{state.stateDuringCreate.creatingWorkspace && <>
New project (<a className={BaseLinkClass} href="#" onClick={() => switchToExistingProject(state)}>
select an existing project instead
</a>)
select an existing project instead
</a>)
<Input id={FormIds.title}
placeholder={"Please enter the title of your project"}
height="42px"
value={state.stateDuringCreate.reference ?? ""}
onInput={onNewProjectInput} required />
placeholder={"Please enter the title of your project"}
height="42px"
value={state.stateDuringCreate.reference ?? ""}
onInput={onNewProjectInput} required />
</>}
{!state.stateDuringCreate.creatingWorkspace && <>
Existing project (<a href="#" className={BaseLinkClass} onClick={switchToNewProject}>
create a new project instead
</a>)
create a new project instead
</a>)
<Select value={state.stateDuringCreate.reference || "null"}
onChange={onProjectSelected}>
onChange={onProjectSelected}>
{state.loadedProjects.map(workspace =>
<React.Fragment key={workspace.id ?? "null"}>
<option value={workspace.id ?? "null"}>
Expand Down Expand Up @@ -2129,9 +2128,9 @@ export function Editor(): React.ReactNode {
{referenceIdsToShow.map((id, idx) => <label key={idx}>
Reference ID #{idx + 1}
<Input id={FormIds.deicId + "-" + idx}
disabled={state.locked || !state?.stateDuringEdit?.wallets?.length}
placeholder={state.locked ? "None specified" : "DeiC-SDU-L1-0000"}
value={id} onInput={onReferenceIdInput} onBlur={onReferenceBlur} />
disabled={state.locked || !state?.stateDuringEdit?.wallets?.length}
placeholder={state.locked ? "None specified" : "DeiC-SDU-L1-0000"}
value={id} onInput={onReferenceIdInput} onBlur={onReferenceBlur} />
</label>)}
</FormField>
</>}
Expand Down Expand Up @@ -2266,7 +2265,7 @@ export function Editor(): React.ReactNode {

if (hideZeroFields && !anyNonZeroValues) return null;

const currentProvider = providerBrandingData.providers[providerId];
const currentProvider = providerBrandingStore.getSnapshot().providers[providerId];
const productDescription = currentProvider?.productDescription?.find(it => it.category === category.category.name);
const showDescriptions = productDescription != undefined;

Expand Down Expand Up @@ -2600,7 +2599,7 @@ const CommentSection: React.FunctionComponent<{
<div className="wrapper">
<UserAvatar avatar={avatars.avatar(Client.username!)} width={"48px"} />
<TextArea inputRef={textAreaRef} rows={3} disabled={props.disabled}
placeholder={"Your comment"} onKeyDown={onKeyDown} />
placeholder={"Your comment"} onKeyDown={onKeyDown} />
</div>

<div className="buttons">
Expand Down Expand Up @@ -2838,7 +2837,7 @@ const FormField: React.FunctionComponent<{
return <>
<div>
<label htmlFor={props.id}
className={`section ${props.showDescriptionInEditMode === false ? "optional" : ""}`}>
className={`section ${props.showDescriptionInEditMode === false ? "optional" : ""}`}>
{props.icon && <Icon name={props.icon} mr={"8px"} size={30} />}
{props.title}
{props.mandatory && <span className={"mandatory"} />}
Expand Down
8 changes: 4 additions & 4 deletions frontend-web/webclient/app/Products/Selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ import {useUState} from "@/Utilities/UState";
import {clamp, grantsLink, stopPropagation} from "@/UtilityFunctions";
import {ProductSupport, ResolvedSupport} from "@/UCloud/ResourceApi";
import {explainMaintenance, maintenanceIconColor, shouldAllowMaintenanceAccess} from "@/Products/Maintenance";
import {classConcat, injectStyle, injectStyleSimple} from "@/Unstyled";
import {classConcat, injectStyle} from "@/Unstyled";
import {MandatoryField, NoResultsBody} from "@/UtilityComponents";
import {ComputeSupport, JobQueueStatus} from "@/UCloud/JobsApi";
import {ThemeColor} from "@/ui-components/theme";
import {TooltipV2} from "@/ui-components/Tooltip";
import {useSelector} from "react-redux";
import {ServiceProviderItem, ServiceProviderSelector} from "@/Applications/ApiTokens/Add";
import {InputClass} from "@/ui-components/Input";
import {useProjectId} from "@/Project/Api";
import {stupidPluralize} from "@/Utilities/TextUtilities";
import {useProviderBrandings} from "@/Providers/Overview";

interface ComputeCategory {
provider: string;
Expand Down Expand Up @@ -610,8 +610,8 @@ function ProductDescription({serviceProvider, category}: {serviceProvider: strin
}

function useProductDescription(serviceProvider: string, category: string): string {
const providerBrandings = useSelector((r: ReduxObject) => r.providerBrandings.providers);
return providerBrandings[serviceProvider]?.productDescription.find(it => it.category === category)?.shortDescription ?? "No description"
const providerBrandings = useProviderBrandings();
return providerBrandings?.[serviceProvider]?.productDescription.find(it => it.category === category)?.shortDescription ?? "No description"
}

function naiveCategoryStatus(products: (ComputeCategory | ProductV2)[], productSupport: ResolvedSupport<Product, ProductSupport>[]): Record<string, JobQueueStatus> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,49 +1,40 @@
import * as React from "react";
import {useCloudAPI} from "@/Authentication/DataHook";
import {providerBrandingApi, ProviderBrandingResponse} from "@/UCloud/ProviderBrandingApi";
import {createSlice, PayloadAction} from "@reduxjs/toolkit";
import {useDispatch} from "react-redux";
import {callAPI} from "@/Authentication/DataHook";
import {ProviderBranding, providerBrandingApi, ProviderBrandingResponse} from "@/UCloud/ProviderBrandingApi";
import {ExternalStoreBase} from "@/Utilities/ReduxUtilities";
import ProviderInfo from "@/Assets/provider_info.json";

export const AutomaticProviderBranding: React.FunctionComponent = () => {
const [providerBrandings, fetchBranding] = useCloudAPI<ProviderBrandingResponse>(
providerBrandingApi.browse(),
{providers: {}}
);

React.useEffect(() => {
const intervalId = setInterval(() => {
fetchBranding(providerBrandingApi.browse());
}, 1000 * 60 * 60);
return () => {
clearInterval(intervalId);
}
}, []);

const dispatch = useDispatch();

React.useEffect(() => {
dispatch(addProviderBranding(providerBrandings.data));
}, [providerBrandings.data]);
export const providerBrandingStore = new class extends ExternalStoreBase {
private branding: ProviderBrandingResponse = {providers: {}};

return null;
};
async onInit(): Promise<void> {
this.fetch();
window.setInterval(() => {
this.fetch();
}, 3 * 600_000);
}

async fetch() {
try {
const response = await callAPI(providerBrandingApi.browse());
this.branding = response;
this.emitChange();
} catch (e: any) {
console.warn(e);
}
}

export function initProviderBranding(): ProviderBrandingResponse {
return {
providers: {},
public getSnapshot(): Readonly<ProviderBrandingResponse> {
return this.branding;
}
}

const providerBrandingSlice = createSlice({
name: "providerBranding",
initialState: initProviderBranding(),
reducers: {
addProviderBranding(state, action: PayloadAction<ProviderBrandingResponse>) {
state.providers = action.payload.providers;
}
public getProviderProperty<Property extends keyof ProviderBranding>(providerId: string, providerProperty: Property): ProviderBranding[Property] | undefined {
const property = this.branding.providers[providerId]?.[providerProperty];
const useFallback = providerProperty === "logo" && (property as string)?.includes("/");
if (!property) console.warn(`Property '${providerProperty}' missing for ${providerId}`, this.branding);
if (useFallback) console.warn(`Using fallback logo for ${providerId}. Actual value: ${property}`)
return property && !useFallback ? property : ProviderInfo.providers.find(it => it.id === providerId)?.[providerProperty as string];
}
});
}

const {addProviderBranding} = providerBrandingSlice.actions;
export const providerBrandingReducer = providerBrandingSlice.reducer;
providerBrandingStore.onInit();
32 changes: 16 additions & 16 deletions frontend-web/webclient/app/Providers/Detailed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import {ProviderLogo} from "./ProviderLogo";
import {ProviderTitle} from "./ProviderTitle";
import TitledCard from "@/ui-components/HighlightedCard";
import {SidebarTabId} from "@/ui-components/SidebarComponents";
import {useSelector} from "react-redux";
import {ProviderBranding} from "@/UCloud/ProviderBrandingApi";
import {providerBrandingStore} from "@/ProviderBrandings/AutomaticProviderBranding";

function useProviderBranding(id?: string): ProviderBranding | undefined {
const data = useSelector((it: ReduxObject) => it.providerBrandings);
if (!id) return undefined;
return data.providers[id];
const providers = providerBrandingStore.getSnapshot();
return providers.providers[id];
}

export default function DetailedProvider() {
Expand Down Expand Up @@ -50,7 +50,7 @@ export default function DetailedProvider() {
<Flex>
{section.image !== "" ? <Flex flexDirection="column" mr="24px" my="8px">
<Box flexGrow={1} />
<img alt={`Provider detail image`} style={{height: "150px", objectFit: "scale-down"}} src={section.image} />
<img alt={`Provider detail image`} style={{height: "150px", objectFit: "scale-down"}} src={section.image} />
<Box flexGrow={1} />
</Flex> : <Box />}
<div>
Expand All @@ -62,27 +62,27 @@ export default function DetailedProvider() {
</TitledCard>
</Box>
)}
{entry.productDescription.map((prod, index) =>
{entry.productDescription.map((prod, index) =>
<Box key={index} my="32px">
<TitledCard>
<Flex>
<h2>{prod.category}</h2>
<Box flexGrow={1}/>
<Box flexGrow={1} />
<div>
{prod.shortDescription}
</div>
</Flex>
<Flex>
<div>
{prod.section.image ? <img alt={`Product Section Image`} style={{height: "150px", objectFit: "scale-down"}} src={prod.section.image} /> : <div />}
</div>
<div>
<br/>
<br/>
<Markdown>
{prod.section.description}
</Markdown>
</div>
<div>
{prod.section.image ? <img alt={`Product Section Image`} style={{height: "150px", objectFit: "scale-down"}} src={prod.section.image} /> : <div />}
</div>
<div>
<br />
<br />
<Markdown>
{prod.section.description}
</Markdown>
</div>
</Flex>
</TitledCard>
</Box>
Expand Down
Loading