Skip to content
This repository has been archived by the owner on Apr 1, 2024. It is now read-only.

Refactor #86

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 42 additions & 65 deletions src/components/App.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
/* eslint-disable react/prop-types */
/* eslint-disable react/no-deprecated */
import React, { useState, useEffect, useMemo } from 'react';
import React, { useState, useEffect } from 'react';
import Search from './Search';
import Results from './Results';
import Footer from './Footer';
import Privacy from './Privacy';
import Terms from './Terms';
import LandingPage from './Landing';

import { APP_STATES } from '../constants';

import 'antd/dist/antd.css';
import '../styles/App.css';

Expand All @@ -17,72 +15,51 @@ export default function App({ match }) {
const { page } = match.params;
const [services, setServices] = useState([]);
const [username, setUsername] = useState('');

const fetchServices = async () => {
const response = await fetch(window.apiUrl + '/services');
const responseJSON = await response.json();
setServices(responseJSON);
};
const [appState, setAppState] = useState(APP_STATES.EMPTY);

useEffect(() => {
fetchServices();
}, []);
const fetchServices = async () => {
const response = await fetch(window.apiUrl + '/services');
const responseJSON = await response.json();
setServices(responseJSON);
};
console.log('fetchServices');

// handle while user types
const inputChanged = text => {
setUsername(text);
};
// fetch services if not already fetched (due to a network issue etc.)
if (services.length === 0) {
fetchServices();
}
}, [appState, services]);

return useMemo(() => {
// main content of page
let content;
let content;

if (username.length > 0) {
content = (
<div className="container" id="content">
<Results username={username} services={services} />
</div>
);
} else {
// search is empty, show the page content
switch (page) {
case 'privacy':
content = (
<div className="container" id="content">
<Privacy />
</div>
);
break;
case 'terms':
//terms and conditions
content = (
<div className="container" id="content">
<Terms />
</div>
);
break;
default:
content = <LandingPage />;
break;
}
}
switch (appState) {
case APP_STATES.USER_TYPING:
content = 'loading';
break;
case APP_STATES.SEARCH:
content = <Results username={username} services={services} />;
break;
case APP_STATES.EMPTY:
default:
content = <LandingPage page={page} />;
break;
}

return (
<>
<div className="jumbotron">
<div className="container" id="jumbotron">
<Search input={username} onChange={inputChanged} />
</div>
return (
<>
<div className="jumbotron">
<div className="container" id="jumbotron">
<Search onInputChange={setUsername} onStateChange={setAppState} />
</div>
{content}
<div id="footer">
<hr />
<div className="container">
<Footer page={page} />
</div>
</div>
{content}
<div id="footer">
<hr />
<div className="container">
<Footer page={page} />
</div>
</>
);
// eslint-disable-next-line
}, [username, services, page]);
</div>
</>
);
}
50 changes: 34 additions & 16 deletions src/components/Landing.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';

import Privacy from './Privacy';
import Terms from './Terms';
import WhyUs from './WhyUs';
import NewsletterSubscription from './NewsletterSubscription';
import Donations from './Donation';
Expand Down Expand Up @@ -36,20 +37,37 @@ const whyUsRows = [
},
];

export default function Landing() {
return (
<div className="landing">
<div className="container" id="whyUs">
<WhyUs headerConfig={whyUsHeader} rows={whyUsRows} />
</div>
<div id="newsletter-wrapper">
<div className="container" id="newsLetter">
<NewsletterSubscription illustrationEnabled />
export default function Landing({ page }) {
switch (page) {
case 'privacy':
return (
<div className="container" id="content">
<Privacy />
</div>
);

case 'terms':
return (
<div className="container" id="content">
<Terms />
</div>
);

default:
return (
<div className="landing">
<div className="container" id="whyUs">
<WhyUs headerConfig={whyUsHeader} rows={whyUsRows} />
</div>
<div id="newsletter-wrapper">
<div className="container" id="newsLetter">
<NewsletterSubscription illustrationEnabled />
</div>
</div>
<div className="container" id="donations">
<Donations />
</div>
</div>
</div>
<div className="container" id="donations">
<Donations />
</div>
</div>
);
);
}
}
6 changes: 5 additions & 1 deletion src/components/Results.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ export default function Results({ username, services }) {

// render cards
return useMemo(() => {
return <div className="results">{services.length > 0 ? cards : <CubeSpinner />}</div>;
return (
<div className="container" id="content">
<div className="results">{services.length > 0 ? cards : <CubeSpinner />}</div>
</div>
);
}, [cards, services]);
}

Expand Down
86 changes: 50 additions & 36 deletions src/components/Search.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,58 @@
import React, { useMemo } from 'react';
import React, { useState, useCallback } from 'react';
import { Input, Icon } from 'antd';
import { Link } from 'react-router-dom';
// TODO: use styled components instead
import isEmpty from 'lodash/isEmpty';
import debounce from 'debounce';

import '../styles/Search.css';

export default function Search({ input, onChange }) {
const inputChanged = ({ target }) => {
// niceInput is the url friendly version of the input
let niceInput = target.value.replace(/[^a-zA-Z0-9-_.]/g, '');
onChange(niceInput);
};
export default function Search({ onInputChange, onStateChange }) {
const [input, setInput] = useState('');

const debouncedSearchHandler = useCallback(
debounce(input => {
onInputChange(input);
onStateChange('search');
}, 800),
[],
);

const handleChange = text => {
if (isEmpty(text)) {
debouncedSearchHandler.clear();
setInput('');
onInputChange('');
onStateChange('empty');
return;
}
onStateChange('userTyping');

const clearInput = () => {
onChange('');
let urlFriendlyInput = text.replace(/[^a-zA-Z0-9-_.]/g, '');
setInput(urlFriendlyInput);
debouncedSearchHandler(urlFriendlyInput);
};

return useMemo(() => {
return (
<div className="search">
<Link
to={'/'}
onClick={() => {
clearInput();
}}
>
<div className="header">
<Icon type="thunderbolt" theme="filled" />
<h1>{'Instant Username Search'}</h1>
</div>
</Link>
<Input
placeholder={'Search username'}
size="large"
allowClear
value={input}
onChange={inputChanged}
autoFocus
/>
</div>
);
// eslint-disable-next-line
}, [input]);
return (
<div className="search">
<Link
to={'/'}
onClick={() => {
handleChange('');
}}
>
<div className="header">
<Icon type="thunderbolt" theme="filled" />
<h1>Instant Username Search</h1>
</div>
</Link>
<Input
placeholder={'Search username'}
size="large"
allowClear
value={input}
onChange={event => handleChange(event.target.value)}
autoFocus
/>
</div>
);
}
5 changes: 5 additions & 0 deletions src/constants/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const APP_STATES = {
EMPTY: 'empty', // no search input
USER_TYPING: 'userTyping', // user is typing
SEARCH: 'search', // user has searched
};