diff --git a/.preview/config.ts b/.preview/config.ts index f244bad6..0aada187 100644 --- a/.preview/config.ts +++ b/.preview/config.ts @@ -1,4 +1,5 @@ import { encode } from '@devprotocol/clubs-core' +import { GlobalConfig, HomeConfig } from '../src/types' export default () => encode({ @@ -9,19 +10,32 @@ export default () => propertyAddress: '', chainId: 137, rpcUrl: 'https://polygon-rpc.com/', - adminRolePoints: 50, + adminRolePoints: 0, plugins: [ { - id: 'example-theme', - options: [], - }, - { - id: 'unique-and-descriptive-name-here', + id: 'devprotocol:clubs:theme-2', options: [ - { key: 'slug', value: 'stokens' }, - { key: 'rpc', value: 'https://polygon-rpc.com/' }, - { key: 'maxpage', value: 30 }, + { + key: 'homeConfig', + value: { + hero: { + image: + 'https://images.unsplash.com/photo-1681106447892-fde093d56df8?q=80&w=2626&auto=format&fit=crop', + text: '', + }, + body: 'Hendrerit pellentesque tincidunt, sociis dictumst.', + description: 'Description', + } satisfies HomeConfig, + }, + { + key: 'globalConfig', + value: { bg: '#ADADAD', ink: '#fff' } satisfies GlobalConfig, + }, ], }, + { + id: 'example', + options: [], + }, ], }) diff --git a/.preview/example/Default.astro b/.preview/example/Default.astro new file mode 100644 index 00000000..643d0bf5 --- /dev/null +++ b/.preview/example/Default.astro @@ -0,0 +1,52 @@ +
+

+ Hendrerit pellentesque tincidunt, sociis dictumst. Rutrum laoreet nulla + taciti ante felis. Ornare quisque et ad hac enim. Vulputate habitasse lacus + mattis elit magna nunc. Mollis elit dui aliquet ultricies nunc tortor dolor. + Vehicula feugiat netus commodo nec quisque netus tempor nam condimentum. + Tortor himenaeos magna netus suspendisse morbi iaculis. Ut magnis nam + sagittis dapibus nulla eget nullam tempor mi maecenas. Habitasse magnis + faucibus scelerisque quisque sagittis eget. Montes eleifend luctus molestie + lacinia nisl etiam montes pharetra aptent sociosqu. Eleifend magna natoque + adipiscing fermentum in sed sit ligula ridiculus vel suscipit augue. Posuere + consectetur, phasellus. +

+

+ Nostra ridiculus mus placerat litora tortor senectus eleifend dictumst orci + cum volutpat netus. Velit pellentesque morbi suscipit platea ultricies + purus? Habitant penatibus laoreet consectetur feugiat dapibus praesent ipsum + vestibulum auctor netus. Purus, aliquet fringilla tempor curabitur ipsum + lacus tristique sapien sed. Id cursus vestibulum nibh rutrum conubia in + ultricies eu donec etiam. Elit amet dictumst orci. Porta potenti habitasse + duis adipiscing sed magnis elementum. Nam ultrices molestie parturient + ultrices, suspendisse scelerisque suspendisse luctus. Metus at aliquam enim + orci pretium interdum risus lacus quam pretium. At mollis proin ridiculus. +

+

+ Aptent feugiat, ornare adipiscing inceptos nisl. Aliquam nec netus metus + dictum pretium viverra tempus nam sapien suscipit. Leo vestibulum cum quis + quisque donec risus. Dui euismod lobortis, himenaeos fusce. Nisl, aptent + aliquam curae;. Sed eleifend senectus dolor rutrum facilisi natoque purus. + Praesent lorem in turpis est velit. Felis sagittis sociosqu morbi sapien. + Per. +

+

+ Inceptos vehicula class purus rhoncus sagittis! Lacus interdum tempor + dignissim lorem netus suspendisse. Feugiat venenatis fermentum congue + sodales sociosqu porta fermentum cubilia pellentesque class. Fringilla hac + feugiat tempor ullamcorper tellus quam. Habitant lacus etiam donec, primis + mattis. Neque gravida a non ante ad semper cras suscipit ultricies. Diam, + dignissim consectetur commodo posuere. Hac tortor sem, semper aliquam cursus + a lectus tempus. +

+

+ Turpis, magna bibendum mauris. Imperdiet volutpat leo imperdiet inceptos + cubilia fusce per, magnis eros. Arcu erat purus fringilla habitant sit. + Varius praesent, lobortis erat dui dapibus elit quam ullamcorper vel + praesent sit. Hac laoreet cras natoque tincidunt eleifend dignissim eget non + a varius facilisis potenti. Vivamus et curae; curabitur cum sapien venenatis + aliquam porta? Sociosqu elit tincidunt senectus aliquet ultricies hendrerit + feugiat montes. Ligula augue at massa sem tellus facilisis dui congue + faucibus laoreet. +

+
diff --git a/.preview/example/index.ts b/.preview/example/index.ts new file mode 100644 index 00000000..0fc658b0 --- /dev/null +++ b/.preview/example/index.ts @@ -0,0 +1,22 @@ +import { + type ClubsFunctionGetPagePaths, + ClubsPluginCategory, + ClubsPluginMeta, + ClubsFunctionPlugin, +} from '@devprotocol/clubs-core' +import Default from './Default.astro' + +export const getPagePaths = (async () => [ + { paths: ['example'], component: Default }, +]) satisfies ClubsFunctionGetPagePaths + +export const meta = { + id: 'example', + displayName: 'Example', + category: ClubsPluginCategory.Uncategorized, +} satisfies ClubsPluginMeta + +export default { + getPagePaths, + meta, +} satisfies ClubsFunctionPlugin diff --git a/.preview/plugins.ts b/.preview/plugins.ts index 5feea435..13def84d 100644 --- a/.preview/plugins.ts +++ b/.preview/plugins.ts @@ -1,4 +1,4 @@ -import theme from './theme' +import example from './example' import thisPlugin from '../src/index' -export default [theme, thisPlugin] +export default [example, thisPlugin] diff --git a/.preview/preview.ssr/.astro/settings.json b/.preview/preview.ssr/.astro/settings.json new file mode 100644 index 00000000..62f65bef --- /dev/null +++ b/.preview/preview.ssr/.astro/settings.json @@ -0,0 +1,5 @@ +{ + "_variables": { + "lastUpdateCheck": 1725850446302 + } +} \ No newline at end of file diff --git a/.preview/theme/Default.astro b/.preview/theme/Default.astro deleted file mode 100644 index 9c7ccf85..00000000 --- a/.preview/theme/Default.astro +++ /dev/null @@ -1,17 +0,0 @@ ---- -import '@devprotocol/clubs-core/styles' ---- - - - - - - - - - -
- -
- - diff --git a/.preview/theme/index.ts b/.preview/theme/index.ts deleted file mode 100644 index 6ecfd6e0..00000000 --- a/.preview/theme/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - type ClubsFunctionGetAdminPaths, - type ClubsFunctionGetPagePaths, - type ClubsFunctionGetLayout, - type ClubsFunctionThemePlugin, - ClubsPluginCategory, - ClubsThemePluginMeta, -} from '@devprotocol/clubs-core' -import { default as Default } from './Default.astro' - -export const getPagePaths = (async () => []) satisfies ClubsFunctionGetPagePaths - -export const getAdminPaths = - (async () => []) satisfies ClubsFunctionGetAdminPaths - -export const getLayout = (async () => ({ - layout: Default, -})) satisfies ClubsFunctionGetLayout - -export const meta = { - id: 'example-theme', - displayName: 'Example theme', - category: ClubsPluginCategory.Theme, - theme: { - previewImage: 'https://dummyimage.com/600x400/000/fff', - }, -} satisfies ClubsThemePluginMeta - -export default { - getPagePaths, - getAdminPaths, - getLayout, - meta, -} satisfies ClubsFunctionThemePlugin diff --git a/src/README.md b/src/README.md new file mode 100644 index 00000000..78fd1022 --- /dev/null +++ b/src/README.md @@ -0,0 +1,22 @@ +**Built-in home page:** + +- Display all your memberships in beautifully +- Responsive design & mobile first +- Short description to let people know about your project +- YouTube-embed enabled profiles to show more detailed descriptions + +**Multiple color schemes:** +| | Preview | +| --- | --- | +| Purple | | +| Grey | | +| Black | | +| Brown | | +| Stone | | +| Matcha | | +| Pink | | + +**Supported slots:** + +- `ClubsSlotName.PageContentHomeBeforeContent` = "page:content:home:before-content" +- `ClubsSlotName.PageContentHomeAfterContent` = "page:content:home:after-content" diff --git a/src/assets/Start-with-Clubs--dark.png b/src/assets/Start-with-Clubs--dark.png new file mode 100644 index 00000000..3c43689c Binary files /dev/null and b/src/assets/Start-with-Clubs--dark.png differ diff --git a/src/assets/default-theme-1.jpg b/src/assets/default-theme-1.jpg new file mode 100644 index 00000000..7761deb3 Binary files /dev/null and b/src/assets/default-theme-1.jpg differ diff --git a/src/assets/default-theme-2.jpg b/src/assets/default-theme-2.jpg new file mode 100644 index 00000000..13a9934d Binary files /dev/null and b/src/assets/default-theme-2.jpg differ diff --git a/src/assets/default-theme-3.jpg b/src/assets/default-theme-3.jpg new file mode 100644 index 00000000..da18cf10 Binary files /dev/null and b/src/assets/default-theme-3.jpg differ diff --git a/src/assets/icon.svg b/src/assets/icon.svg new file mode 100644 index 00000000..1a584c3b --- /dev/null +++ b/src/assets/icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/preview.jpg b/src/assets/preview.jpg new file mode 100644 index 00000000..8c47843b Binary files /dev/null and b/src/assets/preview.jpg differ diff --git a/src/components/About.svelte b/src/components/About.svelte new file mode 100644 index 00000000..1c4abfb7 --- /dev/null +++ b/src/components/About.svelte @@ -0,0 +1,19 @@ + + +

+ {i18n('About', [name])} +

diff --git a/src/components/Admin/Admin.vue b/src/components/Admin/Admin.vue deleted file mode 100644 index 790dd98d..00000000 --- a/src/components/Admin/Admin.vue +++ /dev/null @@ -1,59 +0,0 @@ - - - diff --git a/src/components/Card/Card.astro b/src/components/Card/Card.astro new file mode 100644 index 00000000..b4759a0a --- /dev/null +++ b/src/components/Card/Card.astro @@ -0,0 +1,10 @@ +--- +const { class: className } = Astro.props as { + blur?: boolean + class?: string +} +--- + +
+ +
diff --git a/src/components/Header/Header.astro b/src/components/Header/Header.astro new file mode 100644 index 00000000..1e250f15 --- /dev/null +++ b/src/components/Header/Header.astro @@ -0,0 +1,25 @@ +--- +import { ClubsLogo as Clubs } from '@devprotocol/clubs-core/layouts' +import type { ClubsPropsPages } from '@devprotocol/clubs-core' + +const { connection = true } = Astro.props as ClubsPropsPages & { + connection?: boolean +} +--- + +
+ +
+ + {connection && } +
+
diff --git a/src/components/Hero/Description.svelte b/src/components/Hero/Description.svelte new file mode 100644 index 00000000..c1c7ea14 --- /dev/null +++ b/src/components/Hero/Description.svelte @@ -0,0 +1,14 @@ + + + +
+

+ +

+
diff --git a/src/components/Hero/Hero.astro b/src/components/Hero/Hero.astro new file mode 100644 index 00000000..f8eca425 --- /dev/null +++ b/src/components/Hero/Hero.astro @@ -0,0 +1,122 @@ +--- +import Description from './Description.svelte' +import type { MembersCountVisibilityValue } from '../../index' + +const { globalConfig, image, avatar, name, description, compact } = + Astro.props as { + globalConfig?: { + bg: string + backgroundGradient?: [string, string] + } + image?: string + avatar?: string + name?: string + description?: string + compact?: boolean + } + +const backgroundGradientColorFrom = globalConfig?.backgroundGradient?.[0] +const backgroundGradientColorTo = globalConfig?.backgroundGradient?.[1] +--- + + + + +
+
+
+ { + !compact && ( + + ) + } +
+ + +
+
+ +

+ + + + {name} +

+
+
+ + { + description ?? + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit' + } + +
+
+
diff --git a/src/components/Image/Image.vue b/src/components/Image/Image.vue deleted file mode 100644 index 417c3606..00000000 --- a/src/components/Image/Image.vue +++ /dev/null @@ -1,22 +0,0 @@ - - - diff --git a/src/components/List/List.vue b/src/components/List/List.vue deleted file mode 100644 index 699c5f5b..00000000 --- a/src/components/List/List.vue +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/src/components/MembershipTitle.svelte b/src/components/MembershipTitle.svelte new file mode 100644 index 00000000..4053bf02 --- /dev/null +++ b/src/components/MembershipTitle.svelte @@ -0,0 +1,19 @@ + + +

+ {i18n(hasSingleMembership ? 'BecomeMember' : 'SelectMembership')} +

diff --git a/src/components/Memberships/Memberships.astro b/src/components/Memberships/Memberships.astro new file mode 100644 index 00000000..1eaecb8f --- /dev/null +++ b/src/components/Memberships/Memberships.astro @@ -0,0 +1,63 @@ +--- +import MembershipOption from '@components/AdminMembershipsForm/MembershipOption.svelte' +import { bytes32Hex } from '@devprotocol/clubs-core' +import type { ComposedItem } from '../../types' +import type { Membership } from '@devprotocol/clubs-core' +import { i18nFactory } from '@devprotocol/clubs-core' +import { Strings } from '../../i18n' + +type Props = { + memberships: Membership[] + name: string + overrides?: ComposedItem[] +} + +const { memberships, name, overrides } = Astro.props + +const i18nBase = i18nFactory(Strings) +const langs = Astro.request.headers + .get('accept-language') + ?.replace(/;q=[\d\.]+/g, '') + .split(',') ?? ['en'] +const i18n = i18nBase(langs) + +const find = ( + membership: Membership, +): { membership: Membership; override?: ComposedItem } => { + return { + membership, + override: overrides?.find( + (ov) => bytes32Hex(ov.source.payload) === bytes32Hex(membership.payload), + ), + } +} +--- + + diff --git a/src/components/Navigation/Navigation.astro b/src/components/Navigation/Navigation.astro new file mode 100644 index 00000000..0fef255c --- /dev/null +++ b/src/components/Navigation/Navigation.astro @@ -0,0 +1,52 @@ +--- +import type { ClubsNavigationLink as NavLink } from '@devprotocol/clubs-core' + +const { links, type = 'large-buttons' } = Astro.props as { + links: NavLink[] + current?: NavLink + type?: 'large-buttons' | 'tab' +} +--- + + diff --git a/src/components/Page/Page.vue b/src/components/Page/Page.vue deleted file mode 100644 index d45bc0cc..00000000 --- a/src/components/Page/Page.vue +++ /dev/null @@ -1,19 +0,0 @@ - - - diff --git a/src/components/Share/Share.svelte b/src/components/Share/Share.svelte new file mode 100644 index 00000000..e8c28ad8 --- /dev/null +++ b/src/components/Share/Share.svelte @@ -0,0 +1,22 @@ + + + diff --git a/src/components/WaitForLaunch.svelte b/src/components/WaitForLaunch.svelte new file mode 100644 index 00000000..e00cb6b3 --- /dev/null +++ b/src/components/WaitForLaunch.svelte @@ -0,0 +1,21 @@ + + +
+

+ {i18n('WaitForLaunch')} +

+
diff --git a/src/i18n/index.ts b/src/i18n/index.ts new file mode 100644 index 00000000..cba763d8 --- /dev/null +++ b/src/i18n/index.ts @@ -0,0 +1,24 @@ +import type { ClubsI18nParts } from '@devprotocol/clubs-core' + +export const Strings = { + SelectMembership: { + en: 'Select a membership', + ja: 'メンバーシップを選択してください', + }, + BecomeMember: { + en: 'Become a member', + ja: 'メンバーになる', + }, + WaitForLaunch: { + en: 'Please wait for the launch', + ja: 'ローンチまでお待ちください', + }, + About: { + en: ([name]) => `About ${name}`, + ja: ([name]) => `${name} について`, + }, + JPY: { + en: 'JPY', + ja: '円', + }, +} satisfies ClubsI18nParts diff --git a/src/index.test.ts b/src/index.test.ts index 8433e4f4..326b942e 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1,19 +1,12 @@ import { describe, it, expect } from 'vitest' -import plugin, { getPagePaths, getAdminPaths, meta } from './index' +import plugin, { getPagePaths, getLayout, meta } from './index' describe('plugin default export', () => { - it('should have getPagePaths, getAdminPaths and meta', async () => { + it('should have getPagePaths, getLayout and meta', async () => { expect(plugin).toEqual({ getPagePaths, - getAdminPaths, + getLayout, meta, }) }) }) - -describe('getPagePaths', () => { - it('should return empty array if options are not set', async () => { - const res = await getPagePaths([]) - expect(res).toEqual([]) - }) -}) diff --git a/src/index.ts b/src/index.ts index 8fc6c6b7..e01f8e80 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,73 +1,194 @@ +import type { UndefinedOr } from '@devprotocol/util-ts' import type { - ClubsFunctionGetAdminPaths, + ClubsFunctionGetLayout, ClubsFunctionGetPagePaths, - ClubsFunctionPlugin, - ClubsPluginMeta, - ClubsStaticPaths, + ClubsFunctionThemePlugin, + ClubsThemePluginMeta, } from '@devprotocol/clubs-core' import { ClubsPluginCategory } from '@devprotocol/clubs-core' -import { clientsSTokens } from '@devprotocol/dev-kit/agent' -import { type UndefinedOr, whenDefined } from '@devprotocol/util-ts' -import { JsonRpcProvider } from 'ethers' -import { always } from 'ramda' -import List from './pages/List.astro' -import Page from './pages/Page.astro' -import Admin from './pages/Admin.astro' -import Readme from './readme.astro' - -export const getPagePaths = (async (options) => { - const slug = options.find(({ key }) => key === 'slug') - ?.value as UndefinedOr - const rpc = options.find(({ key }) => key === 'rpc') - ?.value as UndefinedOr - const maxpage = - (options.find(({ key }) => key === 'maxpage') - ?.value as UndefinedOr) ?? 10 - - const provider = whenDefined(rpc, (url) => new JsonRpcProvider(url)) - const clients = await whenDefined(provider, clientsSTokens) - const contract = whenDefined(clients, ([l1, l2]) => l1 || l2) - const tokens = await whenDefined(contract, (sTokens) => - Promise.all( - new Array(maxpage).fill(null).map(async (_, i) => { - const id = i + 1 - const token = await sTokens.tokenURI(id).catch(always(undefined)) - return whenDefined(token, (item) => ({ id, ...item })) - }), - ), +import { default as Layout } from './layouts/Default.astro' +import { default as Index } from './pages/index.astro' +import type { GlobalConfig, HomeConfig } from './types' +import type { ClubsNavigationLink as NavLink } from '@devprotocol/clubs-core' +import type { Membership } from '@devprotocol/clubs-core' +import PreviewImage from './assets/preview.jpg' +import { default as Icon } from './assets/icon.svg' +import { Content as Readme } from './README.md' +import Preview1 from './assets/default-theme-1.jpg' +import Preview2 from './assets/default-theme-2.jpg' +import Preview3 from './assets/default-theme-3.jpg' +import { composeItems } from './utils/compose-items' + +export const colorPresets = { + Purple: { + bg: 'rgba(131, 138, 176, 1)', + backgroundGradient: ['rgba(204, 0, 255, 0.2)', 'rgba(204, 0, 255, 0)'], + ink: 'rgba(255, 255, 255)', + }, + Grey: { + bg: 'rgba(173, 173, 173, 1)', + ink: '#111111', + }, + Black: { + bg: 'rgba(29, 36, 38, 1)', + ink: 'rgba(255, 255, 255, 1)', + }, + Brown: { + bg: 'rgba(68, 59, 45, 1)', + backgroundGradient: ['rgba(255, 201, 119, 0.2)', 'rgba(255, 201, 119, 0)'], + ink: 'rgb(252, 225, 203)', + }, + Stone: { + bg: 'rgba(96, 119, 124, 1)', + backgroundGradient: ['rgba(196, 196, 196, 0.5)', 'rgba(196, 196, 196, 0)'], + ink: 'rgba(255, 255, 255, 1)', + }, + Matcha: { + bg: 'rgba(63, 78, 38, 1)', + ink: 'rgb(252, 225, 203)', + }, + Pink: { + bg: 'rgba(255, 187, 195, 1)', + backgroundGradient: ['rgba(255, 173, 217, 1)', 'rgba(255, 173, 217, 0)'], + ink: 'rgba(255, 255, 255)', + }, + Blue: { + bg: 'rgba(91, 139, 245, 1)', + ink: 'rgba(255, 255, 255)', + }, + Orange: { + bg: 'rgba(255, 131, 83, 1)', + backgroundGradient: ['rgba(249, 192, 82, 1)', 'rgba(249, 192, 82, 0)'], + ink: 'rgba(255, 255, 255)', + }, +} + +export type SectionOrderingValue = 'about-first' | 'memberships-first' + +export type MembersCountVisibilityValue = 'hidden' | 'visible' + +export const getPagePaths = (async (options, config, utils) => { + const { name, propertyAddress, rpcUrl, chainId } = config + + const [membershipConfig] = utils.getPluginConfigById( + 'devprotocol:clubs:simple-memberships', ) - const secondaryPages = tokens - ? (tokens.map((token) => ({ - paths: [slug, token?.id], - props: { token }, - component: Page, - })) as ClubsStaticPaths) - : [] + const allMemberships = membershipConfig?.options.find( + (opt) => opt.key === 'memberships', + )?.value as UndefinedOr + + // Filter out deprecated memberships. + const memberships = allMemberships?.filter( + (membership) => !membership.deprecated, + ) + + const homeConfig = options.find((opt) => opt.key === 'homeConfig') + ?.value as UndefinedOr - return slug && tokens + const sectionsOrderConfig = + ( + options.find((opt) => opt.key === 'sectionsOrder') as UndefinedOr<{ + key: 'sectionsOrder' + value: SectionOrderingValue + }> + )?.value ?? 'memberships-first' + + const sidebarPrimaryLinks = + config.options?.find((option) => option.key === 'sidebarPrimaryLinks') + ?.value ?? ([] as NavLink[]) + + const sidebarLinks = + config.options?.find((option) => option.key === 'sidebarLinks')?.value ?? + ([] as NavLink[]) + + const avatarImgSrc = config.options?.find( + (option) => option.key === 'avatarImgSrc', + )?.value + + const [clubsPay] = utils.getPluginConfigById( + 'devprotocol:clubs:plugin:clubs-payments', + ) + const clubsPaymentsOverrides = composeItems(clubsPay?.options || [], utils) + + return homeConfig ? [ - { paths: [slug], component: List, props: { slug, tokens } }, - ...secondaryPages, + { + paths: [], + component: Index, + props: { + name, + propertyAddress, + memberships, + homeConfig, + rpcUrl, + chainId, + sidebarPrimaryLinks, + sidebarLinks, + avatarImgSrc, + sectionsOrderConfig, + clubsPaymentsOverrides, + signals: ['connection-button-hide'], + }, + }, ] : [] }) satisfies ClubsFunctionGetPagePaths -export const getAdminPaths = (async (options) => { - const slug = options.find(({ key }) => key === 'slug') - ?.value as UndefinedOr +export const getLayout = (async (options, config, { getPluginConfigById }) => { + const [membershipConfig] = getPluginConfigById( + 'devprotocol:clubs:simple-memberships', + ) + const memberships = membershipConfig?.options.find( + (opt) => opt.key === 'memberships', + )?.value as UndefinedOr + + const globalConfig = options.find((opt) => opt.key === 'globalConfig') + ?.value as UndefinedOr + const homeConfig = options.find((opt) => opt.key === 'homeConfig') + ?.value as UndefinedOr + const membersCountConfig: MembersCountVisibilityValue = + ( + options.find((option) => option.key === 'membersCount') as UndefinedOr<{ + key: 'membersCount' + value: MembersCountVisibilityValue + }> + )?.value ?? 'visible' + const description = homeConfig?.description - return [{ paths: [slug ?? 'stokens'], component: Admin, props: { options } }] -}) satisfies ClubsFunctionGetAdminPaths + return { + layout: Layout, + props: { + theme2: { + config, + homeConfig, + globalConfig, + membersCountConfig, + memberships, + description, + }, + }, + } +}) satisfies ClubsFunctionGetLayout export const meta = { - id: 'unique-and-descriptive-name-here', - displayName: 'sTokens Viewer', - category: ClubsPluginCategory.Uncategorized, + id: 'devprotocol:clubs:theme-2', + displayName: 'Minimalist', + category: ClubsPluginCategory.Theme, + theme: { + previewImage: PreviewImage.src, + }, + icon: Icon.src, + offer: { + price: 0, + priceCurrency: 'DEV', + }, + description: `Basic theme with multiple color schemes.`, + previewImages: [Preview1.src, Preview2.src, Preview3.src], readme: Readme, -} satisfies ClubsPluginMeta +} satisfies ClubsThemePluginMeta export default { getPagePaths, - getAdminPaths, + getLayout, meta, -} satisfies ClubsFunctionPlugin +} satisfies ClubsFunctionThemePlugin diff --git a/src/layouts/Default.astro b/src/layouts/Default.astro new file mode 100644 index 00000000..4342b1dd --- /dev/null +++ b/src/layouts/Default.astro @@ -0,0 +1,242 @@ +--- +import { + type ClubsConfiguration, + ClubsPluginSignal, +} from '@devprotocol/clubs-core' +import { ClubsPictogramAdaptable as Favicon } from '@devprotocol/clubs-core/images' +import type { UndefinedOr } from '@devprotocol/util-ts' +import '@devprotocol/clubs-core/styles' +import Header from '../components/Header/Header.astro' +import Hero from '../components/Hero/Hero.astro' +import type { ClubsNavigationLink as NavLink } from '@devprotocol/clubs-core' +import Navigation from '../components/Navigation/Navigation.astro' +import StartWithClubs from '../assets/Start-with-Clubs--dark.png' +import type { Membership } from '@devprotocol/clubs-core' +import type { MembersCountVisibilityValue } from '..' +import type { GlobalConfig, HomeConfig } from '../types' + +const { page } = Astro.params +const { + theme2: { + metaTitle, + config, + homeConfig, + globalConfig, + memberships, + description, + membersCountConfig, + }, + signals, +} = Astro.props as { + theme2: { + memberships?: Membership[] + metaTitle?: string + config: ClubsConfiguration + globalConfig?: GlobalConfig + homeConfig?: HomeConfig + membersCountConfig?: MembersCountVisibilityValue + description?: string + } + signals?: string[] +} + +const displayFullPage = signals?.includes(ClubsPluginSignal.DisplayFullPage) +const notDisplayConnection = signals?.includes('connection-button-hide') + +const { name, propertyAddress, rpcUrl, chainId } = config +const title = metaTitle ? `${config.name} : ${metaTitle}` : config.name + +const ogp = config.options?.find((option) => option.key === 'ogp') + ?.value as UndefinedOr<{ image?: string }> + +const avatarImgSrc: UndefinedOr = config.options?.find( + (option) => option.key === 'avatarImgSrc', +)?.value as string + +const socialLinks = + (config.options?.find((option) => option.key === 'socialLinks') + ?.value as UndefinedOr) ?? ([] as NavLink[]) + +const twitter = socialLinks?.find((l) => l.kind === 'twitter') +const discord = socialLinks?.find((l) => l.kind === 'discord') +const socials = + twitter || discord ? [twitter, discord].map((x) => x) : socialLinks + +const navigationLinks: NavLink[] = [ + ...((config.options?.find((option) => option.key === 'navigationLinks') + ?.value as NavLink[]) ?? []), +] +const currentLink = navigationLinks?.find((link) => + ((pathname) => pathname === link.path)(`/${page ?? ''}`), +) +const footerLinks = config.options?.find( + (option) => option.key === 'footerLinks', +)?.value as UndefinedOr + +const url = Astro.url.origin + +const bodyColor = globalConfig?.ink ?? '#fff' +const backgroundColor = globalConfig?.bg ?? '#ADADAD' + +const hasNotAMembership = memberships ? memberships.length < 1 : true + +const truncatedDescription = ((desc) => + desc.slice(0, 139).join('') + (desc.length > 139 ? '...' : ''))([ + ...(description?.replace(/[\s|\n|\r\n|\r]/g, ' ') ?? ''), +]) +--- + + + + + + {title} + + + + + + + + + + + + + + + + + + +
+ +
+ + { + !displayFullPage && ( +
+ + + + + + Join + + + + + + + + + +
+ ) + } + + { + !displayFullPage && ( +
+ +
+ ) + } +
+ + { + !displayFullPage && ( + <> +
+ + + ) + } +
+
+ { + footerLinks && ( +
+ +
+ ) + } + +
+ + diff --git a/src/pages/Admin.astro b/src/pages/Admin.astro deleted file mode 100644 index fa4c6907..00000000 --- a/src/pages/Admin.astro +++ /dev/null @@ -1,11 +0,0 @@ ---- -import Admin_ from '../components/Admin/Admin.vue' -import type { ClubsPropsAdminPages } from '@devprotocol/clubs-core' -import type { Option } from '../types' - -type Props = ClubsPropsAdminPages & { - options: Option[] -} ---- - - diff --git a/src/pages/List.astro b/src/pages/List.astro deleted file mode 100644 index 726b52bf..00000000 --- a/src/pages/List.astro +++ /dev/null @@ -1,11 +0,0 @@ ---- -import List_ from '../components/List/List.vue' -import type { TokenURIWithId } from '../types' - -type Props = { - slug: string - tokens: TokenURIWithId[] -} ---- - - diff --git a/src/pages/Page.astro b/src/pages/Page.astro deleted file mode 100644 index 1a1a9554..00000000 --- a/src/pages/Page.astro +++ /dev/null @@ -1,10 +0,0 @@ ---- -import type { TokenURIWithId } from '../types' -import Page_ from '../components/Page/Page.vue' - -type Props = { - token: TokenURIWithId -} ---- - - diff --git a/src/pages/index.astro b/src/pages/index.astro new file mode 100644 index 00000000..0dc034f5 --- /dev/null +++ b/src/pages/index.astro @@ -0,0 +1,112 @@ +--- +import type { Membership } from '@devprotocol/clubs-core' +import { markdownToHtml, ProseTextInherit } from '@devprotocol/clubs-core' +import { ClubsSlotName, type ClubsPropsPages } from '@devprotocol/clubs-core' + +import type { SectionOrderingValue } from '..' +import About_ from '../components/About.svelte' +import WaitForLaunch from '../components/WaitForLaunch.svelte' +import MembershipTitle from '../components/MembershipTitle.svelte' +import Memberships from '../components/Memberships/Memberships.astro' +import type { ComposedItem, HomeConfig } from '../types' + +type Props = ClubsPropsPages & { + memberships?: Membership[] + homeConfig: HomeConfig + name: string + sectionsOrderConfig: SectionOrderingValue + clubsPaymentsOverrides: ComposedItem[] +} + +const { + memberships, + homeConfig, + name, + clubs, + sectionsOrderConfig, + clubsPaymentsOverrides, +} = Astro.props + +const htmlString = markdownToHtml(homeConfig.body) + +const SlotsPageContentHomeBeforeContent = clubs.slots.filter(({ slot }) => { + return slot === ClubsSlotName.PageContentHomeBeforeContent +}) +const SlotsPageContentHomeAfterContent = clubs.slots.filter(({ slot }) => { + return slot === ClubsSlotName.PageContentHomeAfterContent +}) +--- + +
+ { + // Display slots of PageContentHomeBeforeContent + SlotsPageContentHomeBeforeContent.map((Slot) => ( + + )) + } + + { + sectionsOrderConfig && sectionsOrderConfig === 'about-first' ? ( + <> + +
+ +
+ + ) : ( +
+ {memberships ? ( + <> + + + + ) : ( + + )} +
+ ) + } + + { + sectionsOrderConfig && sectionsOrderConfig === 'about-first' ? ( +
+ {memberships ? ( + <> + + + + ) : ( + + )} +
+ ) : ( + <> + +
+ +
+ + ) + } + + { + // Display slots of SlotsPageContentHomeAfterContent + SlotsPageContentHomeAfterContent.map((Slot) => ( + + )) + } +
diff --git a/src/readme.astro b/src/readme.astro deleted file mode 100644 index 26cb9f22..00000000 --- a/src/readme.astro +++ /dev/null @@ -1,5 +0,0 @@ ---- -import { Content } from './readme.md' ---- - - diff --git a/src/readme.md b/src/readme.md deleted file mode 100644 index e6eb04bf..00000000 --- a/src/readme.md +++ /dev/null @@ -1 +0,0 @@ -Awesome description 💖 diff --git a/src/types.ts b/src/types.ts index 522852e5..ba6bc4fc 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,9 +1,28 @@ -export type Option = - | Readonly<{ key: 'slug'; value?: string }> - | Readonly<{ key: 'rpc'; value?: string }> - | Readonly<{ key: 'maxpage'; value?: number }> +/* eslint-disable functional/type-declaration-immutability */ +import type { Membership } from '@devprotocol/clubs-core' -export type TokenURIWithId = Readonly<{ - id: number - image: string +export type HomeConfig = { + hero: { + image: string + text: string + } + body: string + description: string +} + +export type GlobalConfig = { + bg?: string + backgroundGradient?: [string, string] + ink?: string +} + +export type Override = Readonly<{ + id: string + importFrom: string + key: string + payload: string | Uint8Array + price: { + yen: number + } }> +export type ComposedItem = Override & { source: Membership } diff --git a/src/utils/compose-items.ts b/src/utils/compose-items.ts new file mode 100644 index 00000000..9be19d0a --- /dev/null +++ b/src/utils/compose-items.ts @@ -0,0 +1,62 @@ +import { + bytes32Hex, + type ClubsFactoryUtils, + type ClubsPluginOptions, +} from '@devprotocol/clubs-core' +import { whenDefined, type UndefinedOr } from '@devprotocol/util-ts' + +import type { ComposedItem, Override } from '../types' +import type { Membership } from '@devprotocol/clubs-core' + +// eslint-disable-next-line functional/type-declaration-immutability +export type CollectionMembership = Membership & { + memberCount?: number +} + +// eslint-disable-next-line functional/type-declaration-immutability +export type Collection = { + id: string + name: string + imageSrc: string + status: 'Draft' | 'Published' + endTime?: number + description: string + memberships: CollectionMembership[] + requiredMemberships?: string[] +} + +export const composeItems = ( + options: ClubsPluginOptions, + { getPluginConfigById }: ClubsFactoryUtils, +): ComposedItem[] => { + const overrides = + (options.find((opt) => opt.key === 'override')?.value as UndefinedOr< + Override[] + >) ?? [] + + const items: ComposedItem[] = overrides + .map((ov) => { + const [sourceConfig] = getPluginConfigById(ov.importFrom) + const source = ( + sourceConfig?.options?.find((op) => op.key === ov.key)?.value as + | undefined + | Membership[] + | Collection[] + )?.find((s) => { + // Check if the keys from collection object are present in s, if yes then it's a collection. + return 'memberships' in s || + 'requiredMemberships' in s || + 'endTime' in s + ? !!(s as Collection).memberships.find( + // This is a collection, so find the membership and return boolean. + (m) => bytes32Hex(m.payload) === bytes32Hex(ov.payload), + ) + : bytes32Hex(s.payload) === bytes32Hex(ov.payload) // This is a membership so directly compare payload. + }) + const composed = whenDefined(source, (so) => ({ ...ov, source: so })) + return composed + }) + .filter((x) => x !== undefined) as ComposedItem[] + + return items +}