diff --git a/guides/CUSTOM_BLOCK.md b/guides/CUSTOM_BLOCK.md
new file mode 100644
index 000000000..29c96d9c2
--- /dev/null
+++ b/guides/CUSTOM_BLOCK.md
@@ -0,0 +1,99 @@
+# MyMeta - Custom Blocks
+
+This shall serve as a minimal guide towards developing custom blocks and linking your MyMeta player profiles, guild profiles and/or personal dashboards with external applications or APIs.
+
+## Pre-requesites
+
+- The MyMeta frontend codebase ([`packages/web`](https://github.com/MetaFam/TheGame/blob/develop/packages/web)) uses NextJS, so a basic understanding of NextJS architecture is a must.
+- Some important files that you must understand before proceeding to develop your own custom blocks:
+ 1. Dashboard Page - [`pages/dashboard.tsx`](https://github.com/MetaFam/TheGame/blob/develop/packages/web/pages/dashboard.tsx)
+ 2. Player Profile Page- [`pages/player/[username].tsx`](https://github.com/MetaFam/TheGame/blob/develop/packages/web/pages/player/[username.tsx])
+ 3. Guild Profile Page - [`pages/guild/[guildname]/index.tsx`](https://github.com/MetaFam/TheGame/blob/develop/packages/web/pages/guild/%5Bguildname%5D/index.tsx)
+ 4. Common EditableGridLayout Component - [`components/EditableGridLayout.tsx`](https://github.com/MetaFam/TheGame/blob/develop/packages/web/components/EditableGridLayout.tsx)
+ - This component is the basis for building the layout of blocks.
+ - One of the props for `EditableGridLayout`, is the `displayComponent` which renders the actual block. The different display components for different pages are given below.
+ 5. Dashboard Display Component - [`components/Dashboard/DashboardSection.tsx`](https://github.com/MetaFam/TheGame/blob/develop/packages/web/components/Dashboard/DashboardSection.tsx)
+ - The `displayComponent` for `pages/dashboard.tsx`
+ 6. Player Profile Display Component - [`components/Player/PlayerSection.tsx`](https://github.com/MetaFam/TheGame/blob/develop/packages/web/components/Player/PlayerSection.tsx)
+ - The `displayComponent` for `pages/player/[username].tsx`
+ 7. Guild Profile Display Component - [`components/Guild/GuildSection.tsx`](https://github.com/MetaFam/TheGame/blob/develop/packages/web/components/Guild/GuildSection.tsx)
+ - The `displayComponent` for `pages/guild/[guildname]/index.tsx`
+ 8. AddBoxSection Component - [`components/Section/AddBoxSection.tsx`](https://github.com/MetaFam/TheGame/blob/develop/packages/web/components/Section/AddBoxSection.tsx)
+ - Another key component in `EditableGridLayout`.
+ - This renders the UI for the user to customize and create a new block on their `EditableGridLayout`.
+ 9. Box Types Utils - [`utils/boxTypes.ts`](https://github.com/MetaFam/TheGame/blob/develop/packages/web/utils/boxTypes.ts)
+ - This file contains all the basic utilities methods and constants required for the functionality of custom blocks
+
+## Steps
+
+> We shall be creating a simple block with a custom title and content. Similar steps can be followed to create more complex custom blocks.
+
+### Step 1: Create a new BoxType
+
+- Add a new `BoxType` in `utils/boxTypes.ts`.
+
+```
+ CUSTOM_TEXT: 'custom-text'
+```
+
+- Refer to the commit [830bb74](https://github.com/MetaFam/TheGame/commit/830bb7423be0d1f9af7d871d50e41ec9d3695d37) for the code changes
+
+### Step 2: Define the structure of metadata
+
+- Each block can use a custom metadata with the type of `BoxMetadata`, and we must define the type for our custom block. For this usecase we shall assume that it requires two text parameters `title` and `content`.
+
+### Step 3: Create the component for this boxType
+
+- Now create a new component `CustomTextSection` with `title` and `content` as props.
+- Refer to the commit [e00d05b](https://github.com/MetaFam/TheGame/commit/e00d05be92bbebbd24743143386d41a581384901) for the code changes
+
+### Step 4: Create the metadata editor component
+
+- Next, we can create a component `CustomTextSectionMetadata` which has two props `metadata` & `setMetadata` which shall be used in `AddBoxSection` for the user to create the metadata required for `CustomTextSection`.
+- Refer to the commit [e00d05b](https://github.com/MetaFam/TheGame/commit/e00d05be92bbebbd24743143386d41a581384901) for the code changes
+
+### Step 5: Add the render option in the display components
+
+- Add an extra case in the switch statements in `DashboardSection`, `PlayerSection` and `GuildSection` components to handle the metadata and render the `CustomTextSection`
+
+```
+case BoxTypes.CUSTOM_TEXT: {
+ const { title, content } = metadata ?? {};
+ return title && content ? (
+
+ ) : null;
+ }
+```
+
+- In this case, our custom text can be added to all three pages, but if your custom box is relavant only to one of the pages then add it only to that particular display component.
+- Refer to the commit [f1ba43d](https://github.com/MetaFam/TheGame/commit/f1ba43dd29195d4032a2c34cd668e2ba7c38d275) for the code changes
+
+### Step 6: Add our boxType to the supported list of boxes
+
+- Add `BoxTypes.CUSTOM_TEXT` to the list of `ALL_BOXES` to the `config.ts` of each page
+ - `components/Player/Section/config.ts` for player profiles
+ - `components/Guild/Section/config.ts` for guild profiles
+ - `components/Dashboard/config.ts` for dashboard
+- Refer to the commit [6307ef1](https://github.com/MetaFam/TheGame/commit/6307ef135805a73f0380c172ec3d276ef561f23f) for the code changes
+
+### Step 7: Add our metadata editor component
+
+- Add the `CustomTextSectionMetadata` editor component to the `AddBoxSection` component
+- Refer to the commit [bb9e74a](https://github.com/MetaFam/TheGame/commit/bb9e74afc78e0f29078a10635d53632a6cccc47f) for the code changes
+
+### Step 7: Add specific config/helpers for this boxType
+
+- Add a default box height in `utils/layoutHelpers.ts` under `DEFAULT_BOX_HEIGHTS`.
+- In this case, the custom text box can be added multiple times with different metadata, therefore we shall add it the list of `MULTIPLE_ALLOWED_BOXES` as well.
+- If needed we may need to edit `isResizableBox` if this block is resizable, although we leave it untouched for our usecase. See `components/Dashboard/Calendar.tsx` for an example of a resizable block.
+- Refer to the commit [594d22b](https://github.com/MetaFam/TheGame/commit/594d22baf7a9fc3a6d2415e5d74040db7bc5b89e) for the code changes
+
+## Conclusion
+
+- Hurray! We have successfully added a basic custom block to MyMeta.
+- Next step is to create a PR with our `develop` branch.
+- Please follow our [`CONTRIBUTING_GUIDE`](https://github.com/MetaFam/TheGame/blob/develop/guides/CONTRIBUTING.md) for complete instructions on contributing to MyMeta.
+
+## Help & Support
+
+- For any queries, please join our discord and go to the `#builders` channel for help!
diff --git a/packages/utils/package.json b/packages/utils/package.json
index c269953af..87da498d5 100644
--- a/packages/utils/package.json
+++ b/packages/utils/package.json
@@ -19,7 +19,7 @@
"@ethersproject/address": "5.6.0",
"bignumber.js": "^9.1.0",
"cids": "1.1.9",
- "ethers": "5.6.9",
+ "ethers": "5.7.0",
"imgix-core-js": "2.3.2",
"js-base64": "3.7.2",
"node-fetch": "3.2.1",
diff --git a/packages/web/components/Dashboard/Calendar.tsx b/packages/web/components/Dashboard/Calendar.tsx
index 25a0aeca1..97558383a 100644
--- a/packages/web/components/Dashboard/Calendar.tsx
+++ b/packages/web/components/Dashboard/Calendar.tsx
@@ -1,3 +1,4 @@
+import { Flex, Text } from '@metafam/ds';
import { CONFIG } from 'config';
import React, { useEffect, useState } from 'react';
@@ -13,15 +14,20 @@ export const Calendar: React.FC = () => {
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
return (
-
+
+
+ Calendar
+
+
+
);
};
diff --git a/packages/web/components/Dashboard/DashboardSection.tsx b/packages/web/components/Dashboard/DashboardSection.tsx
index 84a0f4f42..73720f2ec 100644
--- a/packages/web/components/Dashboard/DashboardSection.tsx
+++ b/packages/web/components/Dashboard/DashboardSection.tsx
@@ -1,9 +1,10 @@
-import { Flex, IconButton, Text } from '@metafam/ds';
+import { Flex, IconButton } from '@metafam/ds';
import { Calendar } from 'components/Dashboard/Calendar';
import { LatestContent } from 'components/Dashboard/LatestContent';
import { Leaderboard } from 'components/Dashboard/Leaderboard';
import { Seed } from 'components/Dashboard/Seed';
import { XP } from 'components/Dashboard/XP';
+import { CustomTextSection } from 'components/Section/CustomTextSection';
import { EmbeddedUrl } from 'components/Section/EmbeddedUrlSection';
import { Player } from 'graphql/autogen/types';
import React, { forwardRef } from 'react';
@@ -46,37 +47,20 @@ const DashboardSectionInner: React.FC = ({
const { url } = metadata ?? {};
return url ? : null;
}
+ case BoxTypes.CUSTOM_TEXT: {
+ const { title, content } = metadata ?? {};
+ return title && content ? (
+
+ ) : null;
+ }
default:
return null;
}
};
-const getTitle = (type: BoxType, metadata?: BoxMetadata) => {
- if (metadata?.title) return metadata?.title.toString();
- switch (type) {
- case BoxTypes.DASHBOARD_LASTEST_CONTENT:
- return 'Latest Content';
- case BoxTypes.DASHBOARD_XP_INFO:
- return 'XP';
- case BoxTypes.DASHBOARD_SEEDS_INFO:
- return 'Seed';
- case BoxTypes.DASHBOARD_CALENDER:
- return 'Calendar';
- case BoxTypes.DASHBOARD_LEADERBOARD:
- return 'Leaderboard';
- case BoxTypes.DASHBOARD_COMPLETED_QUESTS:
- return 'Submitted Quests';
- case BoxTypes.DASHBOARD_CREATED_QUESTS:
- return 'Posted Quest Submissions';
- default:
- return '';
- }
-};
-
export const DashboardSection = forwardRef(
({ metadata, type, player, editing = false, onRemoveBox }, ref) => {
const key = createBoxKey(type, metadata);
- const title = getTitle(type, metadata);
if (!player) return null;
return (
@@ -88,29 +72,15 @@ export const DashboardSection = forwardRef(
minH="100%"
boxShadow="md"
pos="relative"
- padding={type !== BoxTypes.EMBEDDED_URL ? 6 : 0}
- paddingRight={
- type === BoxTypes.DASHBOARD_LASTEST_CONTENT ? 4 : undefined
- }
>
- {title && (
-
- {title}
-
- )}
(
-
-
-
-
- {['Read', 'Listen', 'Watch'].map((title) => (
-
- {title}
-
- ))}
-
+
+
+ Latest Content
+
+
+
+
+
+ {['Read', 'Listen', 'Watch'].map((title) => (
+
+ {title}
+
+ ))}
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
);
diff --git a/packages/web/components/Dashboard/Leaderboard.tsx b/packages/web/components/Dashboard/Leaderboard.tsx
index f04a30c85..979b1536f 100644
--- a/packages/web/components/Dashboard/Leaderboard.tsx
+++ b/packages/web/components/Dashboard/Leaderboard.tsx
@@ -1,5 +1,6 @@
import {
Box,
+ Flex,
FlexProps,
HStack,
LabeledValue,
@@ -41,130 +42,135 @@ export const Leaderboard: React.FC = () => {
);
return (
-
-
-
- Sort By: {sortOption.label}
-
- }
- styles={metaFilterSelectStyles}
- hasValue={sortOption.value !== SortOption.SEASON_XP}
- value={[
- { label: sortOption.label ?? '', value: sortOption.value ?? '' },
- ]} // Hack
- onChange={(choice) => {
- if (Array.isArray(choice)) {
- // eslint-disable-next-line no-param-reassign
- [choice] = choice.slice(-1);
+
+
+ Leaderboard
+
+
+
+
+ Sort By: {sortOption.label}
+
}
+ styles={metaFilterSelectStyles}
+ hasValue={sortOption.value !== SortOption.SEASON_XP}
+ value={[
+ { label: sortOption.label ?? '', value: sortOption.value ?? '' },
+ ]} // Hack
+ onChange={(choice) => {
+ if (Array.isArray(choice)) {
+ // eslint-disable-next-line no-param-reassign
+ [choice] = choice.slice(-1);
+ }
- if (choice) {
- const labeled = choice as LabeledValue;
- setSortOption(labeled);
- setQueryVariable('orderBy', labeled.value);
- }
- }}
- options={sortOptions}
- />
-
-
- {error && {`Error: ${error.message}`}}
- {fetching ? (
-
- ) : (
- !error &&
- players.slice(0, 7).map((p, i) => {
- const position = i + 1;
- if (
- (showSeasonalXP && p.seasonXP >= 1) ||
- (!showSeasonalXP && p.totalXP >= 50)
- ) {
- return (
-
- ;
+ setSortOption(labeled);
+ setQueryVariable('orderBy', labeled.value);
+ }
+ }}
+ options={sortOptions}
+ />
+
+
+ {error && {`Error: ${error.message}`}}
+ {fetching ? (
+
+ ) : (
+ !error &&
+ players.slice(0, 7).map((p, i) => {
+ const position = i + 1;
+ if (
+ (showSeasonalXP && p.seasonXP >= 1) ||
+ (!showSeasonalXP && p.totalXP >= 50)
+ ) {
+ return (
+
-
- {position}
-
-
- {getPlayerName(p)}
-
-
- {Math.floor(
- showSeasonalXP ? p.seasonXP : p.totalXP,
- ).toLocaleString()}
+
+ {position}
+
+
+
+ {getPlayerName(p)}
+
+
+ {Math.floor(
+ showSeasonalXP ? p.seasonXP : p.totalXP,
+ ).toLocaleString()}
+
-
-
- );
- }
- return null;
- })
- )}
+
+ );
+ }
+ return null;
+ })
+ )}
+
-
+
);
};
diff --git a/packages/web/components/Dashboard/QuestsCompleted.tsx b/packages/web/components/Dashboard/QuestsCompleted.tsx
index 61389a062..f39265b15 100644
--- a/packages/web/components/Dashboard/QuestsCompleted.tsx
+++ b/packages/web/components/Dashboard/QuestsCompleted.tsx
@@ -63,7 +63,10 @@ export const DashboardQuestsCompleted: React.FC = () => {
}, [allQuests, statusSelection]);
return (
- <>
+
+
+ Submitted Quests
+
{fetching && }
{error && {`Error: ${error.message}`}}
@@ -106,6 +109,6 @@ export const DashboardQuestsCompleted: React.FC = () => {
)}
- >
+
);
};
diff --git a/packages/web/components/Dashboard/QuestsCreated.tsx b/packages/web/components/Dashboard/QuestsCreated.tsx
index a0ff783bc..b5b514440 100644
--- a/packages/web/components/Dashboard/QuestsCreated.tsx
+++ b/packages/web/components/Dashboard/QuestsCreated.tsx
@@ -1,5 +1,6 @@
import {
Box,
+ Flex,
FormControl,
FormLabel,
ListItem,
@@ -37,48 +38,53 @@ export const DashboardQuestsCreated: React.FC = () => {
const createdQuests = data?.quest || null;
return (
-
- {fetching && }
- {error && {`Error: ${error.message}`}}
- {createdQuests != null && createdQuests.length > 0 && (
-
-
-
- Pending submissions only
-
- setPendingOnly(!pendingOnly)}
- />
-
- {createdQuests.map((quest) => (
-
- {quest.title}
- {quest.quest_completions?.length > 0 && (
-
- {quest.quest_completions.map((questCompletion) => (
-
- {getPlayerName(questCompletion.player as Player)}
-
- {moment(questCompletion.submittedAt).format(
- 'MMM D h:mma',
- )}
-
-
- ))}
-
- )}
-
- ))}
-
- )}
- {createdQuests?.length === 0 && You have no active quests.}
-
+
+
+ Posted Quest Submissions
+
+
+ {fetching && }
+ {error && {`Error: ${error.message}`}}
+ {createdQuests != null && createdQuests.length > 0 && (
+
+
+
+ Pending submissions only
+
+ setPendingOnly(!pendingOnly)}
+ />
+
+ {createdQuests.map((quest) => (
+
+ {quest.title}
+ {quest.quest_completions?.length > 0 && (
+
+ {quest.quest_completions.map((questCompletion) => (
+
+ {getPlayerName(questCompletion.player as Player)}
+
+ {moment(questCompletion.submittedAt).format(
+ 'MMM D h:mma',
+ )}
+
+
+ ))}
+
+ )}
+
+ ))}
+
+ )}
+ {createdQuests?.length === 0 && You have no active quests.}
+
+
);
};
diff --git a/packages/web/components/Dashboard/Seed.tsx b/packages/web/components/Dashboard/Seed.tsx
index aa00b3214..3b2aa66bc 100644
--- a/packages/web/components/Dashboard/Seed.tsx
+++ b/packages/web/components/Dashboard/Seed.tsx
@@ -2,6 +2,7 @@ import {
Box,
Button,
ButtonGroup,
+ Flex,
Image,
Link,
Stat,
@@ -10,6 +11,7 @@ import {
StatHelpText,
StatLabel,
StatNumber,
+ Text,
VStack,
} from '@metafam/ds';
import { animated, useSpring } from '@react-spring/web';
@@ -125,75 +127,79 @@ export const Seed = (): ReactElement => {
});
return (
-
-
-
-
- Market Price
- ${token?.market.current_price.usd}
-
-
- {token?.market.price_change_percentage_24h
- ? `${token?.market.price_change_percentage_24h?.toFixed(2)}%`
- : `No data 😞`}
-
-
+
+
+ Seed
+
+
+
+
+
+ Market Price
+ ${token?.market.current_price.usd}
+
+
+ {token?.market.price_change_percentage_24h
+ ? `${token?.market.price_change_percentage_24h?.toFixed(2)}%`
+ : `No data 😞`}
+
+
-
- 24h Trading Volume
- ${token?.market.total_volume.usd}
-
-
- {token?.volumePercent}%
-
-
+
+ 24h Trading Volume
+ ${token?.market.total_volume.usd}
+
+
+ {token?.volumePercent}%
+
+
-
- 7d Low / High
-
- ${token?.highLow7d.low} / ${token?.highLow7d.high}
-
-
-
-
-
- {token?.prices ? (
-
- ) : (
-
- Loading chart...
-
- )}
-
- {/**
- * Delete this component after the CoinGecko API is updated with the Polygon Seed Pool info
- * (and uncomment the following bit to start using the API again for the link)
- */}
-
- Pool Info
-
+
+ 7d Low / High
+
+ ${token?.highLow7d.low} / ${token?.highLow7d.high}
+
+
+
+
+
+ {token?.prices ? (
+
+ ) : (
+
+ Loading chart...
+
+ )}
+
+ {/**
+ * Delete this component after the CoinGecko API is updated with the Polygon Seed Pool info
+ * (and uncomment the following bit to start using the API again for the link)
+ */}
+
+ Pool Info
+
- {/**
+ {/**
* Uncomment this after the CoinGecko API is updated with the Polygon Seed Pool info
{token?.poolTicker && (
@@ -210,7 +216,8 @@ export const Seed = (): ReactElement => {
)}
*/}
-
+
+
);
};
diff --git a/packages/web/components/Dashboard/XP.tsx b/packages/web/components/Dashboard/XP.tsx
index 3d564fad2..d134d2a1a 100644
--- a/packages/web/components/Dashboard/XP.tsx
+++ b/packages/web/components/Dashboard/XP.tsx
@@ -33,9 +33,9 @@ export const XP = (): React.ReactElement => {
if (xpStats == null) {
return (
-
+
-
+
If you want your XP stats to appear, you gotta earn some XP first!
Go{' '}
{
lastWeekXP,
userWeeklyCred,
} = xpStats;
+
return (
-
-
-
-
- This Week
- {thisWeekXP}
-
-
- {variationThisWeek}%
-
-
+
+
+ XP
+
+
+
+
+
+ This Week
+ {thisWeekXP}
+
+
+ {variationThisWeek}%
+
+
-
- Last Week
- {lastWeekXP}
-
-
- {lastWeekXP}%
-
-
+
+ Last Week
+ {lastWeekXP}
+
+
+ {lastWeekXP}%
+
+
-
- All Time
- {userTotalXP.toLocaleString()}
- {user?.rank && (
-
- {user.rank}
-
- )}
-
-
-
-
- {userWeeklyCred && }
-
-
+
+ All Time
+ {userTotalXP.toLocaleString()}
+ {user?.rank && (
+
+ {user.rank}
+
+ )}
+
+
+
+
+ {userWeeklyCred && }
+
+
+
);
};
diff --git a/packages/web/components/Dashboard/config.ts b/packages/web/components/Dashboard/config.ts
index 97950008b..44bcfc7ca 100644
--- a/packages/web/components/Dashboard/config.ts
+++ b/packages/web/components/Dashboard/config.ts
@@ -43,6 +43,7 @@ export const ALL_BOXES = [
BoxTypes.DASHBOARD_COMPLETED_QUESTS,
BoxTypes.DASHBOARD_CREATED_QUESTS,
BoxTypes.EMBEDDED_URL,
+ BoxTypes.CUSTOM_TEXT,
// TODO: Add more types of sections
];
diff --git a/packages/web/components/Guild/GuildSection.tsx b/packages/web/components/Guild/GuildSection.tsx
index ba3193828..8112d25a7 100644
--- a/packages/web/components/Guild/GuildSection.tsx
+++ b/packages/web/components/Guild/GuildSection.tsx
@@ -1,8 +1,9 @@
-import { Flex, IconButton } from '@metafam/ds';
+import { Box, Flex, IconButton } from '@metafam/ds';
import { GuildAnnouncements } from 'components/Guild/Section/GuildAnnouncements';
import { GuildHero } from 'components/Guild/Section/GuildHero';
import { GuildLinks } from 'components/Guild/Section/GuildLinks';
import { GuildPlayers } from 'components/Guild/Section/GuildPlayers';
+import { CustomTextSection } from 'components/Section/CustomTextSection';
import { EmbeddedUrl } from 'components/Section/EmbeddedUrlSection';
import { GuildFragment } from 'graphql/autogen/types';
import React, { forwardRef } from 'react';
@@ -36,6 +37,12 @@ const GuildSectionInner: React.FC = ({
const { url } = metadata ?? {};
return url ? : null;
}
+ case BoxTypes.CUSTOM_TEXT: {
+ const { title, content } = metadata ?? {};
+ return title && content ? (
+
+ ) : null;
+ }
default:
return null;
}
@@ -57,14 +64,16 @@ export const GuildSection = forwardRef(
boxShadow="md"
pos="relative"
>
-
+
+
+
{editing && (
: null;
}
+ case BoxTypes.CUSTOM_TEXT: {
+ const { title, content } = metadata ?? {};
+ return title && content ? (
+
+ ) : null;
+ }
default:
return null;
}
@@ -78,17 +85,18 @@ export const PlayerSection = forwardRef(
minH="100%"
boxShadow="md"
pos="relative"
- pointerEvents={editing ? 'none' : 'initial'}
>
-
+
+
+
{editing && (
(
{editing && type && type !== BoxTypes.PLAYER_HERO && (
= ({
defined any skills.
) : (
-
+
{(skills || []).map(({ id, name, category }) => (
(
export const EditMetadata: React.FC<{
type: BoxType;
metadata: BoxMetadata;
- setMetadata: (d: BoxMetadata) => void;
+ setMetadata: React.Dispatch>;
}> = ({ type, ...props }) => {
switch (type) {
case BoxTypes.EMBEDDED_URL:
return ;
+ case BoxTypes.CUSTOM_TEXT:
+ return ;
default:
return null;
}
diff --git a/packages/web/components/Section/CustomTextSection.tsx b/packages/web/components/Section/CustomTextSection.tsx
new file mode 100644
index 000000000..35c45cc2f
--- /dev/null
+++ b/packages/web/components/Section/CustomTextSection.tsx
@@ -0,0 +1,50 @@
+import { Input, Text, VStack } from '@metafam/ds';
+import { MarkdownEditor } from 'components/MarkdownEditor';
+import { MarkdownViewer } from 'components/MarkdownViewer';
+import React from 'react';
+import { BoxMetadata } from 'utils/boxTypes';
+
+type CustomTextSectionProps = {
+ title?: string;
+ content?: string;
+};
+
+export const CustomTextSection: React.FC = ({
+ title = '',
+ content = '',
+}) => (
+
+
+ {title}
+
+ {content}
+
+);
+
+export const CustomTextSectionMetadata: React.FC<{
+ metadata: BoxMetadata;
+ setMetadata: React.Dispatch>;
+}> = ({ metadata, setMetadata }) => (
+ <>
+
+ setMetadata((old) => ({ ...old, title }))
+ }
+ size="lg"
+ borderRadius={0}
+ borderColor="borderPurple"
+ fontSize="md"
+ borderWidth="2px"
+ />
+ setMetadata((old) => ({ ...old, content }))}
+ />
+ >
+);
diff --git a/packages/web/utils/boxTypes.ts b/packages/web/utils/boxTypes.ts
index 864a23238..71c69e7f8 100644
--- a/packages/web/utils/boxTypes.ts
+++ b/packages/web/utils/boxTypes.ts
@@ -33,6 +33,7 @@ export const BoxTypes = {
// Common Boxes
ADD_NEW_BOX: 'add-new-box',
EMBEDDED_URL: 'embedded-url',
+ CUSTOM_TEXT: 'custom-text',
} as const;
export type BoxType = Values;
diff --git a/packages/web/utils/layoutHelpers.ts b/packages/web/utils/layoutHelpers.ts
index 17d9ed003..d0366fbad 100644
--- a/packages/web/utils/layoutHelpers.ts
+++ b/packages/web/utils/layoutHelpers.ts
@@ -11,7 +11,10 @@ import {
export const GRID_ROW_HEIGHT = 32;
export const HEIGHT_MODIFIER = 1.8;
-export const MULTIPLE_ALLOWED_BOXES = [BoxTypes.EMBEDDED_URL] as Array;
+export const MULTIPLE_ALLOWED_BOXES = [
+ BoxTypes.EMBEDDED_URL,
+ BoxTypes.CUSTOM_TEXT,
+] as Array;
export const removeBoxFromLayouts = (
layouts: Layouts,
@@ -189,10 +192,11 @@ const DEFAULT_BOX_HEIGHTS: Partial> = {
// common boxes
[BoxTypes.ADD_NEW_BOX]: 3,
[BoxTypes.EMBEDDED_URL]: 7,
+ [BoxTypes.CUSTOM_TEXT]: 7,
};
export const getBoxLayoutItemDefaults = (type: BoxType): Layout => ({
- i: createBoxKey(type, type === BoxTypes.EMBEDDED_URL ? {} : undefined),
+ i: createBoxKey(type, undefined),
x: 0,
y: 0,
w: 1,