import { create } from "zustand";
import { TFeedbackItem } from "../lib/types";
type Store = {
feedbackItems: TFeedbackItem[];
isLoading: boolean;
errorMessage: string;
selectedCompany: string;
actions: {
getCompanyList: () => string[];
getFilteredFeedbackItems: () => TFeedbackItem[];
addItemToList: (text: string) => Promise<void>;
selectCompany: (company: string) => void;
fetchFeedbackItems: () => Promise<void>;
};
};
export const useFeedbackItemsStore = create<Store>((set, get) => ({
feedbackItems: [],
isLoading: false,
errorMessage: "",
selectedCompany: "",
actions: {
getCompanyList: () => {
const state = get();
return state
.feedbackItems.map((item) => item.company)
.filter((company, index, array) => {
return array.indexOf(company) === index;
});
},
getFilteredFeedbackItems: () => {
const state = get();
return state.selectedCompany
? state.feedbackItems.filter(
(feedbackItem) => feedbackItem.company === state.selectedCompany
)
: state.feedbackItems;
},
addItemToList: async (text: string) => {
const companyName = text
.split(" ")
.find((word) => word.includes("#"))!
.substring(1);
const newItem: TFeedbackItem = {
id: new Date().getTime(),
text: text,
upvoteCount: 0,
daysAgo: 0,
company: companyName,
badgeLetter: companyName.substring(0, 1).toUpperCase(),
};
set((state) => ({
feedbackItems: [...state.feedbackItems, newItem],
}));
await fetch(
"https://bytegrad.com/course-assets/projects/corpcomment/api/feedbacks",
{
method: "POST",
body: JSON.stringify(newItem),
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
}
);
},
selectCompany: (company: string) => {
set(() => ({
selectedCompany: company,
}));
},
fetchFeedbackItems: async () => {
set(() => ({
isLoading: true,
}));
try {
const response = await fetch(
"https://bytegrad.com/course-assets/projects/corpcomment/api/feedbacks"
);
if (!response.ok) {
throw new Error();
}
const data = await response.json();
set(() => ({
feedbackItems: data.feedbacks,
}));
} catch (error) {
set(() => ({
errorMessage: "Something went wrong. Please try again later.",
}));
}
set(() => ({
isLoading: false,
}));
},
},
}));
export const useFeedbackItems = () => useFeedbackItemsStore((state) => state.feedbackItems);
export const useIsLoading = () => useFeedbackItemsStore((state) => state.isLoading);
export const useErrorMessage = () => useFeedbackItemsStore((state) => state.errorMessage);
export const useSelectedCompany = () => useFeedbackItemsStore((state) => state.selectedCompany);
export const useFeedbackItemActions = () => useFeedbackItemsStore((state) => state.actions);
export const useCompanyList = () => useFeedbackItemsStore((state) => state.actions.getCompanyList()); //Make sure it executes inside Zustand store.
export const useFilteredFeedbackItems = () => useFeedbackItemsStore((state) => state.actions.getFilteredFeedbackItems()); //Make sure it executes inside Zustand store.
import { useState } from "react";
import { MAX_CHARACTERS } from "../../lib/constants";
import { useFeedbackItemActions } from "../../stores/feedbackItemsStore";
export default function FeedbackForm() {
const {addItemToList} = useFeedbackItemActions();
const [text, setText] = useState("");
const [showValidIndicator, setShowValidIndicator] = useState(false);
const [showInvalidIndicator, setShowInvalidIndicator] = useState(false);
const charCount = MAX_CHARACTERS - text.length;
const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
const newText = event.target.value;
if (newText.length > MAX_CHARACTERS) {
return;
}
setText(newText);
};
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
// basic validation
if (text.includes("#") && text.length >= 5) {
setShowValidIndicator(true);
setTimeout(() => setShowValidIndicator(false), 2000);
} else {
setShowInvalidIndicator(true);
setTimeout(() => setShowInvalidIndicator(false), 2000);
return;
}
addItemToList(text);
setText("");
};
return (
<form
onSubmit={handleSubmit}
className={`form ${showValidIndicator && "form--valid"} ${
showInvalidIndicator && "form--invalid"
}`}
>
<textarea
value={text}
onChange={handleChange}
id="feedback-textarea"
placeholder="blabla"
spellCheck={false}
/>
<label htmlFor="feedback-textarea">
Enter your feedback here, remember to #hashtag the company
</label>
<div>
<p className="u-italic">{charCount}</p>
<button>
<span>Submit</span>
</button>
</div>
</form>
);
}
import {
useErrorMessage,
useFilteredFeedbackItems,
useIsLoading
} from "../../stores/feedbackItemsStore";
import ErrorMessage from "../ErrorMessage";
import Spinner from "../Spinner";
import FeedbackItem from "./FeedbackItem";
export default function FeedbackList() {
const isLoading = useIsLoading();
const errorMessage = useErrorMessage();
const filteredFeedbackItems = useFilteredFeedbackItems();
return (
<ol className="feedback-list">
{isLoading && <Spinner />}
{errorMessage && <ErrorMessage message={errorMessage} />}
{filteredFeedbackItems.map((feedbackItem) => (
<FeedbackItem key={feedbackItem.id} feedbackItem={feedbackItem} />
))}
</ol>
);
}
import { useFeedbackItemActions } from "../../stores/feedbackItemsStore";
type HashtagItemProps = {
company: string;
};
export default function HashtagItem({
company,
}: HashtagItemProps) {
const { selectCompany } = useFeedbackItemActions();
return (
<li key={company}>
<button onClick={() => selectCompany(company)}>#{company}</button>
</li>
);
}
import { useCompanyList } from "../../stores/feedbackItemsStore";
import HashtagItem from "./HashtagItem";
export default function HashtagList() {
const companyList = useCompanyList();
return (
<ul className="hashtags">
{companyList.map((company) => (
<HashtagItem
key={company}
company={company}
/>
))}
</ul>
);
}
import { useEffect } from "react";
import { useFeedbackItemActions } from "../stores/feedbackItemsStore";
import HashtagList from "./hashtag/HashtagList";
import Container from "./layout/Container";
import Footer from "./layout/Footer";
function App() {
const { fetchFeedbackItems } = useFeedbackItemActions();
useEffect(() => {
fetchFeedbackItems();
}, [fetchFeedbackItems]);
return (
<div className="app">
<Footer />
<Container />
<HashtagList />
</div>
);
}
export default App;
feedbackItemsStore.ts
FeedbackForm.tsx
FeedbackList.tsx
HashtagItem.tsx
HashtagList.tsx
App.tsx