add lang
This commit is contained in:
6
.idea/inspectionProfiles/Project_Default.xml
generated
6
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<profile version="1.0">
|
|
||||||
<option name="myName" value="Project Default" />
|
|
||||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
|
||||||
</profile>
|
|
||||||
</component>
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -2,6 +2,7 @@ import { createContext } from "preact";
|
|||||||
import usePlants from "./hooks/PlantsHook";
|
import usePlants from "./hooks/PlantsHook";
|
||||||
import { useLocalStorage } from "./hooks/LocalStorageHook"
|
import { useLocalStorage } from "./hooks/LocalStorageHook"
|
||||||
import useUser from "./hooks/UserHook";
|
import useUser from "./hooks/UserHook";
|
||||||
|
import {TranslateProvider} from "./components/Translation";
|
||||||
|
|
||||||
export const UserContext = createContext(null)
|
export const UserContext = createContext(null)
|
||||||
export const PlantsContext = createContext(null)
|
export const PlantsContext = createContext(null)
|
||||||
@@ -12,9 +13,11 @@ export default function ContextsProviders({children}) {
|
|||||||
const [user, setUser] = useUser(data, setData)
|
const [user, setUser] = useUser(data, setData)
|
||||||
const {plants, addPlant, editPlant, removePlant, addAction, doneTask, history, archivedEntries} = usePlants(data, setData)
|
const {plants, addPlant, editPlant, removePlant, addAction, doneTask, history, archivedEntries} = usePlants(data, setData)
|
||||||
|
|
||||||
return <UserContext.Provider value={[user, setUser]}>
|
return <TranslateProvider>
|
||||||
<PlantsContext.Provider value={{plants, addPlant, editPlant, removePlant, addAction, doneTask, history, archivedEntries}}>
|
<UserContext.Provider value={[user, setUser]}>
|
||||||
{children}
|
<PlantsContext.Provider value={{plants, addPlant, editPlant, removePlant, addAction, doneTask, history, archivedEntries}}>
|
||||||
</PlantsContext.Provider>
|
{children}
|
||||||
</UserContext.Provider>
|
</PlantsContext.Provider>
|
||||||
|
</UserContext.Provider>
|
||||||
|
</TranslateProvider>
|
||||||
}
|
}
|
||||||
@@ -8,20 +8,12 @@ import Header from './Header';
|
|||||||
import Home from '../routes/Home';
|
import Home from '../routes/Home';
|
||||||
import Plant from '../routes/Plant';
|
import Plant from '../routes/Plant';
|
||||||
import Profile from '../routes/Profile';
|
import Profile from '../routes/Profile';
|
||||||
import {useEffect} from "preact/hooks";
|
import Style from "../routes/Style";
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// if (!Notification) {
|
|
||||||
// alert('Le navigateur ne supporte pas les notifications.');
|
|
||||||
// } else if (Notification.permission !== 'granted') {
|
|
||||||
// Notification.requestPermission();
|
|
||||||
// }
|
|
||||||
// }, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="app" class="h-screen overflow-auto flex flex-col">
|
<div id="app" className="h-screen overflow-auto flex flex-col">
|
||||||
<ContextsProviders>
|
<ContextsProviders>
|
||||||
<Header />
|
<Header />
|
||||||
<main className="flex-1 dark:bg-gray-800 dark:text-white">
|
<main className="flex-1 dark:bg-gray-800 dark:text-white">
|
||||||
@@ -29,6 +21,7 @@ const App = () => {
|
|||||||
<Home path="/" />
|
<Home path="/" />
|
||||||
<Plant path="plant/:id" />
|
<Plant path="plant/:id" />
|
||||||
<Profile path="/profile" />
|
<Profile path="/profile" />
|
||||||
|
<Style path="/style" />
|
||||||
</Router>
|
</Router>
|
||||||
</main>
|
</main>
|
||||||
</ContextsProviders>
|
</ContextsProviders>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { classNames } from "../utilities/classNames"
|
import { classNames } from "../utilities/classNames"
|
||||||
|
|
||||||
export const Button = ({ children, className = "", type = "text", ...props }) => {
|
export const Button = ({ children, className = "", type = "button", ...props }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button type={type} className={classNames("border px-2 py-1 shadow transition-all duration-300 rounded", className)} {...props}>
|
<button type={type} className={classNames("border px-2 py-1 shadow transition-all duration-300 rounded hover:ring-2 focus:ring-2 ring-offset disabled:opacity-50 disabled:cursor-not-allowed", className)} {...props}>
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {classNames} from "../utilities/classNames";
|
import {classNames} from "../utilities/classNames";
|
||||||
|
import {Text} from "./Translation";
|
||||||
|
|
||||||
export const InputField = ({children, name, value = "", type = "text", textSupport = "", ...props}) => {
|
export const InputField = ({children, name, value = "", type = "text", textSupport = "", ...props}) => {
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@ export const InputField = ({children, name, value = "", type = "text", textSuppo
|
|||||||
value={value}
|
value={value}
|
||||||
className="focus:ring-indigo-500 focus:border-indigo-500 block w-full px-2 py-1 mt-1 sm:text-sm border border-gray-300 rounded-md dark:bg-gray-500"
|
className="focus:ring-indigo-500 focus:border-indigo-500 block w-full px-2 py-1 mt-1 sm:text-sm border border-gray-300 rounded-md dark:bg-gray-500"
|
||||||
{...props}/>
|
{...props}/>
|
||||||
<span className="text-gray-500 text-sm">{textSupport}</span>
|
<span className="text-gray-500 text-sm"><Text text={textSupport} /></span>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ export const SelectField = ({children, name, options, className = '', ...props})
|
|||||||
</label>}
|
</label>}
|
||||||
<select id={id} name={name} className="focus:ring-indigo-500 focus:border-indigo-500 block w-full px-2 py-1 mt-1 sm:text-sm border border-gray-300 dark:bg-gray-500 rounded-md" {...props}>
|
<select id={id} name={name} className="focus:ring-indigo-500 focus:border-indigo-500 block w-full px-2 py-1 mt-1 sm:text-sm border border-gray-300 dark:bg-gray-500 rounded-md" {...props}>
|
||||||
{options.map((option, index) => <option key={index} value={option}
|
{options.map((option, index) => <option key={index} value={option}
|
||||||
className="capitalize">{option}</option>)}
|
className="capitalize"><Text text={option} /></option>)}
|
||||||
</select>
|
</select>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
import { h } from "preact"
|
|
||||||
import { Link } from "preact-router/match"
|
import { Link } from "preact-router/match"
|
||||||
|
import {Text} from "./Translation"
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
return (
|
return (
|
||||||
<header class="bg-green-700 text-white flex justify-between items-center p-2 text-lg">
|
<header className="bg-primary text-white flex justify-between items-center p-2 text-lg">
|
||||||
<Link href="/" class="font-bold text-xl">
|
<Link href="/" class="font-bold text-xl cursor-pointer">
|
||||||
Plantes
|
<Text text="Plantes" />
|
||||||
</Link>
|
</Link>
|
||||||
<nav class="flex">
|
<nav className="flex gap-2">
|
||||||
<NavLink path="/profile">Me</NavLink>
|
<NavLink path="/style"><Text text="Style" /></NavLink>
|
||||||
|
<NavLink path="/profile"><Text text="Me" /></NavLink>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
)
|
)
|
||||||
@@ -20,8 +21,8 @@ const NavLink = ({ path, children }) => {
|
|||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={path}
|
href={path}
|
||||||
activeClassName="font-bold bg-green-800"
|
activeClassName="font-bold bg-primary-dark"
|
||||||
class="py-1 px-2 rounded"
|
class="py-1 px-2 rounded cursor-pointer"
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import {Text} from "./Translation";
|
||||||
|
|
||||||
export const Modal = ({children, isOpen, customClose = false, ...props}) => {
|
export const Modal = ({children, isOpen, customClose = false, ...props}) => {
|
||||||
|
|
||||||
@@ -7,16 +8,15 @@ export const Modal = ({children, isOpen, customClose = false, ...props}) => {
|
|||||||
{isOpen &&
|
{isOpen &&
|
||||||
<div className="overlay fixed block top-0 bottom-0 left-0 right-0 z-10 bg-gray-800 bg-opacity-80" onClick={handleClose}>
|
<div className="overlay fixed block top-0 bottom-0 left-0 right-0 z-10 bg-gray-800 bg-opacity-80" onClick={handleClose}>
|
||||||
<div className="h-full flex justify-center items-center">
|
<div className="h-full flex justify-center items-center">
|
||||||
<div className="flex flex-col bg-white dark:bg-gray-700 dark:text-white rounded p-2 min-h-48 min-w-48" {...props}>
|
<div className="flex flex-col bg-white dark:bg-gray-700 dark:text-white rounded p-2 min-h-48 min-w-48" {...props}>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
{children}
|
{children}
|
||||||
|
</div>
|
||||||
|
{!customClose &&<div className="flex justify-end">
|
||||||
|
<button type="button" className="bg-gray-300 dark:bg-gray-600 hover:bg-gray-400 hover:dark:bg-gray-800 px-2 py-1 rounded shadow close-button" onClick={handleClose}><Text text="Close" /></button>
|
||||||
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
{!customClose &&<div className="flex justify-end">
|
|
||||||
<button type="button" className="bg-gray-300 dark:bg-gray-600 hover:bg-gray-400 hover:dark:bg-gray-800 px-2 py-1 rounded shadow close-button" onClick={handleClose}>Close</button>
|
|
||||||
</div>}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -3,16 +3,17 @@ import {getPicture} from "../utilities/pictures"
|
|||||||
import {InputField, TextAreaField} from "./Form"
|
import {InputField, TextAreaField} from "./Form"
|
||||||
import {Button} from "./Button"
|
import {Button} from "./Button"
|
||||||
import {useState} from "preact/hooks"
|
import {useState} from "preact/hooks"
|
||||||
|
import {Text} from "./Translation"
|
||||||
|
|
||||||
export const PlantThumb = ({ plant, children }) => {
|
export const PlantThumb = ({ plant, children }) => {
|
||||||
|
|
||||||
return <Link href={`/plant/${plant.id}`} class="block h-48">
|
return <Link href={`/plant/${plant.id}`} class="block h-48 cursor-pointer">
|
||||||
<div className="bg-green-400 relative rounded shadow-lg flex flex-col">
|
<div className="group bg-primary-light relative rounded shadow-lg flex flex-col">
|
||||||
<img src={getPicture(plant.id)} alt="" className="object-cover h-48 w-full min-h-48 min-w-48 rounded" />
|
<img src={getPicture(plant.id)} alt="" className="object-cover h-48 w-full min-h-48 min-w-48 rounded" />
|
||||||
<div className="bg-green-700 text-white p-2 text-center absolute bottom-0 w-full rounded-bl rounded-br">
|
<div className="bg-primary group-hover:bg-primary-dark text-white p-2 text-center absolute bottom-0 w-full rounded-bl rounded-br">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
<div title="Actions" className="absolute right-2 top-2 rounded-full flex justify-center items-center bg-green-700 w-6 h-6">
|
<div title="Actions" className="absolute right-2 top-2 rounded-full flex justify-center items-center bg-primary group-hover:bg-primary-dark w-6 h-6">
|
||||||
{plant.actions.length}
|
{plant.actions.length}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -29,13 +30,13 @@ export const PlantForm = ({children, plant, ...props}) => {
|
|||||||
className="mb-2 mt-5"
|
className="mb-2 mt-5"
|
||||||
value={plantForm.name}
|
value={plantForm.name}
|
||||||
onChange={(e) => setPlantForm({ ...plantForm, name: e.target.value }) }>
|
onChange={(e) => setPlantForm({ ...plantForm, name: e.target.value }) }>
|
||||||
Name
|
<Text text="Name" />
|
||||||
</InputField>
|
</InputField>
|
||||||
<TextAreaField name="description"
|
<TextAreaField name="description"
|
||||||
className="mb-5"
|
className="mb-5"
|
||||||
value={plantForm.description}
|
value={plantForm.description}
|
||||||
onChange={(e) => setPlantForm({ ...plantForm, description: e.target.value })}>
|
onChange={(e) => setPlantForm({ ...plantForm, description: e.target.value })}>
|
||||||
Description
|
<Text text="Description" />
|
||||||
</TextAreaField>
|
</TextAreaField>
|
||||||
|
|
||||||
<fieldset className="mb-5 flex gap-2 max-w-[300px] mx-auto">
|
<fieldset className="mb-5 flex gap-2 max-w-[300px] mx-auto">
|
||||||
@@ -48,7 +49,7 @@ export const PlantForm = ({children, plant, ...props}) => {
|
|||||||
className="sr-only peer"
|
className="sr-only peer"
|
||||||
/>
|
/>
|
||||||
<label htmlFor="indoor" className="w-full block peer-checked:bg-blue-700 bg-blue-500 hover:bg-blue-700 px-2 py-1 text-center rounded border dark:border-white">
|
<label htmlFor="indoor" className="w-full block peer-checked:bg-blue-700 bg-blue-500 hover:bg-blue-700 px-2 py-1 text-center rounded border dark:border-white">
|
||||||
Indoor
|
<Text text="Indoor" />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -61,7 +62,7 @@ export const PlantForm = ({children, plant, ...props}) => {
|
|||||||
className="sr-only peer"
|
className="sr-only peer"
|
||||||
/>
|
/>
|
||||||
<label htmlFor="outdoor" className="w-full block peer-checked:bg-blue-700 bg-blue-500 hover:bg-blue-700 px-2 py-1 text-center rounded border dark:border-white">
|
<label htmlFor="outdoor" className="w-full block peer-checked:bg-blue-700 bg-blue-500 hover:bg-blue-700 px-2 py-1 text-center rounded border dark:border-white">
|
||||||
Outdoor
|
<Text text="Outdoor" />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
@@ -70,7 +71,7 @@ export const PlantForm = ({children, plant, ...props}) => {
|
|||||||
className="mt-5"
|
className="mt-5"
|
||||||
value={plantForm.spot}
|
value={plantForm.spot}
|
||||||
onChange={(e) => setPlantForm({ ...plantForm, spot: e.target.value }) }>
|
onChange={(e) => setPlantForm({ ...plantForm, spot: e.target.value }) }>
|
||||||
Spot
|
<Text text="Spot" />
|
||||||
</InputField>
|
</InputField>
|
||||||
|
|
||||||
<Button type="submit" className="block w-full mt-5 mb-2 bg-green-800 hover:bg-green-900 text-white mx-auto px-2 py-1 shadow">
|
<Button type="submit" className="block w-full mt-5 mb-2 bg-green-800 hover:bg-green-900 text-white mx-auto px-2 py-1 shadow">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {Button} from "./Button"
|
|||||||
import { Link } from "preact-router/match"
|
import { Link } from "preact-router/match"
|
||||||
import {useContext} from "preact/hooks"
|
import {useContext} from "preact/hooks"
|
||||||
import {PlantsContext} from "../Contexts"
|
import {PlantsContext} from "../Contexts"
|
||||||
|
import {Text} from "./Translation"
|
||||||
|
|
||||||
export const Tasks = () => {
|
export const Tasks = () => {
|
||||||
|
|
||||||
@@ -19,18 +20,34 @@ export const Tasks = () => {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const taskDelay = (action) => {
|
||||||
|
if (history()[action.id]) {
|
||||||
|
let lastTask = new Date(history()[action.id])
|
||||||
|
const oneDay = 1000 * 60 * 60 * 24
|
||||||
|
return Math.round(((new Date()) - lastTask)/oneDay)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
return <div className="mb-5">
|
return <div className="mb-5">
|
||||||
<h1>Tasks</h1>
|
<h1>
|
||||||
|
<Text text="Tasks" />
|
||||||
|
</h1>
|
||||||
<div>
|
<div>
|
||||||
{plants.map(plant => plant.actions.filter(action => taskIsRequired(action)).map(action => <div className="flex items-center gap-2 my-2">
|
{plants.map(plant => plant.actions.filter(action => taskIsRequired(action)).map(action => <div className="flex items-center gap-2 my-2">
|
||||||
<span><Button className="bg-blue-500 hover:bg-blue-700" onClick={() => doneTask(action.id)}>Done</Button></span>
|
<span>
|
||||||
|
<Button className="bg-blue-500 hover:bg-blue-700" onClick={() => doneTask(action.id)}>
|
||||||
|
<Text text="Done" />
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
<span className="capitalize">
|
<span className="capitalize">
|
||||||
<Link href={`/plant/${plant.id}`} class="font-bold mr-2">{plant.name}</Link>
|
<Link href={`/plant/${plant.id}`} class="font-bold mr-2">{plant.name}</Link>
|
||||||
{action.action_type}
|
<Text text={action.action_type} />
|
||||||
</span>
|
</span>
|
||||||
{Number(action.frequency) === 0
|
{Number(action.frequency) === 0
|
||||||
? <span>when you want</span>
|
? <span><Text text="when you want" /></span>
|
||||||
: <span> every {action.frequency} days</span>}
|
: (taskDelay(action) > 1 ? <span><Text text="since {{count}} days" count={taskDelay(action)} /></span>
|
||||||
|
: <span><Text text="every {{count}} days" count={action.frequency} /></span>)}
|
||||||
</div>))}
|
</div>))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
47
src/components/Translation.js
Normal file
47
src/components/Translation.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import {en} from "../lang/en"
|
||||||
|
import {fr} from "../lang/fr"
|
||||||
|
import {useContext, useEffect, useState} from "preact/hooks"
|
||||||
|
import {createContext} from "preact"
|
||||||
|
|
||||||
|
export const TranslateContext = createContext(null)
|
||||||
|
|
||||||
|
export const Text = ({text, count = null}) => {
|
||||||
|
|
||||||
|
const lang = useContext(TranslateContext)
|
||||||
|
const [translate, setTranslate] = useState()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (lang && lang[text]) {
|
||||||
|
if (count > 1 && lang[text].many) {
|
||||||
|
text = lang[text].many
|
||||||
|
} else if (count <= 1 && lang[text].one) {
|
||||||
|
text = lang[text].one
|
||||||
|
} else {
|
||||||
|
text = lang[text]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count && text.includes('{{count}}')) {
|
||||||
|
text = text.replace('{{count}}', count)
|
||||||
|
}
|
||||||
|
setTranslate(text)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return <>{ translate }</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TranslateProvider = ({children}) => {
|
||||||
|
|
||||||
|
const userLang = navigator.language || navigator.userLanguage
|
||||||
|
let translate = null
|
||||||
|
if (userLang === "en") {
|
||||||
|
translate = en
|
||||||
|
} else if (userLang === "fr") {
|
||||||
|
translate = fr
|
||||||
|
}
|
||||||
|
|
||||||
|
return <TranslateContext.Provider value={translate}>
|
||||||
|
{ children }
|
||||||
|
</TranslateContext.Provider>
|
||||||
|
|
||||||
|
}
|
||||||
10
src/lang/en.js
Normal file
10
src/lang/en.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
export const en = {
|
||||||
|
"Add": "+",
|
||||||
|
"Add Plant": "Add Plant",
|
||||||
|
"Done": "Done",
|
||||||
|
"Tasks": "Tasks",
|
||||||
|
"watering": "watering",
|
||||||
|
"when you want": "when you want",
|
||||||
|
"since {{count}} day": {one: "since {{count}} day", many: "since {{count}} days"}
|
||||||
|
}
|
||||||
39
src/lang/fr.js
Normal file
39
src/lang/fr.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
export const fr = {
|
||||||
|
"Add": "Ajouter",
|
||||||
|
"Add Action": "Ajouter une action",
|
||||||
|
"Add Plant": "Ajouter une plante",
|
||||||
|
"A number of days between 2 actions. You can use 0 to set no notification.": "Un nombre de jours entre deux actions. Vous pouvez utilisé le 0 pour ne pas avoir de notification.",
|
||||||
|
"bathing": "baigner",
|
||||||
|
"Close": "Fermer",
|
||||||
|
"Confirm delete plant": "Confirmer la suppression de la plante",
|
||||||
|
"Description": "Description",
|
||||||
|
"Dark Mode": "Mode sombre",
|
||||||
|
"Delete": "Supprimer",
|
||||||
|
"Delete Plant ?": "Supprimer la plante ?",
|
||||||
|
"Done": "Fait",
|
||||||
|
"Download": "Télécharger",
|
||||||
|
"Download config": "Télécharger votre vos données",
|
||||||
|
"Edit": "Editer",
|
||||||
|
"Edit Plant": "Editer la plante",
|
||||||
|
"every {{count}} days": {one: "tous les jours", many: "tous les {{count}} jours"},
|
||||||
|
"Frequency": "Fréquence",
|
||||||
|
"Indoor": "Intérieur",
|
||||||
|
"last task": "dernière fois",
|
||||||
|
"Me": "Moi",
|
||||||
|
"Name": "Nom",
|
||||||
|
"never": "jamais",
|
||||||
|
"Outdoor": "Extérieur",
|
||||||
|
"Plantes": "Plantes",
|
||||||
|
"Plants": "Plantes",
|
||||||
|
"{{count}} Plants": "{{count}} Plantes",
|
||||||
|
"Profile": "Profile",
|
||||||
|
"Spot": "Emplacement",
|
||||||
|
"Spot:": "Emplacement : ",
|
||||||
|
"spraying": "asperger",
|
||||||
|
"Style": "Style",
|
||||||
|
"Tasks": "Tâches",
|
||||||
|
"watering": "arrossage",
|
||||||
|
"when you want": "quand je veux",
|
||||||
|
"since {{count}} days": {one: "depuis {{count}} jour", many: "depuis {{count}} jours"}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import { PageLayout } from "../components/PageLayout"
|
|||||||
import {PlantForm, PlantThumb} from "../components/Plants"
|
import {PlantForm, PlantThumb} from "../components/Plants"
|
||||||
import { PlantsContext } from "../Contexts"
|
import { PlantsContext } from "../Contexts"
|
||||||
import {Tasks} from "../components/Tasks"
|
import {Tasks} from "../components/Tasks"
|
||||||
|
import {Text} from "../components/Translation";
|
||||||
|
|
||||||
export const Home = () => {
|
export const Home = () => {
|
||||||
const [addModal, setAddModal] = useState(false)
|
const [addModal, setAddModal] = useState(false)
|
||||||
@@ -22,7 +23,7 @@ export const Home = () => {
|
|||||||
|
|
||||||
<Tasks />
|
<Tasks />
|
||||||
|
|
||||||
<h1>Plants</h1>
|
<h1><Text text="Plants" /></h1>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2">
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2">
|
||||||
{plants.map((plant) => (
|
{plants.map((plant) => (
|
||||||
<PlantThumb plant={plant}>{plant.name}</PlantThumb>
|
<PlantThumb plant={plant}>{plant.name}</PlantThumb>
|
||||||
@@ -30,17 +31,17 @@ export const Home = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="w-full sticky bottom-5 flex justify-end">
|
<div className="w-full sticky bottom-5 flex justify-end">
|
||||||
<div onClick={() => setAddModal(true)}
|
<div onClick={() => setAddModal(true)}
|
||||||
className="rounded-full shadow w-16 h-16 flex items-center justify-center cursor-pointer bg-green-800 hover-bg-green-900 text-white">
|
className="rounded-full shadow w-16 h-16 flex items-center justify-center cursor-pointer bg-primary hover:bg-primary-dark text-white">
|
||||||
Add +
|
<Text text="+" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{typeof window !== "undefined" && createPortal(
|
{typeof window !== "undefined" && createPortal(
|
||||||
<Modal isOpen={addModal} onChange={e => closeModal(e, setAddModal)}>
|
<Modal isOpen={addModal} onChange={e => closeModal(e, setAddModal)}>
|
||||||
<ModalTitle>
|
<ModalTitle>
|
||||||
Add Plant
|
<Text text="Add Plant" />
|
||||||
</ModalTitle>
|
</ModalTitle>
|
||||||
<PlantForm plant={{}} onChange={handleSubmit}>Add</PlantForm>
|
<PlantForm plant={{}} onChange={handleSubmit}><Text text="Add" /></PlantForm>
|
||||||
</Modal>,
|
</Modal>,
|
||||||
document.getElementById('app')
|
document.getElementById('app')
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {EditSVG, PlusSVG} from "../components/SVG"
|
|||||||
import {classNames} from "../utilities/classNames"
|
import {classNames} from "../utilities/classNames"
|
||||||
import {PlantForm} from "../components/Plants"
|
import {PlantForm} from "../components/Plants"
|
||||||
import {ACTION_TYPES, actionId} from "../utilities/actions"
|
import {ACTION_TYPES, actionId} from "../utilities/actions"
|
||||||
|
import {Text} from "../components/Translation";
|
||||||
|
|
||||||
const Plant = ({id}) => {
|
const Plant = ({id}) => {
|
||||||
|
|
||||||
@@ -61,8 +62,8 @@ const Plant = ({id}) => {
|
|||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<h1 className="my-2 flex-1 text-center">{ plant.name }</h1>
|
<h1 className="my-2 flex-1 text-center">{ plant.name }</h1>
|
||||||
<div>
|
<div>
|
||||||
<Button className="bg-blue-500 hover:bg-blue-700 mr-2" onClick={() => handleOpenEditModal()}>Edit</Button>
|
<Button className="bg-blue-500 hover:bg-blue-700 mr-2" onClick={() => handleOpenEditModal()}><Text text="Edit" /></Button>
|
||||||
<Button className="bg-red-500 hover:bg-red-700" onClick={() => setDeleteModal(true)}>Delete</Button>
|
<Button className="bg-red-500 hover:bg-red-700" onClick={() => setDeleteModal(true)}><Text text="Delete" /></Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-around flex-wrap gap-5">
|
<div className="flex justify-around flex-wrap gap-5">
|
||||||
@@ -77,13 +78,13 @@ const Plant = ({id}) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
{plant.description && <p>{ plant.description }</p>}
|
{plant.description && <p>{ plant.description }</p>}
|
||||||
{plant.hasOwnProperty('indoor') && <span className="inline-block bg-green-500 text-white px-2 py-1 text-sm font-semibold rounded shadow my-2">{ plant.indoor ? 'Indoor' : 'Outdoor' }</span>}
|
{plant.hasOwnProperty('indoor') && <span className="inline-block bg-green-500 text-white px-2 py-1 text-sm font-semibold rounded shadow my-2"><Text text={ plant.indoor ? 'Indoor' : 'Outdoor' } /></span>}
|
||||||
{plant.spot && <p>Spot: <span className="font-bold">{ plant.spot }</span></p>}
|
{plant.spot && <p><Text text="Spot:" /> <span className="font-bold">{ plant.spot }</span></p>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<h2>Actions</h2>
|
<h2><Text text="Actions" /></h2>
|
||||||
<SmallButton className="bg-blue-500 hover:bg-blue-700 mb-2 mt-5"
|
<SmallButton className="bg-blue-500 hover:bg-blue-700 mb-2 mt-5"
|
||||||
onClick={() => setAddModal(true)}>
|
onClick={() => setAddModal(true)}>
|
||||||
<PlusSVG className="w-4 h-4" />
|
<PlusSVG className="w-4 h-4" />
|
||||||
@@ -100,14 +101,14 @@ const Plant = ({id}) => {
|
|||||||
<span>
|
<span>
|
||||||
<Button className={classNames(isDone ? "bg-green-500 hover:bg-green-700" : "bg-blue-500 hover:bg-blue-700")}
|
<Button className={classNames(isDone ? "bg-green-500 hover:bg-green-700" : "bg-blue-500 hover:bg-blue-700")}
|
||||||
onClick={() => doneTask(action.id)}>
|
onClick={() => doneTask(action.id)}>
|
||||||
Done
|
<Text text="Done" />
|
||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
<span className="capitalize font-bold">{action.action_type}</span>
|
<span className="capitalize font-bold"><Text text={action.action_type} /></span>
|
||||||
{Number(action.frequency) === 0
|
{Number(action.frequency) === 0
|
||||||
? <span>when you want</span>
|
? <span><Text text="when you want" /></span>
|
||||||
: <span> every {action.frequency} days</span>}
|
: <span><Text text="every {{count}} days" count={action.frequency} /></span>}
|
||||||
<span className="text-gray-500">last task {lastTask ? lastTask.toFrDate() : 'never'}</span>
|
<span className="text-gray-500"><Text text="last task" /> {lastTask ? lastTask.toFrDate() : <Text text="never" />}</span>
|
||||||
{/*<span>{ archived.where('action', actionId(action.action_type)).length }</span>*/}
|
{/*<span>{ archived.where('action', actionId(action.action_type)).length }</span>*/}
|
||||||
</div>
|
</div>
|
||||||
})}
|
})}
|
||||||
@@ -115,7 +116,7 @@ const Plant = ({id}) => {
|
|||||||
|
|
||||||
{createPortal(
|
{createPortal(
|
||||||
<Modal isOpen={addModal} onChange={e => closeModal(e, setAddModal)}>
|
<Modal isOpen={addModal} onChange={e => closeModal(e, setAddModal)}>
|
||||||
<ModalTitle>Add Action</ModalTitle>
|
<ModalTitle><Text text="Add Action" /></ModalTitle>
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
|
|
||||||
<SelectField name="action_type"
|
<SelectField name="action_type"
|
||||||
@@ -123,7 +124,7 @@ const Plant = ({id}) => {
|
|||||||
value={actionForm.action_type}
|
value={actionForm.action_type}
|
||||||
options={ACTION_TYPES}
|
options={ACTION_TYPES}
|
||||||
onChange={(e) => setActionForm({ ...actionForm, action_type: e.target.value })}>
|
onChange={(e) => setActionForm({ ...actionForm, action_type: e.target.value })}>
|
||||||
Name
|
<Text text="Name" />
|
||||||
</SelectField>
|
</SelectField>
|
||||||
|
|
||||||
<InputField name="frequency"
|
<InputField name="frequency"
|
||||||
@@ -132,13 +133,13 @@ const Plant = ({id}) => {
|
|||||||
textSupport="A number of days between 2 actions. You can use 0 to set no notification."
|
textSupport="A number of days between 2 actions. You can use 0 to set no notification."
|
||||||
value={actionForm.frequency}
|
value={actionForm.frequency}
|
||||||
onChange={(e) => setActionForm({ ...actionForm, frequency: e.target.value })}>
|
onChange={(e) => setActionForm({ ...actionForm, frequency: e.target.value })}>
|
||||||
Frequency
|
<Text text="Frequency" />
|
||||||
</InputField>
|
</InputField>
|
||||||
|
|
||||||
|
|
||||||
<Button type="submit"
|
<Button type="submit"
|
||||||
className="block w-full mt-5 mb-2 bg-green-800 hover:bg-green-900 text-white mx-auto px-2 py-1 shadow">
|
className="block w-full mt-5 mb-2 bg-green-800 hover:bg-green-900 text-white mx-auto px-2 py-1 shadow">
|
||||||
Add
|
<Text text="Add" />
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</Modal>,
|
</Modal>,
|
||||||
@@ -148,9 +149,9 @@ const Plant = ({id}) => {
|
|||||||
{createPortal(
|
{createPortal(
|
||||||
<Modal isOpen={editModal} onChange={e => closeModal(e, setEditModal)}>
|
<Modal isOpen={editModal} onChange={e => closeModal(e, setEditModal)}>
|
||||||
<ModalTitle>
|
<ModalTitle>
|
||||||
Edit Plant
|
<Text text="Edit Plant" />
|
||||||
</ModalTitle>
|
</ModalTitle>
|
||||||
<PlantForm plant={plant} onChange={handleEditSubmit}>Edit</PlantForm>
|
<PlantForm plant={plant} onChange={handleEditSubmit}><Text text="Edit" /></PlantForm>
|
||||||
</Modal>,
|
</Modal>,
|
||||||
app
|
app
|
||||||
)}
|
)}
|
||||||
@@ -158,9 +159,9 @@ const Plant = ({id}) => {
|
|||||||
{createPortal(
|
{createPortal(
|
||||||
<Modal isOpen={deleteModal} onChange={e => closeModal(e, setDeleteModal)}>
|
<Modal isOpen={deleteModal} onChange={e => closeModal(e, setDeleteModal)}>
|
||||||
<ModalTitle>
|
<ModalTitle>
|
||||||
Delete Plant ?
|
<Text text="Delete Plant ?" />
|
||||||
</ModalTitle>
|
</ModalTitle>
|
||||||
<Button className="bg-red-500 hover:bg-red-700 mt-10" onClick={handleDeletePlant}>Confirm delete plant</Button>
|
<Button className="bg-red-500 hover:bg-red-700 mt-10" onClick={handleDeletePlant}><Text text="Confirm delete plant" /></Button>
|
||||||
</Modal>,
|
</Modal>,
|
||||||
app
|
app
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import { useContext, useEffect, useState } from "preact/hooks";
|
import { useContext, useEffect, useState } from "preact/hooks";
|
||||||
import { PageLayout } from "../components/PageLayout";
|
import { PageLayout } from "../components/PageLayout";
|
||||||
import {PlantsContext, UserContext} from "../Contexts";
|
import {PlantsContext, UserContext} from "../Contexts";
|
||||||
|
import {Button} from "../components/Button";
|
||||||
|
import {useLocalStorage} from "../hooks/LocalStorageHook";
|
||||||
|
import {Text} from "../components/Translation";
|
||||||
|
|
||||||
export default function Profile() {
|
export default function Profile() {
|
||||||
const [user, setUser] = useContext(UserContext)
|
const [user, setUser] = useContext(UserContext)
|
||||||
const {plants} = useContext(PlantsContext)
|
const {plants} = useContext(PlantsContext)
|
||||||
const [darkMode, setDarkMode] = useState(false)
|
const [darkMode, setDarkMode] = useState(false)
|
||||||
|
const [data, setData] = useLocalStorage('data', {})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDarkMode(user.dark_mode)
|
setDarkMode(user.dark_mode)
|
||||||
@@ -19,8 +23,21 @@ export default function Profile() {
|
|||||||
setUser({...user, dark_mode: !user.dark_mode})
|
setUser({...user, dark_mode: !user.dark_mode})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDownloadConfig = async () => {
|
||||||
|
const fileName = "plants"
|
||||||
|
const json = JSON.stringify(data)
|
||||||
|
const blob = new Blob([json], {type: 'application/json'})
|
||||||
|
const href = await URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = href
|
||||||
|
link.download = fileName + ".json"
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
}
|
||||||
|
|
||||||
return <PageLayout>
|
return <PageLayout>
|
||||||
<h1 className="">Profile</h1>
|
<h1 className=""><Text text="Profile" /></h1>
|
||||||
<label htmlFor="dark_mode">
|
<label htmlFor="dark_mode">
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
id="dark_mode"
|
id="dark_mode"
|
||||||
@@ -28,10 +45,17 @@ export default function Profile() {
|
|||||||
className="mr-2"
|
className="mr-2"
|
||||||
checked={user.dark_mode}
|
checked={user.dark_mode}
|
||||||
onChange={() => handleChangeDarkMode()} />
|
onChange={() => handleChangeDarkMode()} />
|
||||||
Dark Mode
|
<Text text="Dark Mode" />
|
||||||
</label>
|
</label>
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
{plants.length} Plants
|
<Text text="{{count}} Plants" count={plants.length} />
|
||||||
|
</div>
|
||||||
|
<div className="mt-5">
|
||||||
|
<h2><Text text="Download config" /></h2>
|
||||||
|
<Button className="bg-primary hover:bg-primary-dark border-primary-dark ring-primary"
|
||||||
|
onClick={handleDownloadConfig}>
|
||||||
|
<Text text="Download" />
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
}
|
}
|
||||||
79
src/routes/Style.js
Normal file
79
src/routes/Style.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import {PageLayout} from "../components/PageLayout";
|
||||||
|
import {Button} from "../components/Button";
|
||||||
|
|
||||||
|
export default function Style() {
|
||||||
|
|
||||||
|
return <PageLayout>
|
||||||
|
<h1>Style</h1>
|
||||||
|
|
||||||
|
<div className="flex">
|
||||||
|
<div>
|
||||||
|
<h1>H1 Head</h1>
|
||||||
|
<h2>H2 Headline</h2>
|
||||||
|
<h3>H3 Headline</h3>
|
||||||
|
<h4>H4 Headline</h4>
|
||||||
|
<h5>H5 Headline</h5>
|
||||||
|
<h6>H6 Headline</h6>
|
||||||
|
<p>Text paragraph</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="my-2 flex gap-2">
|
||||||
|
<Button className="bg-primary hover:bg-primary-dark border-primary-dark ring-primary">Primary</Button>
|
||||||
|
<Button className="bg-primary-light hover:bg-primary text-primary hover:text-primary-darkest ring-primary">Smooth</Button>
|
||||||
|
<Button className="border-primary text-primary ring-primary-dark">Ghost</Button>
|
||||||
|
<Button className="border-primary text-primary ring-primary-dark border-transparent">Raised</Button>
|
||||||
|
<Button className="bg-primary hover:bg-primary-dark border-primary-dark ring-primary rounded-full">Previous</Button>
|
||||||
|
</div>
|
||||||
|
<div className="my-2">
|
||||||
|
<Button>Secondary</Button>
|
||||||
|
<Button>Smooth</Button>
|
||||||
|
<Button>Ghost</Button>
|
||||||
|
<Button>Raised</Button>
|
||||||
|
<Button>Previous</Button>
|
||||||
|
</div>
|
||||||
|
<div className="my-2">
|
||||||
|
<Button>Warning</Button>
|
||||||
|
<Button>Smooth</Button>
|
||||||
|
<Button>Ghost</Button>
|
||||||
|
<Button>Raised</Button>
|
||||||
|
<Button>Previous</Button>
|
||||||
|
</div>
|
||||||
|
<div className="my-2">
|
||||||
|
<Button>Success</Button>
|
||||||
|
<Button>Smooth</Button>
|
||||||
|
<Button>Ghost</Button>
|
||||||
|
<Button>Raised</Button>
|
||||||
|
<Button>Previous</Button>
|
||||||
|
</div>
|
||||||
|
<div className="my-2">
|
||||||
|
<Button>Danger</Button>
|
||||||
|
<Button>Smooth</Button>
|
||||||
|
<Button>Ghost</Button>
|
||||||
|
<Button>Raised</Button>
|
||||||
|
<Button>Previous</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="flex">
|
||||||
|
<div className="bg-primary-light my-2 p-2 flex-1">
|
||||||
|
Primary light
|
||||||
|
</div>
|
||||||
|
<div className="bg-primary my-2 p-2 flex-1">
|
||||||
|
Primary
|
||||||
|
</div>
|
||||||
|
<div className="bg-primary-dark my-2 p-2 flex-1">
|
||||||
|
Primary dark
|
||||||
|
</div>
|
||||||
|
<div className="bg-primary-darkest my-2 p-2 flex-1">
|
||||||
|
Primary darkest
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-gray-500 my-2">
|
||||||
|
Background Primary
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PageLayout>
|
||||||
|
}
|
||||||
@@ -1,29 +1,58 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
mode: 'jit',
|
mode: 'jit',
|
||||||
purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
|
purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
|
||||||
darkMode: 'class', // or 'media' or 'class'
|
darkMode: 'class', // or 'media' or 'class'
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
minHeight: {
|
colors: {
|
||||||
'10': '2.5rem',
|
primary: {
|
||||||
'12': '3rem',
|
light: "#70E000",
|
||||||
'24': '6rem',
|
DEFAULT: "#008000",
|
||||||
'32': '9rem',
|
dark: "#006400",
|
||||||
'48': '12rem',
|
darkest: '#004B23',
|
||||||
'80': '20rem',
|
},
|
||||||
},
|
secondary: {
|
||||||
minWidth: {
|
light: "#1E89ED",
|
||||||
'10': '2.5rem',
|
DEFAULT: "#007ED9",
|
||||||
'12': '3rem',
|
dark: "#00528C",
|
||||||
'24': '6rem',
|
darkest: '#003b64',
|
||||||
'32': '9rem',
|
},
|
||||||
'48': '12rem',
|
warning: {
|
||||||
'80': '20rem',
|
light: "#1E89ED",
|
||||||
|
DEFAULT: "#007ED9",
|
||||||
|
dark: "#00528C",
|
||||||
|
darkest: '#003b64',
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
light: "#1E89ED",
|
||||||
|
DEFAULT: "#007ED9",
|
||||||
|
dark: "#00528C",
|
||||||
|
darkest: '#003b64',
|
||||||
|
},
|
||||||
|
danger: {
|
||||||
|
light: "#1E89ED",
|
||||||
|
DEFAULT: "#007ED9",
|
||||||
|
dark: "#00528C",
|
||||||
|
darkest: '#003b64',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
minHeight: {
|
||||||
|
'10': '2.5rem',
|
||||||
|
'12': '3rem',
|
||||||
|
'24': '6rem',
|
||||||
|
'32': '9rem',
|
||||||
|
'48': '12rem',
|
||||||
|
'80': '20rem',
|
||||||
|
},
|
||||||
|
minWidth: {
|
||||||
|
'10': '2.5rem',
|
||||||
|
'12': '3rem',
|
||||||
|
'24': '6rem',
|
||||||
|
'32': '9rem',
|
||||||
|
'48': '12rem',
|
||||||
|
'80': '20rem',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
plugins: [],
|
||||||
},
|
}
|
||||||
variants: {
|
|
||||||
extend: {},
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user