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