diff --git a/src/components/Add.jsx b/src/components/Add.tsx similarity index 82% rename from src/components/Add.jsx rename to src/components/Add.tsx index e0062e5..b1cf2d9 100644 --- a/src/components/Add.jsx +++ b/src/components/Add.tsx @@ -1,12 +1,17 @@ import React from "react"; -import useForm from "../hooks/useForm"; -import useFirebase from "../hooks/useFirebase"; +import useForm, { InputInterface, ActionInterface } from "../hooks/useForm"; +import useFirebase, { WordInterface } from "../hooks/useFirebase"; + +interface PropsInterface { + onAdd: Function; +} const INITIAL_STATE = [ { name: "type", - type: "select", + type: "select" as const, + value: "", placeholder: "Type", required: true, options: [ @@ -19,7 +24,7 @@ const INITIAL_STATE = [ }, { name: "article", - type: "select", + type: "select" as const, value: "", required: true, options: [ @@ -33,26 +38,26 @@ const INITIAL_STATE = [ { name: "word", placeholder: "Word", - type: "text", + type: "text" as const, value: "", required: true }, { name: "meaning", placeholder: "Meaning", - type: "text", + type: "text" as const, value: "", required: true }, { name: "note", placeholder: "Note", - type: "text", + type: "text" as const, value: "" } ]; -function reducer(state, action) { +function reducer(state: InputInterface[], action: ActionInterface) { const { type, name, value } = action; if (type === "edit" && name === "type") { @@ -77,11 +82,11 @@ function reducer(state, action) { return state; } -function validator(inputs) { +function validator(inputs: InputInterface[]) { const inputMap = inputs.reduce((map, input) => { map[input.name] = input; return map; - }, {}); + }, {} as { [key: string]: InputInterface }); const requiredInputs = !inputs.some(input => { if (input.required && !input.value) { @@ -103,7 +108,7 @@ function validator(inputs) { } } -function Add({ onAdd }) { +function Add({ onAdd }: PropsInterface) { const { state, isValid, change, reset } = useForm(INITIAL_STATE, { reducer, validator @@ -116,7 +121,7 @@ function Add({ onAdd }) { ...prev, [property.name]: property.value }; - }, {}); + }, {} as WordInterface); addWordToFirebase(word).then(() => { reset(); onAdd(); @@ -135,7 +140,7 @@ function Add({ onAdd }) { onChange={change} disabled={input.disabled} > - {input.options.map(option => ( + {input.options?.map(option => ( diff --git a/src/components/App.jsx b/src/components/App.tsx similarity index 69% rename from src/components/App.jsx rename to src/components/App.tsx index 0206003..1ab025e 100644 --- a/src/components/App.jsx +++ b/src/components/App.tsx @@ -1,19 +1,23 @@ import React, { useState, useEffect } from "react"; import "./App.css"; -import useFirebase from "../hooks/useFirebase"; +import useFirebase, { WordInterface } from "../hooks/useFirebase"; import Login from "./Login"; import Add from "./Add"; import View from "./View"; +interface Global { + logout?: Function; +} + function App() { - const [words, setWords] = useState([]); + const [words, setWords] = useState([]); const { user, getAllWords, logout } = useFirebase(); - window.logout = logout; + (window as Global).logout = logout; const getWords = () => { - getAllWords().then(setWords); + getAllWords().then(words => words && setWords(words)); }; useEffect(getWords, [getAllWords]); diff --git a/src/components/Login.jsx b/src/components/Login.tsx similarity index 89% rename from src/components/Login.jsx rename to src/components/Login.tsx index 523d109..b8d3709 100644 --- a/src/components/Login.jsx +++ b/src/components/Login.tsx @@ -6,14 +6,14 @@ import useFirebase from "../hooks/useFirebase"; const INITIAL_STATE = [ { placeholder: "Email", - type: "email", + type: "email" as const, name: "email", value: "blessanm86@gmail.com", required: true }, { placeholder: "Password", - type: "password", + type: "password" as const, name: "password", value: "", required: true @@ -30,7 +30,7 @@ function Login() { ...prev, [property.name]: property.value }; - }, {}); + }, {} as { email: string; password: string }); loginToFirebase(email, password); reset(); }; diff --git a/src/components/View.jsx b/src/components/View.tsx similarity index 77% rename from src/components/View.jsx rename to src/components/View.tsx index 086e044..a1646cb 100644 --- a/src/components/View.jsx +++ b/src/components/View.tsx @@ -1,6 +1,11 @@ import React from "react"; +import { WordInterface } from "../hooks/useFirebase"; -function View({ words = [] }) { +interface PropsInterface { + words: WordInterface[]; +} + +function View({ words = [] }: PropsInterface) { return (
{words.map(({ word, article, meaning, note, type }, index) => ( diff --git a/src/hooks/useFirebase.js b/src/hooks/useFirebase.ts similarity index 62% rename from src/hooks/useFirebase.js rename to src/hooks/useFirebase.ts index c56f4ed..974f762 100644 --- a/src/hooks/useFirebase.js +++ b/src/hooks/useFirebase.ts @@ -3,6 +3,15 @@ import * as firebase from "firebase/app"; import "firebase/auth"; import "firebase/firestore"; +export interface WordInterface { + article: string; + meaning: string; + note: string; + timestamp: firebase.firestore.Timestamp; + type: string; + word: string; +} + const firebaseConfig = { apiKey: "AIzaSyAWznMXfEF-FMt9OUT10Twzxr382zfs3MU", authDomain: "mein-woerterbuch.firebaseapp.com", @@ -14,7 +23,7 @@ const firebaseConfig = { measurementId: "G-06SEN5MFN2" }; -function loginToFirebase(email, password) { +function loginToFirebase(email: string, password: string): void { firebase .auth() .signInWithEmailAndPassword(email, password) @@ -34,7 +43,7 @@ function logout() { firebase.auth().signOut(); } -function addWordToFirebase(word) { +function addWordToFirebase(word: WordInterface): Promise { word.timestamp = firebase.firestore.Timestamp.now(); return firebase @@ -42,39 +51,45 @@ function addWordToFirebase(word) { .collection("woerte") .add(word) .then(function(docRef) { - console.log("Document written with ID: ", docRef.id); + const result = `Document written with ID: ${docRef.id}`; + console.log(result); + return result; }) .catch(function(error) { - console.error("Error adding document: ", error); + const result = `Error adding document: ${error}`; + console.error(result); + return result; }); } -function getAllWords() { +function getAllWords(): Promise { return firebase .firestore() .collection("woerte") .orderBy("timestamp", "desc") .get() .then(snapshot => { - const words = []; - snapshot.forEach(doc => words.push(doc.data())); + const words: WordInterface[] = []; + snapshot.forEach(doc => words.push(doc.data() as WordInterface)); return words; }) - .catch(err => console.log(err)); + .catch(error => { + console.log(error); + }); } -let firbaseAuthStateListener; - function useFirebase() { - const [user, setUser] = useState(null); + const [user, setUser] = useState(null); !firebase.apps.length && firebase.initializeApp(firebaseConfig); - useEffect(() => { - firbaseAuthStateListener = firebase.auth().onAuthStateChanged(setUser); - - return () => firbaseAuthStateListener(); - }, []); + useEffect( + () => + firebase.auth().onAuthStateChanged(user => { + user && setUser(user); + }), + [] + ); return { user, diff --git a/src/hooks/useForm.ts b/src/hooks/useForm.ts index 5ac90fe..2161c9b 100644 --- a/src/hooks/useForm.ts +++ b/src/hooks/useForm.ts @@ -1,8 +1,8 @@ -import { useReducer, ChangeEvent } from "react"; +import { useReducer, ChangeEventHandler, ChangeEvent } from "react"; -interface Input { +export interface InputInterface { name: string; - type: string; + type: "text" | "password" | "email" | "select"; placeholder?: string; required?: boolean; disabled?: boolean; @@ -10,26 +10,31 @@ interface Input { value: string; } -interface Options { +interface OptionsInterface { reducer?: Function; validator?: Function; } -interface State { - state: Array; +interface FormInterface { + state: InputInterface[]; isValid: boolean; - change: Function; + change: ChangeEventHandler; reset: Function; } -interface Action { +export interface ActionInterface { type: string; name?: string; value?: string; } -function useForm(inputs: Array, options: Options = {}): State { - function change(evt: ChangeEvent) { +function useForm( + inputs: InputInterface[], + options: OptionsInterface = {} +): FormInterface { + function change( + evt: ChangeEvent + ): void { const { target: { name, value } } = evt; @@ -40,7 +45,7 @@ function useForm(inputs: Array, options: Options = {}): State { dispatch({ type: "reset" }); } - function validator(inputs: Array) { + function validator(inputs: InputInterface[]) { return !inputs.some(input => { if (input.required && !input.value) { return true; @@ -50,7 +55,10 @@ function useForm(inputs: Array, options: Options = {}): State { }); } - function reducer(state: State, action: Action): State { + function reducer( + state: FormInterface, + action: ActionInterface + ): FormInterface { const { state: inputs } = state; const { type, name, value } = action;