Skip to content
Open
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
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ node_modules
.storybook
graphql
contracts.ts
**/src/generated/**
**/rollups-wagmi/src/index.tsx
88 changes: 70 additions & 18 deletions apps/dave/.storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { MantineProvider } from "@mantine/core";
import { useMantineColorScheme } from "@mantine/core";
import "@mantine/core/styles.css";
import { Notifications } from "@mantine/notifications";
import type { Preview, StoryContext, StoryFn } from "@storybook/nextjs";
import { ReactNode, useCallback, useEffect, useState } from "react";
import { UPDATE_GLOBALS } from "storybook/internal/core-events";
import { addons, useGlobals } from 'storybook/preview-api';
import Layout from "../src/components/layout/Layout";
import { Providers } from '../src/providers/Providers';
import theme from "../src/providers/theme";
import './global.css';

try {
Expand All @@ -17,6 +18,8 @@ try {
console.info((error as Error).message)
}

type Globals = ReturnType<typeof useGlobals>[0]

const withLayout = (StoryFn: StoryFn, context: StoryContext) => {
const { title } = context;
const [sectionType] = title.split("/");
Expand All @@ -27,22 +30,71 @@ const withLayout = (StoryFn: StoryFn, context: StoryContext) => {
return <>{StoryFn(context.args, context)}</>;
};

const withProviders = (StoryFn: StoryFn, context: StoryContext) => {
return <Providers>{StoryFn(context.args, context)}</Providers>
const withProviders = (StoryFn: StoryFn, context: StoryContext) => {
return (
<Providers>
<ColorSchemeWrapper context={context}>
{StoryFn(context.args, context)}
</ColorSchemeWrapper>
</Providers>
)
}

const withMantine = (StoryFn: StoryFn, context: StoryContext) => {
const currentBg = context.globals.backgrounds?.value ?? "light";
const channel = addons.getChannel();

return (
<MantineProvider forceColorScheme={currentBg} theme={theme}>
<Notifications />
{StoryFn(context.args, context)}
</MantineProvider>
);
};
const generateNewBackgroundEvt = (colorScheme: unknown) => ({globals: { backgrounds: {value: colorScheme, grid: false}}})

// eslint-disable-next-line react-refresh/only-export-components
function ColorSchemeWrapper({ children, context}: { children: ReactNode, context: StoryContext }) {
const { colorScheme, setColorScheme } = useMantineColorScheme();
const [latestGlobalsBg, setLatestGlobalBg] = useState<string | undefined>(colorScheme);

const handleColorScheme = useCallback(({ globals }: { globals: Globals }) => {
const bgValue = globals.backgrounds?.value
const newMode = bgValue ?? 'light';
if(newMode !== colorScheme) {
setColorScheme(newMode);
setLatestGlobalBg(newMode);
} else if (newMode !== latestGlobalsBg) {
setLatestGlobalBg(newMode)
}
// update the handler function every time both variables change
// as the handler is outside of React's detection. We want
// to make sure the handler works with fresh values.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [colorScheme, latestGlobalsBg]);


useEffect(() => {
// Only when on story mode i.e. not on autodocs view.
// Due to the many re-renders until its finished. That cause slow but infinite loops.
if(context.viewMode === 'story') {
// on-mount emit single event to sync whatever is the default color-scheme on mantine
channel.emit(UPDATE_GLOBALS, generateNewBackgroundEvt(colorScheme))
}
}, [])

useEffect(() => {
channel.on("updateGlobals", handleColorScheme);
return () => {
// unsubscribe to subscribe again with fresher handler.
channel.off("updateGlobals", handleColorScheme);
}
}, [handleColorScheme]);

useEffect(() => {
if(colorScheme !== latestGlobalsBg) {
console.log('color-scheme new value came from App ui interaction. emitting event to sync.');
channel.emit(UPDATE_GLOBALS, generateNewBackgroundEvt(colorScheme));
}
}, [colorScheme, latestGlobalsBg])



return <>{children}</>;
}

const preview: Preview = {
const preview: Preview = {
initialGlobals: {
backgrounds: { value: "light" },
},
Expand All @@ -64,10 +116,10 @@ const preview: Preview = {
},
},
decorators: [
// Order matters. So layout decorator first. Fn calling is router(mantine(layout))
withLayout,
// Order matters. So layout decorator first. Fn calling is providers(layout(Story))
withLayout,
withProviders,
withMantine,

],
};

Expand Down
9 changes: 9 additions & 0 deletions apps/dave/mantine.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ type ExtendedCustomColors =
| core.DefaultMantineColor;
declare module "@mantine/core" {
export { core };

/**
* Making it optional as default values to the
* declared interface members are added in the theme.
*/
export interface SpoilerProps extends core.SpoilerProps {
hideLabel?: core.SpoilerProps["hideLabel"];
showLabel?: core.SpoilerProps["showLabel"];
}
export interface MantineThemeOther {
lgIconSize: number;
mdIconSize: number;
Expand Down
3 changes: 2 additions & 1 deletion apps/dave/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
"clean": "rm -rf .turbo && rm -rf .next && rm -rf node_modules && rm -rf dist && rm -rf src/generated",
"dev": "next dev",
"build": "next build",
"start": "next start",
Expand All @@ -25,6 +25,7 @@
"@mantine/notifications": "^8.3.13",
"@rainbow-me/rainbowkit": "^2.2.10",
"@raugfer/jazzicon": "^1.0.6",
"@react-spring/web": "^10.0.1",
"@tabler/icons-react": "^3.35.0",
"@tanstack/react-query": "catalog:",
"@vercel/analytics": "^1.6.1",
Expand Down
6 changes: 6 additions & 0 deletions apps/dave/src/app/apps/[application]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { ApplicationSummaryContainer } from "../../../containers/ApplicationSummaryContainer";

export default async function Page(props: PageProps<"/apps/[application]">) {
const { application } = await props.params;
return <ApplicationSummaryContainer application={application} />;
}
28 changes: 28 additions & 0 deletions apps/dave/src/components/CenteredText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
Card,
Center,
Text,
type CardProps,
type TextProps,
} from "@mantine/core";
import type { FC } from "react";

interface CenteredTextProps {
text: string;
cardProps?: CardProps;
textProps?: TextProps;
}

const CenteredText: FC<CenteredTextProps> = (props) => {
return (
<Card shadow="md" {...props.cardProps}>
<Center>
<Text c="dimmed" size="xl" tt="uppercase" {...props.textProps}>
{props.text}
</Text>
</Center>
</Card>
);
};

export default CenteredText;
13 changes: 10 additions & 3 deletions apps/dave/src/components/QueryPagination.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import type { Pagination as QPagination } from "@cartesi/viem";
import { Group, Pagination, type GroupProps } from "@mantine/core";
import { isNil } from "ramda";
import type { FC } from "react";

const getActivePage = (offset: number, limit: number) => {
const safeLimit = limit === 0 ? 1 : limit;
return offset / safeLimit + 1;
};

const getTotalPages = (totalCount: number, limit: number) => {
const denominator = limit === 0 || isNil(limit) ? 1 : limit;
return Math.ceil(totalCount / denominator);
};

type QueryPaginationProps = {
pagination: QPagination;
onPaginationChange?: (newOffset: number) => void;
Expand All @@ -20,11 +26,12 @@ export const QueryPagination: FC<QueryPaginationProps> = ({
groupProps,
hideIfSinglePage = true,
}) => {
const totalPages = Math.ceil(pagination.totalCount / pagination.limit);
const totalPages = getTotalPages(pagination.totalCount, pagination.limit);
const activePage = getActivePage(pagination.offset, pagination.limit);
const displayPagination = totalPages > 1 && hideIfSinglePage;
const hasNoPages = totalPages === 0;
const isSinglePage = totalPages === 1;

if (!displayPagination) return "";
if (hasNoPages || (isSinglePage && hideIfSinglePage)) return "";

return (
<Group justify="flex-end" {...groupProps}>
Expand Down
44 changes: 44 additions & 0 deletions apps/dave/src/components/SummaryCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"use client";
import { Card, Flex, Skeleton, Text } from "@mantine/core";
import { type FC } from "react";
import { type IconType } from "react-icons";
import TweenedNumber from "./TweenedNumber";

export type SummaryCardProps = {
icon?: IconType;
title: string;
value: number;
displaySkeleton: boolean;
};

const SummarySkeletonCard = () => (
<Card shadow="xs" w="100%">
<Skeleton animate={false} height={20} circle mb={18} />
<Skeleton animate={false} height={8} radius="xl" />
<Skeleton animate={false} height={8} mt={6} radius="xl" />
<Skeleton animate={false} height={8} mt={6} width="70%" radius="xl" />
</Card>
);

export const SummaryCard: FC<SummaryCardProps> = (props) => {
if (props.displaySkeleton) return <SummarySkeletonCard />;

return (
<Card key={`${props.title}-summary`} shadow="xs">
<Flex gap={5} align="center">
{props.icon && (
<props.icon
size={28}
data-testid={`summary-card-${props.title?.toLowerCase()}-icon`}
/>
)}
<Text c="dimmed" size="lg" inline>
{props.title}
</Text>
</Flex>
<Text fw="bold" fz="2rem">
<TweenedNumber value={props.value} />
</Text>
</Card>
);
};
19 changes: 19 additions & 0 deletions apps/dave/src/components/TweenedNumber.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"use client";
import { animated, useSpring } from "@react-spring/web";

interface TweenedNumber {
value: number;
}

const TweenedNumber = ({ value }: TweenedNumber) => {
const { number } = useSpring({
from: { number: 0 },
number: value,
delay: 200,
config: { mass: 1, tension: 20, friction: 10 },
});

return <animated.span>{number.to((n) => n.toFixed(0))}</animated.span>;
};

export default TweenedNumber;
2 changes: 1 addition & 1 deletion apps/dave/src/components/application/ApplicationCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const ApplicationCard: FC<ApplicationCardProps> = ({ application }) => {
application;
const stateColour = getStateColour(state);
const appConfig = useAppConfig();
const url = pathBuilder.epochs({ application: application.name });
const url = pathBuilder.application({ application: application.name });
const inputsLabel =
processedInputs === 0n
? "no inputs"
Expand Down
9 changes: 7 additions & 2 deletions apps/dave/src/components/epoch/EpochCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@ import type { Epoch } from "@cartesi/viem";
import { Badge, Card, Group, Text, useMantineTheme } from "@mantine/core";
import { useMediaQuery } from "@mantine/hooks";
import Link from "next/link";
import { useParams } from "next/navigation";
import type { FC } from "react";
import { pathBuilder } from "../../routes/routePathBuilder";
import { useEpochStatusColor } from "./useEpochStatusColor";

type Props = { epoch: Epoch };

export const EpochCard: FC<Props> = ({ epoch }) => {
const theme = useMantineTheme();
const epochIndex = epoch.index?.toString() ?? "0";
const url = `epochs/${epochIndex}`;
const params = useParams<{ application: string }>();
const url = pathBuilder.epoch({
application: params.application,
epochIndex: epoch.index,
});
const isMobile = useMediaQuery(`(max-width: ${theme.breakpoints.sm})`);
const color = useEpochStatusColor(epoch);
const inDispute = false; // XXX: how to know if an epoch is in dispute?
Expand Down
18 changes: 10 additions & 8 deletions apps/dave/src/components/input/InputCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Stack,
Text,
Tooltip,
useMantineTheme,
type MantineColor,
} from "@mantine/core";
import { Activity, useMemo, useState, type FC } from "react";
Expand Down Expand Up @@ -41,9 +42,9 @@ const getStatusColor = (status: InputStatus): MantineColor => {
type ViewControl = "payload" | "output" | "report";

const maxHeight = 450;
const iconSize = 21;
// TODO: Define what else will be inside like payload (decoding etc)
export const InputCard: FC<Props> = ({ input }) => {
const theme = useMantineTheme();
const statusColor = useRightColorShade(getStatusColor(input.status));
const [viewControl, setViewControl] = useState<ViewControl>("payload");
const [decoderType, setDecoderType] = useState<DecoderType>("raw");
Expand All @@ -54,7 +55,12 @@ export const InputCard: FC<Props> = ({ input }) => {
<Card shadow="md" withBorder>
<Card.Section withBorder inheritPadding py="sm">
<Group justify="space-between">
<Address value={input.decodedData.sender} icon shorten />
<Address
value={input.decodedData.sender}
icon
shorten
iconSize={theme.other.mdIconSize}
/>
<Group>
<Text fw="bold"># {input.index}</Text>
<Activity
Expand Down Expand Up @@ -105,11 +111,7 @@ export const InputCard: FC<Props> = ({ input }) => {
<Activity
mode={viewControl === "payload" ? "visible" : "hidden"}
>
<Spoiler
maxHeight={80}
showLabel="Show more"
hideLabel="Show less"
>
<Spoiler>
<Text style={{ wordBreak: "break-all" }}>
{decoderFn(input.decodedData.payload)}
</Text>
Expand Down Expand Up @@ -144,7 +146,7 @@ export const InputCard: FC<Props> = ({ input }) => {
<Group gap="xs" justify="space-between">
<Group gap={3}>
<Tooltip label="Transaction hash">
<TbReceipt size={iconSize} />
<TbReceipt size={theme.other.mdIconSize} />
</Tooltip>
<TransactionHash
transactionHash={input.transactionReference}
Expand Down
2 changes: 1 addition & 1 deletion apps/dave/src/components/input/InputList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface Props {

export const InputList: FC<Props> = ({ inputs }) => {
return (
<Stack gap="xs" py="md">
<Stack gap="xs" pb="md">
{inputs.map((input) => (
<InputCard input={input} key={input.index} />
))}
Expand Down
Loading
Loading