Update requirement files with new feature templates and fix backend API error message, along with mobile project config updates and documentation improvements
This commit is contained in:
@@ -18,11 +18,11 @@ import { QuoteItem } from '../components/QuoteItem';
|
||||
import { QuoteStore } from '../store';
|
||||
import { getQuotes } from '../store/Selectors';
|
||||
|
||||
const Home = () => {
|
||||
const quotes = useStoreState(QuoteStore, getQuotes);
|
||||
const [amountLoaded, setAmountLoaded] = useState(20);
|
||||
const Home: React.FC = () => {
|
||||
const quotes: any = useStoreState(QuoteStore, getQuotes);
|
||||
const [amountLoaded, setAmountLoaded] = useState<number>(20);
|
||||
|
||||
const fetchMore = async (e) => {
|
||||
const fetchMore = async (e: any) => {
|
||||
setAmountLoaded((amountLoaded) => amountLoaded + 20);
|
||||
e.target.complete();
|
||||
};
|
||||
@@ -51,11 +51,13 @@ const Home = () => {
|
||||
|
||||
<IonList>
|
||||
<IonRow>
|
||||
{quotes.map((quote, index) => {
|
||||
if (index <= amountLoaded && quote.author) {
|
||||
return <QuoteItem key={index} quote={quote} />;
|
||||
} else return '';
|
||||
})}
|
||||
{quotes.map(
|
||||
(quote: { id: string | number; text: string; author: string }, index: number) => {
|
||||
if (index <= amountLoaded && quote.author) {
|
||||
return <QuoteItem key={index} quote={quote} />;
|
||||
} else return '';
|
||||
}
|
||||
)}
|
||||
</IonRow>
|
||||
</IonList>
|
||||
|
@@ -1,88 +0,0 @@
|
||||
import { IonBackButton, IonButton, IonButtons, IonCard, IonCardContent, IonCol, IonContent, IonHeader, IonIcon, IonImg, IonPage, IonRow, IonTitle, IonToolbar, useIonToast } from '@ionic/react';
|
||||
import { bookmarkOutline, checkmarkOutline, copyOutline } from 'ionicons/icons';
|
||||
import { useStoreState } from 'pullstate';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
import { QuoteStore } from '../store';
|
||||
import { addSavedQuote, removeSavedQuote } from '../store/QuoteStore';
|
||||
import { getQuote, getSavedQuotes } from '../store/Selectors';
|
||||
|
||||
import { Clipboard } from '@capacitor/clipboard';
|
||||
|
||||
const Quote = () => {
|
||||
|
||||
const { id } = useParams();
|
||||
const quote = useStoreState(QuoteStore, getQuote(id));
|
||||
const saved = useStoreState(QuoteStore, getSavedQuotes);
|
||||
const [ bookmarked, setBookmarked ] = useState(false);
|
||||
|
||||
const [ present ] = useIonToast();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
setBookmarked(saved.includes(parseInt(id)));
|
||||
}, [ saved, id ]);
|
||||
|
||||
const copyQuote = async () => {
|
||||
|
||||
await Clipboard.write({
|
||||
|
||||
string: quote.text
|
||||
});
|
||||
|
||||
present({
|
||||
|
||||
header: "Success",
|
||||
message: "Quote copied to clipboard!",
|
||||
duration: 2500,
|
||||
color: "primary"
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton text="Home" />
|
||||
</IonButtons>
|
||||
<IonTitle>Quote</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Quote</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonCard className="animate__animated animate__slideInRight animate__faster">
|
||||
<IonImg src={ quote.image } alt="quote cover" />
|
||||
<IonCardContent>
|
||||
<h1>{ quote.text }</h1>
|
||||
<p>- { quote.author }</p>
|
||||
</IonCardContent>
|
||||
|
||||
<IonRow>
|
||||
<IonCol size="6">
|
||||
<IonButton fill={ !bookmarked ? "outline" : "solid" } onClick={ () => bookmarked ? removeSavedQuote(quote.id) : addSavedQuote(quote.id) }>
|
||||
<IonIcon icon={ bookmarked ? checkmarkOutline : bookmarkOutline } />
|
||||
{ bookmarked ? "Bookmarked" : "Save as Bookmark" }
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="4">
|
||||
<IonButton fill="outline" onClick={ copyQuote }>
|
||||
<IonIcon icon={ copyOutline } />
|
||||
Copy Quote
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCard>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Quote;
|
121
03_source/mobile/src/pages/DemoQuoteApp/AppPages/Quote.tsx
Normal file
121
03_source/mobile/src/pages/DemoQuoteApp/AppPages/Quote.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
import {
|
||||
IonBackButton,
|
||||
IonButton,
|
||||
IonButtons,
|
||||
IonCard,
|
||||
IonCardContent,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonIcon,
|
||||
IonImg,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
useIonToast,
|
||||
} from '@ionic/react';
|
||||
import { bookmarkOutline, checkmarkOutline, copyOutline } from 'ionicons/icons';
|
||||
import { useStoreState } from 'pullstate';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
import { QuoteStore } from '../store';
|
||||
import { addSavedQuote, removeSavedQuote } from '../store/QuoteStore';
|
||||
import { getQuote, getSavedQuotes } from '../store/Selectors';
|
||||
|
||||
import { Clipboard } from '@capacitor/clipboard';
|
||||
|
||||
const Quote: React.FC = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const quote = useStoreState(QuoteStore, (s) => {
|
||||
try {
|
||||
const quotes = getQuote(id);
|
||||
if (!Array.isArray(quotes)) {
|
||||
return undefined;
|
||||
}
|
||||
return quotes.find((q) => q.id === id);
|
||||
} catch (error) {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
const saved = useStoreState(QuoteStore, getSavedQuotes);
|
||||
|
||||
if (!quote) {
|
||||
return (
|
||||
<IonPage>
|
||||
<IonContent>Quote not found</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
}
|
||||
const [bookmarked, setBookmarked] = useState<boolean>(false);
|
||||
|
||||
const [present] = useIonToast();
|
||||
|
||||
useEffect(() => {
|
||||
const quoteId = typeof id === 'string' ? parseInt(id) : id;
|
||||
setBookmarked(saved.some((item) => item.id === quoteId));
|
||||
}, [saved, id]);
|
||||
|
||||
const copyQuote = async () => {
|
||||
await Clipboard.write({
|
||||
string: quote.text,
|
||||
});
|
||||
|
||||
present({
|
||||
header: 'Success',
|
||||
message: 'Quote copied to clipboard!',
|
||||
duration: 2500,
|
||||
color: 'primary',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonBackButton text="Home" />
|
||||
</IonButtons>
|
||||
<IonTitle>Quote</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Quote</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonCard className="animate__animated animate__slideInRight animate__faster">
|
||||
<IonImg src={quote.image} alt="quote cover" />
|
||||
<IonCardContent>
|
||||
<h1>{quote.text}</h1>
|
||||
<p>- {quote.author}</p>
|
||||
</IonCardContent>
|
||||
|
||||
<IonRow>
|
||||
<IonCol size="6">
|
||||
<IonButton
|
||||
fill={!bookmarked ? 'outline' : 'solid'}
|
||||
onClick={() => (bookmarked ? removeSavedQuote(quote.id) : addSavedQuote(quote.id))}
|
||||
>
|
||||
<IonIcon icon={bookmarked ? checkmarkOutline : bookmarkOutline} />
|
||||
{bookmarked ? 'Bookmarked' : 'Save as Bookmark'}
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
|
||||
<IonCol size="4">
|
||||
<IonButton fill="outline" onClick={copyQuote}>
|
||||
<IonIcon icon={copyOutline} />
|
||||
Copy Quote
|
||||
</IonButton>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
</IonCard>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Quote;
|
@@ -1,74 +0,0 @@
|
||||
import { IonButtons, IonCol, IonContent, IonGrid, IonHeader, IonInfiniteScroll, IonInfiniteScrollContent, IonList, IonMenuButton, IonPage, IonRow, IonTitle, IonToolbar } from '@ionic/react';
|
||||
import { useStoreState } from 'pullstate';
|
||||
import { useState } from 'react';
|
||||
import { QuoteItem } from '../components/QuoteItem';
|
||||
import { QuoteStore } from '../store';
|
||||
import { getQuotes, getSavedQuotes } from '../store/Selectors';
|
||||
|
||||
const Saved = () => {
|
||||
|
||||
const quotes = useStoreState(QuoteStore, getQuotes);
|
||||
const saved = useStoreState(QuoteStore, getSavedQuotes);
|
||||
const [ amountLoaded, setAmountLoaded ] = useState(20);
|
||||
|
||||
const fetchMore = async e => {
|
||||
|
||||
setAmountLoaded(amountLoaded => amountLoaded + 20);
|
||||
e.target.complete();
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton />
|
||||
</IonButtons>
|
||||
<IonTitle>Bookmarks</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Bookmarks</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonGrid>
|
||||
{ quotes.length > 0 &&
|
||||
|
||||
<IonList>
|
||||
<IonRow>
|
||||
{ quotes.map((quote, index) => {
|
||||
|
||||
if ((index <= amountLoaded) && saved.includes(parseInt(quote.id))) {
|
||||
return (
|
||||
|
||||
<QuoteItem key={ index } quote={ quote } />
|
||||
);
|
||||
} else return "";
|
||||
})}
|
||||
|
||||
<IonInfiniteScroll threshold="200px" onIonInfinite={ fetchMore }>
|
||||
<IonInfiniteScrollContent loadingSpinner="bubbles" loadingText="Getting more quotes...">
|
||||
</IonInfiniteScrollContent>
|
||||
</IonInfiniteScroll>
|
||||
</IonRow>
|
||||
</IonList>
|
||||
}
|
||||
|
||||
{ quotes.length < 1 &&
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<h3>You haven't saved any bookmarks yet.</h3>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
}
|
||||
</IonGrid>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Saved;
|
87
03_source/mobile/src/pages/DemoQuoteApp/AppPages/Saved.tsx
Normal file
87
03_source/mobile/src/pages/DemoQuoteApp/AppPages/Saved.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import {
|
||||
IonButtons,
|
||||
IonCol,
|
||||
IonContent,
|
||||
IonGrid,
|
||||
IonHeader,
|
||||
IonInfiniteScroll,
|
||||
IonInfiniteScrollContent,
|
||||
IonList,
|
||||
IonMenuButton,
|
||||
IonPage,
|
||||
IonRow,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
} from '@ionic/react';
|
||||
import { useStoreState } from 'pullstate';
|
||||
import { useState } from 'react';
|
||||
import { QuoteItem } from '../components/QuoteItem';
|
||||
import { QuoteStore } from '../store';
|
||||
import { getQuotes, getSavedQuotes } from '../store/Selectors';
|
||||
|
||||
const Saved: React.FC = () => {
|
||||
const quotes = useStoreState(QuoteStore, getQuotes);
|
||||
const saved = useStoreState(QuoteStore, getSavedQuotes);
|
||||
const [amountLoaded, setAmountLoaded] = useState<number>(20);
|
||||
|
||||
const fetchMore = async (e: CustomEvent<void>) => {
|
||||
setAmountLoaded((amountLoaded) => amountLoaded + 20);
|
||||
const target = e.target as HTMLIonInfiniteScrollElement;
|
||||
target.complete();
|
||||
};
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonButtons slot="start">
|
||||
<IonMenuButton />
|
||||
</IonButtons>
|
||||
<IonTitle>Bookmarks</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Bookmarks</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonGrid>
|
||||
{quotes.length > 0 && (
|
||||
<IonList>
|
||||
<IonRow>
|
||||
{quotes.map(
|
||||
(quote: { id: string | number; text: string; author: string }, index: number) => {
|
||||
const quoteId = typeof quote.id === 'string' ? parseInt(quote.id) : quote.id;
|
||||
if (index <= amountLoaded && saved.some((item) => item.id === quoteId)) {
|
||||
return <QuoteItem key={index} quote={quote} />;
|
||||
} else return '';
|
||||
}
|
||||
)}
|
||||
|
||||
<IonInfiniteScroll threshold="200px" onIonInfinite={fetchMore}>
|
||||
<IonInfiniteScrollContent
|
||||
loadingSpinner="bubbles"
|
||||
loadingText="Getting more quotes..."
|
||||
></IonInfiniteScrollContent>
|
||||
</IonInfiniteScroll>
|
||||
</IonRow>
|
||||
</IonList>
|
||||
)}
|
||||
|
||||
{quotes.length < 1 && (
|
||||
<IonRow>
|
||||
<IonCol size="12">
|
||||
<h3>You haven't saved any bookmarks yet.</h3>
|
||||
</IonCol>
|
||||
</IonRow>
|
||||
)}
|
||||
</IonGrid>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Saved;
|
@@ -18,7 +18,6 @@ import { QuoteStore } from '../store';
|
||||
import { getSavedQuotes } from '../store/Selectors';
|
||||
|
||||
const Menu = () => {
|
||||
|
||||
const location = useLocation();
|
||||
const saved = useStoreState(QuoteStore, getSavedQuotes);
|
||||
|
||||
@@ -27,14 +26,14 @@ const Menu = () => {
|
||||
title: 'Home',
|
||||
url: '/home',
|
||||
iosIcon: homeOutline,
|
||||
mdIcon: homeSharp
|
||||
mdIcon: homeSharp,
|
||||
},
|
||||
{
|
||||
title: `Bookmarks (${ saved.length })`,
|
||||
title: `Bookmarks (${saved.length})`,
|
||||
url: '/saved',
|
||||
iosIcon: bookmarkOutline,
|
||||
mdIcon: bookmarkSharp
|
||||
}
|
||||
mdIcon: bookmarkSharp,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -46,7 +45,13 @@ const Menu = () => {
|
||||
{appPages.map((appPage, index) => {
|
||||
return (
|
||||
<IonMenuToggle key={index} autoHide={false}>
|
||||
<IonItem className={location.pathname === appPage.url ? 'selected' : ''} routerLink={appPage.url} routerDirection="none" lines="none" detail={false}>
|
||||
<IonItem
|
||||
className={location.pathname === appPage.url ? 'selected' : ''}
|
||||
routerLink={appPage.url}
|
||||
routerDirection="none"
|
||||
lines="none"
|
||||
detail={false}
|
||||
>
|
||||
<IonIcon slot="start" ios={appPage.iosIcon} md={appPage.mdIcon} />
|
||||
<IonLabel>{appPage.title}</IonLabel>
|
||||
</IonItem>
|
||||
|
@@ -1,17 +0,0 @@
|
||||
import { IonCol, IonItem, IonLabel } from "@ionic/react";
|
||||
import styles from "./QuoteItem.module.css";
|
||||
|
||||
export const QuoteItem = ({ quote }) => {
|
||||
|
||||
return (
|
||||
|
||||
<IonCol size="6" className="animate__animated animate__fadeIn">
|
||||
<IonItem lines="none" className={ styles.quoteItem } routerLink={ `/quote/${ quote.id }`}>
|
||||
<IonLabel className={ styles.quoteText }>
|
||||
<h2>{ quote.text }</h2>
|
||||
<p>{ quote.author }</p>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
</IonCol>
|
||||
);
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
import { IonCol, IonItem, IonLabel } from '@ionic/react';
|
||||
import styles from './QuoteItem.module.css';
|
||||
|
||||
interface Quote {
|
||||
id: string | number;
|
||||
text: string;
|
||||
author: string;
|
||||
}
|
||||
|
||||
interface QuoteItemProps {
|
||||
quote: Quote;
|
||||
}
|
||||
|
||||
export const QuoteItem: React.FC<QuoteItemProps> = ({ quote }) => {
|
||||
return (
|
||||
<IonCol size="6" className="animate__animated animate__fadeIn">
|
||||
<IonItem lines="none" className={styles.quoteItem} routerLink={`/quote/${quote.id}`}>
|
||||
<IonLabel className={styles.quoteText}>
|
||||
<h2>{quote.text}</h2>
|
||||
<p>{quote.author}</p>
|
||||
</IonLabel>
|
||||
</IonItem>
|
||||
</IonCol>
|
||||
);
|
||||
};
|
2
03_source/mobile/src/pages/DemoQuoteApp/store/QuoteStore.d.ts
vendored
Normal file
2
03_source/mobile/src/pages/DemoQuoteApp/store/QuoteStore.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export const addSavedQuote: (id: string | number) => void;
|
||||
export const removeSavedQuote: (id: string | number) => void;
|
@@ -1,33 +1,34 @@
|
||||
import { Store } from "pullstate";
|
||||
import { Store } from 'pullstate';
|
||||
|
||||
const QuoteStore = new Store({
|
||||
|
||||
quotes: [],
|
||||
saved: []
|
||||
quotes: [],
|
||||
saved: [],
|
||||
});
|
||||
|
||||
export default QuoteStore;
|
||||
|
||||
export const addSavedQuote = id => {
|
||||
export const addSavedQuote = (id) => {
|
||||
QuoteStore.update((s) => {
|
||||
s.saved = [...s.saved, id];
|
||||
});
|
||||
};
|
||||
|
||||
QuoteStore.update(s => { s.saved = [ ...s.saved, id ] });
|
||||
}
|
||||
|
||||
export const removeSavedQuote = id => {
|
||||
|
||||
QuoteStore.update(s => { s.saved = s.saved.filter(savedId => parseInt(savedId) !== parseInt(id)) });
|
||||
}
|
||||
export const removeSavedQuote = (id) => {
|
||||
QuoteStore.update((s) => {
|
||||
s.saved = s.saved.filter((savedId) => parseInt(savedId) !== parseInt(id));
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchQuotes = async () => {
|
||||
const response = await fetch('https://type.fit/api/quotes');
|
||||
const data = await response.json();
|
||||
|
||||
const response = await fetch("https://type.fit/api/quotes");
|
||||
const data = await response.json();
|
||||
await data.filter((quote, index) => {
|
||||
quote.id = Date.now() + index;
|
||||
quote.image = `https://source.unsplash.com/random/1200x400?sig=${quote.id}`;
|
||||
});
|
||||
|
||||
await data.filter((quote, index) => {
|
||||
|
||||
quote.id = (Date.now() + index);
|
||||
quote.image = `https://source.unsplash.com/random/1200x400?sig=${ quote.id }`;
|
||||
});
|
||||
|
||||
QuoteStore.update(s => { s.quotes = data });
|
||||
}
|
||||
QuoteStore.update((s) => {
|
||||
s.quotes = data;
|
||||
});
|
||||
};
|
||||
|
19
03_source/mobile/src/pages/DemoQuoteApp/store/Selectors.d.ts
vendored
Normal file
19
03_source/mobile/src/pages/DemoQuoteApp/store/Selectors.d.ts
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
export const getQuotes: () => Array<{
|
||||
id: string | number;
|
||||
text: string;
|
||||
author: string;
|
||||
}>;
|
||||
|
||||
export const getQuote: (id: string | number) =>
|
||||
| {
|
||||
id: string | number;
|
||||
text: string;
|
||||
author: string;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
export const getSavedQuotes: () => Array<{
|
||||
id: string | number;
|
||||
text: string;
|
||||
author: string;
|
||||
}>;
|
@@ -1,10 +1,14 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
const getState = state => state;
|
||||
const getState = (state) => state;
|
||||
|
||||
// General getters
|
||||
export const getQuotes = createSelector(getState, state => state.quotes);
|
||||
export const getSavedQuotes = createSelector(getState, state => state.saved);
|
||||
export const getQuotes = createSelector(getState, (state) => state.quotes);
|
||||
export const getSavedQuotes = createSelector(getState, (state) => state.saved);
|
||||
|
||||
// Specific getters
|
||||
export const getQuote = id => createSelector(getState, state => state.quotes.filter(q => parseInt(q.id) === parseInt(id))[0]);
|
||||
export const getQuote = (id) =>
|
||||
createSelector(
|
||||
getState,
|
||||
(state) => state.quotes.filter((q) => parseInt(q.id) === parseInt(id))[0]
|
||||
);
|
||||
|
9
03_source/mobile/src/pages/DemoQuoteApp/store/index.d.ts
vendored
Normal file
9
03_source/mobile/src/pages/DemoQuoteApp/store/index.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Store } from 'pullstate';
|
||||
|
||||
interface IQuoteStore {
|
||||
savedQuotes: Array<string | number>;
|
||||
isQuoteSaved: (id: string | number) => boolean;
|
||||
}
|
||||
|
||||
export const QuoteStore: Store<IQuoteStore>;
|
||||
export default QuoteStore;
|
@@ -1 +1 @@
|
||||
export { default as QuoteStore } from "./QuoteStore";
|
||||
export { default as QuoteStore } from './QuoteStore';
|
||||
|
Reference in New Issue
Block a user