├── .eslintignore ├── src ├── store │ ├── config │ │ ├── constants.ts │ │ ├── actions.ts │ │ ├── types.d.ts │ │ └── index.ts │ ├── reducer.ts │ ├── main │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── types.d.ts │ │ └── actions.ts │ ├── types.d.ts │ └── index.ts ├── components │ ├── EventItems │ │ ├── types.d.ts │ │ ├── styled.ts │ │ ├── __test__ │ │ │ ├── eventitems.spec.tsx │ │ │ ├── fixtures │ │ │ │ └── DeleteEvent.json │ │ │ └── __snapshots__ │ │ │ │ └── eventitems.spec.tsx.snap │ │ ├── DetailCard.tsx │ │ └── index.tsx │ ├── ErrorCard │ │ └── index.tsx │ ├── EasterEggs.ts │ ├── Tag │ │ └── index.tsx │ ├── App │ │ ├── styles.ts │ │ └── index.tsx │ ├── Layout │ │ └── index.ts │ ├── Header │ │ ├── index.tsx │ │ └── styles.ts │ ├── Card │ │ └── index.tsx │ ├── Loading │ │ └── index.tsx │ ├── Footer │ │ └── index.tsx │ ├── Input │ │ ├── styles.ts │ │ └── index.tsx │ ├── Profile │ │ └── index.tsx │ ├── easing.ts │ └── GitHubCorner.tsx ├── constants.ts ├── service │ ├── Profile.ts │ ├── Loading.ts │ ├── ErrorCard.ts │ ├── Header.ts │ ├── EventItem.ts │ └── Input.ts ├── i18n │ ├── zh-CN.json │ ├── ja-JP.json │ ├── en-US.json │ └── index.ts ├── styles.ts ├── utils.ts ├── index.tsx ├── setupTests.ts └── api │ └── index.ts ├── scripts ├── utils.js ├── config.js ├── webpack.dev.conf.js ├── webpack.prod.conf.js └── webpack.base.conf.js ├── prettier.config.js ├── jest.config.js ├── .travis.yml ├── README.md ├── LICENSE ├── .eslintrc.js ├── .gitignore ├── package.json └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | *.js 3 | -------------------------------------------------------------------------------- /src/store/config/constants.ts: -------------------------------------------------------------------------------- 1 | // actions types 2 | export const CHANGE_LANGUAGE = '@@Config/changeLanguage' 3 | -------------------------------------------------------------------------------- /scripts/utils.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | exports.resolvePath = function(dir = '') { 4 | return path.join(__dirname, '..', dir) 5 | } 6 | -------------------------------------------------------------------------------- /src/components/EventItems/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface Commit { 2 | sha: string 3 | author: Author 4 | message: string 5 | distinct: boolean 6 | url: string 7 | } 8 | 9 | interface Author { 10 | email: string 11 | name: string 12 | } 13 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | // https://prettier.io/docs/en/install.html 2 | module.exports = { 3 | semi: false, 4 | singleQuote: true, 5 | trailingComma: 'all', 6 | printWidth: 100, 7 | // bracketSpacing: true, 8 | // tabWidth: 2, 9 | // useTabs: false, 10 | } 11 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import { author, homepage, license } from '../package.json' 2 | 3 | export const AUTHOR = author 4 | export const HOMEPAGE = homepage 5 | export const AUTHOR_GITHUB_URL = `https://github.com/${AUTHOR}` 6 | export const LICENSE = license 7 | export const LICENSE_URL = `${homepage}/blob/master/LICENSE` 8 | -------------------------------------------------------------------------------- /src/service/Profile.ts: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | 3 | import { GlobalState } from '../store/types' 4 | import Profile from '../components/Profile' 5 | 6 | const mapStateToProps = ({ mainState: { profileInfo } }: GlobalState) => ({ profileInfo }) 7 | 8 | export default connect(mapStateToProps)(Profile) 9 | -------------------------------------------------------------------------------- /src/service/Loading.ts: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | 3 | import Loading, { LoadingProps } from '../components/Loading' 4 | import { GlobalState } from '../store/types' 5 | 6 | const mapStateToProps = ({ mainState: { loading } }: GlobalState): LoadingProps => ({ 7 | loading, 8 | }) 9 | 10 | export default connect(mapStateToProps)(Loading) 11 | -------------------------------------------------------------------------------- /src/service/ErrorCard.ts: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | 3 | import ErrorCard, { ErrorCardProps } from '../components/ErrorCard' 4 | import { GlobalState } from '../store/types' 5 | 6 | const mapStateToProps = ({ mainState: { error } }: GlobalState): ErrorCardProps => ({ 7 | error, 8 | }) 9 | 10 | export default connect(mapStateToProps)(ErrorCard) 11 | -------------------------------------------------------------------------------- /src/components/ErrorCard/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { CenteredCard, INTENTS } from '../Card' 4 | 5 | export interface ErrorCardProps { 6 | error?: string 7 | } 8 | 9 | const ErrorCard = ({ error }: ErrorCardProps) => 10 | error ? 🙁{error} : null 11 | 12 | export default ErrorCard 13 | -------------------------------------------------------------------------------- /src/i18n/zh-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "Language": "语言", 3 | "broadcast event": "广播的事件", 4 | "received event": "接收的事件", 5 | "Search": "搜索", 6 | "Loading": "正在加载...", 7 | "No recent activity": "没有最近的活动", 8 | "PROJECT_NAME": "Buddy's Github Events", 9 | "PROJECT_DESCRIPTION": "偷看大佬们的 GitHub 动态", 10 | "Github username": "$t(GitHub) 用户名", 11 | "GitHub": "Github" 12 | } 13 | -------------------------------------------------------------------------------- /src/store/reducer.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers, Reducer } from 'redux' 2 | 3 | import mainState from './main' 4 | import configState from './config' 5 | import { GlobalState, GlobalActionTypes } from './types' 6 | 7 | const reducer: Reducer = combineReducers({ 8 | mainState, 9 | configState, 10 | }) 11 | 12 | export default reducer 13 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | clearMocks: true, 4 | coverageDirectory: 'coverage', 5 | moduleFileExtensions: ['js', 'json', 'ts', 'tsx', 'node'], 6 | testMatch: ['**/__tests__/**/*.{ts,tsx}', '**/?(*.)+(spec|test).{ts,tsx}'], 7 | setupFilesAfterEnv: ['./src/setupTests.ts'], 8 | collectCoverageFrom: ['./src/**/*.{ts,tsx}'], 9 | } 10 | -------------------------------------------------------------------------------- /src/store/config/actions.ts: -------------------------------------------------------------------------------- 1 | import i18next from '../../i18n' 2 | import { CHANGE_LANGUAGE } from './constants' 3 | import { ChangLanguageAction } from './types' 4 | 5 | export const changeLanguage = (language: string): ChangLanguageAction => { 6 | i18next.changeLanguage(language) 7 | return { 8 | type: CHANGE_LANGUAGE, 9 | payload: { language }, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/i18n/ja-JP.json: -------------------------------------------------------------------------------- 1 | { 2 | "Language": "言語", 3 | "broadcast event": "放送した活動", 4 | "received event": "受け取った活動", 5 | "Search": "サーチ", 6 | "Loading": "ロード中...", 7 | "No recent activity": "最近の活動なし", 8 | "PROJECT_NAME": "Buddy's Github Events", 9 | "PROJECT_DESCRIPTION": "友達のGitHubアクティビティを見る", 10 | "Github username": "$t(GitHub) ユーザー名", 11 | "GitHub": "Github" 12 | } 13 | -------------------------------------------------------------------------------- /src/styles.ts: -------------------------------------------------------------------------------- 1 | // https://www.styled-components.com/ 2 | import { createGlobalStyle } from 'styled-components' 3 | 4 | export const GlobalStyle = createGlobalStyle` 5 | body { 6 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Open Sans, Helvetica Neue, Icons16, sans-serif; 7 | margin: 0; 8 | padding: 0; 9 | font-style: normal; 10 | } 11 | ` 12 | -------------------------------------------------------------------------------- /src/service/Header.ts: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | 3 | import Header, { HeaderProps } from '../components/Header' 4 | import { GlobalState } from '../store/types' 5 | 6 | const mapStateToProps = ({ 7 | mainState: { error, loading, events, profileInfo }, 8 | }: GlobalState): HeaderProps => ({ 9 | isExpand: !error && !loading && !profileInfo && !events, 10 | }) 11 | 12 | export default connect(mapStateToProps)(Header) 13 | -------------------------------------------------------------------------------- /src/i18n/en-US.json: -------------------------------------------------------------------------------- 1 | { 2 | "Language": "Language", 3 | "broadcast event": "broadcast event", 4 | "received event": "received event", 5 | "Search": "Search", 6 | "Loading": "Loading activity...", 7 | "No recent activity": "No recent activity", 8 | "PROJECT_NAME": "Buddy's Github Events", 9 | "PROJECT_DESCRIPTION": "Check out your buddy's github activities", 10 | "Github username": "$t(GitHub) username", 11 | "GitHub": "Github" 12 | } 13 | -------------------------------------------------------------------------------- /src/store/config/types.d.ts: -------------------------------------------------------------------------------- 1 | import i18next from '../../i18n' 2 | import { CHANGE_LANGUAGE } from './constants' 3 | 4 | // config state 5 | export interface ConfigState { 6 | readonly language: typeof i18next.language 7 | } 8 | 9 | // actions 10 | export interface ChangLanguageAction { 11 | type: typeof CHANGE_LANGUAGE 12 | payload: { 13 | language: ConfigState['language'] 14 | } 15 | } 16 | 17 | export type ConfigActionTypes = ChangLanguageAction 18 | -------------------------------------------------------------------------------- /src/components/EasterEggs.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | export default ({ author, homepage }: { author: string; homepage: string }) => { 4 | const say = (...msg: string[]) => console.log(`[info] ${author} >>`, ...msg) 5 | console.group('==========================================') 6 | say('Hello buddy!') 7 | say('You seem to be interested in this project.') 8 | say('This is an open source project.') 9 | say('You can find its source code here.👉', homepage) 10 | say('Goodbye!😀') 11 | console.log('==========================================') 12 | console.groupEnd() 13 | } 14 | -------------------------------------------------------------------------------- /scripts/config.js: -------------------------------------------------------------------------------- 1 | const { resolvePath } = require('./utils') 2 | const { description } = require(resolvePath('package.json')) 3 | 4 | module.exports = { 5 | port: 3000, // dev server port 6 | publicUrl: process.env.PUBLIC_URL || '/', 7 | // @see https://github.com/jaketrent/html-webpack-template 8 | templateConfig: { 9 | title: "Buddy's Github Events", 10 | appMountId: 'root', 11 | mobile: true, 12 | // @see https://github.com/joshbuchea/HEAD#meta 13 | meta: [ 14 | { 15 | name: 'description', 16 | content: description, 17 | }, 18 | ], 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - node 4 | cache: 5 | directories: 6 | - node_modules 7 | install: 8 | - npm install 9 | before_script: 10 | - npm run lint 11 | - npm run test -- --passWithNoTests --coverage 12 | script: 13 | - npm run build 14 | before_deploy: 15 | - cp README.md dist/ 16 | - if [ $CNAME ]; then 17 | echo $CNAME > dist/CNAME; 18 | fi 19 | deploy: 20 | - provider: pages 21 | github_token: $GITHUB_TOKEN 22 | skip-cleanup: true 23 | keep-history: false 24 | local-dir: dist 25 | on: 26 | branch: master 27 | condition: $GITHUB_TOKEN 28 | -------------------------------------------------------------------------------- /src/store/config/index.ts: -------------------------------------------------------------------------------- 1 | import i18next from '../../i18n' 2 | import { CHANGE_LANGUAGE } from './constants' 3 | import { ConfigActionTypes, ConfigState } from './types' 4 | 5 | const initState: ConfigState = { 6 | language: i18next.language, 7 | } 8 | 9 | export default (state = initState, action: ConfigActionTypes): ConfigState => { 10 | switch (action.type) { 11 | case CHANGE_LANGUAGE: { 12 | const { 13 | payload: { language }, 14 | } = action 15 | return { 16 | ...state, 17 | language, 18 | } 19 | } 20 | default: 21 | return state 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/components/Tag/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | interface TagProps { 4 | round?: boolean 5 | } 6 | 7 | export const Tag = styled.span` 8 | display: inline-flex; 9 | flex-direction: row; 10 | align-items: center; 11 | padding: 2px 6px; 12 | white-space: nowrap; 13 | cursor: pointer; 14 | user-select: none; 15 | font-size: 12px; 16 | margin: 0px 4px; 17 | ${({ round = false }) => ` 18 | border-radius: ${round ? '30px' : '3px'}; 19 | `} 20 | 21 | color: #fff; 22 | background-color: #5c7080; 23 | &:hover { 24 | background-color: rgba(92, 112, 128, 0.85); 25 | } 26 | ` 27 | -------------------------------------------------------------------------------- /src/service/EventItem.ts: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | 3 | import EventItems, { EventItemsProps } from '../components/EventItems' 4 | import { GlobalState, StateProperties, AppThunkDispatch, ActionProperties } from '../store/types' 5 | import { fetchMoreEvent } from '../store/main/actions' 6 | 7 | const mapStateToProps = ({ 8 | mainState: { events }, 9 | }: GlobalState): StateProperties => ({ 10 | events, 11 | }) 12 | 13 | const mapDispatchToProps = (dispatch: AppThunkDispatch): ActionProperties => ({ 14 | showMore: () => dispatch(fetchMoreEvent()), 15 | }) 16 | 17 | export default connect(mapStateToProps, mapDispatchToProps)(EventItems) 18 | -------------------------------------------------------------------------------- /src/components/App/styles.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | import { spLayout } from '../Layout' 4 | 5 | export const AppWrapper = styled.div` 6 | display: flex; 7 | min-height: 100vh; 8 | width: 100%; 9 | flex-direction: column; 10 | align-items: center; 11 | ` 12 | 13 | export const Main = styled.main` 14 | display: flex; 15 | flex: 1; 16 | align-items: flex-start; 17 | justify-content: space-evenly; 18 | width: 100%; 19 | margin: 10px; 20 | ` 21 | 22 | export const Center = styled.article` 23 | display: flex; 24 | flex-direction: column; 25 | flex: 0.2 1 500px; 26 | ` 27 | 28 | export const PlaceHolder = styled.aside` 29 | ${spLayout(` 30 | display: none; 31 | `)} 32 | ` 33 | -------------------------------------------------------------------------------- /src/components/Layout/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * ported from yui540's palette 3 | * url: https://github.com/yui540/palette/blob/master/src/breakpoint.ts 4 | * commit 961dec4 5 | * this code is under [MIT licence](https://github.com/yui540/palette/blob/master/LICENSE) 6 | * in thankful acknowledgment of [yui540](https://github.com/yui540) 7 | * fileneme are modified 8 | */ 9 | 10 | export const pcLayout = (styles: string): string => { 11 | return ` 12 | @media screen and (min-width: 761px) { 13 | ${styles} 14 | } 15 | ` 16 | } 17 | 18 | export const spLayout = (styles: string): string => { 19 | return ` 20 | @media screen and (max-width: 760px) { 21 | ${styles} 22 | } 23 | ` 24 | } 25 | -------------------------------------------------------------------------------- /src/components/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { WithTranslation, withTranslation } from 'react-i18next' 3 | 4 | import Input from '../../service/Input' 5 | import GithubCorner from '../GitHubCorner' 6 | import { HOMEPAGE } from '../../constants' 7 | import { HeaderWrapper } from './styles' 8 | 9 | export interface HeaderProps { 10 | isExpand: boolean 11 | } 12 | 13 | const Header = ({ isExpand, t }: HeaderProps & WithTranslation) => ( 14 | 15 |

{t('PROJECT_NAME')}

16 |

{t('PROJECT_DESCRIPTION')}

17 | 18 | 19 |
20 | ) 21 | 22 | export default withTranslation()(Header) 23 | -------------------------------------------------------------------------------- /src/components/App/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import EventItems from '../../service/EventItem' 4 | import Profile from '../../service/Profile' 5 | import Loading from '../../service/Loading' 6 | import Footer from '../Footer' 7 | import ErrorCard from '../../service/ErrorCard' 8 | import Header from '../../service/Header' 9 | import { AppWrapper, Main, PlaceHolder, Center } from './styles' 10 | 11 | const App = () => ( 12 | 13 |
14 |
15 | 16 |
17 | 18 | 19 | 20 |
21 | 22 |
23 |