first commit
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
// setup methods for external use of Axios
|
||||
import axios from 'axios';
|
||||
|
||||
import { cookieExists } from 'Utils/cookies';
|
||||
// import qs from "qs";
|
||||
|
||||
// axios global
|
||||
export const baseURL = process.env.REACT_APP_API_URL;
|
||||
export const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
};
|
||||
|
||||
// axios api instance
|
||||
const instance = axios.create({
|
||||
baseURL,
|
||||
headers,
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
// making the api request
|
||||
const request = async (url: string, requestData: object = {}, requestType = 'get', options = {}) =>
|
||||
await instance[requestType](url, requestData, options);
|
||||
|
||||
// await axios.get('https://httpbin.org/get', { params: { answer: 42 } });
|
||||
|
||||
// const request = async (url: string, requestData: object = {}, requestType = 'get', callBack = defaultCallBack) =>
|
||||
// await addCatch(instance[requestType](url, requestData), callBack);
|
||||
|
||||
// default error handling
|
||||
// const defaultCallBack = (err: any) => {
|
||||
// console.log(err);
|
||||
// };
|
||||
|
||||
// add an error handling callback to existing promise
|
||||
// const addCatch = (promise: any, callBack: any) => {
|
||||
// return promise.catch((err: any) => {
|
||||
// callBack(err);
|
||||
// });
|
||||
// };
|
||||
|
||||
// Perform a get and return the data
|
||||
const get = async (url: string, params = {}) => await request(url, params);
|
||||
// const get = async (url: string, params: object) =>
|
||||
// await instance.get(url, {
|
||||
// params,
|
||||
// paramsSerializer: (params) => qs.stringify(params, { arrayFormat: "repeat" }),
|
||||
// });
|
||||
const post = async (url: string, data: object, options?: object) => await request(url, data, 'post', options);
|
||||
const put = async (url: string, data: object, options?: object) => await request(url, data, 'put', options);
|
||||
|
||||
// delete is a key word and cannot be used in strict mode. Hence deleteFn
|
||||
const deleteFn = async (url: string, data = {}, options = {}) => await request(url, data, 'delete', options);
|
||||
// Set the Authorization token
|
||||
const setAuthorizationToken = (token: string = '') => {
|
||||
instance.defaults.headers.Authorization = `Bearer ${token}`;
|
||||
};
|
||||
|
||||
// Remove the Authorization token
|
||||
const resetAuthorizationToken = () => {
|
||||
// enable this to have a new token on each login
|
||||
delete instance?.defaults?.headers?.Authorization;
|
||||
};
|
||||
|
||||
// Get the initial headers and cookies setup for csrf
|
||||
const csrfHeader = async () => {
|
||||
await request(process.env.REACT_APP_SANCTUM_URL);
|
||||
// Check for the cookie XSRF-TOKEN
|
||||
return cookieExists('XSRF-TOKEN');
|
||||
};
|
||||
|
||||
export const Api = {
|
||||
get,
|
||||
post,
|
||||
put,
|
||||
delete: deleteFn,
|
||||
setAuthorizationToken,
|
||||
resetAuthorizationToken,
|
||||
csrfHeader,
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
// setup methods for external use of Axios
|
||||
import axios from 'axios';
|
||||
import { Api, baseURL, headers } from 'Utils/api';
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
// Create a custom instance, without the withCredentials attribute. AWS doesn't like it
|
||||
const instance = axios.create({
|
||||
baseURL,
|
||||
headers,
|
||||
withCredentials: false,
|
||||
});
|
||||
|
||||
interface Options {
|
||||
bucket?: string;
|
||||
contentType?: string;
|
||||
expires?: string;
|
||||
visibility?: string;
|
||||
headers?: any;
|
||||
options?: any;
|
||||
cancelToken?: any;
|
||||
progress?: (progress: number) => void;
|
||||
}
|
||||
export const awsStore = async (file: any, binaryFileString: any, options: Options = {}) => {
|
||||
const buf = Buffer.from(binaryFileString.replace(/^data:image\/\w+;base64,/, ''), 'base64');
|
||||
// Use our api to get a response from our backend.
|
||||
// const optionStore = { progress: () => {}, ...options };
|
||||
const response = await Api.post(
|
||||
process.env.REACT_VAPOR_URL,
|
||||
{
|
||||
bucket: options?.bucket || '',
|
||||
content_type: options?.contentType || file.type,
|
||||
expires: options?.expires || '',
|
||||
visibility: options?.visibility || '',
|
||||
},
|
||||
{
|
||||
baseURL: null,
|
||||
headers: options?.headers || {},
|
||||
...options?.options,
|
||||
}
|
||||
);
|
||||
|
||||
const headers = response.data.headers;
|
||||
|
||||
if ('Host' in headers) {
|
||||
delete headers.Host;
|
||||
}
|
||||
|
||||
if (typeof options.progress === 'undefined') {
|
||||
options.progress = () => {};
|
||||
}
|
||||
|
||||
const cancelToken = options.cancelToken || '';
|
||||
const fileName = file.name.trim();
|
||||
|
||||
// Find the last period, so we can get the extension of the file
|
||||
const lastPeriod = fileName.lastIndexOf('.');
|
||||
|
||||
// Get the file extension. Slice will return everything after the period.
|
||||
const fileExtension = fileName.slice(lastPeriod);
|
||||
|
||||
// Get the file name, without the extension
|
||||
const name = fileName.slice(0, lastPeriod);
|
||||
|
||||
const data = {
|
||||
cancelToken,
|
||||
headers,
|
||||
// @ts-ignore
|
||||
uuid: response.uuid,
|
||||
key: response.key,
|
||||
bucket: response.bucket,
|
||||
name: fileName,
|
||||
extension: fileExtension,
|
||||
content_type: file.type,
|
||||
};
|
||||
|
||||
// Only add the upload progess if the method is part of options
|
||||
if (options.progress) {
|
||||
// eslint-disable-next-line
|
||||
data['onUploadProgress'] = (progressEvent: any) => {
|
||||
options.progress(progressEvent.loaded / progressEvent.total);
|
||||
};
|
||||
}
|
||||
// Send the file and update it's data on AWS
|
||||
await instance.put(response.data.url, buf, {
|
||||
cancelToken,
|
||||
headers,
|
||||
// @ts-ignore
|
||||
uuid: response.uuid,
|
||||
key: response.key,
|
||||
bucket: response.bucket,
|
||||
name: fileName,
|
||||
extension: fileExtension,
|
||||
content_type: file.type,
|
||||
});
|
||||
response.data.name = name;
|
||||
response.data.extension = fileExtension;
|
||||
response.data.file = fileName;
|
||||
|
||||
return response.data;
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
export const PROJECT_DASHBOARD = "/projectDashboard";
|
||||
export const PHOTO_MANAGEMENT = "/photoManagement";
|
||||
export const ALL_LOCATIONS = "/allLocations";
|
||||
export const ADD_LOCATIONS = "/addLocations";
|
||||
export const SINGLE = "/single";
|
||||
export const MULTI_UNIT = "/multiUnit";
|
||||
@@ -0,0 +1,3 @@
|
||||
export const cookieExists = (name: string) =>
|
||||
// Use find to get the first instance of the named cookie
|
||||
document.cookie.split(';').find((item) => item.trim().startsWith(`${name}=`)) !== undefined;
|
||||
@@ -0,0 +1,88 @@
|
||||
const addresses = [
|
||||
{
|
||||
id: 1,
|
||||
name: '857 Beatty Street',
|
||||
path: '/projects/857 Beatty Street',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Eatons Centre',
|
||||
path: '/projects/Eatons Centre',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Harbour Centre',
|
||||
path: '/projects/Harbour Centre',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '364 King Street W',
|
||||
path: '/projects/364 King Street W',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: '205 Adelaide Ave',
|
||||
path: '/projects/205 Adelaide Ave',
|
||||
},
|
||||
];
|
||||
|
||||
const countries = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'United States',
|
||||
code: 1,
|
||||
flag: 'usa',
|
||||
alpha_2: 'US',
|
||||
country_code: '+1',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Canada',
|
||||
code: 1,
|
||||
flag: 'canada',
|
||||
alpha_2: 'CA',
|
||||
country_code: '+1',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'United Kingdom',
|
||||
code: 44,
|
||||
flag: 'uk',
|
||||
alpha_2: 'GB',
|
||||
country_code: '+44',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Australia',
|
||||
code: 61,
|
||||
flag: 'aus',
|
||||
alpha_2: 'AAU',
|
||||
country_code: '+61',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'New Zealand',
|
||||
code: 64,
|
||||
flag: 'nz',
|
||||
alpha_2: 'NZ',
|
||||
country_code: '+64',
|
||||
},
|
||||
];
|
||||
|
||||
// Property Data
|
||||
const projectClassifications = [
|
||||
{
|
||||
id: 1,
|
||||
value: 'Residential',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
value: 'Commercial',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
value: 'Both',
|
||||
},
|
||||
];
|
||||
|
||||
export { addresses, countries, projectClassifications };
|
||||
@@ -0,0 +1,11 @@
|
||||
// Using Intl.DateTimeFormat()
|
||||
const getDateFromMilliseconds = (milliseconds: number) =>
|
||||
new Date(milliseconds).toLocaleString('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
hour12: true,
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
});
|
||||
export default getDateFromMilliseconds;
|
||||
@@ -0,0 +1,3 @@
|
||||
import debouncer from 'lodash/debounce';
|
||||
|
||||
export const debounce = (fn: any, wait = 300) => debouncer(fn, wait);
|
||||
@@ -0,0 +1,10 @@
|
||||
// Other Node Modules
|
||||
import deepEqual from 'deep-equal';
|
||||
import isMatch from 'lodash/isMatch';
|
||||
|
||||
export const areEqual = (objectOne: any, objectTwo: any) => deepEqual(objectOne, objectTwo, { strict: true });
|
||||
|
||||
export const areEqualNotStrict = (objectOne: any, objectTwo: any) => deepEqual(objectOne, objectTwo);
|
||||
|
||||
// This will do a deep check of the two objects and determine if they are equal
|
||||
export const areEqualShallow = (obj1: any, obj2: any) => isMatch(obj1, obj2);
|
||||
@@ -0,0 +1,26 @@
|
||||
import Geocode from 'react-geocode';
|
||||
import { AddressModal } from 'Containers/Projects/Modals';
|
||||
import { parseNumber } from 'Utils/numbers';
|
||||
|
||||
Geocode.setApiKey(process.env.REACT_GOOGLE_API_KEY);
|
||||
|
||||
// Google autocomplete restrictions
|
||||
export const componentRestrictions: any = { country: ['ca', 'us', 'aus', 'uk', 'nz'] };
|
||||
|
||||
// Get latitude or longitude from address.
|
||||
export const getCoordinatesFromAddress = (address: AddressModal, setLat, setLng) => {
|
||||
const { address: addressOne, city, state, zip, country } = address;
|
||||
|
||||
return Geocode.fromAddress(`${addressOne}, ${city}, ${state}, ${zip}, ${country}`)
|
||||
.then(({ results }: any) => {
|
||||
const [result] = results;
|
||||
const {
|
||||
geometry: {
|
||||
location: { lat, lng },
|
||||
},
|
||||
} = result;
|
||||
setLat(parseNumber(lat));
|
||||
setLng(parseNumber(lng));
|
||||
})
|
||||
.catch(() => null);
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
import { setFetching, setToaster, setFormErrors as setErrors } from 'Containers/Core/actions';
|
||||
import { logout } from 'Containers/Auth';
|
||||
import { createBrowserHistory } from 'history';
|
||||
import { Api } from 'Utils/api';
|
||||
|
||||
const history = createBrowserHistory();
|
||||
|
||||
const {
|
||||
location: { pathname },
|
||||
} = window;
|
||||
|
||||
const HTTP_ERROR_CODES = [401, 403, 404, 429, 500];
|
||||
|
||||
// to handle the async API calls,
|
||||
// dispatch is mandatory
|
||||
// apiRequest is mandatory
|
||||
// types are optional and FORM_ERRORS are default for all the forms.
|
||||
// you can send additional types for specific container to handle multiple API errors in a single form or multiple forms
|
||||
export const handleApiRequest = async (
|
||||
dispatch,
|
||||
apiRequest,
|
||||
errorType = 'FORM_ERRORS',
|
||||
fetchingType = 'SET_FETCHING',
|
||||
setErrorsCallback = null
|
||||
) => {
|
||||
// before make the api call set the loader, progress bars etc...
|
||||
dispatch(setFetching(true, fetchingType || 'SET_FETCHING'));
|
||||
|
||||
// clear form errors for the type
|
||||
dispatch(setErrors(undefined, errorType || 'FORM_ERRORS'));
|
||||
if (setErrorsCallback) setErrorsCallback({});
|
||||
|
||||
try {
|
||||
const { data } = await apiRequest;
|
||||
// turn of the loader, progress bars etc...
|
||||
dispatch(setFetching(false, fetchingType || 'SET_FETCHING'));
|
||||
|
||||
// Destructure any object inside thunks
|
||||
return data;
|
||||
} catch (error: any) {
|
||||
// turn of the loader, progress bars etc...
|
||||
dispatch(setFetching(false, fetchingType || 'SET_FETCHING'));
|
||||
|
||||
if (error?.response) {
|
||||
// Request made and server responded
|
||||
const {
|
||||
status,
|
||||
data: { message, errors },
|
||||
} = error.response;
|
||||
|
||||
// set thunk based error objects
|
||||
// based on this conditionally show error messages
|
||||
if (HTTP_ERROR_CODES.includes(status) && message) {
|
||||
dispatch(setErrors(true, errorType || 'FORM_ERRORS'));
|
||||
}
|
||||
|
||||
// show toast with the error message, need to add more use cases
|
||||
if (status === 403 && message) {
|
||||
dispatch(setToaster(message, false));
|
||||
}
|
||||
if (status === 500 && message) {
|
||||
dispatch(setToaster(message, false));
|
||||
}
|
||||
|
||||
// set form errors for a given type from the thunk if the status code is 422
|
||||
if (status === 422 && (errors || message)) {
|
||||
dispatch(setErrors(errors || message, errorType || 'FORM_ERRORS'));
|
||||
if (setErrorsCallback) setErrorsCallback(errors);
|
||||
}
|
||||
|
||||
// logout user if api returns 401 status
|
||||
if (status === 401 && message) {
|
||||
if (pathname.length > 1 && !pathname.includes('invite')) {
|
||||
dispatch(setToaster(message, false));
|
||||
}
|
||||
if (!pathname.includes('invite')) {
|
||||
dispatch(logout());
|
||||
await Api.csrfHeader();
|
||||
history.push('/');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dispatch(setToaster('Something went wrong, please try again or check back later!', false));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,276 @@
|
||||
import { format, getUnixTime, parseISO } from 'date-fns';
|
||||
import * as uuid from 'uuid';
|
||||
import { parsePhoneNumber, formatPhoneNumberIntl } from 'react-phone-number-input';
|
||||
import { PhotoModal } from 'Containers/PhotoViewCarousel/Models';
|
||||
|
||||
export const getFirstLetterUppercase = (value: string) => (value ? value.charAt(0).toLocaleUpperCase() : '');
|
||||
export const convertFirstLetterUppercase = (value: string) =>
|
||||
value ? getFirstLetterUppercase(value) + value.slice(1) : '';
|
||||
export const convertWordsFirstLetterUppercase = (value: string) => {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const words = value.split(' ');
|
||||
|
||||
if (words.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return words.map((word: any) => convertFirstLetterUppercase(word)).join(' ');
|
||||
};
|
||||
|
||||
export const formatDate = (dateString: string, dateFormat: string) => format(new Date(dateString), dateFormat);
|
||||
|
||||
// convert a date string in ISO 8601 format to a Date object
|
||||
export const parseISODate = (dateString: string) => parseISO(dateString);
|
||||
|
||||
export const getPhotosCount = (albumPhotosCount: number, internalPhotosCount: number) => {
|
||||
const result = albumPhotosCount - internalPhotosCount;
|
||||
|
||||
return result !== -1 ? result : 0;
|
||||
};
|
||||
|
||||
export const getUnixTimeFromString = (date: string) => getUnixTime(new Date(date));
|
||||
|
||||
export const compareTwoDates = (dateOne: string, dateTwo: string) =>
|
||||
getUnixTime(new Date(dateOne)) === getUnixTime(new Date(dateTwo));
|
||||
|
||||
export const convertPhoneNumber = (countryCode: string, phone: string) => {
|
||||
if (phone && countryCode) {
|
||||
let phoneAlt = phone.replace(countryCode, '');
|
||||
phoneAlt = phoneAlt.replace(/-/g, '');
|
||||
phoneAlt = phoneAlt.replace(/ /g, '');
|
||||
phoneAlt = phoneAlt.replace('+', '');
|
||||
|
||||
if (phoneAlt.length < 10) return '';
|
||||
|
||||
if (countryCode && phoneAlt) {
|
||||
const phoneNumber = parsePhoneNumber(countryCode + phoneAlt)?.number;
|
||||
return phoneNumber;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
export const formatPhoneInternationalWithCountryCode = (countryCode: string, phone: string) => {
|
||||
if (phone) {
|
||||
return formatPhoneNumberIntl(phone).replace(`${countryCode} `, '');
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
export const formatPhoneNumberInternational = (phone: string) => {
|
||||
if (phone) {
|
||||
return formatPhoneNumberIntl(phone);
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
export const formatPhone = (phone) => {
|
||||
if (phone) {
|
||||
const phoneAlt = phone.replace('+1', '');
|
||||
// const phoneAlt = parsePhoneNumber(phone).nationalNumber;
|
||||
const part1 = phoneAlt.length > 2 ? phoneAlt.substring(0, 3) : phoneAlt;
|
||||
const part2 = phoneAlt.length > 3 ? `-${phoneAlt.substring(3, 6)}` : '';
|
||||
const part3 = phoneAlt.length > 6 ? `-${phoneAlt.substring(6, 10)}` : '';
|
||||
|
||||
return `${part1}${part2}${part3}`;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
export const getPhotoShareBreadCrumb = ({ photoable: room, albums }: PhotoModal, showAlbum = true) => {
|
||||
const {
|
||||
room_type: { name: roomName },
|
||||
morphable: unit,
|
||||
} = room;
|
||||
const { name: unitName } = unit;
|
||||
|
||||
// getting the first album
|
||||
const [album] = albums;
|
||||
|
||||
let levelName = '';
|
||||
|
||||
if (room.level && room.level?.name) {
|
||||
const { level } = room;
|
||||
levelName = level.name;
|
||||
}
|
||||
|
||||
return `${unitName} / ${levelName ? `${levelName} / ` : ''} ${roomName} Photos ${
|
||||
showAlbum ? `${album?.name} / ` : ''
|
||||
}`;
|
||||
};
|
||||
|
||||
export const getPhotoShareAlbum = ({ albums }: PhotoModal) => {
|
||||
if (albums.length === 0) {
|
||||
return '';
|
||||
}
|
||||
// getting the first album
|
||||
const [album] = albums;
|
||||
return `${album.name}`;
|
||||
};
|
||||
|
||||
export const trimAndToLowerCase = (value: string) => value.replace(/\s/g, '').toLocaleLowerCase();
|
||||
|
||||
export const floorNumbers = (min = -25, max = 150, step = 1) =>
|
||||
Array.from({ length: (max - min) / step + 1 }, (_, i) => min + i).map((item) => ({
|
||||
id: item,
|
||||
name: `${item}`,
|
||||
}));
|
||||
|
||||
export const initials = (firstName, lastName) => getFirstLetterUppercase(firstName) + getFirstLetterUppercase(lastName);
|
||||
|
||||
export const validateImageFileType = (file) => !!(file.type === 'image/png' || file.type === 'image/jpeg');
|
||||
|
||||
export const validateImageResolution = (width, height) => !(width > 4000 || height > 4000);
|
||||
|
||||
// text limit for notes dropdown placeholder
|
||||
export const limitText = (text: string, limit: number) => {
|
||||
if (text.length > limit) {
|
||||
return `${text.substring(0, limit)}...`;
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
// will get auth user details only if the routes are not match to below
|
||||
export const shouldGetAuth = (pathname) =>
|
||||
pathname.includes('/photo-share') ||
|
||||
pathname.includes('/reset-password') ||
|
||||
pathname.includes('/invite') ||
|
||||
pathname.includes('/signinemail') ||
|
||||
pathname.includes('/phoneverification') ||
|
||||
pathname.includes('/phoneverificationcode');
|
||||
|
||||
// add or remove an item from an array
|
||||
export const addOrRemoveFromArray = (previousItems: any[], newItem: any) => {
|
||||
if (typeof newItem === 'object') {
|
||||
return previousItems.some((prevItem: any) => prevItem.id.toString() === newItem.id.toString())
|
||||
? previousItems.filter((prevItem: any) => prevItem.id.toString() !== newItem.id.toString())
|
||||
: [...previousItems, newItem];
|
||||
}
|
||||
|
||||
return previousItems.includes(newItem)
|
||||
? previousItems.filter((prevId: any) => prevId.toString() !== newItem.toString())
|
||||
: [...previousItems, newItem];
|
||||
};
|
||||
|
||||
export const getDisplayedRolesArray = (
|
||||
allRoles: any,
|
||||
excludedRoles = ['super-admin', 'employee'],
|
||||
roleRenameMapping = { 'company-admin': 'Admin' }
|
||||
) => {
|
||||
const filteredRoles = allRoles.filter((role) => !excludedRoles.includes(role.name));
|
||||
|
||||
if (filteredRoles.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return filteredRoles.map((role) => ({
|
||||
id: role.id,
|
||||
name: roleRenameMapping[role.name] ?? role.display_name,
|
||||
}));
|
||||
};
|
||||
|
||||
export const checkGalleryEnabled = (albumId: number, photoAlbums: any[]) =>
|
||||
photoAlbums?.some((album: any) => album.id === albumId && album.enabled === 1);
|
||||
|
||||
export const galleryShouldShow = (
|
||||
galleryId: number,
|
||||
selectedFilterIds: any[],
|
||||
galleryEnabled: boolean,
|
||||
isInEditMode: boolean
|
||||
) => {
|
||||
if (selectedFilterIds.length === 0) {
|
||||
// only show all galleries in edit mode if the "all photos" filter is selected
|
||||
return galleryEnabled || isInEditMode;
|
||||
}
|
||||
|
||||
return selectedFilterIds.includes(galleryId) && galleryEnabled;
|
||||
};
|
||||
|
||||
export const chunkArray = (array, size) =>
|
||||
array.length <= size ? [array] : [array.slice(0, size), ...chunkArray(array.slice(size), size)];
|
||||
|
||||
export const getPhotosChunkSize = (array) => {
|
||||
if (array.length <= 20) {
|
||||
return 10;
|
||||
}
|
||||
return Math.round(array.length / 2);
|
||||
};
|
||||
|
||||
export const checkIfPhotoSelected = (
|
||||
photo: any,
|
||||
selectedPhotos: any[],
|
||||
unSelectedPhotos: any[],
|
||||
selectPhotosMode: boolean,
|
||||
selectAllMode: boolean
|
||||
) => {
|
||||
if (!selectPhotosMode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (selectAllMode) {
|
||||
return !unSelectedPhotos.some((item: any) => item.id === photo.id);
|
||||
}
|
||||
|
||||
return selectPhotosMode && selectedPhotos.some((item: any) => item.id === photo.id);
|
||||
};
|
||||
|
||||
export const generateUUID = () => uuid.v4();
|
||||
|
||||
export const compareArrays = (array1: any[], array2: any[]) => {
|
||||
if (array1.length !== array2.length) return false;
|
||||
for (let i = 0; i < array1.length; i += 1) {
|
||||
if (array1[i] !== array2[i]) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
export const getTimeout = (length: number) => {
|
||||
if (length < 3) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
if (length < 5) {
|
||||
return 300;
|
||||
}
|
||||
|
||||
if (length < 10) {
|
||||
return 1500;
|
||||
}
|
||||
|
||||
return 3000;
|
||||
};
|
||||
|
||||
// download a pdf, or doc etc...
|
||||
export const download = async (url: string, fileName: string, setLoading: any = undefined) => {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
|
||||
const blob = await response.blob();
|
||||
|
||||
const blobUrl = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
|
||||
link.href = blobUrl;
|
||||
link.setAttribute('download', fileName);
|
||||
|
||||
// Append to html link element page
|
||||
document.body.appendChild(link);
|
||||
|
||||
// Start download
|
||||
link.click();
|
||||
|
||||
// Clean up and remove the link
|
||||
link.parentNode.removeChild(link);
|
||||
|
||||
// clear loading if given
|
||||
if (setLoading) setLoading(false);
|
||||
} catch (errors: any) {
|
||||
setLoading(false);
|
||||
alert('Something went wrong. Unable download the file. Please try again later.');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
// const axios = require('axios');
|
||||
|
||||
/*
|
||||
class Vapor {
|
||||
//Store a file in S3 and return its UUID, key, and other information.
|
||||
async store(file, options = {}) {
|
||||
const response = await axios.post(
|
||||
'/vapor/signed-storage-url',
|
||||
{
|
||||
bucket: options.bucket || '',
|
||||
content_type: options.contentType || file.type,
|
||||
expires: options.expires || '',
|
||||
visibility: options.visibility || '',
|
||||
},
|
||||
{
|
||||
baseURL: options.baseURL || null,
|
||||
headers: options.headers || {},
|
||||
...options.options,
|
||||
}
|
||||
);
|
||||
|
||||
let headers = response.data.headers;
|
||||
|
||||
if ('Host' in headers) {
|
||||
delete headers.Host;
|
||||
}
|
||||
|
||||
if (typeof options.progress === 'undefined') {
|
||||
options.progress = () => {};
|
||||
}
|
||||
|
||||
const cancelToken = options.cancelToken || '';
|
||||
|
||||
await axios.put(response.data.url, file, {
|
||||
cancelToken: cancelToken,
|
||||
headers: headers,
|
||||
onUploadProgress: (progressEvent) => {
|
||||
options.progress(progressEvent.loaded / progressEvent.total);
|
||||
},
|
||||
});
|
||||
|
||||
response.data.extension = file.name.split('.').pop();
|
||||
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new Vapor();
|
||||
*/
|
||||
|
||||
// Temp for now to allow the project to compile
|
||||
// module.exports = {};
|
||||
|
||||
export {};
|
||||
@@ -0,0 +1,14 @@
|
||||
export const navItems = [
|
||||
{
|
||||
id: 2,
|
||||
title: 'Projects',
|
||||
path: '/projects',
|
||||
icon: 'projects',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'People',
|
||||
path: '/people',
|
||||
icon: 'people',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1 @@
|
||||
export const parseNumber = (value) => Number(value) || 0;
|
||||
@@ -0,0 +1,132 @@
|
||||
/* eslint-disable */
|
||||
|
||||
import { SET_AUTHENTICATED, SET_AUTHENTICATION_TYPE, SOCIAL_LOGIN_ERRORS } from 'Containers/Auth/actions';
|
||||
import { SET_FETCHING } from 'Containers/Core/actions';
|
||||
import { height, width } from 'Utils/screen';
|
||||
import { userDetails } from 'Containers/User';
|
||||
|
||||
// popup window global settings
|
||||
// feel free to modify this
|
||||
const windowFeatures = `toolbar=no, menubar=no, width=${width}, height=${height}`;
|
||||
|
||||
// Oauth2 will handle the social media login for all the available providers. Currently working providers are 'google', 'facebook', 'apple'
|
||||
export const Oauth2 =
|
||||
(provider: string) =>
|
||||
(dispatch: any, _getState = null, _utils: any) => {
|
||||
provider = provider.toLocaleLowerCase();
|
||||
|
||||
// popup window parameters
|
||||
const URL = `${process.env.REACT_APP_BASE_URL}oauth2/redirect/${provider}`;
|
||||
const windowName = `${provider}Window`;
|
||||
|
||||
// popup window variables
|
||||
let windowObjectReference = null;
|
||||
let previousUrl = null;
|
||||
|
||||
// handle window event listener
|
||||
const receiveMessage = (event: any) => {
|
||||
const { status } = event.data;
|
||||
|
||||
// update redux on successful login
|
||||
if (status === 200) {
|
||||
// important to call this function whenever user authenticates successfully
|
||||
dispatch(userDetails());
|
||||
|
||||
// set progress bars, loaders
|
||||
dispatch({
|
||||
type: SET_FETCHING,
|
||||
payload: true,
|
||||
});
|
||||
|
||||
// reset error messages
|
||||
dispatch({
|
||||
type: SOCIAL_LOGIN_ERRORS,
|
||||
payload: {
|
||||
error: false,
|
||||
message: '',
|
||||
},
|
||||
});
|
||||
|
||||
// we'll close the window
|
||||
windowObjectReference.close();
|
||||
|
||||
// timeout is optional --this will take it time to show the progress bars, loaders etc...
|
||||
setTimeout(() => {
|
||||
// set app authentication state
|
||||
dispatch({
|
||||
type: SET_AUTHENTICATED,
|
||||
payload: true,
|
||||
});
|
||||
|
||||
// set app authentication type
|
||||
dispatch({
|
||||
type: SET_AUTHENTICATION_TYPE,
|
||||
payload: 'social',
|
||||
});
|
||||
|
||||
// set progress bars, loaders
|
||||
dispatch({
|
||||
type: SET_FETCHING,
|
||||
payload: false,
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// handle any errors on unsuccessful login
|
||||
if (status === 500) {
|
||||
const { message } = event.data?.body;
|
||||
|
||||
// we'll close the window
|
||||
windowObjectReference.close();
|
||||
|
||||
// set errors
|
||||
dispatch({
|
||||
type: SOCIAL_LOGIN_ERRORS,
|
||||
payload: {
|
||||
errors: true,
|
||||
message: message,
|
||||
},
|
||||
});
|
||||
|
||||
// disable progress bars, loaders
|
||||
dispatch({
|
||||
type: SET_FETCHING,
|
||||
payload: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// window functions
|
||||
const openSignInWindow = (url, name) => {
|
||||
// remove any existing event listeners
|
||||
window.removeEventListener('message', receiveMessage);
|
||||
|
||||
if (windowObjectReference === null || windowObjectReference.closed) {
|
||||
/* if the pointer to the window object in memory does not exist
|
||||
or if such pointer exists but the window was closed */
|
||||
|
||||
windowObjectReference = window.open(url, name, windowFeatures);
|
||||
} else if (previousUrl !== URL) {
|
||||
/* if the resource to load is different,
|
||||
then we load it in the already opened secondary window and then
|
||||
we bring such window back on top/in front of its parent window. */
|
||||
windowObjectReference = window.open(url, name, windowFeatures);
|
||||
windowObjectReference.focus();
|
||||
} else {
|
||||
/* else the window reference must exist and the window
|
||||
is not closed; therefore, we can bring it back on top of any other
|
||||
window with the focus() method. There would be no need to re-create
|
||||
the window or to reload the referenced resource. */
|
||||
windowObjectReference.focus();
|
||||
}
|
||||
|
||||
// add the listener for receiving a message from the popup
|
||||
window.addEventListener('message', (event) => receiveMessage(event), false);
|
||||
|
||||
// assign the previous URL
|
||||
previousUrl = url;
|
||||
};
|
||||
|
||||
// call the window function
|
||||
openSignInWindow(URL, windowName);
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
/* eslint-disable */
|
||||
//The linter has an issue with the email-validator.
|
||||
import { validate } from "email-validator";
|
||||
import passwordValidator from "password-validator";
|
||||
|
||||
//Validate emails
|
||||
export { validate as emailValidator };
|
||||
|
||||
//Password validation
|
||||
const passwordValidation = new passwordValidator();
|
||||
//Current business rules says each password must be at least 8 chars
|
||||
//If there are any new business rules, add them here.
|
||||
//See this for examples https://www.npmjs.com/package/password-validator
|
||||
passwordValidation.is().min(8); //Must have a password that is a min 8 chars long
|
||||
export { passwordValidation as passwordEightCharactersValidator };
|
||||
@@ -0,0 +1,104 @@
|
||||
const legacyFlooringWhitelist = ['Carpet', 'Laminate', 'Engineered', 'Hardwood', 'Concrete'];
|
||||
|
||||
const legacyWallsWhitelist = ['Interior Wall', 'Fire Wall', 'Exterior Wall'];
|
||||
|
||||
const legacyCeilingWhitelist = [
|
||||
'Flat Drywall Ceiling',
|
||||
'Textured Drywall',
|
||||
'Ceiling Tile',
|
||||
'Concrete',
|
||||
'Concrete Textured',
|
||||
];
|
||||
|
||||
const legacyPlumbingWhitelist = ['Countertop', 'Vanity Unit'];
|
||||
|
||||
const legacyStructuralWhitelist = [
|
||||
'Baseboard',
|
||||
'Countertop',
|
||||
'Door Trim',
|
||||
'Drywall',
|
||||
'Full Height Cabinetry',
|
||||
'Insulation',
|
||||
'Lower Cabinetry',
|
||||
'Shelving',
|
||||
'Upper Cabinetry',
|
||||
'Vanity Unit',
|
||||
'Window Board',
|
||||
];
|
||||
|
||||
export const legacyWhitelists = {
|
||||
flooring: legacyFlooringWhitelist,
|
||||
walls: legacyWallsWhitelist,
|
||||
ceiling: legacyCeilingWhitelist,
|
||||
plumbing: legacyPlumbingWhitelist,
|
||||
structural: legacyStructuralWhitelist,
|
||||
};
|
||||
|
||||
const carpentryWhitelist = [
|
||||
'Door',
|
||||
'Door - Double',
|
||||
'Crown Molding',
|
||||
'Window board',
|
||||
'Door trim',
|
||||
'Baseboard',
|
||||
'Cabinetry - lower',
|
||||
'Cabinetry - upper',
|
||||
'Cabinetry - full height',
|
||||
'Toe kick',
|
||||
'Door - Bifold',
|
||||
'Countertop - Laminate',
|
||||
'Vanity unit',
|
||||
'Furring strip',
|
||||
];
|
||||
|
||||
const ceilingWhitelist = [
|
||||
'Ceiling tile',
|
||||
'Concrete',
|
||||
'Concrete Textured',
|
||||
'Flat Drywall',
|
||||
'Popcorn ceiling',
|
||||
'Textured ceiling',
|
||||
'Textured drywall',
|
||||
];
|
||||
|
||||
const flooringWhitelist = [
|
||||
'Carpet',
|
||||
'Carpet pad',
|
||||
'Lift carpet for drying',
|
||||
'Underlay',
|
||||
'Hardwood',
|
||||
'Tackless strip',
|
||||
'Engineered wood flooring',
|
||||
'Floating floor',
|
||||
'Concrete',
|
||||
];
|
||||
|
||||
const plumbingWhitelist = ['Countertop', 'Vanity Unit'];
|
||||
|
||||
const wallsWhitelist = [
|
||||
'5/8" drywall',
|
||||
'5/8" drywall - 4"',
|
||||
'5/8" drywall - 2\'',
|
||||
'1/2" - drywall - 4"',
|
||||
'Insulation - Batt',
|
||||
'1/2" drywall',
|
||||
'1/2" - drywall - 2\'',
|
||||
'Tape joint for new to existing drywall',
|
||||
'Texture drywall - smooth / skim coat',
|
||||
'Drywall Installer / Finisher - per hour',
|
||||
'Tile',
|
||||
'Wainscoting',
|
||||
'Dado rail',
|
||||
'Insulation - Loose-fill',
|
||||
'Insulation - Rigid foams',
|
||||
'Insulation - Spray foam',
|
||||
];
|
||||
|
||||
export const dryingWhitelists = {
|
||||
carpentry: carpentryWhitelist,
|
||||
ceiling: ceilingWhitelist,
|
||||
flooring: flooringWhitelist,
|
||||
plumbing: plumbingWhitelist,
|
||||
walls: wallsWhitelist,
|
||||
};
|
||||
// Appliances, Cleaning, Electrical, Misc, Mitigation, Protection don't have dryable materials
|
||||
@@ -0,0 +1,4 @@
|
||||
import { UserRoleModal } from 'Containers/User/Models/UserModel/UserModel';
|
||||
|
||||
export const isCompanyAdmin = (roles: Array<UserRoleModal>) =>
|
||||
roles?.length > 0 ? roles.some(({ name }: UserRoleModal) => name.toLocaleLowerCase() === 'company-admin') : false;
|
||||
@@ -0,0 +1,4 @@
|
||||
import ScreenSizeDetector from 'screen-size-detector';
|
||||
|
||||
const { height, width } = new ScreenSizeDetector();
|
||||
export { height, width };
|
||||
@@ -0,0 +1,179 @@
|
||||
export const reportTableHeaders = [
|
||||
{
|
||||
id: 1,
|
||||
displayName: 'Name',
|
||||
column: 'name',
|
||||
canSort: false,
|
||||
action: false,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
displayName: 'Author',
|
||||
column: 'creator',
|
||||
canSort: false,
|
||||
action: false,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
displayName: 'Date Created',
|
||||
column: 'created',
|
||||
canSort: true,
|
||||
action: false,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
displayName: 'Style',
|
||||
column: 'style',
|
||||
canSort: false,
|
||||
action: false,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
displayName: 'Status',
|
||||
column: 'status',
|
||||
canSort: false,
|
||||
action: false,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
displayName: 'Download',
|
||||
column: 'download',
|
||||
canSort: false,
|
||||
action: true,
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
displayName: 'Share',
|
||||
column: 'share',
|
||||
canSort: false,
|
||||
action: true,
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
displayName: 'Delete',
|
||||
column: 'delete',
|
||||
canSort: false,
|
||||
action: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const externalAtmosphericTableHeaders = [
|
||||
{
|
||||
id: 1,
|
||||
displayName: 'Date',
|
||||
column: 'date',
|
||||
canSort: false,
|
||||
action: false,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
displayName: 'Relative Humidity',
|
||||
column: 'rhumidity',
|
||||
canSort: false,
|
||||
action: false,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
displayName: 'Temp',
|
||||
column: 'temp',
|
||||
canSort: false,
|
||||
action: false,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
displayName: 'Pressure',
|
||||
column: 'pressure',
|
||||
canSort: false,
|
||||
action: false,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
displayName: 'Wind Speed',
|
||||
column: 'windspeed',
|
||||
canSort: false,
|
||||
action: false,
|
||||
},
|
||||
];
|
||||
|
||||
export const internalAtmosphericTableHeaders = [
|
||||
{
|
||||
id: 1,
|
||||
displayName: 'Date',
|
||||
column: 'date',
|
||||
canSort: false,
|
||||
action: false,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
displayName: 'Relative Humidity',
|
||||
column: 'rhumidity',
|
||||
canSort: false,
|
||||
action: false,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
displayName: 'Temp',
|
||||
column: 'temp',
|
||||
canSort: false,
|
||||
action: false,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
displayName: 'GPP',
|
||||
column: 'gpp',
|
||||
canSort: false,
|
||||
action: false,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
displayName: 'Dew Point',
|
||||
column: 'dewpoint',
|
||||
canSort: false,
|
||||
action: false,
|
||||
},
|
||||
// {
|
||||
// id: 6,
|
||||
// displayName: 'View Photo',
|
||||
// column: 'viewphoto',
|
||||
// canSort: false,
|
||||
// action: false,
|
||||
// },
|
||||
];
|
||||
|
||||
export const moistureLogTableHeaders = [
|
||||
{
|
||||
id: 1,
|
||||
displayName: 'Material',
|
||||
column: 'material',
|
||||
canSort: false,
|
||||
action: false,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
displayName: 'Date',
|
||||
column: 'date',
|
||||
canSort: false,
|
||||
action: false,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
displayName: 'Goal Average',
|
||||
column: 'goalaverage',
|
||||
canSort: false,
|
||||
action: false,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
displayName: 'Average',
|
||||
column: 'average',
|
||||
canSort: false,
|
||||
action: false,
|
||||
},
|
||||
// {
|
||||
// id: 5,
|
||||
// displayName: 'View Photo',
|
||||
// column: 'viewphoto',
|
||||
// canSort: false,
|
||||
// action: false,
|
||||
// },
|
||||
];
|
||||
@@ -0,0 +1,29 @@
|
||||
export const projectTabs = [
|
||||
// {
|
||||
// title: 'Project Dashboard',
|
||||
// tab: 'projects-dashboard',
|
||||
// },
|
||||
{
|
||||
title: 'Notes',
|
||||
tab: 'notes',
|
||||
},
|
||||
{
|
||||
title: 'Crew',
|
||||
tab: 'crew',
|
||||
},
|
||||
{
|
||||
title: 'Project/Loss Info',
|
||||
tab: 'project-data',
|
||||
},
|
||||
];
|
||||
|
||||
export const userProfileTabs = [
|
||||
{
|
||||
title: 'Account',
|
||||
tab: 'account',
|
||||
},
|
||||
{
|
||||
title: 'About Company',
|
||||
tab: 'about',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,246 @@
|
||||
export type CountryCode =
|
||||
| 'AC'
|
||||
| 'AD'
|
||||
| 'AE'
|
||||
| 'AF'
|
||||
| 'AG'
|
||||
| 'AI'
|
||||
| 'AL'
|
||||
| 'AM'
|
||||
| 'AO'
|
||||
| 'AR'
|
||||
| 'AS'
|
||||
| 'AT'
|
||||
| 'AU'
|
||||
| 'AW'
|
||||
| 'AX'
|
||||
| 'AZ'
|
||||
| 'BA'
|
||||
| 'BB'
|
||||
| 'BD'
|
||||
| 'BE'
|
||||
| 'BF'
|
||||
| 'BG'
|
||||
| 'BH'
|
||||
| 'BI'
|
||||
| 'BJ'
|
||||
| 'BL'
|
||||
| 'BM'
|
||||
| 'BN'
|
||||
| 'BO'
|
||||
| 'BQ'
|
||||
| 'BR'
|
||||
| 'BS'
|
||||
| 'BT'
|
||||
| 'BW'
|
||||
| 'BY'
|
||||
| 'BZ'
|
||||
| 'CA'
|
||||
| 'CC'
|
||||
| 'CD'
|
||||
| 'CF'
|
||||
| 'CG'
|
||||
| 'CH'
|
||||
| 'CI'
|
||||
| 'CK'
|
||||
| 'CL'
|
||||
| 'CM'
|
||||
| 'CN'
|
||||
| 'CO'
|
||||
| 'CR'
|
||||
| 'CU'
|
||||
| 'CV'
|
||||
| 'CW'
|
||||
| 'CX'
|
||||
| 'CY'
|
||||
| 'CZ'
|
||||
| 'DE'
|
||||
| 'DJ'
|
||||
| 'DK'
|
||||
| 'DM'
|
||||
| 'DO'
|
||||
| 'DZ'
|
||||
| 'EC'
|
||||
| 'EE'
|
||||
| 'EG'
|
||||
| 'EH'
|
||||
| 'ER'
|
||||
| 'ES'
|
||||
| 'ET'
|
||||
| 'FI'
|
||||
| 'FJ'
|
||||
| 'FK'
|
||||
| 'FM'
|
||||
| 'FO'
|
||||
| 'FR'
|
||||
| 'GA'
|
||||
| 'GB'
|
||||
| 'GD'
|
||||
| 'GE'
|
||||
| 'GF'
|
||||
| 'GG'
|
||||
| 'GH'
|
||||
| 'GI'
|
||||
| 'GL'
|
||||
| 'GM'
|
||||
| 'GN'
|
||||
| 'GP'
|
||||
| 'GQ'
|
||||
| 'GR'
|
||||
| 'GT'
|
||||
| 'GU'
|
||||
| 'GW'
|
||||
| 'GY'
|
||||
| 'HK'
|
||||
| 'HN'
|
||||
| 'HR'
|
||||
| 'HT'
|
||||
| 'HU'
|
||||
| 'ID'
|
||||
| 'IE'
|
||||
| 'IL'
|
||||
| 'IM'
|
||||
| 'IN'
|
||||
| 'IO'
|
||||
| 'IQ'
|
||||
| 'IR'
|
||||
| 'IS'
|
||||
| 'IT'
|
||||
| 'JE'
|
||||
| 'JM'
|
||||
| 'JO'
|
||||
| 'JP'
|
||||
| 'KE'
|
||||
| 'KG'
|
||||
| 'KH'
|
||||
| 'KI'
|
||||
| 'KM'
|
||||
| 'KN'
|
||||
| 'KP'
|
||||
| 'KR'
|
||||
| 'KW'
|
||||
| 'KY'
|
||||
| 'KZ'
|
||||
| 'LA'
|
||||
| 'LB'
|
||||
| 'LC'
|
||||
| 'LI'
|
||||
| 'LK'
|
||||
| 'LR'
|
||||
| 'LS'
|
||||
| 'LT'
|
||||
| 'LU'
|
||||
| 'LV'
|
||||
| 'LY'
|
||||
| 'MA'
|
||||
| 'MC'
|
||||
| 'MD'
|
||||
| 'ME'
|
||||
| 'MF'
|
||||
| 'MG'
|
||||
| 'MH'
|
||||
| 'MK'
|
||||
| 'ML'
|
||||
| 'MM'
|
||||
| 'MN'
|
||||
| 'MO'
|
||||
| 'MP'
|
||||
| 'MQ'
|
||||
| 'MR'
|
||||
| 'MS'
|
||||
| 'MT'
|
||||
| 'MU'
|
||||
| 'MV'
|
||||
| 'MW'
|
||||
| 'MX'
|
||||
| 'MY'
|
||||
| 'MZ'
|
||||
| 'NA'
|
||||
| 'NC'
|
||||
| 'NE'
|
||||
| 'NF'
|
||||
| 'NG'
|
||||
| 'NI'
|
||||
| 'NL'
|
||||
| 'NO'
|
||||
| 'NP'
|
||||
| 'NR'
|
||||
| 'NU'
|
||||
| 'NZ'
|
||||
| 'OM'
|
||||
| 'PA'
|
||||
| 'PE'
|
||||
| 'PF'
|
||||
| 'PG'
|
||||
| 'PH'
|
||||
| 'PK'
|
||||
| 'PL'
|
||||
| 'PM'
|
||||
| 'PR'
|
||||
| 'PS'
|
||||
| 'PT'
|
||||
| 'PW'
|
||||
| 'PY'
|
||||
| 'QA'
|
||||
| 'RE'
|
||||
| 'RO'
|
||||
| 'RS'
|
||||
| 'RU'
|
||||
| 'RW'
|
||||
| 'SA'
|
||||
| 'SB'
|
||||
| 'SC'
|
||||
| 'SD'
|
||||
| 'SE'
|
||||
| 'SG'
|
||||
| 'SH'
|
||||
| 'SI'
|
||||
| 'SJ'
|
||||
| 'SK'
|
||||
| 'SL'
|
||||
| 'SM'
|
||||
| 'SN'
|
||||
| 'SO'
|
||||
| 'SR'
|
||||
| 'SS'
|
||||
| 'ST'
|
||||
| 'SV'
|
||||
| 'SX'
|
||||
| 'SY'
|
||||
| 'SZ'
|
||||
| 'TA'
|
||||
| 'TC'
|
||||
| 'TD'
|
||||
| 'TG'
|
||||
| 'TH'
|
||||
| 'TJ'
|
||||
| 'TK'
|
||||
| 'TL'
|
||||
| 'TM'
|
||||
| 'TN'
|
||||
| 'TO'
|
||||
| 'TR'
|
||||
| 'TT'
|
||||
| 'TV'
|
||||
| 'TW'
|
||||
| 'TZ'
|
||||
| 'UA'
|
||||
| 'UG'
|
||||
| 'US'
|
||||
| 'UY'
|
||||
| 'UZ'
|
||||
| 'VA'
|
||||
| 'VC'
|
||||
| 'VE'
|
||||
| 'VG'
|
||||
| 'VI'
|
||||
| 'VN'
|
||||
| 'VU'
|
||||
| 'WF'
|
||||
| 'WS'
|
||||
| 'XK'
|
||||
| 'YE'
|
||||
| 'YT'
|
||||
| 'ZA'
|
||||
| 'ZM'
|
||||
| 'ZW';
|
||||
Reference in New Issue
Block a user