Skip to content

Commit

Permalink
Merge branch 'main' into feat/app-cards
Browse files Browse the repository at this point in the history
  • Loading branch information
partyparrotgreg authored Jul 3, 2024
2 parents 9245cd3 + 2bdf88d commit 63c0408
Show file tree
Hide file tree
Showing 446 changed files with 63,330 additions and 683 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
node_modules
.idea/*
.DS_Store
.vscode
.vscode

yarn-error.log
10 changes: 9 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
{
"tabWidth": 2,
"useTabs": false
"bracketSpacing": true,
"jsxBracketSameLine": true,
"printWidth": 164,
"singleQuote": true,
"jsxSingleQuote": true,
"trailingComma": "none",
"arrowParens": "always",
"useTabs": true,
"importOrderSeparation": true
}
183 changes: 183 additions & 0 deletions components/APIEndpoint/APIEndpoint.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { Code, Flex, Paper, Title, Accordion, Breadcrumbs, Anchor, Text } from '@mantine/core';
import { NextSeo } from 'next-seo';
import { EndpointResponseProperty, EndpointParameter, Endpoint } from './types';
import { StyledSyntaxHighlighter } from '../StyledSyntaxHighlighter';

const renderProperties = (properties: EndpointResponseProperty) => {
return Object.entries(properties).map(([key, value]) => (
<Paper key={key} withBorder p='md'>
<Flex gap='lg'>
<strong>{key}</strong>
<Code>{value.type}</Code>
</Flex>
<p>{value.description}</p>
{value.properties && (
<Flex direction='column' gap='xs' mt='lg'>
<Title order={5}>Properties</Title>
{renderProperties(value.properties)}
</Flex>
)}
</Paper>
));
};

const getRouteNames = (operationId: string, parts: string[]) => {
let upperCaseFunctionName = operationId.toUpperCase();

upperCaseFunctionName = upperCaseFunctionName.replace('SEIPROTOCOL', '');
upperCaseFunctionName = upperCaseFunctionName.replace('XV1BETA1', '');

for (let i = 0; i < parts.length - 1; i++) {
// Remove the '-' in 'sei-chain'
const uppercasePart = parts[i].replace('-', '').toUpperCase();
upperCaseFunctionName = upperCaseFunctionName.replace(uppercasePart, '');
}

const indexOf = operationId.toUpperCase().lastIndexOf(upperCaseFunctionName);
const typeName = operationId.slice(indexOf);

return {
functionName: typeName.charAt(0).toLowerCase() + operationId.slice(indexOf + 1),
typeName
};
};

const unquotedStringify = (obj: object) => {
if (!obj) return '{}';
const entries = Object.entries(obj);
const properties = entries.map(([key, value]) => `${key}: ${JSON.stringify(value)}`);
return `{ \n\t${properties.join(',\n\t')}\n }`;
};

const Parameter = (params: EndpointParameter) => {
const { name, description, type, required, format } = params;
return (
<Paper key={name} withBorder p='md'>
<Flex gap='lg'>
<strong>
{name}
{required && <span>*</span>}
</strong>
<Code>{type}</Code>
<Code>{params['in']}</Code>
{format && (
<Text>
format: <Code>{format}</Code>
</Text>
)}
</Flex>
<p>{description}</p>
</Paper>
);
};

const EndpointResponse = ([code, response]) => {
return (
<Accordion.Item key={code} value={code}>
<Flex direction='column' gap='xs'>
<Accordion.Control>
<strong>{code}</strong>
<p>{response.description}</p>
</Accordion.Control>
<Accordion.Panel>
{response.schema && response.schema.properties && (
<Flex direction='column' gap='xs'>
<Title order={5}>Properties</Title>
{renderProperties(response.schema.properties)}
</Flex>
)}
</Accordion.Panel>
</Flex>
</Accordion.Item>
);
};

export const APIEndpoint = ({ endpoint }: { endpoint: Endpoint }) => {
const [path, methods] = endpoint;

const parts = path
.split('/')
.filter((part: string) => !part.startsWith('{') && part !== '')
.map((part: string) => part.replace('-', '').toLowerCase());

const orgName = parts[0];
const moduleName = parts[1];
const version = parts[2];
const functionName = parts[parts.length - 1];

const method = Object.entries(methods)[0];

const [httpMethod, details] = method;

const routeNames = getRouteNames(details.operationId, parts);

const requestType = `Query${routeNames.typeName}Request`;
const responseType = `Query${routeNames.typeName}ResponseSDKType`;

const requiredParams = details.parameters?.filter((param) => param.required);
const optionalParams = details.parameters?.filter((param) => !param.required);

const paramsString = unquotedStringify(
details.parameters
?.filter((param) => param.required)
.map((param) => param.name)
.reduce((acc, curr) => ({ ...acc, [curr]: '' }), {})
);

return (
<>
<NextSeo title={functionName}></NextSeo>
<Flex direction='column' gap='xl'>
<Breadcrumbs mt='md'>
<Anchor href={`/endpoints/cosmos#${orgName}`}>endpoints</Anchor>
<Anchor href={`/endpoints/${orgName}/${moduleName}`}>{moduleName}</Anchor>
</Breadcrumbs>
<Title>{functionName}</Title>
<Flex gap='sm' align='center'>
<Code style={{ minWidth: 'fit-content', height: 'fit-content' }}>{httpMethod.toUpperCase()}</Code>
<Text size='xl' style={{ wordBreak: 'break-all' }}>
{path}
</Text>
</Flex>
<Title order={4}>{details.summary}</Title>
{!!requiredParams && (
<Flex gap='sm' direction='column'>
<Title order={4}>Parameters</Title>
{requiredParams.map(Parameter)}
</Flex>
)}

{optionalParams?.length > 0 && (
<Accordion>
<Accordion.Item value='optional'>
<Accordion.Control>
<Title order={4}>Optional Parameters</Title>
</Accordion.Control>
<Accordion.Panel>
<Flex gap='sm' direction='column' mt='sm'>
{optionalParams.map(Parameter)}
</Flex>
</Accordion.Panel>
</Accordion.Item>
</Accordion>
)}
<Flex gap='sm' direction='column'>
<Title order={4}>Responses</Title>
<Accordion>{Object.entries(details.responses).map(EndpointResponse)}</Accordion>
</Flex>
<Flex gap='sm' direction='column'>
<Title order={4}>Example Usage</Title>
<StyledSyntaxHighlighter language='javascript'>
{`import { getQueryClient } from '@sei-js/cosmjs';
const queryClient = await getQueryClient("YOUR_RPC_URL");
const { ${routeNames.functionName} } = queryClient.${orgName}.${moduleName}.${version};
const params: ${requestType} = ${paramsString};
const response: ${responseType} = await ${routeNames.functionName}(params);`}
</StyledSyntaxHighlighter>
</Flex>
</Flex>
</>
);
};
1 change: 1 addition & 0 deletions components/APIEndpoint/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './APIEndpoint';
30 changes: 30 additions & 0 deletions components/APIEndpoint/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export type EndpointParameter = {
name: string;
in: string;
description: string;
required: boolean;
type: string;
format?: string;
};

export type EndpointResponseProperty = {
[key: string]: {
type: string;
description: string;
properties?: EndpointResponseProperty;
};
};

export type EndpointResponses = {
[code: string]: {
description: string;
schema: {
properties: EndpointResponseProperty;
};
};
};

export type Endpoint = [
string,
{ get: { operationId: string; summary: string; description: string; parameters: EndpointParameter[]; responses: EndpointResponses } }
];
44 changes: 44 additions & 0 deletions components/APIEndpointRoute/APIEndpointRoute.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { APIEndpoint, APIModule } from '../index';
import { useRouter } from 'next/router';
import openapi from '../../data/cosmos-openapi.json';
import { Flex, Title } from '@mantine/core';
import { Button } from 'nextra/components';
import Link from 'next/link';
import { NextSeo } from 'next-seo';
import { filterModuleRoutes } from './utils';

export const APIEndpointRoute = () => {
const router = useRouter();
const routes = router.query.route as string[];

if (!routes?.[0]) return null;

const moduleRoutes = filterModuleRoutes(Object.entries(openapi.paths), routes);

const splitRoutes = moduleRoutes?.[0]?.[0].split('/');
const SEO_TITLE = `Cosmos API - ${splitRoutes?.[2]} - Sei Docs`;

if (routes.length === 2) {
return (
<Flex direction={'column'} gap='md'>
<NextSeo title={SEO_TITLE}></NextSeo>

<Link href={`/endpoints/cosmos#${splitRoutes[1]}`}>
<Button>Back</Button>
</Link>

<Title order={1} mb='xl'>
{routes.join('/')}
</Title>
<APIModule prefix={routes.join('/')} basePaths={moduleRoutes.map((route) => route[0])} />
</Flex>
);
}

return (
<>
<NextSeo title={SEO_TITLE}></NextSeo>
<APIEndpoint endpoint={moduleRoutes[0]} />
</>
);
};
1 change: 1 addition & 0 deletions components/APIEndpointRoute/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './APIEndpointRoute';
1 change: 1 addition & 0 deletions components/APIEndpointRoute/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type ApiPathEntry = [string, any];
14 changes: 14 additions & 0 deletions components/APIEndpointRoute/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ApiPathEntry } from './types';

export const filterModuleRoutes = (paths: ApiPathEntry[], filters: string[]) => {
return paths.filter((path) => {
if (!path[0]) return false;
let parts = path[0].split('/');
for (let i = 0; i < filters.length; i++) {
if (parts[i + 1] !== filters[i]) {
return false;
}
}
return true;
});
};
7 changes: 7 additions & 0 deletions components/APIModule/APIModule.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Card } from 'nextra/components';

export const APIModule = ({ basePaths, prefix }: { basePaths: any[]; prefix: string }) => {
return Object.values(basePaths).map((path) => {
return <Card children={null} icon={null} key={path} title={path.replace(`/${prefix}`, '')} href={`/endpoints${path}`} />;
});
};
1 change: 1 addition & 0 deletions components/APIModule/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './APIModule';
13 changes: 13 additions & 0 deletions components/APIModule/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const getUniqueSections = (paths: object, filters: string[]) => {
const sections = new Set();

Object.keys(paths).forEach((path) => {
const parts = path.split('/');
filters.map((filter) => {
if (parts[1] === filter) {
sections.add(parts[2]);
}
});
});
return Array.from(sections);
};
11 changes: 11 additions & 0 deletions components/APIModulePaths/APIModulePaths.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Card, Cards } from 'nextra/components';

export const APIModulePaths = ({ basePaths, prefix }: { basePaths: any[]; prefix: string }) => {
return (
<Cards>
{Object.values(basePaths).map((path) => {
return <Card key={path} title={path} href={`/endpoints/${prefix}/${path}`} icon={null} children={null} />;
})}
</Cards>
);
};
1 change: 1 addition & 0 deletions components/APIModulePaths/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './APIModulePaths';
9 changes: 5 additions & 4 deletions components/BrandKitGallery/DownloadButton.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
// components/BrandKitGallery/DownloadButton.tsx
import React from 'react';
import styles from '../../styles/custom.module.css';

import { DownloadIcon } from "lucide-react";

const DownloadButton = ({ url, fileName, children }) => {
const handleDownload = () => {
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', fileName);
// TODO: add target blank
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};

return (
<button className={styles.downloadButton} onClick={handleDownload}>
{children}
<button className="inline-flex items-center px-4 py-1.5 font-semibold bg-black text-white rounded-lg hover:bg-gray-800 dark:bg-white dark:text-black dark:hover:bg-gray-200 transition-all duration-200 active:translate-y-px" onClick={handleDownload}>
{children} <DownloadIcon className="w-4 h-4 ml-2" />
</button>
);
};
Expand Down
Loading

0 comments on commit 63c0408

Please sign in to comment.