Skip to content

Commit

Permalink
feat(corel): integrate bundles store (#7040)
Browse files Browse the repository at this point in the history
* feat(corel): add bundles store
  • Loading branch information
pedrobonamin authored and bjoerge committed Aug 16, 2024
1 parent 6eb55c1 commit 344015c
Show file tree
Hide file tree
Showing 8 changed files with 578 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import {Card, Flex, Stack, Text} from '@sanity/ui'
import {type ComponentType, type FormEvent, useCallback, useState} from 'react'

import {Button} from '../../../../ui-components'
import {LoadingBlock} from '../../../components/loadingBlock/LoadingBlock'
import {AddonDatasetProvider} from '../../../studio/addonDataset/AddonDatasetProvider'
import {type BundleDocument} from '../types'
import {useBundleOperations} from '../useBundleOperations'
import {useBundlesStore} from '../useBundlesStore'
import {ReleaseForm} from './ReleaseForm'

const WithAddonDatasetProvider = <P extends object>(Component: ComponentType<P>): React.FC<P> => {
const WrappedComponent: React.FC<P> = (props) => (
<AddonDatasetProvider>
<Component {...props} />
</AddonDatasetProvider>
)
WrappedComponent.displayName = `WithAddonDatasetProvider(${Component.displayName || Component.name || 'Component'})`

return WrappedComponent
}

const initialValue = {name: '', title: '', tone: undefined, publishAt: undefined}
const BundlesStoreStory = () => {
const {data, loading} = useBundlesStore()
const {createBundle, deleteBundle} = useBundleOperations()
const [creating, setCreating] = useState(false)
const [deleting, setDeleting] = useState<string | null>(null)
const [value, setValue] = useState<Partial<BundleDocument>>(initialValue)
const handleCreateBundle = useCallback(
async (event: FormEvent<HTMLFormElement>) => {
try {
event.preventDefault()
setCreating(true)
await createBundle(value)
setValue(initialValue)
} catch (err) {
console.error(err)
} finally {
setCreating(false)
}
},
[createBundle, value],
)

const handleDeleteBundle = useCallback(
async (id: string) => {
try {
setDeleting(id)
await deleteBundle(id)
} catch (err) {
console.error(err)
} finally {
setDeleting(null)
}
},
[deleteBundle],
)

return (
<Stack space={3}>
<Flex gap={2}>
<Card margin={3} padding={3} border>
<form onSubmit={handleCreateBundle}>
<Stack space={4}>
<Text weight="medium">Create a new release</Text>
<ReleaseForm onChange={setValue} value={value} />
<Flex justify="flex-end">
<Button
text="Create"
tone="primary"
type="submit"
disabled={creating}
loading={creating}
/>
</Flex>
</Stack>
</form>
</Card>
<Card margin={3} border padding={3}>
<div style={{maxHeight: '400px', overflow: 'scroll'}}>
<Text>Data</Text>
{loading ? <LoadingBlock /> : <pre>{JSON.stringify(data, null, 2)}</pre>}
</div>
</Card>
</Flex>
<Card margin={3} border padding={3}>
<Stack space={3}>
{data?.map((bundle) => (
<Card key={bundle._id} padding={3} border radius={3}>
<Flex align="center" gap={3} justify={'space-between'}>
<Text>{bundle.name}</Text>
<Button
text="Delete"
tone="critical"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handleDeleteBundle(bundle._id)}
disabled={deleting === bundle._id}
loading={deleting === bundle._id}
/>
</Flex>
</Card>
))}
</Stack>
</Card>
</Stack>
)
}

export default WithAddonDatasetProvider(BundlesStoreStory)
133 changes: 133 additions & 0 deletions packages/sanity/src/core/store/bundles/__workshop__/ReleaseForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import {COLOR_HUES} from '@sanity/color'
import {CalendarIcon} from '@sanity/icons'
import {
Box,
type ButtonTone,
Card,
Flex,
Select,
Stack,
Text,
TextArea,
TextInput,
} from '@sanity/ui'
import speakingurl from 'speakingurl'

import {Button} from '../../../../ui-components/button'
import {type BundleDocument} from '../types'

function toSlug(value: string): string {
return speakingurl(value, {truncate: 200, symbols: true})
}

/**
* Copy from Prototype, not a final or complete working implementation.
*/
export function ReleaseForm(props: {
onChange: (params: Partial<BundleDocument>) => void
value: Partial<BundleDocument>
}) {
const {onChange, value} = props

const handleReleaseTitleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const v = event.target.value

onChange({...value, title: v, name: toSlug(v)})
}

const handleReleaseDescriptionChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
const v = event.target.value

onChange({...value, description: v || undefined})
}

const handleReleaseToneChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
onChange({...value, tone: (event.target.value || undefined) as ButtonTone | undefined})
}

const handleReleasePublishAtChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const v = event.target.value

onChange({...value, publishAt: v})
}

return (
<Stack space={5}>
<Stack space={3}>
<Text size={1} weight="medium">
Title
</Text>
<TextInput fontSize={3} onChange={handleReleaseTitleChange} value={value.title} />
</Stack>

<Stack space={3}>
<Text size={1} weight="medium">
Description
</Text>
<TextArea onChange={handleReleaseDescriptionChange} value={value.description} />
</Stack>

<Stack hidden space={3}>
<Text size={1} weight="medium">
Schedule for publishing at
</Text>
<TextInput
onChange={handleReleasePublishAtChange}
suffix={
<Box padding={1} style={{border: '1px solid transparent'}}>
<Button icon={CalendarIcon} mode="bleed" />
</Box>
}
value={value.publishAt || ''}
/>
</Stack>

<Stack space={3}>
<Text size={1} weight="medium">
Color
</Text>
<Flex>
<Card
borderTop
borderLeft
borderBottom
flex="none"
radius={2}
padding={2}
style={{
borderTopRightRadius: 0,
borderBottomRightRadius: 0,
}}
>
<div
style={{
borderRadius: 1,
width: 17,
height: 17,
backgroundColor: `var(--card-avatar-${value.tone || 'gray'}-bg-color)`,
}}
>
&nbsp;
</div>
</Card>
<Stack flex={1}>
<Select
onChange={handleReleaseToneChange}
style={{
borderTopLeftRadius: 0,
borderBottomLeftRadius: 0,
}}
value={value.tone || ''}
>
{COLOR_HUES.map((hue) => (
<option key={hue} value={hue === 'gray' ? '' : hue}>
{hue}
</option>
))}
</Select>
</Stack>
</Flex>
</Stack>
</Stack>
)
}
14 changes: 14 additions & 0 deletions packages/sanity/src/core/store/bundles/__workshop__/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {defineScope} from '@sanity/ui-workshop'
import {lazy} from 'react'

export default defineScope({
name: 'core/bundles',
title: 'bundles',
stories: [
{
name: 'bundles-store',
title: 'BundlesStore',
component: lazy(() => import('./BundlesStoreStory')),
},
],
})
1 change: 1 addition & 0 deletions packages/sanity/src/core/store/bundles/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useBundlesStore'
109 changes: 109 additions & 0 deletions packages/sanity/src/core/store/bundles/reducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {type BundleDocument} from './types'

interface BundleAddedAction {
payload: BundleDocument
type: 'BUNDLE_ADDED'
}

interface BundleDeletedAction {
id: string
type: 'BUNDLE_DELETED'
}

interface BundleUpdatedAction {
payload: BundleDocument
type: 'BUNDLE_UPDATED'
}

interface BundlesSetAction {
payload: BundleDocument[]
type: 'BUNDLES_SET'
}

interface BundleReceivedAction {
payload: BundleDocument
type: 'BUNDLE_RECEIVED'
}

export type bundlesReducerAction =
| BundleAddedAction
| BundleDeletedAction
| BundleUpdatedAction
| BundlesSetAction
| BundleReceivedAction

export interface bundlesReducerState {
bundles: Map<string, BundleDocument>
}

function createBundlesSet(bundles: BundleDocument[]) {
const bundlesById = bundles.reduce((acc, bundle) => {
acc.set(bundle._id, bundle)
return acc
}, new Map<string, BundleDocument>())
return bundlesById
}

export function bundlesReducer(
state: bundlesReducerState,
action: bundlesReducerAction,
): bundlesReducerState {
switch (action.type) {
case 'BUNDLES_SET': {
// Create an object with the BUNDLE id as key
const bundlesById = createBundlesSet(action.payload)

return {
...state,
bundles: bundlesById,
}
}

case 'BUNDLE_ADDED': {
const addedBundle = action.payload as BundleDocument
const currentBundles = new Map(state.bundles)
currentBundles.set(addedBundle._id, addedBundle)

return {
...state,
bundles: currentBundles,
}
}

case 'BUNDLE_RECEIVED': {
const receivedBundle = action.payload as BundleDocument
const currentBundles = new Map(state.bundles)
currentBundles.set(receivedBundle._id, receivedBundle)

return {
...state,
bundles: currentBundles,
}
}

case 'BUNDLE_DELETED': {
const currentBundles = new Map(state.bundles)
currentBundles.delete(action.id)

return {
...state,
bundles: currentBundles,
}
}

case 'BUNDLE_UPDATED': {
const updatedBundle = action.payload
const id = updatedBundle._id as string
const currentBundles = new Map(state.bundles)
currentBundles.set(id, updatedBundle)

return {
...state,
bundles: currentBundles,
}
}

default:
return state
}
}
12 changes: 12 additions & 0 deletions packages/sanity/src/core/store/bundles/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {type SanityDocument} from '@sanity/types'
import {type ButtonTone} from '@sanity/ui'

export interface BundleDocument extends SanityDocument {
_type: 'bundle'
title: string
name: string
description?: string
tone?: ButtonTone
icon?: string
authorId: string
}
Loading

0 comments on commit 344015c

Please sign in to comment.