Initial commit

This commit is contained in:
Luka Radenovic 2021-09-27 12:17:33 +02:00
commit fa30c04815
117 changed files with 33513 additions and 0 deletions

19
src/common/const.ts Normal file
View file

@ -0,0 +1,19 @@
export const DEFAULT_API_DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
export const DEFAULT_API_DATE_FORMAT = 'yyyy-MM-dd';
export const DEFAULT_DATE_FORMAT = 'dd/MM/yyyy HH:mm';
export const MAX_TABLE_ROWS = 15;
export const VALIDATION_MESSAGES = {
required: (name: string) => `${name} cannot be blank`,
invalidEmail: () => 'Email address is invalid',
passwordConfirmation: () => 'Passwords do not match',
min: (name: string, min: number) => `${name} must be at least ${min}`,
};
export const BACK_TO_PAGE = 'prevPage';
export const BACK_TO_SEARCH = 'prevSearch';
export const RETURN_TO = 'returnTo';
export const RICH_EDITOR_DEFAULT_TAB = 'richEditorTab';

1
src/common/index.ts Normal file
View file

@ -0,0 +1 @@
export { isTouched, addParamsToLink } from './util';

39
src/common/types.ts Normal file
View file

@ -0,0 +1,39 @@
export interface ImageApiData {
data: {
id: number;
fileId: number;
name: string;
isMain: boolean;
url: string;
smallUrl: string;
mediumUrl: string;
originalFileId: number;
mediumFileId: number;
smallFileId: number;
};
}
export interface ImageMediaData {
id: number;
fileId: number;
name: string;
isMain: boolean;
url: string;
smallUrl: string;
mediumUrl: string;
originalFileId: number;
mediumFileId: number;
smallFileId: number;
smallFile?: {
id: number;
name: string;
url: string;
};
}
export type ImageUploaderValue = File | string | ImageMediaData;
export type Breadcrumb = {
label: string;
href: string;
};

View file

@ -0,0 +1,17 @@
import urlcat from 'urlcat';
import { BACK_TO_PAGE, BACK_TO_SEARCH, RETURN_TO, RICH_EDITOR_DEFAULT_TAB } from '../const';
type UrlParams = {
currentPage?: number;
prevSearch?: string;
returnTo?: [string, number | string];
richEditorTab?: string;
};
export function addParamsToLink(url: string, params: UrlParams) {
return urlcat(url, {
[BACK_TO_PAGE]: params.currentPage,
[BACK_TO_SEARCH]: params.prevSearch,
[RETURN_TO]: params.returnTo?.join(':'),
[RICH_EDITOR_DEFAULT_TAB]: params.richEditorTab,
});
}

2
src/common/util/index.ts Normal file
View file

@ -0,0 +1,2 @@
export { isTouched } from './is-touched';
export { addParamsToLink } from './add-params-to-link';

View file

@ -0,0 +1,6 @@
import { get } from 'lodash';
import { FormState } from 'react-hook-form';
export function isTouched<T>(formState: FormState<T>, fieldName: keyof T | string) {
return formState.isSubmitted || get(formState.touchedFields, fieldName);
}

View file

@ -0,0 +1,92 @@
import React, { Fragment } from 'react';
import { toast } from 'react-hot-toast';
import { Transition } from '@headlessui/react';
import { XCircleIcon, CheckCircleIcon } from '@heroicons/react/outline';
import { XIcon } from '@heroicons/react/solid';
export enum ToastType {
Success = 'success',
Error = 'error',
}
export const showToast = (text: string, type?: ToastType) => {
switch (type) {
case ToastType.Error:
toast.custom(
(t) => (
<Transition
show={t.visible}
as={Fragment}
enter="transform ease-out duration-300 transition"
enterFrom="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
enterTo="translate-y-0 opacity-100 sm:translate-x-0"
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="max-w-sm w-full bg-white shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden">
<div className="p-4">
<div className="flex items-start">
<div className="flex-shrink-0">
<XCircleIcon className="h-6 w-6 text-red-400" aria-hidden="true" />
</div>
<div className="ml-3 w-0 flex-1 pt-0.5">
<p className="text-sm font-medium text-gray-900">{text}</p>
</div>
<div className="ml-4 flex-shrink-0 flex">
<button
className="bg-white rounded-md inline-flex text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
onClick={() => toast.dismiss(t.id)}
>
<span className="sr-only">Close</span>
<XIcon className="h-5 w-5" aria-hidden="true" />
</button>
</div>
</div>
</div>
</div>
</Transition>
),
{ position: 'top-right' },
);
break;
default:
toast.custom(
(t) => (
<Transition
show={t.visible}
as={Fragment}
enter="transform ease-out duration-300 transition"
enterFrom="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
enterTo="translate-y-0 opacity-100 sm:translate-x-0"
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="max-w-sm w-full bg-white shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden">
<div className="p-4">
<div className="flex items-start">
<div className="flex-shrink-0">
<CheckCircleIcon className="h-6 w-6 text-primary" aria-hidden="true" />
</div>
<div className="ml-3 w-0 flex-1 pt-0.5">
<p className="text-sm font-medium text-gray-900">{text}</p>
</div>
<div className="ml-4 flex-shrink-0 flex">
<button
className="bg-white rounded-md inline-flex text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
onClick={() => toast.dismiss(t.id)}
>
<span className="sr-only">Close</span>
<XIcon className="h-5 w-5" aria-hidden="true" />
</button>
</div>
</div>
</div>
</div>
</Transition>
),
{ position: 'top-right' },
);
}
};