WIP: Working on endpoint for /all in ViewAll.js
parent
b7348afb49
commit
9d6f948c51
@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||||
|
</state>
|
||||||
|
</component>
|
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||||
|
<data-source source="LOCAL" name="hamza@dev.teamortix.com" uuid="84aa8c1d-dc47-44b7-8255-6c8c16c0c001">
|
||||||
|
<driver-ref>postgresql</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:postgresql://dev.teamortix.com:5432/hamza</jdbc-url>
|
||||||
|
</data-source>
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -0,0 +1,7 @@
|
|||||||
|
<component name="ProjectDictionaryState">
|
||||||
|
<dictionary name="dev">
|
||||||
|
<words>
|
||||||
|
<w>navbar</w>
|
||||||
|
</words>
|
||||||
|
</dictionary>
|
||||||
|
</component>
|
@ -0,0 +1,11 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="CssInvalidAtRule" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="ES6CheckImport" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="JSCheckFunctionSignatures" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="JSUnresolvedFunction" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="JSUnresolvedVariable" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="JSUnusedGlobalSymbols" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,30 @@
|
|||||||
|
const tailwindcss = require('tailwindcss');
|
||||||
|
const nano = require('cssnano')
|
||||||
|
|
||||||
|
const purgecss = require('@fullhuman/postcss-purgecss')({
|
||||||
|
//
|
||||||
|
content: [
|
||||||
|
"./src/components/*.js",
|
||||||
|
"./node_modules/react-toastify/dist/ReactToastify.min.css",
|
||||||
|
"./public/index.html",
|
||||||
|
"./src/custom.js",
|
||||||
|
"./src/App.js",
|
||||||
|
],
|
||||||
|
|
||||||
|
defaultExtractor: content => {
|
||||||
|
const broadMatches = content.match(/[^<>"'`\s]*[^<>"'`\s:]/g) || []
|
||||||
|
const innerMatches = content.match(/[^<>"'`\s.()]*[^<>"'`\s.():]/g) || []
|
||||||
|
|
||||||
|
return broadMatches.concat(innerMatches)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
tailwindcss('./tailwind.js'),
|
||||||
|
require('autoprefixer'),
|
||||||
|
...process.env.NODE_ENV === 'production'
|
||||||
|
? [purgecss, nano]
|
||||||
|
: []
|
||||||
|
]
|
||||||
|
}
|
@ -1,38 +0,0 @@
|
|||||||
.App {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-logo {
|
|
||||||
height: 40vmin;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
|
||||||
.App-logo {
|
|
||||||
animation: App-logo-spin infinite 20s linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-header {
|
|
||||||
background-color: #282c34;
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: calc(10px + 2vmin);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-link {
|
|
||||||
color: #61dafb;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes App-logo-spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1 @@
|
|||||||
|
<svg height="368pt" viewBox="0 0 368 368" width="368pt" xmlns="http://www.w3.org/2000/svg"><path d="m328 8h-288c-17.679688 0-32 14.320312-32 32v288c0 17.679688 14.320312 32 32 32h288c17.679688 0 32-14.320312 32-32v-288c0-17.679688-14.320312-32-32-32zm-96 176v112h-96v-112h-64l112-112 112 112zm0 0" fill="#cce4ff"/><g fill="#007aff"><path d="m189.65625 66.34375c-3.128906-3.128906-8.183594-3.128906-11.3125 0l-112 112c-2.289062 2.289062-2.976562 5.726562-1.734375 8.71875 1.230469 2.992188 4.160156 4.9375 7.390625 4.9375h56v104c0 4.425781 3.574219 8 8 8h96c4.425781 0 8-3.574219 8-8v-104h56c3.230469 0 6.160156-1.945312 7.390625-4.9375 1.242187-2.992188.554687-6.429688-1.734375-8.71875zm42.34375 109.65625c-4.425781 0-8 3.574219-8 8v104h-80v-104c0-4.425781-3.574219-8-8-8h-44.6875l92.6875-92.6875 92.6875 92.6875zm0 0"/><path d="m328 0h-288c-22.054688 0-40 17.945312-40 40v288c0 22.054688 17.945312 40 40 40h288c22.054688 0 40-17.945312 40-40v-288c0-22.054688-17.945312-40-40-40zm24 328c0 13.230469-10.769531 24-24 24h-288c-13.230469 0-24-10.769531-24-24v-288c0-13.230469 10.769531-24 24-24h288c13.230469 0 24 10.769531 24 24zm0 0"/></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 94 KiB |
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
@ -0,0 +1,74 @@
|
|||||||
|
import {toast} from "react-toastify";
|
||||||
|
|
||||||
|
// export const SERVER_URL = "http://192.168.1.8:8080/api"
|
||||||
|
export const SERVER_URL = "http://dev.teamortix.com:4290/api"
|
||||||
|
|
||||||
|
export const defaultAuth = () => ({
|
||||||
|
loggedIn: window.localStorage.getItem("authtoken") !== null,
|
||||||
|
token: window.localStorage.getItem("authtoken"),
|
||||||
|
username: '',
|
||||||
|
email: '',
|
||||||
|
admin: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export const reducer = (state, action) => {
|
||||||
|
return {...state, ...action.response}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function authenticate(token) {
|
||||||
|
if (token == null)
|
||||||
|
return defaultAuth()
|
||||||
|
|
||||||
|
const {status, json} = await query("/account/info", {token}).catch(() => {
|
||||||
|
toast.error("The authentication server is offline!")
|
||||||
|
return defaultAuth()
|
||||||
|
})
|
||||||
|
|
||||||
|
if (status !== 200) {
|
||||||
|
window.localStorage.removeItem("authtoken")
|
||||||
|
toast.warn("You have been logged out.")
|
||||||
|
return defaultAuth()
|
||||||
|
}
|
||||||
|
console.debug("Authentication Response:", json)
|
||||||
|
|
||||||
|
const {email, username, admin} = json;
|
||||||
|
return {username, email, admin, loggedIn: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function registration(username, email, password) {
|
||||||
|
const {status, json} = await query("/register", {username, email, password}).catch(() => {
|
||||||
|
return {status: 500, json: {reason: "The authentication server is offline!"}}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.debug("Registration Response:", json)
|
||||||
|
return {status, json}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function login(usernameOrEmail, password) {
|
||||||
|
const {status, json} = await query("/login", {usernameOrEmail, password}).catch(() => {
|
||||||
|
return {status: 500, json: {reason: "The authentication server is offline!"}}
|
||||||
|
})
|
||||||
|
console.debug("Login Response:", json)
|
||||||
|
|
||||||
|
return {status, json}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logout(token) {
|
||||||
|
window.localStorage.removeItem("authtoken")
|
||||||
|
query("/logout", {token}).then(() => {
|
||||||
|
toast.warn("You have been logged out.")
|
||||||
|
})
|
||||||
|
return defaultAuth()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function query(url, data) {
|
||||||
|
console.debug("FETCH: " + SERVER_URL + url + " - " + JSON.stringify(data))
|
||||||
|
let response = await fetch(SERVER_URL + url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {"Content-Type": "application/json"},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
})
|
||||||
|
|
||||||
|
return {status: response.status, json: await response.json()}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
import React, {useContext, useState} from 'react';
|
||||||
|
import {toast} from "react-toastify";
|
||||||
|
import {AuthContext} from "../App";
|
||||||
|
import {useForm} from "react-hook-form";
|
||||||
|
import {query} from "../auth";
|
||||||
|
import {Input} from "../custom";
|
||||||
|
|
||||||
|
function FileManualForm() {
|
||||||
|
const {authState} = useContext(AuthContext)
|
||||||
|
const [manualError, setManualError] = useState("")
|
||||||
|
const {register, handleSubmit, errors, setError, reset} = useForm();
|
||||||
|
|
||||||
|
const manualUpload = async (data) => {
|
||||||
|
const {status, json} = await query("/data/add", data).catch(() => {
|
||||||
|
return {status: 500, json: {reason: "The upload server is offline!"}}
|
||||||
|
})
|
||||||
|
console.debug("Manual Upload Response:", json)
|
||||||
|
return {status, json}
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitManual(data) {
|
||||||
|
let req = data
|
||||||
|
|
||||||
|
let split = data.date.split(/[/-]/);
|
||||||
|
// noinspection JSCheckFunctionSignatures
|
||||||
|
let date = new Date(split[2], Number(split[0]) - 1, split[1])
|
||||||
|
req.date = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate()
|
||||||
|
if (date > new Date()) return setError("date", "time", "Your date must be in the past.")
|
||||||
|
if (date < new Date(1900, 0, 0)) return setError("date", "time", "Your date must be after 1900.")
|
||||||
|
if (date.getDay() === 0) return setError("date", "format", "Invalid Date format (MM-DD-YYYY)")
|
||||||
|
|
||||||
|
if (isNaN(parseFloat(data["precipitation"]))) {
|
||||||
|
setError("precipitation", "format", "Invalid format. Precipitation must be a number. E.g 0.24")
|
||||||
|
}
|
||||||
|
req.precipitation = parseFloat(data["precipitation"])
|
||||||
|
req.token = authState.token;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
let {json, status} = await manualUpload(data)
|
||||||
|
if (status !== 200) return setManualError(json.reason)
|
||||||
|
reset()
|
||||||
|
toast.success("Entry successfully uploaded")
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className="bg-white shadow p-4 overflow-hidden rounded-lg
|
||||||
|
my-6 px-6 w-11/12 lg:mx-2 lg:my-5 lg:px-5 lg:w-5/12" onSubmit={handleSubmit(submitManual)}>
|
||||||
|
|
||||||
|
<div className="text-lg font-medium mb-4">Upload Individual Entry</div>
|
||||||
|
{manualError && <div
|
||||||
|
className="px-2 py-2 mb-8 bg-red-300 text-red-900 text-xs font-semibold border border-red-600 rounded">
|
||||||
|
{manualError}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<Input name="date" type="text" placeholder="Date (MM-DD-YYYY)" errors={errors}
|
||||||
|
register={register({
|
||||||
|
required: {value: true, message: "You must provide a date."},
|
||||||
|
pattern: {
|
||||||
|
value: /^\d\d?[/-]\d\d?[/-]\d\d\d?\d?$/,
|
||||||
|
message: "Invalid Date format (MM-DD-YYYY)"
|
||||||
|
},
|
||||||
|
})}/>
|
||||||
|
|
||||||
|
<Input name="precipitation" placeholder="Precipitation (Inches)" errors={errors}
|
||||||
|
register={register({
|
||||||
|
required: {value: true, message: "You must provide the precipitation."},
|
||||||
|
pattern: {
|
||||||
|
value: /^\d*\.?\d*$/,
|
||||||
|
message: "Invalid format. Precipitation must be a number. E.g 0.24"
|
||||||
|
}
|
||||||
|
})}/>
|
||||||
|
|
||||||
|
<Input name="latitude" type="text" placeholder="Latitude" errors={errors}
|
||||||
|
register={register({
|
||||||
|
required: {value: true, message: "Latitude and Longitude are both required."}
|
||||||
|
})}/>
|
||||||
|
|
||||||
|
<Input name="longitude" type="text" placeholder="Longitude" errors={errors}
|
||||||
|
register={register({
|
||||||
|
required: {value: true, message: "Latitude and Longitude are both required."}
|
||||||
|
})}/>
|
||||||
|
|
||||||
|
<Input name="remarks" type="text" placeholder="Remarks (Optional)" errors={errors}
|
||||||
|
register={register}/>
|
||||||
|
|
||||||
|
<div className="flex flex-1 justify-center">
|
||||||
|
<input className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 text-center rounded"
|
||||||
|
type="submit" value="Submit"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FileManualForm;
|
@ -0,0 +1,129 @@
|
|||||||
|
import React, {useCallback, useContext, useEffect, useReducer, useRef, useState} from 'react';
|
||||||
|
import {toast} from "react-toastify";
|
||||||
|
import {AuthContext} from "../App";
|
||||||
|
import {SERVER_URL} from "../auth";
|
||||||
|
import {Input} from "../custom";
|
||||||
|
|
||||||
|
const LIMIT = 2 * 1024 * 1024; // 2 mb
|
||||||
|
|
||||||
|
const dispatch = (state, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case "lat":
|
||||||
|
return {...state, lat: action.value}
|
||||||
|
case "long":
|
||||||
|
return {...state, long: action.value}
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function FileUploadForm() {
|
||||||
|
const {authState} = useContext(AuthContext)
|
||||||
|
const [files, setFiles] = useState(null);
|
||||||
|
const [load, setLoad] = useState(0)
|
||||||
|
const [status, setStatus] = useState("Upload a file")
|
||||||
|
const [latLong, setLatLong] = useReducer(dispatch, {lat: "", long: ""}, undefined)
|
||||||
|
let form = useRef(null);
|
||||||
|
|
||||||
|
async function query(url, data) {
|
||||||
|
console.debug("FETCH: " + SERVER_URL + url + " - " + data)
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let req = new XMLHttpRequest()
|
||||||
|
req.open('post', SERVER_URL + url)
|
||||||
|
req.upload.addEventListener('progress', e => {
|
||||||
|
if (e.loaded === e.total) setStatus("Processing...")
|
||||||
|
else {
|
||||||
|
let percent = (e.loaded / e.total * 100)
|
||||||
|
setLoad(percent)
|
||||||
|
setStatus("Uploading " + percent.toFixed(0) + "%")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
req.addEventListener('load', () => {
|
||||||
|
resolve({status: req.status, json: JSON.parse(req.response)})
|
||||||
|
});
|
||||||
|
req.addEventListener('error', e => {
|
||||||
|
resolve({status: req.status, json: JSON.parse(req.response)})
|
||||||
|
})
|
||||||
|
req.send(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const upload = useCallback(async () => {
|
||||||
|
if (!files || files.length === 0) return
|
||||||
|
const target = files[0]
|
||||||
|
if (target.size > LIMIT) return toast.error("File size too large! Max 2 mb.")
|
||||||
|
if (!target.name.includes(".xlsx")) return toast.error("Invalid file type!")
|
||||||
|
const {lat, long} = latLong;
|
||||||
|
|
||||||
|
setStatus("Uploading")
|
||||||
|
|
||||||
|
let file = new FormData()
|
||||||
|
file.append("file", target)
|
||||||
|
file.append("token", authState.token);
|
||||||
|
file.append("latitude", lat);
|
||||||
|
file.append("longitude", long);
|
||||||
|
|
||||||
|
let {status, json} = await query("/data/upload", file)
|
||||||
|
form.current.reset();
|
||||||
|
|
||||||
|
setStatus("Upload a file")
|
||||||
|
setLoad(0)
|
||||||
|
|
||||||
|
if (status !== 200) return toast.error(json.reason)
|
||||||
|
return toast.success("File successfully uploaded.")
|
||||||
|
}, [files, authState.token, form, latLong])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => upload())();
|
||||||
|
}, [files, authState.token, upload, authState, setLoad, setStatus])
|
||||||
|
|
||||||
|
return <div className="bg-white shadow p-4 overflow-hidden rounded-lg flex flex-col justify-center
|
||||||
|
my-6 px-6 w-11/12 lg:mx-2 lg:my-5 lg:px-5 lg:w-5/12">
|
||||||
|
<div>
|
||||||
|
<div className="text-lg font-medium text-center mb-2">Upload File Entries</div>
|
||||||
|
<div className="text-xs font-light text-center text-black mb-2">
|
||||||
|
Click the button to upload an .xlsx file
|
||||||
|
</div>
|
||||||
|
<hr className="mb-6 border-gray-300 mx-12"/>
|
||||||
|
</div>
|
||||||
|
<form className="mt-2 items-center" ref={form}>
|
||||||
|
<p className="text-gray-500 italic text-xs ">Latitude and longitude is required if your data doesn't have it</p>
|
||||||
|
|
||||||
|
<Input name="lat" type="text" placeholder="Latitude"
|
||||||
|
setValue={e => setLatLong({type: "lat", value: e})}/>
|
||||||
|
<Input name="long" type="text" placeholder="Longitude"
|
||||||
|
setValue={e => setLatLong({type: "long", value: e})}/>
|
||||||
|
|
||||||
|
<div className="w-full flex justify-center">
|
||||||
|
|
||||||
|
<label className="px-4 pb-2 bg-white text-blue rounded-lg shadow-lg loading inline-block
|
||||||
|
relative border border-blue cursor-pointer bg-blue-500 hover:bg-blue-700 text-white">
|
||||||
|
<style dangerouslySetInnerHTML={{
|
||||||
|
__html:
|
||||||
|
'.loading::after {' +
|
||||||
|
'content: "";' +
|
||||||
|
`width: ${load}%;` +
|
||||||
|
'background: rgb(255, 255, 255, 0.4);' +
|
||||||
|
'position: absolute;' +
|
||||||
|
'top: 0;' +
|
||||||
|
'bottom: 0;' +
|
||||||
|
'left: 0;' +
|
||||||
|
'transition: width 0.3s;'
|
||||||
|
}}/>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<svg className="w-4 h-4 mt-2 mr-3" fill="currentColor" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20">
|
||||||
|
<path
|
||||||
|
d="M16.88 9.1A4 4 0 0 1 16 17H5a5 5 0 0 1-1-9.9V7a3 3 0 0 1 4.52-2.59A4.98 4.98 0 0 1 17 8c0 .38-.04.74-.12 1.1zM11 11h3l-4-4-4 4h3v3h2v-3z"/>
|
||||||
|
</svg>
|
||||||
|
<span className="mt-2 font-bold">{status}</span>
|
||||||
|
<input type='file' className="hidden" name="upload" accept=".xlsx"
|
||||||
|
onChange={e => setFiles(e.target.files)}/>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FileUploadForm;
|
@ -0,0 +1,30 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Navbar from "./Navbar";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
|
||||||
|
const background = require('../assets/background.png')
|
||||||
|
|
||||||
|
function Home(props) {
|
||||||
|
return (
|
||||||
|
<div className="w-screen h-screen overflow-x-hidden bg-cover bg-center text-white flex items-center flex-col"
|
||||||
|
style={{backgroundImage: `url(${background})`}}>
|
||||||
|
<Navbar history={props.history}/>
|
||||||
|
<div
|
||||||
|
className="flex flex-col lg:max-w-full sm:max-w-xl text-center justify-center
|
||||||
|
lg:px-0 md:mt-32 sm:mt-20 mt-16 px-4">
|
||||||
|
|
||||||
|
<h1 className="md:text-4xl text-2xl font-bold">Your Rainfall Data in One Place</h1>
|
||||||
|
|
||||||
|
<div className="mt-6 sm:text-xl text-sm font-light">Look through your rainfall data with ease.</div>
|
||||||
|
|
||||||
|
<Link to="/upload">
|
||||||
|
<div className="rounded-full bg-blue-600 inline-block mt-4 sm:py-3 py-3 sm:px-8 px-4">
|
||||||
|
Start Uploading Data
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Home;
|
@ -0,0 +1,83 @@
|
|||||||
|
import React, {useContext, useEffect, useState} from 'react';
|
||||||
|
import Navbar from "./Navbar";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
import {Input} from "../custom";
|
||||||
|
import {AuthContext} from "../App";
|
||||||
|
import {login} from "../auth";
|
||||||
|
import {toast} from "react-toastify";
|
||||||
|
import {useForm} from "react-hook-form";
|
||||||
|
|
||||||
|
const background = require('../assets/background.png')
|
||||||
|
|
||||||
|
function Login(props) {
|
||||||
|
const {register, handleSubmit, errors} = useForm();
|
||||||
|
const [error, setError] = useState("")
|
||||||
|
const [status, setStatus] = useState("Sign In")
|
||||||
|
const {authState, authDispatch} = useContext(AuthContext)
|
||||||
|
const [canLogin, setLogin] = useState(true)
|
||||||
|
const [cooldown, setCooldown] = useState(0)
|
||||||
|
|
||||||
|
function submit(data) {
|
||||||
|
if (!canLogin) return;
|
||||||
|
setError("");
|
||||||
|
setStatus("Loading..");
|
||||||
|
setLogin(false);
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
let {status, json} = await login(data.username.trim(), data.password.trim())
|
||||||
|
setCooldown(setTimeout(() => setLogin(true), 1000));
|
||||||
|
|
||||||
|
if (status !== 200) {
|
||||||
|
setStatus("Sign In")
|
||||||
|
return setError(json.reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success("You have been logged in.")
|
||||||
|
window.localStorage.setItem("authtoken", json.token)
|
||||||
|
authDispatch({response: {token: json.token}})
|
||||||
|
})()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let loc = new URLSearchParams(props.location.search).get("return")
|
||||||
|
if (authState.loggedIn) props.history.push("/" + (loc ? loc : ""))
|
||||||
|
return () => clearTimeout(cooldown)
|
||||||
|
}, [props, authState, cooldown])
|
||||||
|
|
||||||
|
|
||||||
|
return <div className="w-screen h-screen overflow-x-hidden bg-cover bg-center flex items-center flex-col"
|
||||||
|
style={{backgroundImage: `url(${background})`}}>
|
||||||
|
<Navbar history={props.history}/>
|
||||||
|
|
||||||
|
<form className="mt-32 sm:w-96 w-full" onSubmit={handleSubmit(submit)}>
|
||||||
|
<div className="text-2xl w-full font-semibold block ">
|
||||||
|
<div className="rounded-t-md inline bg-gray-100 text-gray-700 px-5 py-2">Log In</div>
|
||||||
|
</div>
|
||||||
|
<div className="px-8 pt-6 pb-8 mb-4 px-12 rounded-md rounded-tl-none bg-gray-100">
|
||||||
|
{error &&
|
||||||
|
<div className="px-2 py-2 mb-8 bg-red-300 text-red-900 text-xs font-semibold border border-red-600 rounded">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<Input name="username" type="text" placeholder="Username or Email" errors={errors}
|
||||||
|
register={register({required: {value: true, message: "You must provide a username."}})}>Username</Input>
|
||||||
|
|
||||||
|
<Input name="password" type="password" placeholder="********" errors={errors}
|
||||||
|
register={register({required: {value: true, message: "You must provide a password."}})}>Password</Input>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<input className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 mr-2 rounded" type="submit"
|
||||||
|
value={status}/>
|
||||||
|
<Link to="/reset" className="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800">
|
||||||
|
Reset Password
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Login;
|
||||||
|
|
@ -0,0 +1,20 @@
|
|||||||
|
import React, {useContext, useEffect} from 'react';
|
||||||
|
import {AuthContext} from "../App";
|
||||||
|
import {logout} from "../auth";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
import {toast} from "react-toastify";
|
||||||
|
|
||||||
|
function Logout(props) {
|
||||||
|
const {authDispatch, authState} = useContext(AuthContext)
|
||||||
|
useEffect(() => {
|
||||||
|
if (authState.loggedIn) authDispatch({response: logout(authState.token)})
|
||||||
|
else toast.warn("You are not logged in!")
|
||||||
|
props.history.push("/")
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>You will soon be redirected. Click <Link to="/">here</Link> if you are not.</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Logout;
|
@ -0,0 +1,69 @@
|
|||||||
|
import React, {useContext, useEffect, useState} from 'react';
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
import {AuthContext} from "../App";
|
||||||
|
|
||||||
|
const pathsCenter = [
|
||||||
|
{name: "All Data", to: "/all", extra: ""},
|
||||||
|
{name: "Upload Data", to: "/upload", extra: ""},
|
||||||
|
{name: "Your Data", to: "/", extra: ""},
|
||||||
|
]
|
||||||
|
|
||||||
|
const pathsGuest = [
|
||||||
|
{name: "Log In", to: "/login", extra: ""},
|
||||||
|
{name: "Sign Up", to: "/register", extra: "rounded-full border border-white px-4 pb-1 pt-1 inline-block"},
|
||||||
|
]
|
||||||
|
|
||||||
|
const pathsLoggedIn = [
|
||||||
|
{name: "Account", to: "/account", extra: ""},
|
||||||
|
{name: "Sign Out", to: "/logout", extra: ""},
|
||||||
|
]
|
||||||
|
|
||||||
|
function Navbar({color = "text-white", hover="hover:border-white"}) {
|
||||||
|
const [showDropdown, setDropdown] = useState(false)
|
||||||
|
const [userPaths, setUserPaths] = useState(pathsGuest)
|
||||||
|
const {authState} = useContext(AuthContext)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (authState.loggedIn) {
|
||||||
|
setUserPaths(() => pathsLoggedIn)
|
||||||
|
} else setUserPaths(() => pathsGuest)
|
||||||
|
}, [authState.loggedIn])
|
||||||
|
|
||||||
|
return <header className={"lg:flex lg:justify-between lg:items-center px-4 py-3 w-full mt-2 " + color}>
|
||||||
|
<div className="flex flex-1 justify-between items-center mt-3 self-start">
|
||||||
|
<Link to={"/"}>
|
||||||
|
<h1 className="text-xl font-bold">RainTrack</h1>
|
||||||
|
</Link>
|
||||||
|
<button onClick={() => setDropdown(!showDropdown)}
|
||||||
|
className="px-2 py-1 lg:hidden block border border-white rounded-md cursor-pointer">
|
||||||
|
<svg className="fill-current" xmlns="http://www.w3.org/2000/svg" width="20" height="20"
|
||||||
|
viewBox="0 0 20 20"><title>Toggle Navigation</title>
|
||||||
|
<path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<LinksContainer flex="lg:justify-center flex-2"
|
||||||
|
dropDown={showDropdown} paths={pathsCenter} color={color} hover={hover}/>
|
||||||
|
|
||||||
|
<LinksContainer flex="lg:justify-end flex-1"
|
||||||
|
dropDown={showDropdown} paths={userPaths} color={color} hover={hover}/>
|
||||||
|
|
||||||
|
</header>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function LinksContainer(props) {
|
||||||
|
return <div
|
||||||
|
className={`lg:px-2 lg:pt-2 lg:pb-4 lg:flex lg:p-0 ${props.flex} ${props.dropDown ? "block" : "hidden"}`}>
|
||||||
|
{props.paths.map(path => <Link to={path.to} key={path.name}>
|
||||||
|
<div
|
||||||
|
className={`lg:px-2 py-1 lg:mx-1 mt-1 block text-left font-semibold border-b-2 border-transparent
|
||||||
|
${props.hover} ${path.extra}`}>
|
||||||
|
{path.name}
|
||||||
|
</div>
|
||||||
|
</Link>)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Navbar;
|
||||||
|
|
@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Navbar from "./Navbar";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
|
||||||
|
const background = require('../assets/background.png')
|
||||||
|
|
||||||
|
function NotFound(props) {
|
||||||
|
return (
|
||||||
|
<div className="w-screen h-screen overflow-x-hidden bg-cover bg-center text-white flex items-center flex-col"
|
||||||
|
style={{backgroundImage: `url(${background})`}}>
|
||||||
|
<Navbar history={props.history}/>
|
||||||
|
<div className="flex flex-col lg:max-w-full sm:max-w-xl text-center justify-center
|
||||||
|
lg:px-0 md:mt-32 sm:mt-20 mt-16 px-4">
|
||||||
|
|
||||||
|
<h1 className="md:text-3xl sm:text-2xl text-xl font-bold">
|
||||||
|
The page you requested was not found. <br/>
|
||||||
|
Please confirm your URL or go back to the home page.
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<Link to="/">
|
||||||
|
<div className="rounded-full bg-blue-600 inline-block mt-4 sm:py-3 py-3 sm:px-8 px-4">Go Home</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NotFound;
|
@ -0,0 +1,86 @@
|
|||||||
|
import React, {useContext, useEffect, useState} from 'react';
|
||||||
|
import Navbar from "./Navbar";
|
||||||
|
import {Input} from "../custom";
|
||||||
|
import {AuthContext} from "../App";
|
||||||
|
import {registration} from "../auth";
|
||||||
|
import {toast} from "react-toastify";
|
||||||
|
import {useForm} from "react-hook-form";
|
||||||
|
|
||||||
|
const background = require('../assets/background.png')
|
||||||
|
|
||||||
|
function Register(props) {
|
||||||
|
const {authState, authDispatch} = useContext(AuthContext)
|
||||||
|
const {register, handleSubmit, errors} = useForm();
|
||||||
|
const [error, setError] = useState("")
|
||||||
|
const [status, setStatus] = useState("Register")
|
||||||
|
const [canRegister, setRegister] = useState(true)
|
||||||
|
|
||||||
|
function submit(data) {
|
||||||
|
if (!canRegister) return;
|
||||||
|
setError("")
|
||||||
|
setStatus("Loading..");
|
||||||
|
setRegister(false);
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
let {json, status} = await registration(data.user.trim(), data.email.trim(), data.pass.trim())
|
||||||
|
setTimeout(() => setRegister(true), 1000);
|
||||||
|
if (status !== 200) {
|
||||||
|
setStatus("Register");
|
||||||
|
return setError(json.reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success("Your account has been registered.")
|
||||||
|
window.localStorage.setItem("authtoken", json.token)
|
||||||
|
authDispatch({response: {token: json.token, loggedIn: true}})
|
||||||
|
})()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (authState.loggedIn) props.history.push("/")
|
||||||
|
}, [authState.loggedIn, props.history])
|
||||||
|
|
||||||
|
return <div className="w-screen h-screen overflow-x-hidden bg-cover bg-center flex items-center flex-col"
|
||||||
|
style={{backgroundImage: `url(${background})`}}>
|
||||||
|
<Navbar history={props.history}/>
|
||||||
|
|
||||||
|
<form className="mt-24 sm:w-96 w-full" onSubmit={handleSubmit(submit)}>
|
||||||
|
<div className="text-2xl w-full font-semibold block ">
|
||||||
|
<div className="rounded-t-md inline bg-gray-100 text-gray-700 px-5 py-2">Create Account</div>
|
||||||
|
</div>
|
||||||
|
<div className="px-8 pt-6 pb-8 -mt-1 mb-4 px-12 rounded-md rounded-tl-none bg-gray-100">
|
||||||
|
{error &&
|
||||||
|
<div
|
||||||
|
className="px-2 py-2 mb-8 bg-red-300 text-red-900 text-xs font-semibold border border-red-600 rounded">{error}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<Input name="user" type="text" placeholder="Username" errors={errors}
|
||||||
|
register={register({
|
||||||
|
required: {value: true, message: "You must provide a username."},
|
||||||
|
minLength: {value: 3, message: "Your username must be between 3 and 16 characters."},
|
||||||
|
maxLength: {value: 16, message: "Your username must be between 3 and 16 characters."},
|
||||||
|
})}>Username</Input>
|
||||||
|
|
||||||
|
<Input name="email" type="text" placeholder="Email" errors={errors}
|
||||||
|
register={register({
|
||||||
|
required: {value: true, message: "You must provide a password."},
|
||||||
|
pattern: {value: /\S+@\S+\.\S+/, message: "Invalid Email"}
|
||||||
|
})}>Email</Input>
|
||||||
|
|
||||||
|
<Input name="pass" type="password" placeholder="********" errors={errors}
|
||||||
|
register={register({
|
||||||
|
required: {value: true, message: "You must provide a password."},
|
||||||
|
minLength: {value: 8, message: "Your password must be at least 8 characters long."}
|
||||||
|
})}>Password</Input>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<input className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 mr-2 rounded" type="submit"
|
||||||
|
value={status}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Register;
|
@ -0,0 +1,32 @@
|
|||||||
|
import React, {useContext, useEffect} from 'react';
|
||||||
|
import Navbar from "./Navbar";
|
||||||
|
import {AuthContext} from "../App";
|
||||||
|
import FileUploadForm from "./FileUploadForm";
|
||||||
|
import FileManualForm from "./FileManualForm";
|
||||||
|
|
||||||
|
const background = require('../assets/wave.png')
|
||||||
|
|
||||||
|
|
||||||
|
function Upload(props) {
|
||||||
|
const {authState} = useContext(AuthContext)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!authState.loggedIn) props.history.push("/login?return=upload")
|
||||||
|
}, [authState.loggedIn, props.history])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-screen h-screen overflow-x-hidden bg-cover bg-center text-white flex items-center flex-col"
|
||||||
|
style={{backgroundImage: `url(${background})`}}>
|
||||||
|
<Navbar history={props.history} color={"text-blue-600"} hover={"hover:border-blue-600"}/>
|
||||||
|
|
||||||
|
<div className="container text-gray-700">
|
||||||
|
<div className="flex flex-wrap justify-center -mx-6 md:mx-0 overflow-hidden lg:-mx-12 xl:-mx-5 mt-16">
|
||||||
|
<FileUploadForm/>
|
||||||
|
<FileManualForm/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Upload;
|
@ -0,0 +1,109 @@
|
|||||||
|
import React, {useCallback, useContext, useEffect, useState} from 'react';
|
||||||
|
import Navbar from "./Navbar";
|
||||||
|
|
||||||
|
import {AuthContext} from "../App";
|
||||||
|
import Griddle, {ColumnDefinition, plugins, RowDefinition} from "griddle-react";
|
||||||
|
import {query} from "../auth";
|
||||||
|
|
||||||
|
const background = require('../assets/wave.png')
|
||||||
|
|
||||||
|
|
||||||
|
function ViewAll(props) {
|
||||||
|
const {authState} = useContext(AuthContext)
|
||||||
|
const [data, setData] = useState([{}])
|
||||||
|
const [items, setItems] = useState(10)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!authState.loggedIn) props.history.push("/login?return=all")
|
||||||
|
}, [authState.loggedIn, props.history])
|
||||||
|
|
||||||
|
const initiateData = useCallback(async () => {
|
||||||
|
let {json, status} = await query("/data/query", {token: authState.token, validated: false}).catch(() => {
|
||||||
|
return {status: 500, json: {reason: "The authentication server is offline!"}}
|
||||||
|
})
|
||||||
|
if (status !== 200) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = []
|
||||||
|
json.entries.forEach((entry, index) => {
|
||||||
|
const date = new Date(entry.date);
|
||||||
|
const formattedDate = date.getFullYear() + "-" + (date.getMonth() + 1).toString().padStart(2, "0") + "-" + date.getDate().toString().padStart(2, "0")
|
||||||
|
const group = json.groups.find(gr => gr.id === entry.group_id)
|
||||||
|
data[index] = {
|
||||||
|
ID: entry.id,
|
||||||
|
Date: formattedDate,
|
||||||
|
Precipitation: entry.precipitation,
|
||||||
|
Latitude: group.latitude,
|
||||||
|
Longitude: group.longitude,
|
||||||
|
Remarks: entry.remarks,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setData(data)
|
||||||
|
|
||||||
|
}, [setData, authState.token])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
initiateData()
|
||||||
|
})();
|
||||||
|
}, [setData, initiateData])
|
||||||
|
|
||||||
|
const styleConfig = {
|
||||||
|
classNames: {
|
||||||
|
Filter: "shadow appearance-none border rounded mb-1 py-2 px-3 mb-4 text-gray-700 leading-tight h-8",
|
||||||
|
Table: "table table-bordered table-hover table-striped",
|
||||||
|
PreviousButton: "border border-gray-300 px-2 py-1 w-20 mr-2 rounded",
|
||||||
|
NextButton: "border border-gray-300 px-2 py-1 w-20 rounded ml-2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-screen h-screen overflow-x-hidden bg-cover bg-center text-white flex items-center flex-col"
|
||||||
|
style={{backgroundImage: `url(${background})`}}>
|
||||||
|
<Navbar history={props.history} color={"text-blue-600"} hover={"hover:border-blue-600"}/>
|
||||||
|
|
||||||
|
<div className="container text-gray-700 bg-white p-6 mt-12 mb-32 rounded">
|
||||||
|
<Griddle data={data} plugins={[plugins.LocalPlugin]} styleConfig={styleConfig}
|
||||||
|
pageProperties={{
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: items
|
||||||
|
}}
|
||||||
|
components={{
|
||||||
|
Layout: Layout(setItems)
|
||||||
|
}}>
|
||||||
|
<RowDefinition>
|
||||||
|
<ColumnDefinition id="ID"/>
|
||||||
|
<ColumnDefinition id="Date"/>
|
||||||
|
<ColumnDefinition id="Precipitation"/>
|
||||||
|
<ColumnDefinition id="Latitude"/>
|
||||||
|
<ColumnDefinition id="Longitude"/>
|
||||||
|
<ColumnDefinition id="Remarks"/>
|
||||||
|
</RowDefinition>
|
||||||
|
</Griddle>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="container text-gray-700 bg-white p-6 mb-32 rounded">
|
||||||
|
<div className="text-2xl font-semibold">Statistics</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ViewAll;
|
||||||
|
|
||||||
|
const Layout = (setItems) => (({Table, Filter, Pagination}) => (
|
||||||
|
<div>
|
||||||
|
<div className="flex w-96 pb-4 h-12 justify-between">
|
||||||
|
<Filter/>
|
||||||
|
<select onChange={e => setItems(Number(e.target.value))}>
|
||||||
|
<option value="10">Show 10 items</option>
|
||||||
|
<option value="20">Show 20 items</option>
|
||||||
|
<option value="50">Show 50 items</option>
|
||||||
|
<option value="100">Show 100 items</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<Table/>
|
||||||
|
<Pagination/>
|
||||||
|
</div>
|
||||||
|
))
|
@ -0,0 +1,14 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export function Input({name, type, errors = {}, mainClasses = "mb-2", ...props}) {
|
||||||
|
const classes = () => "input " + (errors[name] ? "error" : "")
|
||||||
|
|
||||||
|
return <div className={mainClasses}>
|
||||||
|
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor={name}>{props.children}</label>
|
||||||
|
<input className={classes()} id={name} name={name} ref={props.register}
|
||||||
|
type={type} autoComplete="off" placeholder={props.placeholder}
|
||||||
|
defaultValue={props.value && props.value} onChange={props.setValue && (e => props.setValue(e.target.value))}
|
||||||
|
/>
|
||||||
|
<p className="input-error"> {errors[name]?.message}</p>
|
||||||
|
</div>
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
||||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
||||||
sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
|
||||||
monospace;
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
|
|
||||||
<g fill="#61DAFB">
|
|
||||||
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
|
|
||||||
<circle cx="420.9" cy="296.5" r="45.7"/>
|
|
||||||
<path d="M520.5 78.1z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2.6 KiB |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,47 @@
|
|||||||
|
/* purgecss start ignore */
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
/* purgecss end ignore */
|
||||||
|
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/*forms*/
|
||||||
|
|
||||||
|
.input {
|
||||||
|
@apply shadow appearance-none border rounded w-full mb-1 py-2 px-3 text-gray-700 leading-tight h-10 align-text-top;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input.error {
|
||||||
|
@apply border-red-500
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:focus {
|
||||||
|
@apply text-black outline-none;
|
||||||
|
-webkit-box-shadow: 0px 0px 4px 3px #4f89ed;
|
||||||
|
-moz-box-shadow: 0px 0px 4px 3px #4f89ed;
|
||||||
|
box-shadow: 0px 0px 4px 3px #4f89ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input.error:focus {
|
||||||
|
-webkit-box-shadow: 0px 0px 4px 3px #f56565;
|
||||||
|
-moz-box-shadow: 0px 0px 4px 3px #f56565;
|
||||||
|
box-shadow: 0px 0px 4px 3px #f56565;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-error {
|
||||||
|
@apply text-red-500 text-xs italic h-5
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.Toastify__toast {
|
||||||
|
padding-left: 24px !important;
|
||||||
|
@apply text-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* table stuff */
|
||||||
|
|
||||||
|
.griddle-table {
|
||||||
|
@apply table-auto;
|
||||||
|
}
|
@ -0,0 +1,742 @@
|
|||||||
|
module.exports = {
|
||||||
|
purge: {
|
||||||
|
content: [
|
||||||
|
"./src/components/*.js",
|
||||||
|
"./node_modules/react-toastify/dist/ReactToastify.min.css",
|
||||||
|
"./public/index.html",
|
||||||
|
"./src/custom.js",
|
||||||
|
"./src/App.js",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
target: 'relaxed',
|
||||||
|
prefix: '',
|
||||||
|
important: false,
|
||||||
|
separator: ':',
|
||||||
|
theme: {
|
||||||
|
screens: {
|
||||||
|
sm: '640px',
|
||||||
|
md: '768px',
|
||||||
|
lg: '1024px',
|
||||||
|
xl: '1280px',
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
transparent: 'transparent',
|
||||||
|
current: 'currentColor',
|
||||||
|
|
||||||
|
black: '#000',
|
||||||
|
white: '#fff',
|
||||||
|
|
||||||
|
gray: {
|
||||||
|
100: '#f7fafc',
|
||||||
|
200: '#edf2f7',
|
||||||
|
300: '#e2e8f0',
|
||||||
|
400: '#cbd5e0',
|
||||||
|
500: '#a0aec0',
|
||||||
|
600: '#718096',
|
||||||
|
700: '#4a5568',
|
||||||
|
800: '#2d3748',
|
||||||
|
900: '#1a202c',
|
||||||
|
},
|
||||||
|
red: {
|
||||||
|
100: '#fff5f5',
|
||||||
|
200: '#fed7d7',
|
||||||
|
300: '#feb2b2',
|
||||||
|
400: '#fc8181',
|
||||||
|
500: '#f56565',
|
||||||
|
600: '#e53e3e',
|
||||||
|
700: '#c53030',
|
||||||
|
800: '#9b2c2c',
|
||||||
|
900: '#742a2a',
|
||||||
|
},
|
||||||
|
orange: {
|
||||||
|
100: '#fffaf0',
|
||||||
|
200: '#feebc8',
|
||||||
|
300: '#fbd38d',
|
||||||
|
400: '#f6ad55',
|
||||||
|
500: '#ed8936',
|
||||||
|
600: '#dd6b20',
|
||||||
|
700: '#c05621',
|
||||||
|
800: '#9c4221',
|
||||||
|
900: '#7b341e',
|
||||||
|
},
|
||||||
|
yellow: {
|
||||||
|
100: '#fffff0',
|
||||||
|
200: '#fefcbf',
|
||||||
|
300: '#faf089',
|
||||||
|
400: '#f6e05e',
|
||||||
|
500: '#ecc94b',
|
||||||
|
600: '#d69e2e',
|
||||||
|
700: '#b7791f',
|
||||||
|
800: '#975a16',
|
||||||
|
900: '#744210',
|
||||||
|
},
|
||||||
|
green: {
|
||||||
|
100: '#f0fff4',
|
||||||
|
200: '#c6f6d5',
|
||||||
|
300: '#9ae6b4',
|
||||||
|
400: '#68d391',
|
||||||
|
500: '#48bb78',
|
||||||
|
600: '#38a169',
|
||||||
|
700: '#2f855a',
|
||||||
|
800: '#276749',
|
||||||
|
900: '#22543d',
|
||||||
|
},
|
||||||
|
teal: {
|
||||||
|
100: '#e6fffa',
|
||||||
|
200: '#b2f5ea',
|
||||||
|
300: '#81e6d9',
|
||||||
|
400: '#4fd1c5',
|
||||||
|
500: '#38b2ac',
|
||||||
|
600: '#319795',
|
||||||
|
700: '#2c7a7b',
|
||||||
|
800: '#285e61',
|
||||||
|
900: '#234e52',
|
||||||
|
},
|
||||||
|
blue: {
|
||||||
|
100: '#ebf8ff',
|
||||||
|
200: '#bee3f8',
|
||||||
|
300: '#90cdf4',
|
||||||
|
400: '#63b3ed',
|
||||||
|
500: '#4299e1',
|
||||||
|
600: '#487AE4',
|
||||||
|
700: '#2b6cb0',
|
||||||
|
800: '#2c5282',
|
||||||
|
900: '#2a4365',
|
||||||
|
},
|
||||||
|
indigo: {
|
||||||
|
100: '#ebf4ff',
|
||||||
|
200: '#c3dafe',
|
||||||
|
300: '#a3bffa',
|
||||||
|
400: '#7f9cf5',
|
||||||
|
500: '#667eea',
|
||||||
|
600: '#5a67d8',
|
||||||
|
700: '#4c51bf',
|
||||||
|
800: '#434190',
|
||||||
|
900: '#3c366b',
|
||||||
|
},
|
||||||
|
purple: {
|
||||||
|
100: '#faf5ff',
|
||||||
|
200: '#e9d8fd',
|
||||||
|
300: '#d6bcfa',
|
||||||
|
400: '#b794f4',
|
||||||
|
500: '#9f7aea',
|
||||||
|
600: '#805ad5',
|
||||||
|
700: '#6b46c1',
|
||||||
|
800: '#553c9a',
|
||||||
|
900: '#44337a',
|
||||||
|
},
|
||||||
|
pink: {
|
||||||
|
100: '#fff5f7',
|
||||||
|
200: '#fed7e2',
|
||||||
|
300: '#fbb6ce',
|
||||||
|
400: '#f687b3',
|
||||||
|
500: '#ed64a6',
|
||||||
|
600: '#d53f8c',
|
||||||
|
700: '#b83280',
|
||||||
|
800: '#97266d',
|
||||||
|
900: '#702459',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
spacing: {
|
||||||
|
px: '1px',
|
||||||
|
'0': '0',
|
||||||
|
'1': '0.25rem',
|
||||||
|
'2': '0.5rem',
|
||||||
|
'3': '0.75rem',
|
||||||
|
'4': '1rem',
|
||||||
|
'5': '1.25rem',
|
||||||
|
'6': '1.5rem',
|
||||||
|
'8': '2rem',
|
||||||
|
'10': '2.5rem',
|
||||||
|
'12': '3rem',
|
||||||
|
'16': '4rem',
|
||||||
|
'20': '5rem',
|
||||||
|
'24': '6rem',
|
||||||
|
'32': '8rem',
|
||||||
|
'40': '10rem',
|
||||||
|
'48': '12rem',
|
||||||
|
'56': '14rem',
|
||||||
|
'64': '16rem',
|
||||||
|
'72': '18rem',
|
||||||
|
'96': '24rem',
|
||||||
|
'128': '32rem',
|
||||||
|
},
|
||||||
|
backgroundColor: theme => theme('colors'),
|
||||||
|
backgroundOpacity: theme => theme('opacity'),
|
||||||
|
backgroundPosition: {
|
||||||
|
bottom: 'bottom',
|
||||||
|
center: 'center',
|
||||||
|
left: 'left',
|
||||||
|
'left-bottom': 'left bottom',
|
||||||
|
'left-top': 'left top',
|
||||||
|
right: 'right',
|
||||||
|
'right-bottom': 'right bottom',
|
||||||
|
'right-top': 'right top',
|
||||||
|
top: 'top',
|
||||||
|
},
|
||||||
|
backgroundSize: {
|
||||||
|
auto: 'auto',
|
||||||
|
cover: 'cover',
|
||||||
|
contain: 'contain',
|
||||||
|
},
|
||||||
|
borderColor: theme => ({
|
||||||
|
...theme('colors'),
|
||||||
|
default: theme('colors.gray.300', 'currentColor'),
|
||||||
|
}),
|
||||||
|
borderOpacity: theme => theme('opacity'),
|
||||||
|
borderRadius: {
|
||||||
|
none: '0',
|
||||||
|
sm: '0.125rem',
|
||||||
|
default: '0.25rem',
|
||||||
|
md: '0.375rem',
|
||||||
|
lg: '0.5rem',
|
||||||
|
full: '9999px',
|
||||||
|
},
|
||||||
|
borderWidth: {
|
||||||
|
default: '1px',
|
||||||
|
'0': '0',
|
||||||
|
'2': '2px',
|
||||||
|
'4': '4px',
|
||||||
|
'8': '8px',
|
||||||
|
},
|
||||||
|
boxShadow: {
|
||||||
|
xs: '0 0 0 1px rgba(0, 0, 0, 0.05)',
|
||||||
|
sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
|
||||||
|
default: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
|
||||||
|
md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
|
||||||
|
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
|
||||||
|
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
|
||||||
|
'2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
|
||||||
|
inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
|
||||||
|
outline: '0 0 0 3px rgba(66, 153, 225, 0.5)',
|
||||||
|
none: 'none',
|
||||||
|
},
|
||||||
|
container: {},
|
||||||
|
cursor: {
|
||||||
|
auto: 'auto',
|
||||||
|
default: 'default',
|
||||||
|
pointer: 'pointer',
|
||||||
|
wait: 'wait',
|
||||||
|
text: 'text',
|
||||||
|
move: 'move',
|
||||||
|
'not-allowed': 'not-allowed',
|
||||||
|
},
|
||||||
|
divideColor: theme => theme('borderColor'),
|
||||||
|
divideOpacity: theme => theme('borderOpacity'),
|
||||||
|
divideWidth: theme => theme('borderWidth'),
|
||||||
|
fill: {
|
||||||
|
current: 'currentColor',
|
||||||
|
},
|
||||||
|
flex: {
|
||||||
|
'1': '1 1 0%',
|
||||||
|
'2': '2 2 0%',
|
||||||
|
auto: '1 1 auto',
|
||||||
|
initial: '0 1 auto',
|
||||||
|
none: 'none',
|
||||||
|
},
|
||||||
|
flexGrow: {
|
||||||
|
'0': '0',
|
||||||
|
default: '1',
|
||||||
|
},
|
||||||
|
flexShrink: {
|
||||||
|
'0': '0',
|
||||||
|
default: '1',
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
sans: [
|
||||||
|
// -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"
|
||||||
|
'-apple-system',
|
||||||
|
'BlinkMacSystemFont',
|
||||||
|
'"Segoe UI"',
|
||||||
|
'Roboto',
|
||||||
|
'"Helvetica Neue"',
|
||||||
|
'Arial',
|
||||||
|
'"Noto Sans"',
|
||||||
|
'sans-serif',
|
||||||
|
'"Apple Color Emoji"',
|
||||||
|
'"Segoe UI Emoji"',
|
||||||
|
'"Segoe UI Symbol"',
|
||||||
|
'"Noto Color Emoji"',
|
||||||
|
],
|
||||||
|
serif: ['Georgia', 'Cambria', '"Times New Roman"', 'Times', 'serif'],
|
||||||
|
mono: ['Menlo', 'Monaco', 'Consolas', '"Liberation Mono"', '"Courier New"', 'monospace'],
|
||||||
|
},
|
||||||
|
fontSize: {
|
||||||
|
xs: '0.75rem',
|
||||||
|
sm: '0.875rem',
|
||||||
|
base: '1rem',
|
||||||
|
lg: '1.125rem',
|
||||||
|
xl: '1.25rem',
|
||||||
|
'2xl': '1.5rem',
|
||||||
|
'3xl': '1.875rem',
|
||||||
|
'4xl': '2.25rem',
|
||||||
|
'5xl': '3rem',
|
||||||
|
'6xl': '4rem',
|
||||||
|
},
|
||||||
|
fontWeight: {
|
||||||
|
hairline: '100',
|
||||||
|
thin: '200',
|
||||||
|
light: '300',
|
||||||
|
normal: '400',
|
||||||
|
medium: '500',
|
||||||
|
semibold: '600',
|
||||||
|
bold: '700',
|
||||||
|
extrabold: '800',
|
||||||
|
black: '900',
|
||||||
|
},
|
||||||
|
height: theme => ({
|
||||||
|
auto: 'auto',
|
||||||
|
...theme('spacing'),
|
||||||
|
full: '100%',
|
||||||
|
screen: '100vh',
|
||||||
|
}),
|
||||||
|
inset: {
|
||||||
|
'0': '0',
|
||||||
|
auto: 'auto',
|
||||||
|
},
|
||||||
|
letterSpacing: {
|
||||||
|
tighter: '-0.05em',
|
||||||
|
tight: '-0.025em',
|
||||||
|
normal: '0',
|
||||||
|
wide: '0.025em',
|
||||||
|
wider: '0.05em',
|
||||||
|
widest: '0.1em',
|
||||||
|
},
|
||||||
|
lineHeight: {
|
||||||
|
none: '1',
|
||||||
|
tight: '1.25',
|
||||||
|
snug: '1.375',
|
||||||
|
normal: '1.5',
|
||||||
|
relaxed: '1.625',
|
||||||
|
loose: '2',
|
||||||
|
'3': '.75rem',
|
||||||
|
'4': '1rem',
|
||||||
|
'5': '1.25rem',
|
||||||
|
'6': '1.5rem',
|
||||||
|
'7': '1.75rem',
|
||||||
|
'8': '2rem',
|
||||||
|
'9': '2.25rem',
|
||||||
|
'10': '2.5rem',
|
||||||
|
},
|
||||||
|
listStyleType: {
|
||||||
|
none: 'none',
|
||||||
|
disc: 'disc',
|
||||||
|
decimal: 'decimal',
|
||||||
|
},
|
||||||
|
margin: (theme, {negative}) => ({
|
||||||
|
auto: 'auto',
|
||||||
|
...theme('spacing'),
|
||||||
|
...negative(theme('spacing')),
|
||||||
|
}),
|
||||||
|
maxHeight: {
|
||||||
|
full: '100%',
|
||||||
|
screen: '100vh',
|
||||||
|
},
|
||||||
|
maxWidth: (theme, {breakpoints}) => ({
|
||||||
|
none: 'none',
|
||||||
|
xs: '20rem',
|
||||||
|
sm: '24rem',
|
||||||
|
md: '28rem',
|
||||||
|
lg: '32rem',
|
||||||
|
xl: '36rem',
|
||||||
|
'2xl': '42rem',
|
||||||
|
'3xl': '48rem',
|
||||||
|
'4xl': '56rem',
|
||||||
|
'5xl': '64rem',
|
||||||
|
'6xl': '72rem',
|
||||||
|
'8xl': '92rem',
|
||||||
|
full: '100%',
|
||||||
|
...breakpoints(theme('screens')),
|
||||||
|
}),
|
||||||
|
minHeight: {
|
||||||
|
'0': '0',
|
||||||
|
full: '100%',
|
||||||
|
screen: '100vh',
|
||||||
|
},
|
||||||
|
minWidth: {
|
||||||
|
'0': '0',
|
||||||
|
full: '100%',
|
||||||
|
},
|
||||||
|
objectPosition: {
|
||||||
|
bottom: 'bottom',
|
||||||
|
center: 'center',
|
||||||
|
left: 'left',
|
||||||
|
'left-bottom': 'left bottom',
|
||||||
|
'left-top': 'left top',
|
||||||
|
right: 'right',
|
||||||
|
'right-bottom': 'right bottom',
|
||||||
|
'right-top': 'right top',
|
||||||
|
top: 'top',
|
||||||
|
},
|
||||||
|
opacity: {
|
||||||
|
'0': '0',
|
||||||
|
'25': '0.25',
|
||||||
|
'50': '0.5',
|
||||||
|
'75': '0.75',
|
||||||
|
'100': '1',
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
first: '-9999',
|
||||||
|
last: '9999',
|
||||||
|
none: '0',
|
||||||
|
'1': '1',
|
||||||
|
'2': '2',
|
||||||
|
'3': '3',
|
||||||
|
'4': '4',
|
||||||
|
'5': '5',
|
||||||
|
'6': '6',
|
||||||
|
'7': '7',
|
||||||
|
'8': '8',
|
||||||
|
'9': '9',
|
||||||
|
'10': '10',
|
||||||
|
'11': '11',
|
||||||
|
'12': '12',
|
||||||
|
},
|
||||||
|
padding: theme => theme('spacing'),
|
||||||
|
placeholderColor: theme => theme('colors'),
|
||||||
|
placeholderOpacity: theme => theme('opacity'),
|
||||||
|
space: (theme, {negative}) => ({
|
||||||
|
...theme('spacing'),
|
||||||
|
...negative(theme('spacing')),
|
||||||
|
}),
|
||||||
|
stroke: {
|
||||||
|
current: 'currentColor',
|
||||||
|
},
|
||||||
|
strokeWidth: {
|
||||||
|
'0': '0',
|
||||||
|
'1': '1',
|
||||||
|
'2': '2',
|
||||||
|
},
|
||||||
|
textColor: theme => theme('colors'),
|
||||||
|
textOpacity: theme => theme('opacity'),
|
||||||
|
width: theme => ({
|
||||||
|
auto: 'auto',
|
||||||
|
...theme('spacing'),
|
||||||
|
'1/2': '50%',
|
||||||
|
'1/3': '33.333333%',
|
||||||
|
'2/3': '66.666667%',
|
||||||
|
'1/4': '25%',
|
||||||
|
'2/4': '50%',
|
||||||
|
'3/4': '75%',
|
||||||
|
'1/5': '20%',
|
||||||
|
'2/5': '40%',
|
||||||
|
'3/5': '60%',
|
||||||
|
'4/5': '80%',
|
||||||
|
'1/6': '16.666667%',
|
||||||
|
'2/6': '33.333333%',
|
||||||
|
'3/6': '50%',
|
||||||
|
'4/6': '66.666667%',
|
||||||
|
'5/6': '83.333333%',
|
||||||
|
'1/12': '8.333333%',
|
||||||
|
'2/12': '16.666667%',
|
||||||
|
'3/12': '25%',
|
||||||
|
'4/12': '33.333333%',
|
||||||
|
'5/12': '41.666667%',
|
||||||
|
'6/12': '50%',
|
||||||
|
'7/12': '58.333333%',
|
||||||
|
'8/12': '66.666667%',
|
||||||
|
'9/12': '75%',
|
||||||
|
'10/12': '83.333333%',
|
||||||
|
'11/12': '91.666667%',
|
||||||
|
full: '100%',
|
||||||
|
screen: '100vw',
|
||||||
|
}),
|
||||||
|
zIndex: {
|
||||||
|
auto: 'auto',
|
||||||
|
'0': '0',
|
||||||
|
'10': '10',
|
||||||
|
'20': '20',
|
||||||
|
'30': '30',
|
||||||
|
'40': '40',
|
||||||
|
'50': '50',
|
||||||
|
},
|
||||||
|
gap: theme => theme('spacing'),
|
||||||
|
gridTemplateColumns: {
|
||||||
|
none: 'none',
|
||||||
|
'1': 'repeat(1, minmax(0, 1fr))',
|
||||||
|
'2': 'repeat(2, minmax(0, 1fr))',
|
||||||
|
'3': 'repeat(3, minmax(0, 1fr))',
|
||||||
|
'4': 'repeat(4, minmax(0, 1fr))',
|
||||||
|
'5': 'repeat(5, minmax(0, 1fr))',
|
||||||
|
'6': 'repeat(6, minmax(0, 1fr))',
|
||||||
|
'7': 'repeat(7, minmax(0, 1fr))',
|
||||||
|
'8': 'repeat(8, minmax(0, 1fr))',
|
||||||
|
'9': 'repeat(9, minmax(0, 1fr))',
|
||||||
|
'10': 'repeat(10, minmax(0, 1fr))',
|
||||||
|
'11': 'repeat(11, minmax(0, 1fr))',
|
||||||
|
'12': 'repeat(12, minmax(0, 1fr))',
|
||||||
|
},
|
||||||
|
gridColumn: {
|
||||||
|
auto: 'auto',
|
||||||
|
'span-1': 'span 1 / span 1',
|
||||||
|
'span-2': 'span 2 / span 2',
|
||||||
|
'span-3': 'span 3 / span 3',
|
||||||
|
'span-4': 'span 4 / span 4',
|
||||||
|
'span-5': 'span 5 / span 5',
|
||||||
|
'span-6': 'span 6 / span 6',
|
||||||
|
'span-7': 'span 7 / span 7',
|
||||||
|
'span-8': 'span 8 / span 8',
|
||||||
|
'span-9': 'span 9 / span 9',
|
||||||
|
'span-10': 'span 10 / span 10',
|
||||||
|
'span-11': 'span 11 / span 11',
|
||||||
|
'span-12': 'span 12 / span 12',
|
||||||
|
},
|
||||||
|
gridColumnStart: {
|
||||||
|
auto: 'auto',
|
||||||
|
'1': '1',
|
||||||
|
'2': '2',
|
||||||
|
'3': '3',
|
||||||
|
'4': '4',
|
||||||
|
'5': '5',
|
||||||
|
'6': '6',
|
||||||
|
'7': '7',
|
||||||
|
'8': '8',
|
||||||
|
'9': '9',
|
||||||
|
'10': '10',
|
||||||
|
'11': '11',
|
||||||
|
'12': '12',
|
||||||
|
'13': '13',
|
||||||
|
},
|
||||||
|
gridColumnEnd: {
|
||||||
|
auto: 'auto',
|
||||||
|
'1': '1',
|
||||||
|
'2': '2',
|
||||||
|
'3': '3',
|
||||||
|
'4': '4',
|
||||||
|
'5': '5',
|
||||||
|
'6': '6',
|
||||||
|
'7': '7',
|
||||||
|
'8': '8',
|
||||||
|
'9': '9',
|
||||||
|
'10': '10',
|
||||||
|
'11': '11',
|
||||||
|
'12': '12',
|
||||||
|
'13': '13',
|
||||||
|
},
|
||||||
|
gridTemplateRows: {
|
||||||
|
none: 'none',
|
||||||
|
'1': 'repeat(1, minmax(0, 1fr))',
|
||||||
|
'2': 'repeat(2, minmax(0, 1fr))',
|
||||||
|
'3': 'repeat(3, minmax(0, 1fr))',
|
||||||
|
'4': 'repeat(4, minmax(0, 1fr))',
|
||||||
|
'5': 'repeat(5, minmax(0, 1fr))',
|
||||||
|
'6': 'repeat(6, minmax(0, 1fr))',
|
||||||
|
},
|
||||||
|
gridRow: {
|
||||||
|
auto: 'auto',
|
||||||
|
'span-1': 'span 1 / span 1',
|
||||||
|
'span-2': 'span 2 / span 2',
|
||||||
|
'span-3': 'span 3 / span 3',
|
||||||
|
'span-4': 'span 4 / span 4',
|
||||||
|
'span-5': 'span 5 / span 5',
|
||||||
|
'span-6': 'span 6 / span 6',
|
||||||
|
},
|
||||||
|
gridRowStart: {
|
||||||
|
auto: 'auto',
|
||||||
|
'1': '1',
|
||||||
|
'2': '2',
|
||||||
|
'3': '3',
|
||||||
|
'4': '4',
|
||||||
|
'5': '5',
|
||||||
|
'6': '6',
|
||||||
|
'7': '7',
|
||||||
|
},
|
||||||
|
gridRowEnd: {
|
||||||
|
auto: 'auto',
|
||||||
|
'1': '1',
|
||||||
|
'2': '2',
|
||||||
|
'3': '3',
|
||||||
|
'4': '4',
|
||||||
|
'5': '5',
|
||||||
|
'6': '6',
|
||||||
|
'7': '7',
|
||||||
|
},
|
||||||
|
transformOrigin: {
|
||||||
|
center: 'center',
|
||||||
|
top: 'top',
|
||||||
|
'top-right': 'top right',
|
||||||
|
right: 'right',
|
||||||
|
'bottom-right': 'bottom right',
|
||||||
|
bottom: 'bottom',
|
||||||
|
'bottom-left': 'bottom left',
|
||||||
|
left: 'left',
|
||||||
|
'top-left': 'top left',
|
||||||
|
},
|
||||||
|
scale: {
|
||||||
|
'0': '0',
|
||||||
|
'50': '.5',
|
||||||
|
'75': '.75',
|
||||||
|
'90': '.9',
|
||||||
|
'95': '.95',
|
||||||
|
'100': '1',
|
||||||
|
'105': '1.05',
|
||||||
|
'110': '1.1',
|
||||||
|
'125': '1.25',
|
||||||
|
'150': '1.5',
|
||||||
|
},
|
||||||
|
rotate: {
|
||||||
|
'-180': '-180deg',
|
||||||
|
'-90': '-90deg',
|
||||||
|
'-45': '-45deg',
|
||||||
|
'0': '0',
|
||||||
|
'45': '45deg',
|
||||||
|
'90': '90deg',
|
||||||
|
'180': '180deg',
|
||||||
|
},
|
||||||
|
translate: (theme, {negative}) => ({
|
||||||
|
...theme('spacing'),
|
||||||
|
...negative(theme('spacing')),
|
||||||
|
'-full': '-100%',
|
||||||
|
'-1/2': '-50%',
|
||||||
|
'1/2': '50%',
|
||||||
|
full: '100%',
|
||||||
|
}),
|
||||||
|
skew: {
|
||||||
|
'-12': '-12deg',
|
||||||
|
'-6': '-6deg',
|
||||||
|
'-3': '-3deg',
|
||||||
|
'0': '0',
|
||||||
|
'3': '3deg',
|
||||||
|
'6': '6deg',
|
||||||
|
'12': '12deg',
|
||||||
|
},
|
||||||
|
transitionProperty: {
|
||||||
|
none: 'none',
|
||||||
|
all: 'all',
|
||||||
|
default: 'background-color, border-color, color, fill, stroke, opacity, box-shadow, transform',
|
||||||
|
colors: 'background-color, border-color, color, fill, stroke',
|
||||||
|
opacity: 'opacity',
|
||||||
|
shadow: 'box-shadow',
|
||||||
|
transform: 'transform',
|
||||||
|
},
|
||||||
|
transitionTimingFunction: {
|
||||||
|
linear: 'linear',
|
||||||
|
in: 'cubic-bezier(0.4, 0, 1, 1)',
|
||||||
|
out: 'cubic-bezier(0, 0, 0.2, 1)',
|
||||||
|
'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
},
|
||||||
|
transitionDuration: {
|
||||||
|
'75': '75ms',
|
||||||
|
'100': '100ms',
|
||||||
|
'150': '150ms',
|
||||||
|
'200': '200ms',
|
||||||
|
'300': '300ms',
|
||||||
|
'500': '500ms',
|
||||||
|
'700': '700ms',
|
||||||
|
'1000': '1000ms',
|
||||||
|
},
|
||||||
|
transitionDelay: {
|
||||||
|
'75': '75ms',
|
||||||
|
'100': '100ms',
|
||||||
|
'150': '150ms',
|
||||||
|
'200': '200ms',
|
||||||
|
'300': '300ms',
|
||||||
|
'500': '500ms',
|
||||||
|
'700': '700ms',
|
||||||
|
'1000': '1000ms',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
accessibility: ['responsive', 'focus'],
|
||||||
|
alignContent: ['responsive'],
|
||||||
|
alignItems: ['responsive'],
|
||||||
|
alignSelf: ['responsive'],
|
||||||
|
appearance: ['responsive'],
|
||||||
|
backgroundAttachment: ['responsive'],
|
||||||
|
backgroundColor: ['responsive', 'hover', 'focus'],
|
||||||
|
backgroundOpacity: ['responsive', 'hover', 'focus'],
|
||||||
|
backgroundPosition: ['responsive'],
|
||||||
|
backgroundRepeat: ['responsive'],
|
||||||
|
backgroundSize: ['responsive'],
|
||||||
|
borderCollapse: ['responsive'],
|
||||||
|
borderColor: ['responsive', 'hover', 'focus'],
|
||||||
|
borderOpacity: ['responsive', 'hover', 'focus'],
|
||||||
|
borderRadius: ['responsive'],
|
||||||
|
borderStyle: ['responsive'],
|
||||||
|
borderWidth: ['responsive', 'hover'],
|
||||||
|
boxShadow: ['responsive', 'hover', 'focus'],
|
||||||
|
boxSizing: ['responsive'],
|
||||||
|
cursor: ['responsive'],
|
||||||
|
display: ['responsive'],
|
||||||
|
divideColor: ['responsive'],
|
||||||
|
divideOpacity: ['responsive'],
|
||||||
|
divideWidth: ['responsive'],
|
||||||
|
fill: ['responsive'],
|
||||||
|
flex: ['responsive'],
|
||||||
|
flexDirection: ['responsive'],
|
||||||
|
flexGrow: ['responsive'],
|
||||||
|
flexShrink: ['responsive'],
|
||||||
|
flexWrap: ['responsive'],
|
||||||
|
float: ['responsive'],
|
||||||
|
clear: ['responsive'],
|
||||||
|
fontFamily: ['responsive'],
|
||||||
|
fontSize: ['responsive'],
|
||||||
|
fontSmoothing: ['responsive'],
|
||||||
|
fontStyle: ['responsive'],
|
||||||
|
fontWeight: ['responsive', 'hover', 'focus'],
|
||||||
|
height: ['responsive'],
|
||||||
|
inset: ['responsive'],
|
||||||
|
justifyContent: ['responsive'],
|
||||||
|
letterSpacing: ['responsive'],
|
||||||
|
lineHeight: ['responsive'],
|
||||||
|
listStylePosition: ['responsive'],
|
||||||
|
listStyleType: ['responsive'],
|
||||||
|
margin: ['responsive'],
|
||||||
|
maxHeight: ['responsive'],
|
||||||
|
maxWidth: ['responsive'],
|
||||||
|
minHeight: ['responsive'],
|
||||||
|
minWidth: ['responsive'],
|
||||||
|
objectFit: ['responsive'],
|
||||||
|
objectPosition: ['responsive'],
|
||||||
|
opacity: ['responsive', 'hover', 'focus'],
|
||||||
|
order: ['responsive'],
|
||||||
|
outline: ['responsive', 'focus'],
|
||||||
|
overflow: ['responsive'],
|
||||||
|
padding: ['responsive'],
|
||||||
|
placeholderColor: ['responsive', 'focus'],
|
||||||
|
placeholderOpacity: ['responsive', 'focus'],
|
||||||
|
pointerEvents: ['responsive'],
|
||||||
|
position: ['responsive'],
|
||||||
|
resize: ['responsive'],
|
||||||
|
space: ['responsive'],
|
||||||
|
stroke: ['responsive'],
|
||||||
|
strokeWidth: ['responsive'],
|
||||||
|
tableLayout: ['responsive'],
|
||||||
|
textAlign: ['responsive'],
|
||||||
|
textColor: ['responsive', 'hover', 'focus'],
|
||||||
|
textOpacity: ['responsive', 'hover', 'focus'],
|
||||||
|
textDecoration: ['responsive', 'hover', 'focus'],
|
||||||
|
textTransform: ['responsive'],
|
||||||
|
userSelect: ['responsive'],
|
||||||
|
verticalAlign: ['responsive'],
|
||||||
|
visibility: ['responsive'],
|
||||||
|
whitespace: ['responsive'],
|
||||||
|
width: ['responsive'],
|
||||||
|
wordBreak: ['responsive'],
|
||||||
|
zIndex: ['responsive'],
|
||||||
|
gap: ['responsive'],
|
||||||
|
gridAutoFlow: ['responsive'],
|
||||||
|
gridTemplateColumns: ['responsive'],
|
||||||
|
gridColumn: ['responsive'],
|
||||||
|
gridColumnStart: ['responsive'],
|
||||||
|
gridColumnEnd: ['responsive'],
|
||||||
|
gridTemplateRows: ['responsive'],
|
||||||
|
gridRow: ['responsive'],
|
||||||
|
gridRowStart: ['responsive'],
|
||||||
|
gridRowEnd: ['responsive'],
|
||||||
|
transform: ['responsive'],
|
||||||
|
transformOrigin: ['responsive'],
|
||||||
|
scale: ['responsive', 'hover', 'focus'],
|
||||||
|
rotate: ['responsive', 'hover', 'focus'],
|
||||||
|
translate: ['responsive', 'hover', 'focus'],
|
||||||
|
skew: ['responsive', 'hover', 'focus'],
|
||||||
|
transitionProperty: ['responsive'],
|
||||||
|
transitionTimingFunction: ['responsive'],
|
||||||
|
transitionDuration: ['responsive'],
|
||||||
|
transitionDelay: ['responsive'],
|
||||||
|
},
|
||||||
|
corePlugins: {},
|
||||||
|
plugins: [
|
||||||
|
require('tailwindcss-tables')(),
|
||||||
|
],
|
||||||
|
}
|
Loading…
Reference in New Issue