Skip to content

Commit

Permalink
feat: implement Hygraph Plugin Starter App
Browse files Browse the repository at this point in the history
  • Loading branch information
devranowski committed Nov 29, 2023
1 parent d58ea2b commit 5ae7743
Show file tree
Hide file tree
Showing 51 changed files with 1,521 additions and 194 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage
Expand All @@ -19,6 +18,7 @@
# misc
.DS_Store
*.pem
.idea/

# debug
npm-debug.log*
Expand Down
8 changes: 8 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"tabWidth": 2,
"useTabs": false,
"printWidth": 120,
"singleQuote": true,
"trailingComma": "none",
"plugins": ["prettier-plugin-tailwindcss"]
}
259 changes: 236 additions & 23 deletions README.md

Large diffs are not rendered by default.

Binary file modified bun.lockb
Binary file not shown.
14 changes: 12 additions & 2 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
/** @type {import('next').NextConfig} */
const nextConfig = {}
const nextConfig = {
webpack(config) {
config.module.rules.push({
test: /.svg$/i,
issuer: /.[jt]sx?$/,
use: ['@svgr/webpack']
});

module.exports = nextConfig
return config;
}
};

module.exports = nextConfig;
29 changes: 25 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,40 @@
"lint": "next lint"
},
"dependencies": {
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/modifiers": "^6.0.1",
"@dnd-kit/sortable": "^7.0.2",
"@dnd-kit/utilities": "^3.2.1",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@hygraph/app-sdk-react": "^0.0.2",
"@hygraph/baukasten": "^0.3.0",
"@hygraph/icons": "^0.2.1",
"@tanstack/react-query": "^5.4.3",
"i18next": "^23.6.0",
"lodash": "^4.17.21",
"nanoid": "^5.0.2",
"next": "14.0.1",
"react": "^18",
"react-dom": "^18",
"next": "14.0.1"
"react-i18next": "^13.3.1",
"zod": "^3.22.4"
},
"devDependencies": {
"typescript": "^5",
"@svgr/webpack": "^8.1.0",
"@tanstack/react-query-devtools": "^5.4.3",
"@total-typescript/ts-reset": "^0.5.1",
"@types/lodash": "^4.14.200",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
"eslint": "^8",
"eslint-config-next": "14.0.1",
"postcss": "^8",
"prettier": "^3.0.3",
"prettier-plugin-tailwindcss": "^0.5.6",
"tailwindcss": "^3.3.0",
"eslint": "^8",
"eslint-config-next": "14.0.1"
"typescript": "^5"
}
}
Binary file added public/hygraph-logo.webp
Binary file not shown.
1 change: 0 additions & 1 deletion public/next.svg

This file was deleted.

1 change: 0 additions & 1 deletion public/vercel.svg

This file was deleted.

31 changes: 31 additions & 0 deletions src/app/api/cats/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { NextRequest, NextResponse } from 'next/server';
import { CAT_API_URL } from '../consts';
import { RequestBody } from './types';
import { ApiAsset } from '@/hooks/useAssetsQuery/useAssetsQuery.types';

export async function POST(req: NextRequest) {
try {
const requestBody = (await req.json()) as RequestBody;

const apiKey = requestBody.configuration.apiKey;

if (!apiKey) {
throw new Error('API key is missing');
}

const response = await fetch(CAT_API_URL, {
headers: {
'x-api-key': apiKey
}
});

const data = (await response.json()) as ApiAsset[];

const cats = data.slice(0, 10); // Example: Fetch only the first 10 cats

return NextResponse.json({ data: cats });
} catch (error) {
console.error('Error fetching cat data:', error);
return NextResponse.json({ message: 'Error fetching data' }, { status: 500 });
}
}
10 changes: 10 additions & 0 deletions src/app/api/cats/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Config } from '@/hooks/useAppConfig/useAppConfig';

export type RequestBody = {
configuration: Config;
};

export type ErrorData = {
message: string;
statusCode: number;
};
4 changes: 4 additions & 0 deletions src/app/api/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const CATS_API_BASE_ROUTE = '/api/cats';

export { CATS_API_BASE_ROUTE };
export const CAT_API_URL = 'https://api.thecatapi.com/v1/breeds';
91 changes: 91 additions & 0 deletions src/app/asset-dialog/components/DialogTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Box, Button, Flex, Grid, Pill } from '@hygraph/baukasten';
import { FieldAsset } from '@hygraph/icons';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useAssetsQuery } from '@/hooks/useAssetsQuery/useAssetsQuery';
import { DialogTable, AssetThumbnail as AssetThumbnail, AssetsGrid } from './DialogTable.types';
import { Asset } from '@/app/asset-field/components/AssetCard/AssetCard.types';

const DialogTable: React.FC<DialogTable> = ({ onCloseDialog, isSingleSelect }) => {
const [selectedAssets, setSelectedAssets] = useState<Asset[]>([]);
const { t } = useTranslation();
const assets = useAssetsQuery();

const handleSelectItem = (item: Asset) => {
if (isSingleSelect) {
setSelectedAssets([item]);
} else {
if (selectedAssets.find((i) => i.id === item.id)) {
setSelectedAssets(selectedAssets.filter((i) => i.id !== item.id));
} else {
setSelectedAssets([...selectedAssets, item]);
}
}
};

return (
<Box padding="1.5rem">
<AssetsGrid assets={assets} handleSelectItem={handleSelectItem} selectedAssets={selectedAssets} />

<Button
className="mx-auto mt-10 block"
variantColor="primary"
size="large"
onClick={() => onCloseDialog(selectedAssets)}
>
{t('assetPicker.passSelectionButtonLabel')}
</Button>
</Box>
);
};

const AssetThumbnail = ({ asset, isSelected }: AssetThumbnail) => {
const { t } = useTranslation();
const { imageUrl, name } = asset;

return (
<Flex
justifyContent="center"
alignassets="center"
flexDirection="column"
padding="1rem"
width="100%"
height="100%"
border={isSelected ? '2px solid blue' : '1px solid gray'}
gap="0.5rem"
>
{imageUrl ? (
<img src={imageUrl} alt={t('general.assetThumbnailAlt')} className="object-fit h-full" />
) : (
<Box as={FieldAsset} color="neutral.200" width={60} height={60} />
)}
<Flex alignassets="center">
<Pill variantColor="primary" variant="outline" maxWidth="12rem" isTruncated>
{name}
</Pill>
</Flex>
</Flex>
);
};

const AssetsGrid = ({ assets, handleSelectItem, selectedAssets }: AssetsGrid) => {
return (
<Grid gridTemplateColumns="repeat(4, 1fr)" gap="8">
{assets.map((asset, index) => (
<Box
key={index}
onClick={() => handleSelectItem(asset)}
height={200}
cursor="pointer"
display="flex"
justifyContent="center"
alignassets="center"
>
<AssetThumbnail asset={asset} isSelected={selectedAssets.includes(asset)} />
</Box>
))}
</Grid>
);
};

export { DialogTable };
17 changes: 17 additions & 0 deletions src/app/asset-dialog/components/DialogTable.types.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Asset } from '@/app/asset-field/components/AssetCard/AssetCard.types';

export interface DialogTable {
onCloseDialog: (selectedItems: Asset[]) => void;
isSingleSelect: boolean;
}

export interface AssetThumbnail {
asset: Asset;
isSelected: boolean;
}

export interface AssetsGrid {
assets: Asset[];
handleSelectItem: (asset: Asset) => void;
selectedAssets: Asset[];
}
27 changes: 27 additions & 0 deletions src/app/asset-dialog/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use client';

import React from 'react';
import { DialogContent, Divider, Heading } from '@hygraph/baukasten';
import { Config } from '@/hooks/useAppConfig/useAppConfig';
import { useUiExtensionDialog } from '@hygraph/app-sdk-react';
import { DialogTable } from './components/DialogTable';
import { Asset } from '../asset-field/components/AssetCard/AssetCard.types';

const AssetDialog = () => {
const { onCloseDialog, isSingleSelect, configuration } = useUiExtensionDialog<
unknown,
{ onCloseDialog: (assets: Asset[]) => void; isSingleSelect: boolean; configuration: Config }
>();

return (
<DialogContent padding="0" height="60rem">
<Heading as="h1" className="p-24 text-2xl">
Select asset
</Heading>
<Divider margin="0" />
<DialogTable onCloseDialog={onCloseDialog} isSingleSelect={isSingleSelect} />
</DialogContent>
);
};

export default AssetDialog;
49 changes: 49 additions & 0 deletions src/app/asset-field/components/AssetCard/AssetCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Box, Card, Heading, IconButton, Flex } from '@hygraph/baukasten';
import { Close, DragHandle, FieldAsset } from '@hygraph/icons';
import { Icon } from '@/types/common';
import { useTranslation } from 'react-i18next';
import { AssetCard } from './AssetCard.types';

const AssetCard = ({ dragHandleProps, onRemoveItem, name, id, isSingleAsset, imageUrl, isDragging }: AssetCard) => {

const setCursor = (isSingleAsset: boolean, isDragging: boolean | undefined) => {
if (isSingleAsset) {
return 'default';
}

if (isDragging) {
return 'grabbing';
}

return 'grab';
};

return (
<Card className="flex max-h-[70px]">
<Box className="m-8 flex flex-col justify-center text-neutral-400" {...dragHandleProps}>
{(DragHandle as Icon)({
style: { fontSize: '0.8rem', cursor: setCursor(isSingleAsset, isDragging) }
})}
</Box>
<Flex justifyContent="center" alignItems="center" width={70} minWidth={70} height={70}>
{imageUrl ? (
<img src={imageUrl} className="h-[60px] w-[60px] object-cover" />
) : (
<Box as={FieldAsset} color="neutral.200" width={50} height={50} />
)}
</Flex>
<Flex className="px-8 py-16" alignItems="center">
<Heading className="text-[13px]/[16px] text-neutral-900">{name || ''}</Heading>
</Flex>
<IconButton
icon={Close as Icon}
variantColor="secondary"
variant="ghost"
className="ml-auto mr-8"
onClick={() => onRemoveItem(id)}
/>
</Card>
);
};

export { AssetCard };
15 changes: 15 additions & 0 deletions src/app/asset-field/components/AssetCard/AssetCard.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export interface Asset {
id: string;
name: string;
imageUrl: string;
}

export interface AssetCard {
dragHandleProps?: { [x: string]: Function };
id: string;
onRemoveItem: (id: string) => void;
isSingleAsset: boolean;
imageUrl: string;
name: string;
isDragging?: boolean;
}
Loading

0 comments on commit 5ae7743

Please sign in to comment.