Merge pull request #523 from acelaya-forks/feature/home-alignment

Updated landing page to be vertically aligned on mobile devices
This commit is contained in:
Alejandro Celaya 2021-11-13 23:08:44 +01:00 committed by GitHub
commit 7db9974e8d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 67 additions and 25 deletions

View file

@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org).
## [Unreleased]
### Added
* [#520](https://github.com/shlinkio/shlink-web-client/issues/520) Allowed to select "all visits" as the default interval for visits.
### Changed
* Moved ci workflow to external repo and reused
### Deprecated
* *Nothing*
### Removed
* *Nothing*
### Fixed
* *Nothing*
## [3.4.0] - 2021-11-11 ## [3.4.0] - 2021-11-11
### Added ### Added
* [#496](https://github.com/shlinkio/shlink-web-client/issues/496) Allowed to select "all visits" as the default interval for visits. * [#496](https://github.com/shlinkio/shlink-web-client/issues/496) Allowed to select "all visits" as the default interval for visits.

View file

@ -1,5 +1,6 @@
import { useEffect, FC } from 'react'; import { useEffect, FC } from 'react';
import { Route, Switch } from 'react-router-dom'; import { Route, RouteChildrenProps, Switch } from 'react-router-dom';
import classNames from 'classnames';
import NotFound from '../common/NotFound'; import NotFound from '../common/NotFound';
import { ServersMap } from '../servers/data'; import { ServersMap } from '../servers/data';
import { Settings } from '../settings/reducers/settings'; import { Settings } from '../settings/reducers/settings';
@ -8,7 +9,7 @@ import { AppUpdateBanner } from '../common/AppUpdateBanner';
import { forceUpdate } from '../utils/helpers/sw'; import { forceUpdate } from '../utils/helpers/sw';
import './App.scss'; import './App.scss';
interface AppProps { interface AppProps extends RouteChildrenProps {
fetchServers: () => void; fetchServers: () => void;
servers: ServersMap; servers: ServersMap;
settings: Settings; settings: Settings;
@ -25,7 +26,9 @@ const App = (
Settings: FC, Settings: FC,
ManageServers: FC, ManageServers: FC,
ShlinkVersionsContainer: FC, ShlinkVersionsContainer: FC,
) => ({ fetchServers, servers, settings, appUpdated, resetAppUpdate }: AppProps) => { ) => ({ fetchServers, servers, settings, appUpdated, resetAppUpdate, location }: AppProps) => {
const isHome = location.pathname === '/';
useEffect(() => { useEffect(() => {
// On first load, try to fetch the remote servers if the list is empty // On first load, try to fetch the remote servers if the list is empty
if (Object.keys(servers).length === 0) { if (Object.keys(servers).length === 0) {
@ -40,7 +43,7 @@ const App = (
<MainHeader /> <MainHeader />
<div className="app"> <div className="app">
<div className="shlink-wrapper"> <div className={classNames('shlink-wrapper', { 'd-flex d-md-block align-items-center': isHome })}>
<Switch> <Switch>
<Route exact path="/" component={Home} /> <Route exact path="/" component={Home} />
<Route exact path="/settings" component={Settings} /> <Route exact path="/settings" component={Settings} />

View file

@ -1,9 +1,9 @@
import Bottle from 'bottlejs'; import Bottle, { Decorator } from 'bottlejs';
import { appUpdateAvailable, resetAppUpdate } from '../reducers/appUpdates'; import { appUpdateAvailable, resetAppUpdate } from '../reducers/appUpdates';
import App from '../App'; import App from '../App';
import { ConnectDecorator } from '../../container/types'; import { ConnectDecorator } from '../../container/types';
const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter: Decorator) => {
// Components // Components
bottle.serviceFactory( bottle.serviceFactory(
'App', 'App',
@ -18,6 +18,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
'ShlinkVersionsContainer', 'ShlinkVersionsContainer',
); );
bottle.decorator('App', connect([ 'servers', 'settings', 'appUpdated' ], [ 'fetchServers', 'resetAppUpdate' ])); bottle.decorator('App', connect([ 'servers', 'settings', 'appUpdated' ], [ 'fetchServers', 'resetAppUpdate' ]));
bottle.decorator('App', withRouter);
// Actions // Actions
bottle.serviceFactory('appUpdateAvailable', () => appUpdateAvailable); bottle.serviceFactory('appUpdateAvailable', () => appUpdateAvailable);

View file

@ -4,6 +4,7 @@
.home { .home {
position: relative; position: relative;
padding-top: 15px; padding-top: 15px;
width: 100%;
@media (min-width: $mdMin) { @media (min-width: $mdMin) {
padding-top: 0; padding-top: 0;

View file

@ -33,7 +33,7 @@ const connect: ConnectDecorator = (propsFromState: string[] | null, actionServic
actionServiceNames.reduce(mapActionService, {}), actionServiceNames.reduce(mapActionService, {}),
); );
provideAppServices(bottle, connect); provideAppServices(bottle, connect, withRouter);
provideCommonServices(bottle, connect, withRouter); provideCommonServices(bottle, connect, withRouter);
provideApiServices(bottle); provideApiServices(bottle);
provideShortUrlsServices(bottle, connect, withRouter); provideShortUrlsServices(bottle, connect, withRouter);

View file

@ -1,6 +1,8 @@
import { shallow, ShallowWrapper } from 'enzyme'; import { shallow, ShallowWrapper } from 'enzyme';
import { Route } from 'react-router-dom'; import { Route } from 'react-router-dom';
import { Mock } from 'ts-mockery'; import { Mock } from 'ts-mockery';
import { match } from 'react-router';
import { History, Location } from 'history';
import { Settings } from '../../src/settings/reducers/settings'; import { Settings } from '../../src/settings/reducers/settings';
import appFactory from '../../src/app/App'; import appFactory from '../../src/app/App';
import { AppUpdateBanner } from '../../src/common/AppUpdateBanner'; import { AppUpdateBanner } from '../../src/common/AppUpdateBanner';
@ -9,19 +11,17 @@ describe('<App />', () => {
let wrapper: ShallowWrapper; let wrapper: ShallowWrapper;
const MainHeader = () => null; const MainHeader = () => null;
const ShlinkVersions = () => null; const ShlinkVersions = () => null;
const App = appFactory(
beforeEach(() => { MainHeader,
const App = appFactory( () => null,
MainHeader, () => null,
() => null, () => null,
() => null, () => null,
() => null, () => null,
() => null, () => null,
() => null, ShlinkVersions,
() => null, );
ShlinkVersions, const createWrapper = (pathname = '') => {
);
wrapper = shallow( wrapper = shallow(
<App <App
fetchServers={() => {}} fetchServers={() => {}}
@ -29,18 +29,27 @@ describe('<App />', () => {
settings={Mock.all<Settings>()} settings={Mock.all<Settings>()}
appUpdated={false} appUpdated={false}
resetAppUpdate={() => {}} resetAppUpdate={() => {}}
match={Mock.all<match>()}
history={Mock.all<History>()}
location={Mock.of<Location>({ pathname })}
/>, />,
); );
});
return wrapper;
};
afterEach(() => wrapper.unmount()); afterEach(() => wrapper.unmount());
it('renders a header', () => expect(wrapper.find(MainHeader)).toHaveLength(1)); it('renders children components', () => {
const wrapper = createWrapper();
it('renders versions', () => expect(wrapper.find(ShlinkVersions)).toHaveLength(1)); expect(wrapper.find(MainHeader)).toHaveLength(1);
expect(wrapper.find(ShlinkVersions)).toHaveLength(1);
it('renders an update banner', () => expect(wrapper.find(AppUpdateBanner)).toHaveLength(1)); expect(wrapper.find(AppUpdateBanner)).toHaveLength(1);
});
it('renders app main routes', () => { it('renders app main routes', () => {
const wrapper = createWrapper();
const routes = wrapper.find(Route); const routes = wrapper.find(Route);
const expectedPaths = [ const expectedPaths = [
'/', '/',
@ -57,4 +66,15 @@ describe('<App />', () => {
expect(routes.at(index).prop('path')).toEqual(path); expect(routes.at(index).prop('path')).toEqual(path);
}); });
}); });
it.each([
[ '/foo', 'shlink-wrapper' ],
[ '/bar', 'shlink-wrapper' ],
[ '/', 'shlink-wrapper d-flex d-md-block align-items-center' ],
])('renders expected classes on shlink-wrapper based on current pathname', (pathname, expectedClasses) => {
const wrapper = createWrapper(pathname);
const shlinkWrapper = wrapper.find('.shlink-wrapper');
expect(shlinkWrapper.prop('className')).toEqual(expectedClasses);
});
}); });