diff --git a/.DS_Store b/.DS_Store
index 5008ddf..63f1315 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000..391bfe6
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,26 @@
+version: 2.1
+
+orbs:
+ node: circleci/node@5.0.0
+
+jobs:
+ build-test:
+ executor:
+ name: node/default
+ tag: 14.15.1
+ steps:
+ - checkout
+ - run: node --version
+ - node/install-packages:
+ app-dir: ~/project
+ override-ci-command: npm install
+ - run: sudo npm install -g npm@latest
+ - run:
+ name: "Run tests"
+ command: npm test
+
+workflows:
+ version: 2.1
+ test_and_release:
+ jobs:
+ - build-test
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4d29575
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,23 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..d942982
--- /dev/null
+++ b/package.json
@@ -0,0 +1,40 @@
+{
+ "name": "frontend-coding-challenge",
+ "version": "0.1.0",
+ "private": true,
+ "dependencies": {
+ "@testing-library/jest-dom": "^5.16.1",
+ "@testing-library/react": "^12.1.2",
+ "@testing-library/user-event": "^13.5.0",
+ "axios": "^0.24.0",
+ "mobx": "^6.3.9",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2",
+ "react-scripts": "5.0.0",
+ "web-vitals": "^2.1.2"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test",
+ "eject": "react-scripts eject"
+ },
+ "eslintConfig": {
+ "extends": [
+ "react-app",
+ "react-app/jest"
+ ]
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ }
+}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..a11777c
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..aa069f2
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ React App
+
+
+
+
+
+
+
diff --git a/public/logo192.png b/public/logo192.png
new file mode 100644
index 0000000..fc44b0a
Binary files /dev/null and b/public/logo192.png differ
diff --git a/public/logo512.png b/public/logo512.png
new file mode 100644
index 0000000..a4e47a6
Binary files /dev/null and b/public/logo512.png differ
diff --git a/public/manifest.json b/public/manifest.json
new file mode 100644
index 0000000..080d6c7
--- /dev/null
+++ b/public/manifest.json
@@ -0,0 +1,25 @@
+{
+ "short_name": "React App",
+ "name": "Create React App Sample",
+ "icons": [
+ {
+ "src": "favicon.ico",
+ "sizes": "64x64 32x32 24x24 16x16",
+ "type": "image/x-icon"
+ },
+ {
+ "src": "logo192.png",
+ "type": "image/png",
+ "sizes": "192x192"
+ },
+ {
+ "src": "logo512.png",
+ "type": "image/png",
+ "sizes": "512x512"
+ }
+ ],
+ "start_url": ".",
+ "display": "standalone",
+ "theme_color": "#000000",
+ "background_color": "#ffffff"
+}
diff --git a/public/robots.txt b/public/robots.txt
new file mode 100644
index 0000000..e9e57dc
--- /dev/null
+++ b/public/robots.txt
@@ -0,0 +1,3 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Disallow:
diff --git a/src/App.css b/src/App.css
new file mode 100644
index 0000000..74b5e05
--- /dev/null
+++ b/src/App.css
@@ -0,0 +1,38 @@
+.App {
+ text-align: center;
+}
+
+.App-logo {
+ height: 40vmin;
+ pointer-events: none;
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ .App-logo {
+ animation: App-logo-spin infinite 20s linear;
+ }
+}
+
+.App-header {
+ background-color: #282c34;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ font-size: calc(10px + 2vmin);
+ color: white;
+}
+
+.App-link {
+ color: #61dafb;
+}
+
+@keyframes App-logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
diff --git a/src/App.js b/src/App.js
new file mode 100644
index 0000000..3784575
--- /dev/null
+++ b/src/App.js
@@ -0,0 +1,25 @@
+import logo from './logo.svg';
+import './App.css';
+
+function App() {
+ return (
+
+ );
+}
+
+export default App;
diff --git a/src/App.test.js b/src/App.test.js
new file mode 100644
index 0000000..1f03afe
--- /dev/null
+++ b/src/App.test.js
@@ -0,0 +1,8 @@
+import { render, screen } from '@testing-library/react';
+import App from './App';
+
+test('renders learn react link', () => {
+ render();
+ const linkElement = screen.getByText(/learn react/i);
+ expect(linkElement).toBeInTheDocument();
+});
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 0000000..ec2585e
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,13 @@
+body {
+ margin: 0;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+code {
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
+ monospace;
+}
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..ef2edf8
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,17 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import './index.css';
+import App from './App';
+import reportWebVitals from './reportWebVitals';
+
+ReactDOM.render(
+
+
+ ,
+ document.getElementById('root')
+);
+
+// If you want to start measuring performance in your app, pass a function
+// to log results (for example: reportWebVitals(console.log))
+// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
+reportWebVitals();
diff --git a/src/logo.svg b/src/logo.svg
new file mode 100644
index 0000000..9dfc1c0
--- /dev/null
+++ b/src/logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js
new file mode 100644
index 0000000..5253d3a
--- /dev/null
+++ b/src/reportWebVitals.js
@@ -0,0 +1,13 @@
+const reportWebVitals = onPerfEntry => {
+ if (onPerfEntry && onPerfEntry instanceof Function) {
+ import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
+ getCLS(onPerfEntry);
+ getFID(onPerfEntry);
+ getFCP(onPerfEntry);
+ getLCP(onPerfEntry);
+ getTTFB(onPerfEntry);
+ });
+ }
+};
+
+export default reportWebVitals;
diff --git a/src/setupTests.js b/src/setupTests.js
new file mode 100644
index 0000000..8f2609b
--- /dev/null
+++ b/src/setupTests.js
@@ -0,0 +1,5 @@
+// jest-dom adds custom jest matchers for asserting on DOM nodes.
+// allows you to do things like:
+// expect(element).toHaveTextContent(/react/i)
+// learn more: https://github.com/testing-library/jest-dom
+import '@testing-library/jest-dom';
diff --git a/src/store/githubRepo/index.ts b/src/store/githubRepo/index.ts
new file mode 100644
index 0000000..4f44c19
--- /dev/null
+++ b/src/store/githubRepo/index.ts
@@ -0,0 +1,4 @@
+export * from "./types";
+export * from "./repository";
+export * from "./store";
+
diff --git a/src/store/githubRepo/repository.ts b/src/store/githubRepo/repository.ts
new file mode 100644
index 0000000..a07b920
--- /dev/null
+++ b/src/store/githubRepo/repository.ts
@@ -0,0 +1,16 @@
+import axios from "axios";
+import { GithubRepoInputOptions, GithubRepoType } from "./types";
+
+export interface IGithubRepoRepository {
+ load(options: GithubRepoInputOptions): Promise;
+ }
+ const BaseUrl = "https://api.github.com/search/repositories";
+
+ export function GithubRepoRepository(): IGithubRepoRepository {
+ return {
+ load: async (options) => {
+ const url = `${BaseUrl}?q=created:>${options.date}&sort=${options.sort}&order=${options.order}&page=${options.page}`
+ const result = await axios({url , method: 'GET'})
+ return result.data;
+ }
+ }}
diff --git a/src/store/githubRepo/store.ts b/src/store/githubRepo/store.ts
new file mode 100644
index 0000000..997e439
--- /dev/null
+++ b/src/store/githubRepo/store.ts
@@ -0,0 +1,61 @@
+import { action, observable, flow } from "mobx";
+import { CancellablePromise } from "mobx/lib/api/flow";
+import { IRootStore } from "../root/store";
+import { GithubRepoRepository } from "./repository";
+
+import { GithubRepoInputOptions, GithubRepoType } from "./types";
+
+export interface IGithubRepoStore {
+ repos: GithubRepoType[] | null;
+ load: (options: GithubRepoInputOptions) => void;
+ loading: boolean;
+ error: Error | null;
+ clearError: () => void;
+ clear: () => void;
+}
+
+export function GithubRepoStore(rootStore: IRootStore) {
+ const _repository = GithubRepoRepository();
+ let _currentLoad: CancellablePromise | null = null;
+ const _cancelLoad = () => {
+ if (_currentLoad !== null) {
+ _currentLoad.catch(() => null);
+ _currentLoad.cancel();
+ _currentLoad = null;
+ }
+ }
+
+ const _load = flow(async function*(options: GithubRepoInputOptions) {
+ store.loading = true;
+ store.repos = null;
+ try {
+ const repos = yield _repository.load(options);
+ store.repos = repos;
+ } catch (error) {
+ store.error = error;
+ }
+ store.loading = false;
+ });
+
+ const store: IGithubRepoStore = observable({
+ repos: null,
+ load: action((options) => {
+ _cancelLoad();
+ _currentLoad = _load(options);
+ }),
+ loading: false,
+ error: null,
+ clearError: action(() => {
+ store.error = null;
+ }),
+ clear: action(() => {
+ _cancelLoad();
+ if (store.loading) {
+ store.loading = false;
+ }
+ }),
+
+ });
+
+ return store;
+}
\ No newline at end of file
diff --git a/src/store/githubRepo/types.ts b/src/store/githubRepo/types.ts
new file mode 100644
index 0000000..fec2789
--- /dev/null
+++ b/src/store/githubRepo/types.ts
@@ -0,0 +1,18 @@
+
+export interface GithubRepoType {
+ owner:string;
+ name:string;
+ description:string;
+ has_issues:boolean;
+ open_issues_count:string;
+ stargazers_count:string;
+ created_at:string;
+}
+
+export interface GithubRepoInputOptions {
+ date:string;
+ sort: string;
+ order: string;
+ page: number|1;
+
+ }
\ No newline at end of file
diff --git a/src/store/root/store.ts b/src/store/root/store.ts
new file mode 100644
index 0000000..d5fc767
--- /dev/null
+++ b/src/store/root/store.ts
@@ -0,0 +1,30 @@
+import React from "react";
+import { GithubRepoStore, IGithubRepoStore } from "../githubRepo/store";
+
+export interface IRootStore {
+ githubRepoStore: IGithubRepoStore;
+ // other stores should come here
+}
+
+export function RootStore() {
+ const lazyStore = (factory: (root: IRootStore) => S) => {
+ let _store: S | null = null;
+ return () => {
+ if (_store === null) {
+ _store = factory(store);
+ }
+ return _store;
+ };
+ };
+
+ const _githubRepoStore = lazyStore(GithubRepoStore);
+
+ const store: IRootStore = {
+ get githubRepoStore() {
+ return _githubRepoStore();
+ }
+ };
+ return store;
+ }
+
+ export const RootStoreContext = React.createContext(RootStore());
\ No newline at end of file
diff --git a/src/store/utils/dateUtils.ts b/src/store/utils/dateUtils.ts
new file mode 100644
index 0000000..3c941a1
--- /dev/null
+++ b/src/store/utils/dateUtils.ts
@@ -0,0 +1,16 @@
+
+export function getPreviousDateFromDays(days:number) {
+ let today = new Date()
+ var priorDate = new Date().setDate(today.getDate() - days)
+ let d = new Date(priorDate)
+
+ let month = addPadToDate(d.getMonth() + 1),
+ day = addPadToDate(d.getDate()),
+ year = `${d.getFullYear()}`;
+
+ return [year, month, day].join('-');
+}
+
+function addPadToDate(date:number) {
+ return date < 10 ? '0' + date : '' + date;
+ }
\ No newline at end of file