diff --git a/src/@types/events.ts b/src/@types/events.ts deleted file mode 100644 index 193b610..0000000 --- a/src/@types/events.ts +++ /dev/null @@ -1,8 +0,0 @@ -/*===============================* - EVENT TYPES - *===============================* -*/ -export type ReactFormEvent = React.FormEvent; -export type ReactSelectEvent = React.MouseEvent; -export type ReactInputEvent = React.ChangeEvent; -export type ReactMouseEvent = React.MouseEvent; diff --git a/src/@types/helpers.ts b/src/@types/helpers.ts deleted file mode 100644 index 81e91ef..0000000 --- a/src/@types/helpers.ts +++ /dev/null @@ -1,101 +0,0 @@ -/*===============================* - HELPER TYPES - *===============================* -*/ - -export type Immutable = T extends PrimitiveType - ? T - : T extends AtomicObject - ? T - : T extends Array - ? ReadonlyArray> - : T extends IfAvailable> - ? ReadonlyMap, Immutable> - : T extends IfAvailable> - ? ReadonlySet> - : T extends WeakReferences - ? T - : T extends object - ? { - // !NOTE: removes methods on object - readonly [P in NonFunctionPropertyNames]: Immutable; - // !NOTE: use { readonly [P in keyof T]: Immutable } to add methods - } - : unknown; - -export type RequiredKeys = { - [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; -}[keyof T]; - -export type LooseAutocomplete = T | Omit; - -export type Expand = T extends (...args: infer A) => infer R - ? (...args: Expand) => Expand - : T extends object - ? T extends infer O - ? { [K in keyof O]: Expand } - : never - : T; - -export type DeepPartial = T extends Function - ? T - : T extends Array - ? DeepPartialArray - : T extends object - ? DeepPartialObject - : T | undefined; - -type DeepPartialArray = Array>; -type DeepPartialObject = { - [K in keyof T]?: DeepPartial; -}; - -export type KeyValuePair = Record< - K, - V ->; -export interface RecursiveKeyValuePair< - K extends keyof any = string, - V = string -> { - [key: string]: V | RecursiveKeyValuePair; -} - -export type OptionalUnion< - U extends Record, - A extends keyof U = U extends U ? keyof U : never -> = U extends unknown ? { [P in Exclude]?: never } & U : never; - -type WeakReferences = - | IfAvailable> - | IfAvailable>; - -type IfAvailable = true | false extends ( - T extends never ? true : false -) - ? Fallback - : keyof T extends never - ? Fallback - : T; - -// get function properties -type FunctionPropertyNames = { - [P in keyof T]: T[P] extends Function ? P : never; -}[keyof T]; -type FunctionProperties = Pick>; - -// get non function properties -type NonFunctionPropertyNames = { - [P in keyof T]: T[P] extends Function ? never : P; -}[keyof T]; -type NonFunctionProperties = Pick>; - -type PrimitiveType = - | string - | number - | boolean - | undefined - | null - | bigint - | symbol; -type AtomicObject = Function | RegExp | Promise | Date; diff --git a/src/@types/index.d.ts b/src/@types/index.d.ts new file mode 100644 index 0000000..66cff5a --- /dev/null +++ b/src/@types/index.d.ts @@ -0,0 +1,245 @@ +// import type { RequestInit } from 'graphql-request/build/esm/types.dom'; +// This helps with type inference when using the component +export declare module 'react' { + function forwardRef( + render: (props: P, ref: React.Ref) => React.ReactElement | null + ): (props: P & React.RefAttributes) => React.ReactElement | null; +} + +export declare namespace Project { + type ElementProps> = $ElementProps & + Omit, keyof $ElementProps>; + + interface IconProps extends React.ComponentPropsWithoutRef<'svg'> {} + + type $ElementProps> = { + children: React.ReactNode; + as?: E; + }; + + // type ElementProps = T extends React.ComponentType + // ? Props extends object + // ? Props + // : never + // : never; + + type ElementProps> = $ElementProps & + Omit, keyof $ElementProps>; + + interface IconProps extends React.ComponentPropsWithoutRef<'svg'> {} + + type PropsFrom = T extends React.FC + ? Props + : T extends React.Component + ? Props + : T extends object + ? { [K in keyof T]: T[K] } + : never; + + /*===============================* + EVENT TYPES + *===============================* +*/ + type ReactFormEvent = React.FormEvent; + type ReactSelectEvent = React.MouseEvent; + type ReactInputEvent = React.ChangeEvent; + type ReactMouseEvent = React.MouseEvent; + + /*===============================* + PROJECT MODEL TYPES + *===============================* +*/ + + /*===============================* + DATA MODELS + *===============================* +*/ + + interface IUser { + id: string; + firstName: string; + lastName: string; + email: string; + photo: string; + } + + type Invoices = Array; + + type IStatus = ['PAID', 'PENDING', 'DRAFT']; + + type Invoice = DraftInvoice | PendingInvoice | PaidInvoice; + + type InvoiceStatus = IStatus[number]; + + interface DraftInvoice extends InvoiceFormType { + status: 'DRAFT'; + } + interface PendingInvoice extends InvoiceFormType { + status: 'PENDING'; + } + interface PaidInvoice extends InvoiceFormType { + status: 'PAID'; + } + + interface InvoiceFormType { + clientAddress: IAddress; + clientEmail: string; + clientName: string; + description: string; + items: ILineItem[]; + senderAddress: IAddress; + issueDate?: string | undefined; + paymentDue?: string | undefined; + paymentTerms?: number | undefined; + status?: InvoiceStatus | undefined; + tag?: string | undefined; + total?: number | undefined; + userId?: string | undefined; + } + + interface IAddress { + street: string; + city: string; + postCode: string; + country: string; + } + interface ILineItem { + name: string; + quantity: number; + price: number; + id?: string | undefined; + total?: number | undefined; + } + + interface IErrorResponse { + response: { errors: IError[]; data: IErrorData }; + } + + interface IError { + message: string; + locations: IErrorLocation[]; + path: string[]; + extensions: IErrorExtensions; + } + interface IErrorLocation { + line: number; + column: number; + } + interface IErrorExtensions { + code: string; + http: { status: number }; + stacktrace: string[]; + } + interface IErrorData { + [x: string]: any; + } + + /*===============================* + HELPER TYPES + *===============================* +*/ + + type Immutable = T extends PrimitiveType + ? T + : T extends AtomicObject + ? T + : T extends Array + ? ReadonlyArray> + : T extends IfAvailable> + ? ReadonlyMap, Immutable> + : T extends IfAvailable> + ? ReadonlySet> + : T extends WeakReferences + ? T + : T extends object + ? { + // !NOTE: removes methods on object + readonly [P in NonFunctionPropertyNames]: Immutable; + // !NOTE: use { readonly [P in keyof T]: Immutable } to add methods + } + : unknown; + + type RequiredKeys = { + [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K; + }[keyof T]; + + type LooseAutocomplete = T | Omit; + + type Expand = T extends (...args: infer A) => infer R + ? (...args: Expand) => Expand + : T extends object + ? T extends infer O + ? { [K in keyof O]: Expand } + : never + : T; + + type DeepPartial = T extends Function + ? T + : T extends Array + ? DeepPartialArray + : T extends object + ? DeepPartialObject + : T | undefined; + + type DeepPartialArray = Array>; + type DeepPartialObject = { + [K in keyof T]?: DeepPartial; + }; + + type KeyValuePair = Record; + interface RecursiveKeyValuePair { + [key: string]: V | RecursiveKeyValuePair; + } + + type OptionalUnion< + U extends Record, + A extends keyof U = U extends U ? keyof U : never + > = U extends unknown ? { [P in Exclude]?: never } & U : never; + + type WeakReferences = + | IfAvailable> + | IfAvailable>; + + type IfAvailable = true | false extends ( + T extends never ? true : false + ) + ? Fallback + : keyof T extends never + ? Fallback + : T; + + // get function properties + type FunctionPropertyNames = { + [P in keyof T]: T[P] extends Function ? P : never; + }[keyof T]; + type FunctionProperties = Pick>; + + // get non function properties + type NonFunctionPropertyNames = { + [P in keyof T]: T[P] extends Function ? never : P; + }[keyof T]; + type NonFunctionProperties = Pick>; + + type PrimitiveType = + | string + | number + | boolean + | undefined + | null + | bigint + | symbol; + type AtomicObject = Function | RegExp | Promise | Date; +} + +export declare global { + interface GlobalReducerActions {} +} + +type GlobalReducer = ( + state: IState, + action: { + [ActionType in keyof GlobalReducerActions]: { + type: ActionType; + } & GlobalReducerActions[ActionType]; + }[keyof GlobalReducerActions] +) => IState; diff --git a/src/@types/index.ts b/src/@types/index.ts deleted file mode 100644 index 763ac7e..0000000 --- a/src/@types/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './events'; -export * from './helpers'; -export * from './models'; -export * from './project.d'; -export * from './types'; diff --git a/src/@types/models.ts b/src/@types/models.ts deleted file mode 100644 index b700d13..0000000 --- a/src/@types/models.ts +++ /dev/null @@ -1,101 +0,0 @@ -/*===============================* - DATA MODELS - *===============================* -*/ - -export interface IUser { - id: string; - firstName: string; - lastName: string; - email: string; - photo: string; -} - -export type Invoices = Array; - -export type Invoice = DraftInvoice | PendingInvoice | PaidInvoice; - -export type IStatus = ['PAID', 'PENDING', 'DRAFT']; - -export type InvoiceStatus = IStatus[number]; -export type PaidInvoice = { - id: string; - createdAt: string; - updatedAt?: string; - paymentDue: string; - description: string; - paymentTerms: number; - clientName: string; - clientEmail: string; - status: Extract; - senderAddress: IAddress; - clientAddress: IAddress; - items: ILineItem[]; - total: number; -}; -export type PendingInvoice = { - id: string; - createdAt: string; - updatedAt?: string; - paymentDue: string; - description: string; - paymentTerms: number; - clientName: string; - clientEmail: string; - status: Extract; - senderAddress: IAddress; - clientAddress: IAddress; - items: ILineItem[]; - total: number; -}; -export type DraftInvoice = { - id: string; - createdAt: string; - updatedAt?: string; - paymentDue?: string; - description?: string; - paymentTerms: number; - clientName?: string; - clientEmail?: string; - status: Extract; - senderAddress?: IAddress; - clientAddress?: Partial; - items?: ILineItem[]; - total?: number; -}; - -interface IAddress { - street: string; - city: string; - postCode: string; - country: string; -} -interface ILineItem { - name: string; - quantity: number; - price: number; - total: number; -} - -export interface IErrorResponse { - response: { errors: IError[]; data: IErrorData }; -} - -interface IError { - message: string; - locations: IErrorLocation[]; - path: string[]; - extensions: IErrorExtensions; -} -interface IErrorLocation { - line: number; - column: number; -} -interface IErrorExtensions { - code: string; - http: { status: number }; - stacktrace: string[]; -} -interface IErrorData { - [x: string]: any; -} diff --git a/src/@types/project.d.ts b/src/@types/project.d.ts deleted file mode 100644 index 65b00d6..0000000 --- a/src/@types/project.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -// import type { RequestInit } from 'graphql-request/build/esm/types.dom'; -// This helps with type inference when using the component -export declare module 'react' { - function forwardRef( - render: (props: P, ref: React.Ref) => React.ReactElement | null - ): (props: P & React.RefAttributes) => React.ReactElement | null; -} - -export declare type $ElementProps = T extends React.ComponentType< - infer Props -> - ? Props extends object - ? Props - : never - : never; - -export type PropsFrom = T extends React.FC - ? Props - : T extends React.Component - ? Props - : T extends object - ? { [K in keyof T]: T[K] } - : never; diff --git a/src/@types/types.ts b/src/@types/types.ts deleted file mode 100755 index 4e67838..0000000 --- a/src/@types/types.ts +++ /dev/null @@ -1,22 +0,0 @@ -import * as React from 'react'; - -declare global { - interface GlobalReducerActions {} -} - -export type GlobalReducer = ( - state: IState, - action: { - [ActionType in keyof GlobalReducerActions]: { - type: ActionType; - } & GlobalReducerActions[ActionType]; - }[keyof GlobalReducerActions] -) => IState; - -type ElementProps> = { - children: React.ReactNode; - as?: E; -}; - -export type Props> = ElementProps & - Omit, keyof ElementProps>; diff --git a/src/common/links.tsx b/src/common/links.tsx index f275c88..8fc2b5e 100755 --- a/src/common/links.tsx +++ b/src/common/links.tsx @@ -1,4 +1,3 @@ -import * as React from 'react'; import { IconAddSVG, IconArrowDownSVG, @@ -10,8 +9,6 @@ import { LogoSVG, } from './svg-icons'; -export interface IconProps extends React.ComponentPropsWithoutRef<'svg'> {} - export const icons = { arrow: { right: IconArrowRightSVG, diff --git a/src/components/atoms/form-control.tsx b/src/components/atoms/form-control.tsx index f2a4e1f..ec22fa1 100644 --- a/src/components/atoms/form-control.tsx +++ b/src/components/atoms/form-control.tsx @@ -1,4 +1,4 @@ -import type { Props } from '@src/@types'; +import type { Project } from '@src/@types'; import clsx from 'clsx'; const FormControl = ({ @@ -6,7 +6,7 @@ const FormControl = ({ className, children, ...rest -}: Props) => { +}: Project.ElementProps) => { const RenderedElement = as || 'div'; return ( diff --git a/src/components/atoms/logo.tsx b/src/components/atoms/logo.tsx index 3edfe93..64822e5 100644 --- a/src/components/atoms/logo.tsx +++ b/src/components/atoms/logo.tsx @@ -1,6 +1,6 @@ import { Link } from 'react-router-dom'; -type Props = {}; +interface Props {} const Logo = (props: Props) => { return ( diff --git a/src/components/atoms/logout-button.tsx b/src/components/atoms/logout-button.tsx index 4ae93f4..712b31a 100644 --- a/src/components/atoms/logout-button.tsx +++ b/src/components/atoms/logout-button.tsx @@ -1,4 +1,4 @@ -import { IErrorResponse } from '@src/@types'; +import type { Project } from '@src/@types'; import { client, useAuthDispatch, useLogoutQuery } from '@src/lib'; import { useQueryClient } from '@tanstack/react-query'; import { useCallback } from 'react'; @@ -22,7 +22,7 @@ const LogoutButton = (props: Props) => { queryClient.clear(); navigate('/login'); }, - onError(error: IErrorResponse) { + onError(error: Project.IErrorResponse) { dispatch('auth/logout'); queryClient.clear(); navigate('/login'); diff --git a/src/components/atoms/svg-icon.tsx b/src/components/atoms/svg-icon.tsx index 919d4b1..ff3ca73 100644 --- a/src/components/atoms/svg-icon.tsx +++ b/src/components/atoms/svg-icon.tsx @@ -1,7 +1,7 @@ -import { IconProps } from '@src/common'; +import { Project } from '@src/@types'; import * as React from 'react'; -interface IProps extends IconProps { +interface IProps extends Project.IconProps { fileName: string; iconPath?: string; onCompleted: () => void; diff --git a/src/components/atoms/text.tsx b/src/components/atoms/text.tsx index e28b26a..7a3c5a6 100755 --- a/src/components/atoms/text.tsx +++ b/src/components/atoms/text.tsx @@ -1,10 +1,10 @@ -import type { Props } from '@src/@types'; +import type { Project } from '@src/@types'; const Text = ({ children, as, ...rest -}: Props) => { +}: Project.ElementProps) => { const RenderedElement = as || 'p'; return {children}; }; diff --git a/src/components/molecules/@types.ts b/src/components/molecules/@types.ts deleted file mode 100644 index 1b6946e..0000000 --- a/src/components/molecules/@types.ts +++ /dev/null @@ -1,40 +0,0 @@ -export interface InvoiceFormType { - clientAddress: { - street: string; - city: string; - country: string; - postCode: string; - }; - clientEmail: string; - clientName: string; - description: string; - items: [ - { - name: string; - quantity: number; - price: number; - id?: string | undefined; - total?: number | undefined; - }, - ...{ - name: string; - quantity: number; - price: number; - id?: string | undefined; - total?: number | undefined; - }[] - ]; - senderAddress: { - street: string; - city: string; - country: string; - postCode: string; - }; - issueDate?: string | undefined; - paymentDue?: string | undefined; - paymentTerms?: number | undefined; - status?: 'PAID' | 'PENDING' | 'DRAFT' | undefined; - tag?: string | undefined; - total?: number | undefined; - userId?: string | undefined; -} diff --git a/src/components/molecules/actions-btns.tsx b/src/components/molecules/actions-btns.tsx index 5d93b8e..17962aa 100644 --- a/src/components/molecules/actions-btns.tsx +++ b/src/components/molecules/actions-btns.tsx @@ -1,4 +1,4 @@ -import type { InvoiceType } from '@src/lib'; +import { InvoiceType, isPaidInvoice } from '@src/lib'; import clsx from 'clsx'; import { Link } from 'react-router-dom'; @@ -32,7 +32,8 @@ const InvoiceActions = ({ type='button' className='btn btn-invoice font-bold' onClick={() => { - if (invoice?.status !== 'PAID') updateStatus(invoice); + // @ts-expect-error disable typecheck error + if (!isPaidInvoice(invoice)) updateStatus(invoice); }} > Mark as Paid diff --git a/src/components/molecules/edit-item-list.tsx b/src/components/molecules/edit-item-list.tsx index 8193fcd..60818e1 100644 --- a/src/components/molecules/edit-item-list.tsx +++ b/src/components/molecules/edit-item-list.tsx @@ -9,12 +9,13 @@ import { } from 'react-hook-form'; import { v4 as uuid } from 'uuid'; import { FormErrorText, FormLabel } from '../atoms'; -import type { InvoiceFormType } from './@types'; + +import { Project } from '@src/@types'; import { PriceOutput } from './price-output'; interface Props { - methods: UseFormReturn; - invoice: InvoiceFormType; + methods: UseFormReturn; + invoice: Project.InvoiceFormType; } const EditItemList = ({ methods, invoice }: Props) => { diff --git a/src/components/molecules/index.ts b/src/components/molecules/index.ts index a517db8..cf35730 100755 --- a/src/components/molecules/index.ts +++ b/src/components/molecules/index.ts @@ -1,4 +1,3 @@ -export * from './@types'; export * from './actions-btns'; export * from './calendar'; export * from './dropdown'; diff --git a/src/components/molecules/new-item-list.tsx b/src/components/molecules/new-item-list.tsx index 6b12a32..e38821c 100644 --- a/src/components/molecules/new-item-list.tsx +++ b/src/components/molecules/new-item-list.tsx @@ -1,3 +1,4 @@ +import type { Project } from '@src/@types'; import { calculateTotal, endsWith, useMedia } from '@src/lib'; import clsx from 'clsx'; import * as React from 'react'; @@ -9,11 +10,10 @@ import { } from 'react-hook-form'; import { v4 as uuid } from 'uuid'; import { FormErrorText, FormLabel } from '../atoms'; -import { InvoiceFormType } from './@types'; import { PriceOutput } from './price-output'; interface Props { - methods: UseFormReturn; + methods: UseFormReturn; } const NewItemList = ({ methods }: Props) => { diff --git a/src/components/molecules/query-result.tsx b/src/components/molecules/query-result.tsx index 8980d94..515754e 100755 --- a/src/components/molecules/query-result.tsx +++ b/src/components/molecules/query-result.tsx @@ -1,4 +1,4 @@ -import type { IErrorResponse } from '@src/@types'; +import type { Project } from '@src/@types'; import * as React from 'react'; import { toast } from 'react-toastify'; import { LoadingSpinner } from '../atoms'; @@ -25,7 +25,7 @@ const QueryResult = ({ children, }: Props) => { if (error) { - (error as IErrorResponse).response.errors.forEach((err) => { + (error as Project.IErrorResponse).response.errors.forEach((err) => { if (err.message.includes('Authorised')) toast.error('Something unexpected happened. Please try again.'); else toast.error(err.message); diff --git a/src/components/organisms/content.tsx b/src/components/organisms/content.tsx index a396ec3..d9d741e 100644 --- a/src/components/organisms/content.tsx +++ b/src/components/organisms/content.tsx @@ -1,6 +1,6 @@ import { Outlet } from 'react-router-dom'; -type Props = {}; +interface Props {} const MainContent = (props: Props) => { return ( diff --git a/src/components/organisms/edit-invoice.tsx b/src/components/organisms/edit-invoice.tsx index 81b766a..864ff65 100644 --- a/src/components/organisms/edit-invoice.tsx +++ b/src/components/organisms/edit-invoice.tsx @@ -4,27 +4,27 @@ import { RHFSubmitHandler, calculateTotal, client, + constants, hasValues, terms, useGetInvoiceQuery, useGetInvoicesQuery, useUpdateInvoiceMutation, useZodForm, - constants, } from '@src/lib'; import { useQueryClient } from '@tanstack/react-query'; import { produce } from 'immer'; import { useState } from 'react'; import { FormProvider } from 'react-hook-form'; -import { useLocation, useNavigate, useParams } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import { v4 as uuid } from 'uuid'; import { Text } from '../atoms'; import { Calendar, Dropdown, EditItemList, FormField } from '../molecules'; + interface Props {} const EditInvoiceForm = (props: Props) => { const { invoiceId } = useParams(); - const location = useLocation(); const navigate = useNavigate(); const queryClient = useQueryClient(); diff --git a/src/components/organisms/invoice-details.tsx b/src/components/organisms/invoice-details.tsx index af0beb3..c3354b3 100644 --- a/src/components/organisms/invoice-details.tsx +++ b/src/components/organisms/invoice-details.tsx @@ -137,7 +137,7 @@ const InvoiceDetails = ({ invoice, updateStatus, openDeleteModal }: Props) => { {isMobile ? ( -
+
{ toast.success('Login Successful'); navigate('/'); }, - onError(e: IErrorResponse) { + onError(e: Project.IErrorResponse) { e.response.errors.forEach(async (error) => { toast.error(error.message); }); @@ -80,7 +80,7 @@ const LoginForm = (props: Props) => { name='password' label={'Password'} className='col-span-6' - autoComplete='new-password' + autoComplete='current-password' />
-
+