From 48e464e2e022d65c0f190dc5cf727aa2cee59079 Mon Sep 17 00:00:00 2001 From: Nick Lionis Date: Tue, 28 Jan 2025 15:58:39 +0200 Subject: [PATCH 1/8] chore: add mock service worker file --- packages/ui/.gitignore | 3 +- packages/ui/public/mockServiceWorker.js | 295 ++++++++++++++++++++++++ 2 files changed, 296 insertions(+), 2 deletions(-) create mode 100644 packages/ui/public/mockServiceWorker.js diff --git a/packages/ui/.gitignore b/packages/ui/.gitignore index 65fa6bba..6291b7a6 100644 --- a/packages/ui/.gitignore +++ b/packages/ui/.gitignore @@ -24,5 +24,4 @@ dist-ssr *.sw? .env -storybook-static -mockServiceWorker.js \ No newline at end of file +storybook-static \ No newline at end of file diff --git a/packages/ui/public/mockServiceWorker.js b/packages/ui/public/mockServiceWorker.js new file mode 100644 index 00000000..c9c662a2 --- /dev/null +++ b/packages/ui/public/mockServiceWorker.js @@ -0,0 +1,295 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const PACKAGE_VERSION = '2.6.6' +const INTEGRITY_CHECKSUM = 'ca7800994cc8bfb5eb961e037c877074' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const activeClientIds = new Set() + +self.addEventListener('install', function () { + self.skipWaiting() +}) + +self.addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +self.addEventListener('message', async function (event) { + const clientId = event.source.id + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: { + client: { + id: client.id, + frameType: client.frameType, + }, + }, + }) + break + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +self.addEventListener('fetch', function (event) { + const { request } = event + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + // Generate unique request ID. + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId)) +}) + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event) + const response = await getResponse(event, client, requestId) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + ;(async function () { + const responseClone = response.clone() + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + requestId, + isMockedResponse: IS_MOCKED_RESPONSE in response, + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + body: responseClone.body, + headers: Object.fromEntries(responseClone.headers.entries()), + }, + }, + [responseClone.body], + ) + })() + } + + return response +} + +// Resolve the main client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (activeClientIds.has(event.clientId)) { + return client + } + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +async function getResponse(event, client, requestId) { + const { request } = event + + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = request.clone() + + function passthrough() { + // Cast the request headers to a new Headers instance + // so the headers can be manipulated with. + const headers = new Headers(requestClone.headers) + + // Remove the "accept" header value that marked this request as passthrough. + // This prevents request alteration and also keeps it compliant with the + // user-defined CORS policies. + headers.delete('accept', 'msw/passthrough') + + return fetch(requestClone, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const requestBuffer = await request.arrayBuffer() + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: requestBuffer, + keepalive: request.keepalive, + }, + }, + [requestBuffer], + ) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'PASSTHROUGH': { + return passthrough() + } + } + + return passthrough() +} + +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage( + message, + [channel.port2].concat(transferrables.filter(Boolean)), + ) + }) +} + +async function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error() + } + + const mockedResponse = new Response(response.body, response) + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, + }) + + return mockedResponse +} \ No newline at end of file From ef1f83a30bd335e4d935d5bcbfb46d0be3162db9 Mon Sep 17 00:00:00 2001 From: Nick Lionis Date: Tue, 28 Jan 2025 16:04:58 +0200 Subject: [PATCH 2/8] feat: refactored carousel --- .../Carousel/Carousel.tsx} | 88 ++++++++++++------- packages/ui/src/primitives/Carousel/index.ts | 1 + packages/ui/src/primitives/index.ts | 1 + 3 files changed, 58 insertions(+), 32 deletions(-) rename packages/ui/src/{ui-shadcn/carousel.tsx => primitives/Carousel/Carousel.tsx} (73%) create mode 100644 packages/ui/src/primitives/Carousel/index.ts diff --git a/packages/ui/src/ui-shadcn/carousel.tsx b/packages/ui/src/primitives/Carousel/Carousel.tsx similarity index 73% rename from packages/ui/src/ui-shadcn/carousel.tsx rename to packages/ui/src/primitives/Carousel/Carousel.tsx index 298ec809..4f292eb3 100644 --- a/packages/ui/src/ui-shadcn/carousel.tsx +++ b/packages/ui/src/primitives/Carousel/Carousel.tsx @@ -1,11 +1,46 @@ import * as React from "react"; +import { ArrowCircleLeftIcon, ArrowCircleRightIcon } from "@heroicons/react/outline"; import useEmblaCarousel, { type UseEmblaCarouselType } from "embla-carousel-react"; import { ArrowLeft, ArrowRight } from "lucide-react"; +import { tv, VariantProps } from "tailwind-variants"; import { cn } from "@/lib/utils"; import { Button } from "@/primitives/Button/Button"; +export const carouselVariants = tv({ + slots: { + icon: "size-12 text-black", + }, + variants: { + size: { + sm: { + icon: "size-8", + }, + md: { + icon: "size-10", + }, + lg: { + icon: "size-12", + }, + xl: { + icon: "size-14", + }, + }, + }, +}); + +export const CarouselIcons = { + default: { + left: ArrowCircleLeftIcon, + right: ArrowCircleRightIcon, + }, + simple: { + left: ArrowLeft, + right: ArrowRight, + }, +} as const; + type CarouselApi = UseEmblaCarouselType[1]; type UseCarouselParameters = Parameters; type CarouselOptions = UseCarouselParameters[0]; @@ -175,57 +210,46 @@ const CarouselItem = React.forwardRef>( - ({ className, variant = "outlined-primary", size = "icon", ...props }, ref) => { - const { orientation, scrollPrev, canScrollPrev } = useCarousel(); +export interface NavigateButtonProps extends React.ComponentProps { + carouselIconType?: keyof typeof CarouselIcons; + size?: VariantProps["size"]; +} + +const CarouselPrevious = React.forwardRef( + ({ className, size = "lg", carouselIconType = "default", ...props }, ref) => { + const { scrollPrev, canScrollPrev } = useCarousel(); + const { icon } = carouselVariants({ size }); + const Icon = CarouselIcons[carouselIconType].left; return ( + /> ); }, ); CarouselPrevious.displayName = "CarouselPrevious"; -const CarouselNext = React.forwardRef>( - ({ className, variant = "outlined-primary", size = "icon", ...props }, ref) => { - const { orientation, scrollNext, canScrollNext } = useCarousel(); +const CarouselNext = React.forwardRef( + ({ className, size = "lg", carouselIconType = "default", ...props }, ref) => { + const { scrollNext, canScrollNext } = useCarousel(); + const { icon } = carouselVariants({ size }); + const Icon = CarouselIcons[carouselIconType].right; return ( + /> ); }, ); diff --git a/packages/ui/src/primitives/Carousel/index.ts b/packages/ui/src/primitives/Carousel/index.ts new file mode 100644 index 00000000..2b91cb69 --- /dev/null +++ b/packages/ui/src/primitives/Carousel/index.ts @@ -0,0 +1 @@ +export * from "./Carousel"; \ No newline at end of file diff --git a/packages/ui/src/primitives/index.ts b/packages/ui/src/primitives/index.ts index 82baa6fa..c787a08c 100644 --- a/packages/ui/src/primitives/index.ts +++ b/packages/ui/src/primitives/index.ts @@ -23,3 +23,4 @@ export * from "./Toast"; export * from "./VerticalTabs"; export * from "./Checkbox"; export * from "./Switch"; +export * from "./Carousel"; From ec3f06923fae2a459f7fa7ab51f04f0b4649901c Mon Sep 17 00:00:00 2001 From: Nick Lionis Date: Tue, 28 Jan 2025 16:11:26 +0200 Subject: [PATCH 3/8] chore: improved indexdb --- .../GenericProgressForm.stories.tsx | 1 + .../GenericProgressForm.tsx | 8 +- packages/ui/src/hooks/usePersistForm.ts | 14 +-- packages/ui/src/lib/index.ts | 1 + packages/ui/src/lib/objects/compare.ts | 97 +++++++++++++++++++ packages/ui/src/lib/objects/index.ts | 1 + 6 files changed, 114 insertions(+), 8 deletions(-) create mode 100644 packages/ui/src/lib/objects/compare.ts create mode 100644 packages/ui/src/lib/objects/index.ts diff --git a/packages/ui/src/components/GenericProgressForm/GenericProgressForm.stories.tsx b/packages/ui/src/components/GenericProgressForm/GenericProgressForm.stories.tsx index 2afd9c43..e7ff81f3 100644 --- a/packages/ui/src/components/GenericProgressForm/GenericProgressForm.stories.tsx +++ b/packages/ui/src/components/GenericProgressForm/GenericProgressForm.stories.tsx @@ -22,5 +22,6 @@ export const Default: Story = { onSubmit: async (values: any) => onSubmit(values), dbName: "formDB", storeName: "formDrafts", + stepsPersistKey: "roundSetup", }, }; diff --git a/packages/ui/src/components/GenericProgressForm/GenericProgressForm.tsx b/packages/ui/src/components/GenericProgressForm/GenericProgressForm.tsx index f4b660ea..469967e6 100644 --- a/packages/ui/src/components/GenericProgressForm/GenericProgressForm.tsx +++ b/packages/ui/src/components/GenericProgressForm/GenericProgressForm.tsx @@ -18,6 +18,7 @@ export interface GenericProgressFormProps { onSubmit: (values: any) => Promise; dbName: string; storeName: string; + stepsPersistKey: string; } export const GenericProgressForm = ({ @@ -26,9 +27,10 @@ export const GenericProgressForm = ({ onSubmit, dbName, storeName, + stepsPersistKey, }: GenericProgressFormProps) => { - const { currentStep, updateStep } = useFormProgress(name); - const { getValues } = useIndexedDB({ dbName, storeName }); + const { currentStep, updateStep } = useFormProgress(stepsPersistKey); + const { getValues, isReady } = useIndexedDB({ dbName, storeName }); const formRef = useRef<{ isFormValid: () => Promise }>(null); const handleNextStep = () => { @@ -59,6 +61,8 @@ export const GenericProgressForm = ({ const currentStepProps = steps[currentStep]; const progressValue = (currentStep / steps.length) * 100; + if (!isReady) return null; + return (
diff --git a/packages/ui/src/hooks/usePersistForm.ts b/packages/ui/src/hooks/usePersistForm.ts index 9b8a10c2..2f66ef0f 100644 --- a/packages/ui/src/hooks/usePersistForm.ts +++ b/packages/ui/src/hooks/usePersistForm.ts @@ -7,6 +7,8 @@ import { useInterval } from "react-use"; import { zodResolver } from "@hookform/resolvers/zod"; import { ZodSchema } from "zod"; +import { compare } from "@/lib"; + import { useIndexedDB } from "./useIndexedDB"; /** @@ -30,8 +32,6 @@ export const usePersistForm = ( const { getValue, setValue, isReady } = useIndexedDB(config); - const [formValues, setFormValues] = useState(null); - useEffect(() => { if (!isReady || !persistKey) return; @@ -55,10 +55,12 @@ export const usePersistForm = ( (async () => { try { - const values = form.getValues(); - if (JSON.stringify(values) !== JSON.stringify(formValues)) { - await setValue(persistKey, values); - setFormValues(values); + const current_values = form.getValues(); + const form_values = await getValue(persistKey); + const isEqual = compare(current_values, form_values); + if (!isEqual && Object.keys(current_values).length > 0) { + await setValue(persistKey, current_values); + return; } } catch (err) { console.error("Error saving to IndexedDB:", err); diff --git a/packages/ui/src/lib/index.ts b/packages/ui/src/lib/index.ts index ef3cb1a8..8d76d8cc 100644 --- a/packages/ui/src/lib/index.ts +++ b/packages/ui/src/lib/index.ts @@ -3,3 +3,4 @@ export * from "./icons"; export * from "./tanstack"; export * from "./utils"; export * from "./indexDB"; +export * from "./objects"; \ No newline at end of file diff --git a/packages/ui/src/lib/objects/compare.ts b/packages/ui/src/lib/objects/compare.ts new file mode 100644 index 00000000..53108b0b --- /dev/null +++ b/packages/ui/src/lib/objects/compare.ts @@ -0,0 +1,97 @@ +/* + * This is a deep comparison function that compares two objects and returns true if they are equal. + * It is used to compare the values of the form and the values stored in the IndexedDB. + * It is used in the usePersistForm hook. + * + * @param a - The first object to compare + * @param b - The second object to compare + * @param visited - A WeakMap to keep track of visited objects to avoid infinite loops + * @returns true if the objects are equal, false otherwise + */ + +export const compare = (a: any, b: any, visited = new WeakMap()): boolean => { + // Handle primitive types + if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) { + return a === b; // Strict equality for primitives and null + } + + // Check if objects have already been visited + if (visited.has(a) && visited.has(b)) { + return visited.get(a) === visited.get(b); + } + + // Mark objects as visited + visited.set(a, b); + visited.set(b, a); + + // Handle File objects + if (a instanceof File && b instanceof File) { + return a.name === b.name && a.size === b.size && a.type === b.type; + } + + // Handle Blob objects + if (a instanceof Blob && b instanceof Blob) { + return a.size === b.size && a.type === b.type; + } + + // Handle ArrayBuffer objects + if (a instanceof ArrayBuffer && b instanceof ArrayBuffer) { + if (a.byteLength !== b.byteLength) return false; + const viewA = new Uint8Array(a); + const viewB = new Uint8Array(b); + for (let i = 0; i < a.byteLength; i++) { + if (viewA[i] !== viewB[i]) return false; + } + return true; + } + + // Handle Date objects + if (a instanceof Date && b instanceof Date) { + return a.getTime() === b.getTime(); + } + + // Handle RegExp objects + if (a instanceof RegExp && b instanceof RegExp) { + return a.toString() === b.toString(); + } + + // Handle Arrays + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) return false; + for (let i = 0; i < a.length; i++) { + if (!compare(a[i], b[i], visited)) return false; + } + return true; + } + + // Handle Maps + if (a instanceof Map && b instanceof Map) { + if (a.size !== b.size) return false; + for (const [key, value] of a) { + if (!b.has(key) || !compare(value, b.get(key), visited)) return false; + } + return true; + } + + // Handle Sets + if (a instanceof Set && b instanceof Set) { + if (a.size !== b.size) return false; + for (const value of a) { + if (!b.has(value)) return false; + } + return true; + } + + // Handle plain objects + const keysA = Object.keys(a); + const keysB = Object.keys(b); + if (keysA.length !== keysB.length) { + return false; + } + for (const key of keysA) { + if (!keysB.includes(key) || !compare(a[key], b[key], visited)) { + return false; + } + } + return true; +}; diff --git a/packages/ui/src/lib/objects/index.ts b/packages/ui/src/lib/objects/index.ts new file mode 100644 index 00000000..6cc3d545 --- /dev/null +++ b/packages/ui/src/lib/objects/index.ts @@ -0,0 +1 @@ +export * from "./compare"; \ No newline at end of file From b20c0a26c80f2ebc97609691272ac0121363f0f0 Mon Sep 17 00:00:00 2001 From: Nick Lionis Date: Tue, 28 Jan 2025 16:13:05 +0200 Subject: [PATCH 4/8] feat: created programPickerModal --- .../ProgramPickerModal.stories.tsx | 101 ++++++++++++++++++ .../ProgramPickerModal/ProgramPickerModal.tsx | 72 +++++++++++++ .../components/ProgramPickerModal/index.ts | 1 + .../ui/src/features/retrofunding/index.ts | 1 + 4 files changed, 175 insertions(+) create mode 100644 packages/ui/src/features/retrofunding/components/ProgramPickerModal/ProgramPickerModal.stories.tsx create mode 100644 packages/ui/src/features/retrofunding/components/ProgramPickerModal/ProgramPickerModal.tsx create mode 100644 packages/ui/src/features/retrofunding/components/ProgramPickerModal/index.ts diff --git a/packages/ui/src/features/retrofunding/components/ProgramPickerModal/ProgramPickerModal.stories.tsx b/packages/ui/src/features/retrofunding/components/ProgramPickerModal/ProgramPickerModal.stories.tsx new file mode 100644 index 00000000..4910d738 --- /dev/null +++ b/packages/ui/src/features/retrofunding/components/ProgramPickerModal/ProgramPickerModal.stories.tsx @@ -0,0 +1,101 @@ +import { useState } from "react"; + +import { action } from "@storybook/addon-actions"; +import type { Meta, StoryObj } from "@storybook/react"; + +import { CreateButton } from "@/components/CreateButton"; +import { ProgramCardProps } from "@/features/program/components/ProgramCard"; + +import { ProgramPickerModal, ProgramPickerModalProps } from "./ProgramPickerModal"; + +const onProgramClick = action("Program clicked!"); +const meta: Meta = { + title: "Features/Retrofunding/Components/ProgramPickerModal", + component: ProgramPickerModal, + parameters: { + layout: "centered", + }, +}; + +export default meta; +type Story = StoryObj; + +// Mock program data +const mockPrograms: ProgramCardProps[] = [ + { + id: "1", + chainId: 1, + title: "Program 1", + operatorsCount: 1, + roundsCount: 1, + createdAtBlock: 1000000, + }, + { + id: "2", + chainId: 1, + title: "Program 2", + operatorsCount: 2, + roundsCount: 1, + createdAtBlock: 1000000, + }, + { + id: "3", + chainId: 1, + title: "Program 3", + operatorsCount: 3, + roundsCount: 1, + createdAtBlock: 1000000, + }, + { + id: "4", + chainId: 1, + title: "Program 4", + operatorsCount: 4, + roundsCount: 1, + createdAtBlock: 1000000, + }, + { + id: "5", + chainId: 1, + title: "Program 5", + operatorsCount: 5, + roundsCount: 1, + createdAtBlock: 1000000, + }, + { + id: "6", + chainId: 1, + title: "Program 6", + operatorsCount: 6, + roundsCount: 1, + createdAtBlock: 1000000, + }, +].map((program) => ({ + ...program, + onClick: (program?: { programId: string; chainId: number }) => onProgramClick(program), +})); + +const ModalWrapper: React.FC = (props) => { + const [isOpen, setIsOpen] = useState(false); + return ( + <> + + + + ); +}; + +export const Default: Story = { + render: (args) => , + args: { + programs: mockPrograms, + }, +}; + +export const WithFooter: Story = { + render: (args) => , + args: { + programs: mockPrograms, + footer: Create new round, + }, +}; diff --git a/packages/ui/src/features/retrofunding/components/ProgramPickerModal/ProgramPickerModal.tsx b/packages/ui/src/features/retrofunding/components/ProgramPickerModal/ProgramPickerModal.tsx new file mode 100644 index 00000000..278215ad --- /dev/null +++ b/packages/ui/src/features/retrofunding/components/ProgramPickerModal/ProgramPickerModal.tsx @@ -0,0 +1,72 @@ +"use client"; + +import { ProgramCard, ProgramCardProps } from "@/features/program/components/ProgramCard"; +import { + Carousel, + CarouselContent, + CarouselItem, + CarouselPrevious, + CarouselNext, +} from "@/primitives/Carousel"; +import { Icon, IconType } from "@/primitives/Icon"; +import { Modal } from "@/primitives/Modal"; +import { Dialog, DialogTitle } from "@/ui-shadcn/dialog"; + +export interface ProgramPickerModalProps { + programs: ProgramCardProps[]; + isOpen: boolean; + onOpenChange: (open: boolean) => void; + footer?: React.ReactNode; +} + +export const ProgramPickerModal = ({ + programs, + isOpen, + onOpenChange, + footer, +}: ProgramPickerModalProps) => { + return ( + + +
+ Select program + onOpenChange(false)} + /> +
+ + +
+ + + {programs.length === 0 ? ( + +
No programs
+
+ ) : ( + Array.from({ length: Math.ceil(programs.length / 4) }, (_, index) => { + const startIdx = index * 4; + return ( + +
+ {programs.slice(startIdx, startIdx + 4).map((program) => ( +
+ +
+ ))} +
+
+ ); + }) + )} +
+ +
+ {footer} +
+
+
+ ); +}; diff --git a/packages/ui/src/features/retrofunding/components/ProgramPickerModal/index.ts b/packages/ui/src/features/retrofunding/components/ProgramPickerModal/index.ts new file mode 100644 index 00000000..c20c07d2 --- /dev/null +++ b/packages/ui/src/features/retrofunding/components/ProgramPickerModal/index.ts @@ -0,0 +1 @@ +export * from "./ProgramPickerModal"; diff --git a/packages/ui/src/features/retrofunding/index.ts b/packages/ui/src/features/retrofunding/index.ts index d63e8b3b..097db0d4 100644 --- a/packages/ui/src/features/retrofunding/index.ts +++ b/packages/ui/src/features/retrofunding/index.ts @@ -1,3 +1,4 @@ export * from "./components/MetricCard"; export * from "./components/Landing"; export * from "./components/MetricsBallot"; +export * from "./components/ProgramPickerModal"; From b6565e7f3ca8f4a5164920836c2b9feca0d05a96 Mon Sep 17 00:00:00 2001 From: Nick Lionis Date: Tue, 28 Jan 2025 16:14:04 +0200 Subject: [PATCH 5/8] fix: programList filter placeholder --- .../ui/src/features/program/components/ProgramList/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/features/program/components/ProgramList/utils.ts b/packages/ui/src/features/program/components/ProgramList/utils.ts index 51442b2c..4a351609 100644 --- a/packages/ui/src/features/program/components/ProgramList/utils.ts +++ b/packages/ui/src/features/program/components/ProgramList/utils.ts @@ -43,7 +43,7 @@ export const getOrderAndFilterOptions = (programs: ProgramCardProps[]) => { multiple: true, collapsible: true, items: [...new Set(programs.map((program) => program.chainId))].map((chainId) => ({ - label: `Rounds on ${getChainInfo(chainId).name}`, + label: `Programs on ${getChainInfo(chainId).name}`, value: chainId.toString(), })), }, From 50f0f174d72027a91e840cb5cf692fa8d27cc847 Mon Sep 17 00:00:00 2001 From: Nick Lionis Date: Tue, 28 Jan 2025 16:15:01 +0200 Subject: [PATCH 6/8] fix: checker reviewApplicationPage conditional padding --- .../pages/ReviewApplicationsPage/ReviewApplicationsPage.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/features/checker/pages/ReviewApplicationsPage/ReviewApplicationsPage.tsx b/packages/ui/src/features/checker/pages/ReviewApplicationsPage/ReviewApplicationsPage.tsx index c77151d6..f5b47839 100644 --- a/packages/ui/src/features/checker/pages/ReviewApplicationsPage/ReviewApplicationsPage.tsx +++ b/packages/ui/src/features/checker/pages/ReviewApplicationsPage/ReviewApplicationsPage.tsx @@ -2,6 +2,7 @@ import { useMemo } from "react"; +import { cn } from "@/lib"; import { Button } from "@/primitives/Button"; import { Icon, IconType } from "@/primitives/Icon"; import { StatCardProps } from "@/primitives/StatCard"; @@ -16,7 +17,7 @@ import { useCheckerContext, useCheckerDispatchContext, } from "~checker/store"; -import { getManagerUrl, getRoundLinkOnManager } from "~checker/utils"; +import { getRoundLinkOnManager } from "~checker/utils"; import { PoolSummary } from "~pool"; export const ReviewApplicationsPage = ({ isStandalone }: { isStandalone: boolean }) => { @@ -81,7 +82,7 @@ export const ReviewApplicationsPage = ({ isStandalone }: { isStandalone: boolean donationsEndTime={poolData?.donationsEndTime} /> )} -
+
{isStandalone && (