Skip to content

Commit

Permalink
Add hackathon banner to menu (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
pixelfact authored Aug 9, 2024
1 parent 78250ed commit c59a512
Show file tree
Hide file tree
Showing 17 changed files with 193 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./use-get-banner";
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useQuery } from "@tanstack/react-query";

import {
UseQueryFacadeParams,
useQueryAdapter,
} from "@/core/application/react-query-adapter/helpers/use-query-adapter";
import { bootstrap } from "@/core/bootstrap";
import { BannerFacadePort } from "@/core/domain/banner/input/banner-facade-port";
import { BannerInterface } from "@/core/domain/banner/models/banner-model";

export function useGetBanner({ options }: UseQueryFacadeParams<BannerFacadePort["getBanner"], BannerInterface>) {
const bannerStoragePort = bootstrap.getBannerStoragePortForClient();

return useQuery(
useQueryAdapter({
...bannerStoragePort.getBanner({}),
options,
})
);
}
5 changes: 5 additions & 0 deletions core/application/react-query-adapter/banner/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as client from "./client";

export const BannerReactQueryAdapter = {
client,
};
3 changes: 3 additions & 0 deletions core/bootstrap/bootstrap-constructor-mock.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { BootstrapConstructor } from "@/core/bootstrap/index";
import { BannerClientAdapterMock } from "@/core/infrastructure/marketplace-api-client-adapter/mock-adapters/banner-client-adapter-mock";
import { UserClientAdapterMock } from "@/core/infrastructure/marketplace-api-client-adapter/mock-adapters/user-client-adapter-mock";

export const bootstrapConstructorMock: BootstrapConstructor = {
userStoragePortForClient: new UserClientAdapterMock(),
userStoragePortForServer: new UserClientAdapterMock(),
bannerStoragePortForClient: new BannerClientAdapterMock(),
bannerStoragePortForServer: new BannerClientAdapterMock(),
};
18 changes: 18 additions & 0 deletions core/bootstrap/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { BannerStoragePort } from "@/core/domain/banner/outputs/banner-storage-port";
import { UserStoragePort } from "@/core/domain/user/outputs/user-storage-port";
import { BannerClientAdapter } from "@/core/infrastructure/marketplace-api-client-adapter/adapters/banner-client-adapter";
import { UserClientAdapter } from "@/core/infrastructure/marketplace-api-client-adapter/adapters/user-client-adapter";
import { AuthProvider } from "@/core/infrastructure/marketplace-api-client-adapter/auth/auth-provider";
import { FetchHttpClient } from "@/core/infrastructure/marketplace-api-client-adapter/http/fetch-http-client/fetch-http-client";
Expand All @@ -7,6 +9,8 @@ import { ImpersonationProvider } from "@/core/infrastructure/marketplace-api-cli
export interface BootstrapConstructor {
userStoragePortForClient: UserStoragePort;
userStoragePortForServer: UserStoragePort;
bannerStoragePortForClient: BannerStoragePort;
bannerStoragePortForServer: BannerStoragePort;
}

export class Bootstrap {
Expand All @@ -15,10 +19,14 @@ export class Bootstrap {
private impersonationProvider?: ImpersonationProvider | null = null;
userStoragePortForClient: UserStoragePort;
userStoragePortForServer: UserStoragePort;
bannerStoragePortForClient: BannerStoragePort;
bannerStoragePortForServer: BannerStoragePort;

constructor(constructor: BootstrapConstructor) {
this.userStoragePortForClient = constructor.userStoragePortForClient;
this.userStoragePortForServer = constructor.userStoragePortForServer;
this.bannerStoragePortForClient = constructor.bannerStoragePortForClient;
this.bannerStoragePortForServer = constructor.bannerStoragePortForServer;
}

getAuthProvider() {
Expand All @@ -45,11 +53,21 @@ export class Bootstrap {
return this.userStoragePortForServer;
}

getBannerStoragePortForClient() {
return this.bannerStoragePortForClient;
}

getBannerStoragePortForServer() {
return this.bannerStoragePortForServer;
}

public static get getBootstrap(): Bootstrap {
if (!Bootstrap.#instance) {
this.newBootstrap({
userStoragePortForClient: new UserClientAdapter(new FetchHttpClient()),
userStoragePortForServer: new UserClientAdapter(new FetchHttpClient()),
bannerStoragePortForClient: new BannerClientAdapter(new FetchHttpClient()),
bannerStoragePortForServer: new BannerClientAdapter(new FetchHttpClient()),
});
}

Expand Down
14 changes: 14 additions & 0 deletions core/domain/banner/banner-contract.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { BannerInterface } from "@/core/domain/banner/models/banner-model";
import { components } from "@/core/infrastructure/marketplace-api-client-adapter/__generated/api";
import {
HttpClientParameters,
HttpStorageResponse,
} from "@/core/infrastructure/marketplace-api-client-adapter/http/http-client/http-client.types";

/* --------------------------------- Get Banner -------------------------------- */

export type GetBannerResponse = components["schemas"]["BannerResponse"];

export type GetBannerPortParams = HttpClientParameters<object>;

export type GetBannerPortResponse = HttpStorageResponse<BannerInterface>;
5 changes: 5 additions & 0 deletions core/domain/banner/input/banner-facade-port.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { GetBannerPortParams, GetBannerPortResponse } from "@/core/domain/banner/banner-contract.types";

export interface BannerFacadePort {
getBanner(p: GetBannerPortParams): GetBannerPortResponse;
}
17 changes: 17 additions & 0 deletions core/domain/banner/models/banner-model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { components } from "@/core/infrastructure/marketplace-api-client-adapter/__generated/api";

export type BannerResponse = components["schemas"]["BannerResponse"];

export interface BannerInterface extends BannerResponse {}

export class Banner implements BannerInterface {
buttonIconSlug!: BannerResponse["buttonIconSlug"];
buttonLinkUrl!: BannerResponse["buttonLinkUrl"];
buttonText!: BannerResponse["buttonText"];
id!: BannerResponse["id"];
text!: BannerResponse["text"];

constructor(props: BannerResponse) {
Object.assign(this, props);
}
}
6 changes: 6 additions & 0 deletions core/domain/banner/outputs/banner-storage-port.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { GetBannerPortParams, GetBannerPortResponse } from "@/core/domain/banner/banner-contract.types";

export interface BannerStoragePort {
routes: Record<string, string>;
getBanner(p: GetBannerPortParams): GetBannerPortResponse;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { GetBannerResponse } from "@/core/domain/banner/banner-contract.types";
import { Banner } from "@/core/domain/banner/models/banner-model";
import { BannerStoragePort } from "@/core/domain/banner/outputs/banner-storage-port";
import { HttpClient } from "@/core/infrastructure/marketplace-api-client-adapter/http/http-client/http-client";

export class BannerClientAdapter implements BannerStoragePort {
constructor(private readonly client: HttpClient) {}

routes = {
getBanner: "banner",
} as const;

getBanner = () => {
const path = this.routes["getBanner"];
const method = "GET";
const tag = HttpClient.buildTag({ path });
const request = async () => {
const data = await this.client.request<GetBannerResponse>({
path,
method,
tag,
});

return new Banner(data);
};

return {
request,
tag,
};
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { BannerStoragePort } from "core/domain/banner/outputs/banner-storage-port";
import { mockHttpStorageResponse } from "@/core/infrastructure/marketplace-api-client-adapter/http/mock-http-client/mock-http-storage-response";

export class BannerClientAdapterMock implements BannerStoragePort {
constructor() {}

routes = {};

getBanner = mockHttpStorageResponse<BannerStoragePort["getBanner"]>;
}
8 changes: 6 additions & 2 deletions design-system/organisms/plg-banner/plg-banner.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import PlgMarketing from "@/public/images/banners/plg-marketing.png";
import Image from "next/image";
import PlgMarketing from "public/images/banners/plg-marketing.png";

import { Avatar } from "@/design-system/atoms/avatar";
import { Button } from "@/design-system/atoms/button/variants/button-default";
Expand Down Expand Up @@ -30,7 +30,11 @@ function Cta({ cta }: { cta: PlgBannerProps["cta"] }) {

export function PlgBanner({ title, subTitle, date, description, cta }: PlgBannerProps) {
return (
<div className={"relative flex w-full flex-col overflow-hidden rounded-xl p-6"}>
<div
className={
"relative flex h-full w-full flex-col overflow-hidden rounded-xl border border-container-stroke-separator p-6"
}
>
<Image
src={PlgMarketing}
alt={title}
Expand Down
2 changes: 1 addition & 1 deletion design-system/organisms/plg-banner/plg-banner.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ interface Cta {
text: string;
href: string;
isExternal?: boolean;
avatar: AvatarPort;
avatar?: AvatarPort;
}

export interface PlgBannerProps {
Expand Down
44 changes: 44 additions & 0 deletions shared/features/navigation/primary-banner/primary-banner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { ComponentProps } from "react";

import { BannerReactQueryAdapter } from "@/core/application/react-query-adapter/banner";

import { Skeleton } from "@/design-system/atoms/skeleton";
import { PlgBanner } from "@/design-system/organisms/plg-banner/plg-banner";

import { PrimaryBannerProps } from "@/shared/features/navigation/primary-banner/primary-banner.types";

export function PrimaryBanner({ isFolded }: PrimaryBannerProps) {
const { data: bannerData, isLoading, isError } = BannerReactQueryAdapter.client.useGetBanner({});

function getCta(): ComponentProps<typeof PlgBanner>["cta"] {
if (!bannerData?.buttonText || !bannerData?.buttonLinkUrl) return undefined;

return {
text: bannerData.buttonText,
href: bannerData.buttonLinkUrl,
// TODO @Mehdi @Backend add Banner avatar URL to bannerData response and show avatar in CTA
};
}

if (isLoading) {
return <Skeleton className={"h-full w-full"} />;
}

if (isFolded || !bannerData || !bannerData.text || isError) {
return <div className="flex-1" />;
}

return (
<div className="flex flex-1 overflow-hidden">
<div className="h-full w-[260px] min-w-[260px]">
<PlgBanner
title={"title"}
subTitle={"subtitle"}
date={"10.06.2024"}
description={bannerData.text}
cta={getCta()}
/>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface PrimaryBannerProps {
isFolded?: boolean;
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
"use client";

import { useState } from "react";
import { ReactNode, useState } from "react";

import { Paper } from "@/design-system/atoms/paper";

import { AnimatedColumn } from "@/shared/components/animated-column-group/animated-column/animated-column";
import { PrimaryMenu } from "@/shared/features/navigation/menu/primary-menu/primary-menu";
import { SecondaryMenu } from "@/shared/features/navigation/menu/secondary-menu/secondary-menu";
import { UserMenu } from "@/shared/features/navigation/menu/user-menu/user-menu";
import { PrimaryBanner } from "@/shared/features/navigation/primary-banner/primary-banner";
import { HeaderMenu } from "@/shared/features/navigation/primary-navigation-desktop/header-menu/header-menu";

function MenuContainer({ children }: { children: React.ReactNode }) {
function MenuContainer({ children }: { children: ReactNode }) {
return (
<Paper size={"s"} classNames={{ base: "flex w-full flex-col gap-2" }} container={"2"} border={"none"}>
{children}
Expand Down Expand Up @@ -45,7 +46,7 @@ export function PrimaryNavigationDesktop() {
<MenuContainer>
<PrimaryMenu isFolded={folded} />
</MenuContainer>
<div className="flex-1" />
<PrimaryBanner isFolded={folded} />
<MenuContainer>
<SecondaryMenu isFolded={folded} />
</MenuContainer>
Expand Down
8 changes: 4 additions & 4 deletions tooling/cli/create-domain.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ const prettier = require("prettier");
const { COLORS, kebabToPascal, kebabToCamel, defaultPromptName, exists } = require("./global");
const { exec } = require("node:child_process");

const DOMAIN_PATH = "@/core/domain";
const API_PATH = "@/core/infrastructure/marketplace-api-client-adapter/adapters";
const MOCK_API_PATH = "@/core/infrastructure/marketplace-api-client-adapter/mock-adapters";
const DOMAIN_PATH = "core/domain";
const API_PATH = "core/infrastructure/marketplace-api-client-adapter/adapters";
const MOCK_API_PATH = "core/infrastructure/marketplace-api-client-adapter/mock-adapters";

async function createContract({ PascalName, files }) {
await fs.appendFile(
Expand All @@ -20,7 +20,7 @@ async function createContract({ PascalName, files }) {
HttpStorageResponse,
} from "@/core/infrastructure/marketplace-api-client-adapter/http/http-client/http-client.types";
import { components } from "src/__generated/api";
import { components } from "core/__generated/api.d.ts";
export type Get${PascalName}Response = components["schemas"]["${PascalName}Response"];
Expand Down

0 comments on commit c59a512

Please sign in to comment.