Skip to content
This repository has been archived by the owner on Sep 12, 2024. It is now read-only.

refactor: enforce strictest typescript config #157

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion api/helpers/getFileExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { handleApiError } from '@common/helpers/handleApiError'
export function getFileExtension(fileName: string): string {
try {
const result = /\.([^.]+)$/.exec(fileName)
if (result === null) {
if (result === null || result[1] === undefined) {
throw new Error(`Impossible to extract the file extension from "${fileName}" file name."`)
}

Expand Down
2 changes: 1 addition & 1 deletion api/helpers/getFileNameFromFilePath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { handleApiError } from '@common/helpers/handleApiError'
export function getFileNameFromFilePath(filePath: string): string {
try {
const result = /[/\\]([^/\\]+)$/.exec(filePath)
if (result === null) {
if (result === null || result[1] === undefined) {
throw new Error(`Impossible to extract the file name from "${filePath}" file path."`)
}

Expand Down
8 changes: 4 additions & 4 deletions api/libs/StaticServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ enum StaticServerType {

const CACHE: {
type?: StaticServerType
} = {
type: undefined,
}
} = {}

export class StaticServer {
#type?: StaticServerType

constructor() {
this.#type = CACHE.type
if (CACHE.type !== undefined) {
this.#type = CACHE.type
}
}

public async upload(filePath: string, mimeType: MimeType): Promise<UploadedFileInfo> {
Expand Down
3 changes: 3 additions & 0 deletions api/middlewares/withAuth/handleAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ export async function handleAuth(
// Disposable tokens used for internal front-end private API requests.

const oneTimeToken = Array.isArray(req.query.oneTimeToken) ? req.query.oneTimeToken[0] : req.query.oneTimeToken
if (oneTimeToken === undefined) {
throw new ApiError(`Unauthorized.`, 401, true)
}

const maybeOneTimeToken = await prisma.oneTimeToken.findUnique({
where: {
Expand Down
2 changes: 1 addition & 1 deletion app/atoms/CardTitle.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import styled from 'styled-components'

export const CardTitle = styled.h2<{
accent?: string
accent?: 'danger' | 'info' | 'primary' | 'secondary' | 'success' | 'warning'
isFirst?: boolean
withBottomMargin?: boolean
}>`
Expand Down
2 changes: 1 addition & 1 deletion app/helpers/__tests__/getRandomId.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import crypto from 'crypto'

// TODO Repleace this polyfill once this is fixed: https://github.com/jsdom/jsdom/issues/1612.
window.crypto = {
getRandomValues: buffer => crypto.randomFillSync(buffer),
getRandomValues: (buffer: any) => crypto.randomFillSync(buffer),
} as any

// eslint-disable-next-line import/first
Expand Down
29 changes: 29 additions & 0 deletions app/hocs/withCustomFormik.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { withFormik } from 'formik'

import type { CompositeComponent, FormikErrors } from 'formik'
import type { ObjectSchema } from 'yup'

export type FormProps<V> = {
initialValues: Partial<V>
onSubmit: (values: Partial<V>) => Promise<undefined | FormikErrors<V>>
}

export function withCustomFormik<V>(
FormComponent: CompositeComponent<FormProps<V>>,
validationSchema: ObjectSchema<any>,
) {
return withFormik<FormProps<V>, Partial<V>>({
handleSubmit: async (values, { props: { onSubmit }, setErrors, setSubmitting }) => {
const errors = await onSubmit(values)
if (errors !== undefined) {
setErrors(errors)
}

setSubmitting(false)
},

mapPropsToValues: ({ initialValues }) => initialValues,

validationSchema,
})(FormComponent)
}
45 changes: 33 additions & 12 deletions app/libs/SurveyEditorManager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ export class SurveyEditorManager {
return null
}

return this.#blocks[this.#focusedBlockIndex]
const block = this.#blocks[this.#focusedBlockIndex]
if (block === undefined) {
return null
}

return block
}

public get focusedBlockIndex(): number {
Expand All @@ -44,11 +49,26 @@ export class SurveyEditorManager {
this.#blocks = []
this.#focusedBlockIndex = -1

Object.getOwnPropertyNames(Object.getPrototypeOf(this)).forEach(key => {
if (this[key] instanceof Function && key !== 'constructor') {
this[key] = this[key].bind(this)
}
})
this.appendNewBlockAt = this.appendNewBlockAt.bind(this)
this.appendNewBlockToFocusedBlock = this.appendNewBlockToFocusedBlock.bind(this)
this.findBlockIndexById = this.findBlockIndexById.bind(this)
this.focusNextBlock = this.focusNextBlock.bind(this)
this.focusPreviousBlock = this.focusPreviousBlock.bind(this)
this.getQuestionInputTypeAt = this.getQuestionInputTypeAt.bind(this)
this.initializeBlocks = this.initializeBlocks.bind(this)
this.removeBlockAt = this.removeBlockAt.bind(this)
this.removeFocusedBlock = this.removeFocusedBlock.bind(this)
this.setBlockPropsAt = this.setBlockPropsAt.bind(this)
this.setBlockTypeAt = this.appendNewBlockAt.bind(this)
this.setBlockValueAt = this.appendNewBlockAt.bind(this)
this.setFocusAt = this.setFocusAt.bind(this)
this.setFocusedBlockProps = this.setFocusedBlockProps.bind(this)
this.setFocusedBlockType = this.setFocusedBlockType.bind(this)
this.setFocusedBlockValue = this.setFocusedBlockValue.bind(this)
this.setIfSelectedThenShowQuestionIdsAt = this.setIfSelectedThenShowQuestionIdsAt.bind(this)
this.toggleBlockObligationAt = this.toggleBlockObligationAt.bind(this)
this.toggleBlockVisibilityAt = this.toggleBlockVisibilityAt.bind(this)
this.unsetFocus = this.unsetFocus.bind(this)

this.initializeBlocks(initialBlocks)
}
Expand All @@ -62,10 +82,12 @@ export class SurveyEditorManager {
return
}

const oldBlock = this.blocks[index]

const newBlock = {
data: {
pageIndex: index === -1 ? 0 : this.blocks[index].data.pageIndex,
pageRankIndex: index === -1 ? 0 : this.blocks[index].data.pageRankIndex + 1,
pageIndex: index === -1 || oldBlock === undefined ? 0 : oldBlock.data.pageIndex,
pageRankIndex: index === -1 || oldBlock === undefined ? 0 : oldBlock.data.pageRankIndex + 1,
},
id: cuid(),
type,
Expand Down Expand Up @@ -132,14 +154,13 @@ export class SurveyEditorManager {
// eslint-disable-next-line no-plusplus
while (++nextBlockIndex < blocksLength) {
const nextBlock = this.blocks[nextBlockIndex]
if (nextBlock === undefined || isQuestionBlock(nextBlock)) {
break
}

if (isInputBlock(nextBlock)) {
return nextBlock.type
}

if (isQuestionBlock(nextBlock)) {
break
}
}

throw new Error(`This survey question block has no related input block.`)
Expand Down
154 changes: 154 additions & 0 deletions app/organisms/GlobalSettingsForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { CardTitle } from '@app/atoms/CardTitle'
import { withCustomFormik } from '@app/hocs/withCustomFormik'
import { useLocalization } from '@app/hooks/useLocalization'
import { Form } from '@app/molecules/Form'
import { GlobalVariableKey } from '@common/constants'
import { Card, Field } from '@singularity/core'
import { Form as FormikForm } from 'formik'
import { useMemo } from 'react'
import { useIntl } from 'react-intl'
import * as Yup from 'yup'

import type { FormProps } from '@app/hocs/withCustomFormik'

export type GlobalSettingsFormValues = Record<GlobalVariableKey, string>

function GlobalSettingsFormComponent() {
const intl = useIntl()

return (
<FormikForm>
<Card>
<Field>
<Form.TextInput
label={intl.formatMessage({
defaultMessage: 'Base URL',
description: '[Settings Form] Base URL input label.',
id: 'SETTINGS_FORM__BASE_URL_INPUT_LABEL:',
})}
name={GlobalVariableKey.BASE_URL}
/>
</Field>
</Card>

<Card style={{ marginTop: '1rem' }}>
<CardTitle isFirst>
{intl.formatMessage({
defaultMessage: 'S3-compatible Static Assets Server',
description: '[Settings Form] S3 subtitle.',
id: 'SETTINGS_FORM__S3_SUBTITLE',
})}
</CardTitle>

<Field>
<Form.TextInput
label={intl.formatMessage({
defaultMessage: 'Endpoint',
description: '[Settings Form] S3 Endpoint input label.',
id: 'SETTINGS_FORM__S3_ENDPOINT_INPUT_LABEL',
})}
name={GlobalVariableKey.S3_ENDPOINT}
placeholder="s3.amazonaws.com"
/>
</Field>

<Field>
<Form.TextInput
helper={intl.formatMessage({
defaultMessage: 'Only required for MinIO custom servers.',
description: '[Settings Form] S3 Port input helper.',
id: 'SETTINGS_FORM__S3_PORT_INPUT_HELPER',
})}
label={intl.formatMessage({
defaultMessage: 'Port',
description: '[Settings Form] S3 Port input label.',
id: 'SETTINGS_FORM__S3_PORT_INPUT_LABEL',
})}
name={GlobalVariableKey.S3_PORT}
type="number"
/>
</Field>

<Field>
<Form.TextInput
label={intl.formatMessage({
defaultMessage: 'Access Key',
description: '[Settings Form] S3 Access Key input label.',
id: 'SETTINGS_FORM__S3_ACCESS_KEY_INPUT_LABEL',
})}
name={GlobalVariableKey.S3_ACCESS_KEY}
/>
</Field>

<Field>
<Form.TextInput
label={intl.formatMessage({
defaultMessage: 'Secret Key',
description: '[Settings Form] S3 Secret Key input label.',
id: 'SETTINGS_FORM__S3_SECRET_KEY_INPUT_LABEL',
})}
name={GlobalVariableKey.S3_SECRET_KEY}
/>
</Field>

<Form.TextInput
label={intl.formatMessage({
defaultMessage: 'Bucket',
description: '[Settings Form] S3 Bucket input label.',
id: 'SETTINGS_FORM__S3_BUCKET_INPUT_LABEL',
})}
name={GlobalVariableKey.S3_BUCKET}
placeholder="tell-me-assets"
/>

<Form.TextInput
label={intl.formatMessage({
defaultMessage: 'Public URL',
description: '[Settings Form] S3 Public URL input label.',
id: 'SETTINGS_FORM__S3_URL_INPUT_LABEL',
})}
name={GlobalVariableKey.S3_URL}
placeholder="https://tell-me-assets.s3.eu-west-3.amazonaws.com"
/>
</Card>

<Card style={{ marginTop: '1rem' }}>
<Field>
<Form.Submit>
{intl.formatMessage({
defaultMessage: 'Update',
description: '[Settings Form] Form update button label.',
id: 'SETTINGS_FORM__UPDATE_BUTTON_LABEL',
})}
</Form.Submit>
</Field>
</Card>
</FormikForm>
)
}

export function GlobalSettingsForm(props: FormProps<GlobalSettingsFormValues>) {
const localization = useLocalization()
// const intl = useIntl()

const formSchema = useMemo(
() =>
Yup.object().shape({
[GlobalVariableKey.BASE_URL]: Yup.string().trim().nullable().url("This URL doesn't look right."),
[GlobalVariableKey.S3_ACCESS_KEY]: Yup.string().trim().nullable(),
[GlobalVariableKey.S3_BUCKET]: Yup.string().trim().nullable(),
[GlobalVariableKey.S3_ENDPOINT]: Yup.string().trim().nullable(),
[GlobalVariableKey.S3_PORT]: Yup.number().nullable(),
[GlobalVariableKey.S3_SECRET_KEY]: Yup.string().trim().nullable(),
[GlobalVariableKey.S3_URL]: Yup.string().trim().nullable().url("This URL doesn't look right."),
}),
[localization.locale],
)

const WrappedComponent = useMemo(
() => withCustomFormik<GlobalSettingsFormValues>(GlobalSettingsFormComponent, formSchema),
[localization.locale],
)

return <WrappedComponent {...props} />
}
7 changes: 6 additions & 1 deletion common/helpers/handleApiEndpointError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ import type { NextApiResponse } from 'next'
* If <isLast> is not set to `true`, the handler will rethrow a SilentError
* after sending the response to avoid any subsequent code to be run.
*/
export function handleApiEndpointError(error: any, path: string, res: NextApiResponse, isLast: boolean = false): never {
export function handleApiEndpointError(
error: unknown,
path: string,
res: NextApiResponse,
isLast: boolean = false,
): never {
if (isLast && error instanceof SilentError) {
return undefined as never
}
Expand Down
2 changes: 1 addition & 1 deletion common/helpers/handleApiError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { handleError } from './handleError'
*
* ⚠️ Don't use that within SSR pages!
*/
export function handleApiError(error: any, path: string): never {
export function handleApiError(error: unknown, path: string): never {
if (!(error instanceof ApiError)) {
handleError(error, path)
}
Expand Down
2 changes: 1 addition & 1 deletion common/helpers/handleFatalError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { handleError } from './handleError'
/**
* Exit process (with an error code) after handling any passed error.
*/
export function handleFatalError(error: any, path: string): never {
export function handleFatalError(error: unknown, path: string): never {
handleError(error, path)

process.exit(1)
Expand Down
4 changes: 3 additions & 1 deletion pages/_document.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import Document from 'next/document'
import { ServerStyleSheet } from 'styled-components'

import type { DocumentContext } from 'next/document'

export default class TellMeDocument extends Document {
static async getInitialProps(ctx) {
static override async getInitialProps(ctx: DocumentContext) {
const sheet = new ServerStyleSheet()
const originalRenderPage = ctx.renderPage

Expand Down
Loading