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 @@
-
-
-
- sTokens Viewer
-
-
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),
+ ),
+ }
+}
+---
+
+
+ {
+ memberships.map((mem) =>
+ (({ membership, override }) => (
+
+ {/* */}
+ {{ membership, override }}
+
+ ))(find(mem)),
+ )
+ }
+
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'
+}
+---
+
+
+
+ {
+ links.map(
+ (link) =>
+ link.enable !== false && (
+
+
+ {link.display}
+
+
+ ),
+ )
+ }
+
+
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 @@
-
-
-
- #{{ token.id }}
-
-
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 && (
+
+ )
+ }
+
+ {
+ !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
+}