Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add creating text proposal form. Add markdown viewer for proposal description #9

Merged
merged 3 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@
"@hookform/resolvers": "^3.3.1",
"@mui/icons-material": "^5.14.3",
"@mui/material": "^5.14.3",
"@rarimo/client": "^2.2.0",
"@rarimo/client": "^2.3.0",
"graphql": "^16.7.1",
"graphql-tag": "^2.12.6",
"lodash-es": "^4.17.21",
"loglevel": "^1.8.1",
"markdown-to-jsx": "^7.4.4",
"mitt": "^3.0.1",
"mui-markdown": "^1.1.13",
"negotiator": "^0.6.3",
"next": "^13.5.5",
"next-international": "^0.9.3",
Expand Down
171 changes: 171 additions & 0 deletions src/components/Forms/SubmitTextProposalForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import {
Button,
FormControl,
FormHelperText,
TextareaAutosize as BaseTextareaAutosize,
TextField,
Typography,
} from '@mui/material'
import { styled } from '@mui/system'
import { useMemo, useState } from 'react'
import { Controller } from 'react-hook-form'

import { getClient } from '@/client'
import { Dialog } from '@/components/Dialog'
import FormWrapper from '@/components/Forms/FormWrapper'
import MarkdownViewer from '@/components/MarkdownViewer'
import { ErrorHandler } from '@/helpers'
import { useForm, useWeb3 } from '@/hooks'
import { useI18n } from '@/locales/client'
import { FormProps } from '@/types'

enum FieldNames {
Title = 'title',
Description = 'description',
}

const PROPOSAL_MAX_DESC_LENGTH = 10_000

export default function SubmitTextProposalForm({ id, onSubmit, setIsDialogDisabled }: FormProps) {
const t = useI18n()
const { address } = useWeb3()

const [isPreviewDialogOpen, setIsPreviewDialogOpen] = useState(false)

const DEFAULT_VALUES = {
[FieldNames.Title]: '',
[FieldNames.Description]: '',
}

const {
formState,
handleSubmit,
control,
isFormDisabled,
formErrors,
disableForm,
enableForm,
getErrorMessage,
} = useForm(DEFAULT_VALUES, yup =>
yup.object({
[FieldNames.Title]: yup.string().required(),
[FieldNames.Description]: yup.string().max(PROPOSAL_MAX_DESC_LENGTH).required(),
}),
)

const client = useMemo(() => getClient(), [])

const getMinAmount = async (): Promise<string> => {
const { deposit_params } = await client.query.getGovParams('deposit')

return (
deposit_params?.min_deposit?.find(i => i?.denom === client.config.currency.minDenom)
?.amount ?? '0'
)
}

const submit = async (formData: typeof DEFAULT_VALUES) => {
disableForm()
setIsDialogDisabled(true)
try {
const client = getClient()

const amount = await getMinAmount()

await client.tx.submitTextProposal(
address,
[
{
denom: client.config.currency.minDenom,
amount,
},
],
formData[FieldNames.Title],
formData[FieldNames.Description],
)

onSubmit({
message: t('submit-text-proposal-form.submitted-msg'),
})
} catch (e) {
ErrorHandler.process(e)
}
enableForm()
setIsDialogDisabled(false)
}

return (
<FormWrapper id={id} onSubmit={handleSubmit(submit)} isFormDisabled={isFormDisabled}>
<Typography variant={'body2'} color={'var(--col-txt-secondary)'}>
{t('submit-text-proposal-form.helper-text')}
</Typography>

<Controller
name={FieldNames.Title}
control={control}
render={({ field }) => (
<FormControl>
<TextField
{...field}
label={t('submit-text-proposal-form.title-lbl')}
error={!!formErrors[FieldNames.Title]}
/>

{Boolean(formErrors[FieldNames.Title]) && (
<FormHelperText error>{getErrorMessage(formErrors[FieldNames.Title])}</FormHelperText>
)}
</FormControl>
)}
/>

<Controller
name={FieldNames.Description}
control={control}
render={({ field }) => (
<FormControl>
<TextareaAutosize
{...field}
minRows={5}
maxRows={10}
aria-label={`${FieldNames.Description}-textarea`}
placeholder={t('submit-text-proposal-form.description-lbl')}
/>

{Boolean(formErrors[FieldNames.Description]) && (
<FormHelperText error>
{getErrorMessage(formErrors[FieldNames.Description])}
</FormHelperText>
)}
</FormControl>
)}
/>

{formState[FieldNames.Description] && (
<Button onClick={() => setIsPreviewDialogOpen(true)}>
{t('submit-text-proposal-form.desc-preview-btn')}
</Button>
)}

<Dialog
action={<></>}
onClose={() => setIsPreviewDialogOpen(false)}
isOpened={isPreviewDialogOpen}
title={t('submit-text-proposal-form.desc-preview-title')}
>
<MarkdownViewer>{formState[FieldNames.Description]}</MarkdownViewer>
</Dialog>
</FormWrapper>
)
}

const TextareaAutosize = styled(BaseTextareaAutosize)(
({ theme }) => `
box-sizing: border-box;
background: none;
padding: ${theme.spacing(2)} ${theme.spacing(1.75)};

&:focus {
outline: none;
}
`,
)
34 changes: 34 additions & 0 deletions src/components/MarkdownViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Stack, Typography } from '@mui/material'
import { getOverrides, MuiMarkdown } from 'mui-markdown'

export default function MarkdownViewer({ children }: { children: string }) {
return (
<MuiMarkdown
overrides={{
...getOverrides({}),
h2: {
component: Typography,
props: { variant: 'subtitle2', component: 'h2', sx: { mb: 2 } },
},
h3: {
component: Typography,
props: { variant: 'subtitle3', component: 'h3', sx: { mb: 2 } },
},
p: {
component: Typography,
props: { variant: 'body3', component: 'p', sx: { mb: 3 } },
},
ul: {
component: Stack,
props: { component: 'ul', sx: { pl: 5, mt: 0, mb: 3 } },
},
li: {
component: Typography,
props: { variant: 'subtitle4', component: 'li' },
},
}}
>
{children}
</MuiMarkdown>
)
}
6 changes: 5 additions & 1 deletion src/components/Proposal/ProposalDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useMemo } from 'react'

import { AvatarName } from '@/components/Avatar'
import { ContentBox, ContentWrapper } from '@/components/Content'
import MarkdownViewer from '@/components/MarkdownViewer'
import OverviewTable from '@/components/OverviewTable'
import ProposalDetailsContentRow from '@/components/Proposal/ProposalDetailsContentRow'
import ProposalDetailsTallyResult from '@/components/Proposal/ProposalDetailsTallyResult'
Expand Down Expand Up @@ -77,7 +78,10 @@ export default function ProposalDetails({
},
{
head: t('proposal-details.description-lbl'),
body: withSkeleton(metadata?.description, TABLE_TYPE_BOX_SKELETON_SX),
body: withSkeleton(
<MarkdownViewer>{metadata?.description ?? ''}</MarkdownViewer>,
TABLE_TYPE_BOX_SKELETON_SX,
),
},
{
head: t('proposal-details.status-lbl'),
Expand Down
16 changes: 15 additions & 1 deletion src/components/Proposal/Proposals.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
'use client'

import { TableCell } from '@mui/material'
import { useEffect } from 'react'

import { getProposalCount, getProposalList } from '@/callers'
import { ContentBox, ContentSection, ContentWrapper } from '@/components/Content'
import ProposalsRow from '@/components/Proposal/ProposalsRow'
import ProposalsSubmit from '@/components/Proposal/ProposalsSubmit'
import TableWithPagination from '@/components/TableWithPagination'
import { ProposalBaseFragment } from '@/graphql'
import { Bus } from '@/helpers'
import { useLoading, useTablePagination } from '@/hooks'
import { useI18n } from '@/locales/client'
import { TableColumn } from '@/types'
Expand All @@ -28,16 +31,27 @@ export default function Proposals() {
data: proposalCount,
isLoading: isLoadingProposalCount,
isLoadingError: isLoadingProposalCountError,
reload: reloadProposalCount,
} = useLoading<number>(0, getProposalCount)

const {
data: proposalList,
isLoading,
isLoadingError,
reload: reloadProposalsList,
} = useLoading<ProposalBaseFragment[]>([], () => getProposalList(limit, offset), {
loadArgs: [limit, offset],
})

useEffect(() => {
Bus.on(Bus.eventList.reloadVotes, () => {
reloadProposalCount()
reloadProposalsList()
})

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

const columns: readonly TableColumn<ProposalsColumnIds>[] = [
{
id: ProposalsColumnIds.Id,
Expand Down Expand Up @@ -83,7 +97,7 @@ export default function Proposals() {
)

return (
<ContentSection withBackButton title={t('proposals.title-lbl')}>
<ContentSection withBackButton title={t('proposals.title-lbl')} action={<ProposalsSubmit />}>
<ContentBox>
<ContentWrapper>
<TableWithPagination
Expand Down
66 changes: 66 additions & 0 deletions src/components/Proposal/ProposalsSubmit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Stack } from '@mui/material'
import { sleep } from '@rarimo/shared'
import { useMemo, useState } from 'react'

import { DialogFormWrapper } from '@/components/Dialog'
import SubmitTextProposalForm from '@/components/Forms/SubmitTextProposalForm'
import MultipleActionsButton from '@/components/MultipleActionsButton'
import { Bus } from '@/helpers'
import { useContentSectionAction } from '@/hooks'
import { useI18n } from '@/locales/client'

enum ProposalTypes {
Text = 'proposal-submit-form',
}

export default function ProposalsSubmit() {
const t = useI18n()

const [submitType, setSubmitType] = useState<ProposalTypes>()

const { closeDialog, openDialog, setIsDisabled, onSubmit, isDisabled, isDialogOpened } =
useContentSectionAction(async () => {
await sleep(2000)
Bus.emit(Bus.eventList.reloadProposals)
})

const actions = useMemo(
() => {
return [
{
label: t('proposals.submit-proposal-action'),
handler: () => {
setSubmitType(ProposalTypes.Text)
openDialog()
},
isDisabled: false,
},
]
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[t],
)

return (
<Stack>
<MultipleActionsButton actions={actions} isDisabled={isDisabled} />

<DialogFormWrapper
formId={ProposalTypes.Text}
isDisabled={isDisabled}
isDialogOpened={isDialogOpened}
closeDialog={closeDialog}
actionBtnText={t('proposals-submit.dialog-action-btn')}
title={t('proposals-submit.dialog-heading')}
>
{submitType === ProposalTypes.Text && (
<SubmitTextProposalForm
id={ProposalTypes.Text}
onSubmit={onSubmit}
setIsDialogDisabled={setIsDisabled}
/>
)}
</DialogFormWrapper>
</Stack>
)
}
1 change: 1 addition & 0 deletions src/helpers/event-bus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const BUS_EVENT_TYPES = {
info: 'info',
redirectToHome: 'redirectToHome',
reloadVotes: 'reloadVotes',
reloadProposals: 'reloadProposals',
}

export type EventBusEventName = (typeof BUS_EVENT_TYPES)[keyof typeof BUS_EVENT_TYPES]
Expand Down
13 changes: 13 additions & 0 deletions src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,19 @@ export default {
"no-data-subtitle": "It will appear for a while",
"error-title": "There was an error while loading proposals",
"error-subtitle": "Please try again later",
"submit-proposal-action": "Create Proposal",
},
"proposals-submit": {
"dialog-action-btn": "Submit",
"dialog-heading": "Create Text Proposal",
},
"submit-text-proposal-form": {
"submitted-msg": "Text proposal successfully created.",
"helper-text": "Please fill out the form below to create a new text proposal.",
"title-lbl": "Title",
"description-lbl": "Description",
"desc-preview-btn": "Preview",
"desc-preview-title": "Description preview",
},
"proposal-deposits": {
"title-lbl": "Deposits",
Expand Down
Loading
Loading