Skip to content

Commit

Permalink
Add developer tool
Browse files Browse the repository at this point in the history
  • Loading branch information
rygine committed Jan 10, 2025
1 parent 3f59f3d commit d45ac42
Show file tree
Hide file tree
Showing 86 changed files with 5,381 additions and 1,782 deletions.
8 changes: 7 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
{
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"clinyong.vscode-css-modules",
"vunguyentuan.vscode-css-variables",
"vunguyentuan.vscode-postcss"
]
}
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,12 @@
{
"mode": "auto"
}
],
"cssVariables.lookupFiles": [
"**/*.css",
"**/*.scss",
"**/*.sass",
"**/*.less",
"node_modules/@mantine/core/styles.css"
]
}
1 change: 1 addition & 0 deletions apps/xmtp.chat/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_ENCRYPTION_KEY= # 32 byte hex string
19 changes: 19 additions & 0 deletions apps/xmtp.chat/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# xmtp.chat app

Use this React app as a tool to start building an app with XMTP.

The app is built using the [XMTP client browser SDK](/sdks/browser-sdk/README.md), [React](https://react.dev/), and [RainbowKit](https://www.rainbowkit.com/).

To keep up with the latest React app developments, see the [Issues tab](https://github.com/xmtp/xmtp-js/issues) in this repo.

To learn more about XMTP and get answers to frequently asked questions, see the [XMTP documentation](https://xmtp.org/docs).

### Limitations

This React app isn't a complete solution. For example, the list of conversations doesn't update when new messages arrive in existing conversations.

## Useful commands

- `yarn clean`: Removes `node_modules` and `.turbo` folders
- `yarn dev`: Runs the app in development mode
- `yarn typecheck`: Runs `tsc`
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>XMTP V3 Browser SDK Example</title>
<link rel="icon" href="/favicon.ico" />
<title>XMTP Browser Developer Tools</title>
</head>
<body>
<div id="root"></div>
Expand Down
53 changes: 53 additions & 0 deletions apps/xmtp.chat/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "@xmtp/xmtp.chat",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts",
"./styles.css": "./src/styles/index.css"
},
"main": "src/index.ts",
"scripts": {
"build": "vite build",
"clean": "rm -rf .turbo && rm -rf node_modules && yarn clean:dist",
"clean:dist": "rm -rf dist",
"dev": "vite",
"typecheck": "tsc"
},
"dependencies": {
"@mantine/core": "^7.14.3",
"@mantine/form": "^7.14.3",
"@mantine/hooks": "^7.14.3",
"@mantine/modals": "^7.14.3",
"@mantine/notifications": "^7.14.3",
"@tanstack/react-query": "^5.61.5",
"@xmtp/browser-sdk": "^0.0.13",
"@xmtp/content-type-group-updated": "^2.0.0",
"@xmtp/content-type-primitives": "^2.0.0",
"@xmtp/content-type-reaction": "^2.0.0",
"@xmtp/content-type-remote-attachment": "^2.0.0",
"@xmtp/content-type-reply": "^2.0.0",
"@xmtp/content-type-text": "^2.0.0",
"date-fns": "^4.1.0",
"react": "^18.3.1",
"react-18-blockies": "^1.0.6",
"react-confetti": "^6.1.0",
"react-dom": "^18.3.1",
"react-router": "^7.0.2",
"uint8array-extras": "^1.4.0",
"viem": "^2.21.52",
"wagmi": "^2.13.2"
},
"devDependencies": {
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.3",
"postcss": "^8.4.49",
"postcss-preset-mantine": "^1.17.0",
"postcss-simple-vars": "^7.0.1",
"tsconfig": "workspace:*",
"typescript": "^5.7.2",
"vite": "^6.0.2"
}
}
14 changes: 14 additions & 0 deletions apps/xmtp.chat/postcss.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module.exports = {
plugins: {
"postcss-preset-mantine": {},
"postcss-simple-vars": {
variables: {
"mantine-breakpoint-xs": "36em",
"mantine-breakpoint-sm": "48em",
"mantine-breakpoint-md": "62em",
"mantine-breakpoint-lg": "75em",
"mantine-breakpoint-xl": "88em",
},
},
},
};
Binary file added apps/xmtp.chat/public/favicon.ico
Binary file not shown.
File renamed without changes
Binary file added apps/xmtp.chat/src/assets/xmtp-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions apps/xmtp.chat/src/components/Actions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Button, Flex, useMatches } from "@mantine/core";
import { useEffect, useRef } from "react";
import { useNavigate } from "react-router";
import { useClient } from "../hooks/useClient";
import { IconMessagePlus } from "../icons/IconMessagePlus";
import { useRefManager } from "./RefManager";

export const Actions: React.FC = () => {
const { client } = useClient();
const navigate = useNavigate();
const { setRef } = useRefManager();
const ref = useRef<HTMLButtonElement>(null);
const label: React.ReactNode = useMatches({
base: <IconMessagePlus size={24} />,
sm: "New conversation",
});
const px = useMatches({
base: "xs",
sm: "md",
});
const handleClick = () => {
void navigate("/conversations/new");
};

useEffect(() => {
setRef("new-conversation-button", ref);
}, []);

return (
client && (
<Flex align="center" gap="xs">
<Button px={px} onClick={handleClick} ref={ref}>
{label}
</Button>
</Flex>
)
);
};
68 changes: 68 additions & 0 deletions apps/xmtp.chat/src/components/AddressBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Badge, Flex, Text, Tooltip } from "@mantine/core";
import { useClipboard } from "@mantine/hooks";
import { shortAddress } from "../helpers/address";

export type AddressTooltipLabelProps = {
address: string;
};

export const AddressTooltipLabel: React.FC<AddressTooltipLabelProps> = ({
address,
}) => {
return (
<Flex direction="column">
<Text size="sm">{address}</Text>
<Text size="xs" c="dimmed" ta="center">
click to copy
</Text>
</Flex>
);
};

export type AddressBadgeProps = {
address: string;
};

export const AddressBadge: React.FC<AddressBadgeProps> = ({ address }) => {
const clipboard = useClipboard({ timeout: 1000 });

const handleCopy = () => {
clipboard.copy(address);
};

const handleKeyboardCopy = (event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "Enter" || event.key === " ") {
handleCopy();
}
};

return (
<Tooltip
label={
clipboard.copied ? (
<Text size="xs">Copied!</Text>
) : (
<AddressTooltipLabel address={address} />
)
}
withArrow
events={{ hover: true, focus: true, touch: true }}>
<Badge
variant="default"
size="lg"
radius="md"
onKeyDown={handleKeyboardCopy}
onClick={handleCopy}
miw={100}
tabIndex={0}
styles={{
label: {
textTransform: "none",
},
}}
flex="1 0">
{shortAddress(address)}
</Badge>
</Tooltip>
);
};
13 changes: 13 additions & 0 deletions apps/xmtp.chat/src/components/App.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@media (max-width: 48em) {
.main {
--app-shell-footer-offset: 0px !important;
}

.navbar {
--app-shell-footer-offset: 0px !important;
}

.footer {
--app-shell-footer-height: 0px;
}
}
115 changes: 115 additions & 0 deletions apps/xmtp.chat/src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {
AppShell,
Button,
Group,
Modal,
Stack,
Text,
Title,
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { useEffect, useState } from "react";
import { useLocation, useNavigate } from "react-router";
import { useClient } from "../hooks/useClient";
import classes from "./App.module.css";
import { AppFooter } from "./AppFooter";
import { AppHeader } from "./AppHeader";
import { Main } from "./Main";
import { Navbar } from "./Navbar";

export const App: React.FC = () => {
const [opened, { toggle }] = useDisclosure();
const [collapsed, setCollapsed] = useState(true);
const [unhandledRejectionError, setUnhandledRejectionError] = useState<
string | null
>(null);
const { client } = useClient();
const location = useLocation();
const navigate = useNavigate();

useEffect(() => {
if (!client && location.pathname !== "/") {
void navigate("/");
return;
}

if (
location.pathname.startsWith("/conversations") ||
location.pathname.startsWith("/identity")
) {
setCollapsed(false);
} else {
setCollapsed(true);
}

if (location.pathname === "/" && client) {
void navigate("/conversations");
}
}, [location.pathname, client, navigate]);

useEffect(() => {
const handleUnhandledRejection = (event: PromiseRejectionEvent) => {
setUnhandledRejectionError(
(event.reason as Error).message || "Unknown error",
);
};
window.addEventListener("unhandledrejection", handleUnhandledRejection);
return () => {
window.removeEventListener(
"unhandledrejection",
handleUnhandledRejection,
);
};
}, []);

return (
<>
{unhandledRejectionError && (
<Modal
opened={!!unhandledRejectionError}
onClose={() => {
setUnhandledRejectionError(null);
}}
withCloseButton={false}
centered>
<Stack gap="md">
<Title order={4}>Application error</Title>
<Text>{unhandledRejectionError}</Text>
<Group justify="flex-end">
<Button
onClick={() => {
setUnhandledRejectionError(null);
}}>
OK
</Button>
</Group>
</Stack>
</Modal>
)}
<AppShell
header={{ height: 68 }}
footer={{
height: 64,
}}
navbar={{
width: { base: 300, lg: 420 },
breakpoint: "sm",
collapsed: { desktop: collapsed, mobile: !opened },
}}
padding="md">
<AppShell.Header>
<AppHeader opened={opened} toggle={toggle} collapsed={collapsed} />
</AppShell.Header>
<AppShell.Navbar className={classes.navbar}>
{client && <Navbar />}
</AppShell.Navbar>
<AppShell.Main className={classes.main}>
<Main />
</AppShell.Main>
<AppShell.Footer display="flex" className={classes.footer}>
<AppFooter />
</AppShell.Footer>
</AppShell>
</>
);
};
Loading

0 comments on commit d45ac42

Please sign in to comment.