Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/search user #18

Open
wants to merge 17 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
Empty file added .eslintignore
Empty file.
45 changes: 45 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
ecmaFeatures: {
jsx: true
}
},
settings: {
react: {
version: 'detect'
}
},
env: {
browser: true,
amd: true,
node: true,
jest: true
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:jsx-a11y/recommended',
'prettier/@typescript-eslint',
'plugin:prettier/recommended' // Make sure this is always the last element in the array.
],
rules: {
'prettier/prettier': ['error', {}, { usePrettierrc: true }],
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'jsx-a11y/anchor-is-valid': [
'error',
{
components: ['Link'],
specialLink: ['hrefLeft', 'hrefRight'],
aspects: ['invalidHref', 'preferButton']
}
]
}
};
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
.DS_Store
.DS_Store
.next
node_modules
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.DS_Store
.next
node_modules
8 changes: 8 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"semi": true,
"tabWidth": 4,
"printWidth": 100,
"singleQuote": true,
"trailingComma": "none",
"jsxBracketSameLine": true
}
96 changes: 19 additions & 77 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,85 +1,27 @@
# Desafío Front End
# Asignment test for a front end charge at Accenture
This project was developed by Estefany Pacheco (Github: TefaP1993).
Using NextJs framework with react and according to the requests at the main repository wich this project was forked from.

## Propuesta
## Available Scripts
In the project directory, you can run:

Implemente una aplicación cliente, visualice la API de GitHub y vea los repositorios de un usuario específico.
### `yarn install`
Installs all the dependencies that will allow you to run the app in your computer

Esta aplicación debe funcionar en los navegadores más recientes del mercado.
### `yarn start`
Runs the app in the development mode.
Open (http://localhost:3000) to view it in the browser.

### API
The page will reload if you make edits.

https://developer.github.com/v3/
### `yarn export`
Creates a static version of the app.

Puntos de Detalles de un usuario:
### `yarn build`
Builds the app for production to the `.next` folder.\

https://api.github.com/users/{username}
### `yarn format`
Uses prettier to format all the files in the project.

Repositorios de un usuario:

https://api.github.com/users/{username}/repos

### Disposición

Home
<img src="./img/home.png">

Details
<img src="./img/details.png">

Not Found
<img src="./img/notfound.png">

### Navegación

Al buscar un usuario por el login de búsqueda de github, acceda a la página de resultados de la búsqueda con los detalles del usuario, de lo contrario, mostrará la página de Not Found (Layout NotFound).

### Requisitos

- Yo, como usuario, quiero buscar un usuario de GitHub;
- Yo, como usuario, deseo ver los detalles de aquel usuario que fue buscado (número de seguidores, imagen del avatar, e-mail y bio);
- Yo, como usuario, en la pantalla de detalhes puedo hacer una nueva busca;
- Yo, como usuario, deseo ver la lista de repositorios de aquel usuario que fue buscado, ordenada por el número decreciente de estrellas;
- Yo, como usuario, en la pantalla de detalhes puedo hacer click en nombre de repositorio e ir para pantalla del repositorio en github;
- Yo, como usuario, en la pantalla de detalhes puedo hacer click en nombre usuariio y imagem del perfil, y ir para pantalla de perfil en github;

**Definición de listo**

El proyecto debe ser implementado de acuerdo con las especificaciones de Zeplin, no es obligatorio usar una estructura, pero recomendamos el React.js, Angular, Vue, o algún FW más actual. El uso de rutas es obligatorio (Ex: / users / {username} / repos).

### Criterios de evaluación

Buscamos personas que buscan siempre aprender cosas nuevas y estar actualizadas con el Mercado, disfrutar de buenas prácticas y calidad.

- Organización del proyecto: La estructura del proyecto, documentación y uso del control de versión;
- Innovación tecnológica: el uso de nuevas tecnologías, siempre que sean estables;
- Consistencia: Si se cumplen los requisitos;
- Buenas prácticas: Si el proyecto sigue buenas prácticas de desarrollo, incluyendo seguridad, optimización, código limpio y reutilizable, etc;
- Control de Calidad: Si el proyecto tiene calidad asegurada por pruebas unitarias (por ejemplo, Jasmine, Mocha, Chai, Jest, etc).
- No utilice Frameworks CSS (Boostrap, Material Angular, etc). Queremos entender cuál es su conocimiento con CSS.
- El uso de preprocesador es muy bienvenido (Sass, Less) y ganará puntos.
- El layout propuesto es bastante simple, pero tiene puntos que pueden ser reutilizados, reflexione sobre lo que se puede crear como componente. Analice bien el diseño y lo que se repite. Monte su biblioteca de componentes. Sugerencia, un término muy utilizado es webcomponent.
- Documentación del proyecto, en el README.md debe ser creado y explicadar como se puede levantar la aplicacion en ambiente local, ejecutar pruebas unitarias, etc.
- Git, el control de versión se analizará también, por lo que los commits, descripciones que se hizo en aquel commit también contará en el análisis.
- Nomenclatura, el idioma para el código base debe estar en inglés y el idioma dirigido para el usuario en Español.
- Requerimiento obligatorio: la página de busqueda debe pertenecer a un modulo diferente de la página de detalle.
- El layout que esta en Zeplin se comparte cuando nuestro Recursos Humanos entran en contacto, y el css que es generado por él no debe ser utilizado. El foco es crear algo de cero y pensando en los webcomponentes reutilizables.

Cada ítem arriba será evaluado y contará puntos en la evaluación final, por lo tanto aplique todo su conocimiento.

### Entrega

Siga los siguientes pasos para implementar y enviar este desafío:
- Haga un **Fork** a este repositorio. Puedes mirar esta guía para mayores informaciones: [Como hacer fork de un repositorio](https://help.github.com/en/articles/fork-a-repo).
- Implemente el desafío.
- Después de completar el desafío, realice un **Pull Request** a este repositorio, utilizando la interface de **Github**. [Creando un Pull Request](https://help.github.com/en/articles/creating-a-pull-request-from-a-fork).
- Si es posible, deja tu repositorio publico para hacer la revisión de código más sencilla.

Ademas de eso, la aplicación debe alojarse (Heroku, Netlify, Firebase, Plunker, etc.) y cumplir con los requerimientos. Las direcciones URL deben ser agregadas a el README del proyecto.

### Plazo

El tiempo de entrega de 7 días.

Si el equipo de Recursos Humanos no te ha contactado, escribe a [email protected]

¡Buena suerte!
### Check on Vercel
You can check live app at https://search-github-tefap1993.vercel.app/search
15 changes: 15 additions & 0 deletions components/ButtonLink/ButtonLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Link from 'next/link';

interface ButtonProps {
classes: string;
content: unknown;
route: string;
}

const ButtonLink: React.FunctionComponent<ButtonProps> = (props) => (
<Link href={props.route}>
<a className={`btnLink ${props.classes}`}>{props.content}</a>
</Link>
);

export default ButtonLink;
1 change: 1 addition & 0 deletions components/Header/Header.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import "../../styles/partials";
17 changes: 17 additions & 0 deletions components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Logo from '../Logo/Logo';
import SearchBar from '../SearchBar/SearchBar';
interface HeaderProps {
value?: string;
}
const Header: React.FunctionComponent<HeaderProps> = (props) => (
<div className={`container-full`}>
<div className={`side-bar`}>
<Logo small />
</div>
<div className={`side-container`}>
<SearchBar value={props.value} />
</div>
</div>
);

export default Header;
6 changes: 6 additions & 0 deletions components/IconTextItem/IconTextItem.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.iconTextItemProps {
display: flex;
@media only screen and (max-width: 767px) {
display: inline-flex;
}
}
15 changes: 15 additions & 0 deletions components/IconTextItem/IconTextItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import styles from './IconTextItem.module.scss';

interface IconTextItemProps {
icon: string;
text: string | number;
}

const IconTextItem: React.FunctionComponent<IconTextItemProps> = (props) => (
<p className={`${styles.iconTextItem} ai-c fw-l c-neutro mb-1`}>
<img src={props.icon} alt="Favoritos" className={`mr-1`} />
{props.text}
</p>
);

export default IconTextItem;
20 changes: 20 additions & 0 deletions components/Logo/Logo.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@import "../../styles/partials";
.logo {
font-size: $type-5;
display: flex;
flex-wrap: nowrap;
.brand {
font-family: $font-alternative;
margin-right: $spacing-2;
}
.feat {
font-family: $font-extralight-italic;
font-weight: $extralight;
}
&.small {
font-size: $type-4;
.brand {
margin-right: $spacing-1;
}
}
}
14 changes: 14 additions & 0 deletions components/Logo/Logo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import styles from './Logo.module.scss';

interface LogoProps {
small?: boolean;
}

const Logo: React.FunctionComponent<LogoProps> = (props) => (
<div className={`${styles.logo} ${props.small && styles.small} ai-c`}>
<div className={`${styles.brand}`}>Github</div>
<div className={`${styles.feat}`}>Search</div>
</div>
);

export default Logo;
3 changes: 3 additions & 0 deletions components/SearchBar/SearchBar.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.searchBar {
display: flex;
}
55 changes: 55 additions & 0 deletions components/SearchBar/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useState } from 'react';
import ButtonLink from '../ButtonLink/ButtonLink';
import styles from './SearchBar.module.scss';
import { useRouter } from 'next/router';
interface SearchBarProps {
value?: string;
}
function validateRoute(url: string) {
if (url === '') {
return '';
}
const lowUrl = url.toLowerCase();
return `/search/user/${encodeURIComponent(lowUrl)}`;
}
const SearchBar: React.FunctionComponent<SearchBarProps> = (props) => {
const [searchValue, setSearchValue] = useState('');
const [inputObject, setInputObject] = useState({
value: props.value ? props.value : ''
});
const router = useRouter();
const searchIcon = <img src="/svg/search-icon.svg" alt="Búsqueda" />;
const handleEnter = (k) => {
if (k === 'Enter') {
const goTo = validateRoute(searchValue);
if (goTo !== '') {
router.push(goTo);
}
}
};
return (
<div className={`${styles.searchBar} main-shadow`}>
<div className={'fg-1'}>
<input
onKeyDown={(e) => handleEnter(e.key)}
onChange={(event) => {
setSearchValue(event.target.value);
setInputObject({ ...inputObject, value: event.target.value });
}}
name="userSearch"
type="text"
value={inputObject.value}
/>
</div>
<div>
<ButtonLink
classes="primaryBtn"
content={searchIcon}
route={validateRoute(searchValue)}
/>
</div>
</div>
);
};

export default SearchBar;
44 changes: 44 additions & 0 deletions components/UserDataDisplay/UserDataDisplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { UserInterface, ReposInterface } from '../../domain/user';
import IconTextItem from '../IconTextItem/IconTextItem';

const UserDataDisplay: React.FunctionComponent<UserInterface> = ({ userData, userRepos }) => {
let stars = 0;
userRepos.map((repo: ReposInterface) => {
stars += repo.stargazers_count;
});
return (
<div className={`container-full mt-4`}>
<div className={`side-bar`}>
<a href={userData.html_url} rel="noreferrer" target="_blank">
<img src={userData.avatar_url} alt={userData.login} />
<h1>{userData.name || userData.login}</h1>
<h2 className={`fw-l c-neutro`}>{userData.login}</h2>
</a>
{userData.company && (
<IconTextItem icon="/svg/organization-icon.svg" text={userData.company} />
)}
{userData.location && (
<IconTextItem icon="/svg/location-icon.svg" text={userData.location} />
)}
<IconTextItem icon="/svg/star-icon.svg" text={stars} />
<IconTextItem icon="/svg/repositories-icon.svg" text={userData.public_repos} />
<IconTextItem icon="/svg/followers-icon.svg" text={userData.followers} />
</div>
<div className={`side-container`}>
{userRepos
.sort((a, b) => (a.stargazers_count < b.stargazers_count ? 1 : -1))
.map((repo) => (
<div key={repo.node_id} className={`mb-3`}>
<a href={repo.html_url} rel="noreferrer" target="_blank">
<h3 className={`c-primary`}>{repo.name}</h3>
</a>
{repo.description && <p className={`fw-l mb-1`}>{repo.description}</p>}
<IconTextItem icon="/svg/star-icon.svg" text={repo.stargazers_count} />
</div>
))}
</div>
</div>
);
};

export default UserDataDisplay;
Loading