mirror of
https://github.com/aspnet/JavaScriptServices.git
synced 2025-12-24 02:30:13 +00:00
Beginning React+Redux "Music Store" sample
This commit is contained in:
37
samples/react/MusicStore/ReactApp/components/App.tsx
Normal file
37
samples/react/MusicStore/ReactApp/components/App.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import * as React from 'react';
|
||||
import { Router, Route, HistoryBase } from 'react-router';
|
||||
import NavMenu from './NavMenu';
|
||||
import Home from './public/Home';
|
||||
import Genres from './public/Genres';
|
||||
import GenreDetails from './public/GenreDetails';
|
||||
import AlbumDetails from './public/AlbumDetails';
|
||||
|
||||
export interface AppProps {
|
||||
history: HistoryBase;
|
||||
}
|
||||
|
||||
export class App extends React.Component<AppProps, void> {
|
||||
public render() {
|
||||
return (
|
||||
<Router history={ this.props.history }>
|
||||
<Route component={ Layout }>
|
||||
<Route path="/" components={{ body: Home }} />
|
||||
<Route path="/genres" components={{ body: Genres }} />
|
||||
<Route path="/genre/:genreId" components={{ body: GenreDetails }} />
|
||||
<Route path="/album/:albumId" components={{ body: AlbumDetails }} />
|
||||
</Route>
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Layout extends React.Component<{ body: React.ReactElement<any> }, void> {
|
||||
public render() {
|
||||
return <div>
|
||||
<NavMenu />
|
||||
<div className="container">
|
||||
{ this.props.body }
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
49
samples/react/MusicStore/ReactApp/components/NavMenu.tsx
Normal file
49
samples/react/MusicStore/ReactApp/components/NavMenu.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import * as React from 'react';
|
||||
import { Navbar, Nav, NavItem, NavDropdown, MenuItem } from 'react-bootstrap';
|
||||
import { Link } from 'react-router';
|
||||
import { LinkContainer } from 'react-router-bootstrap';
|
||||
import { provide } from '../TypedRedux';
|
||||
import { ApplicationState } from '../store';
|
||||
import * as GenreList from '../store/GenreList';
|
||||
|
||||
class NavMenu extends React.Component<NavMenuProps, void> {
|
||||
componentWillMount() {
|
||||
this.props.requestGenresList();
|
||||
}
|
||||
|
||||
public render() {
|
||||
var genres = this.props.genres.slice(0, 5);
|
||||
return (
|
||||
<Navbar inverse fixedTop>
|
||||
<Navbar.Header>
|
||||
<Navbar.Brand><Link to={ '/' }>Music Store</Link></Navbar.Brand>
|
||||
</Navbar.Header>
|
||||
<Navbar.Collapse>
|
||||
<Nav>
|
||||
<LinkContainer to={ '/' }><NavItem>Home</NavItem></LinkContainer>
|
||||
<NavDropdown id="menu-dropdown" title="Store">
|
||||
{genres.map(genre =>
|
||||
<LinkContainer key={ genre.GenreId } to={ `/genre/${ genre.GenreId }` }>
|
||||
<MenuItem>{ genre.Name }</MenuItem>
|
||||
</LinkContainer>
|
||||
)}
|
||||
<MenuItem divider />
|
||||
<LinkContainer to={ '/genres' }><MenuItem>More…</MenuItem></LinkContainer>
|
||||
</NavDropdown>
|
||||
</Nav>
|
||||
<Nav pullRight>
|
||||
<NavItem href="#">Admin</NavItem>
|
||||
</Nav>
|
||||
</Navbar.Collapse>
|
||||
</Navbar>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Selects which part of global state maps to this component, and defines a type for the resulting props
|
||||
const provider = provide(
|
||||
(state: ApplicationState) => state.genreList,
|
||||
GenreList.actionCreators
|
||||
);
|
||||
type NavMenuProps = typeof provider.allProps;
|
||||
export default provider.connect(NavMenu);
|
||||
@@ -0,0 +1,60 @@
|
||||
import * as React from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { provide } from '../../TypedRedux';
|
||||
import { ApplicationState } from '../../store';
|
||||
import * as AlbumDetailsState from '../../store/AlbumDetails';
|
||||
|
||||
interface RouteParams {
|
||||
albumId: number;
|
||||
}
|
||||
|
||||
class AlbumDetails extends React.Component<AlbumDetailsProps, void> {
|
||||
componentWillMount() {
|
||||
this.props.requestAlbumDetails(this.props.params.albumId);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: AlbumDetailsProps) {
|
||||
if (nextProps.params.albumId !== this.props.params.albumId) {
|
||||
nextProps.requestAlbumDetails(nextProps.params.albumId);
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
if (this.props.isLoaded) {
|
||||
const albumData = this.props.album;
|
||||
return <div>
|
||||
<h2>{ albumData.Title }</h2>
|
||||
|
||||
<p><img alt={ albumData.Title } src={ albumData.AlbumArtUrl } /></p>
|
||||
|
||||
<div id="album-details">
|
||||
<p>
|
||||
<em>Genre:</em>
|
||||
{ albumData.Genre.Name }
|
||||
</p>
|
||||
<p>
|
||||
<em>Artist:</em>
|
||||
{ albumData.Artist.Name }
|
||||
</p>
|
||||
<p>
|
||||
<em>Price:</em>
|
||||
${ albumData.Price.toFixed(2) }
|
||||
</p>
|
||||
<p className="button">
|
||||
Add to cart
|
||||
</p>
|
||||
</div>
|
||||
</div>;
|
||||
} else {
|
||||
return <p>Loading...</p>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Selects which part of global state maps to this component, and defines a type for the resulting props
|
||||
const provider = provide(
|
||||
(state: ApplicationState) => state.albumDetails,
|
||||
AlbumDetailsState.actionCreators
|
||||
).withExternalProps<{ params: RouteParams }>();
|
||||
type AlbumDetailsProps = typeof provider.allProps;
|
||||
export default provider.connect(AlbumDetails);
|
||||
@@ -0,0 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { Album } from '../../store/FeaturedAlbums';
|
||||
|
||||
export class AlbumTile extends React.Component<{ album: Album, key?: any }, void> {
|
||||
public render() {
|
||||
const { album } = this.props;
|
||||
return (
|
||||
<li className="col-lg-2 col-md-2 col-sm-2 col-xs-4 container">
|
||||
<Link to={ '/album/' + album.AlbumId }>
|
||||
<img alt={ album.Title } src={ album.AlbumArtUrl } />
|
||||
<h4>{ album.Title }</h4>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import * as React from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { provide } from '../../TypedRedux';
|
||||
import { ApplicationState } from '../../store';
|
||||
import * as GenreDetailsStore from '../../store/GenreDetails';
|
||||
import { AlbumTile } from './AlbumTile';
|
||||
|
||||
interface RouteParams {
|
||||
genreId: number
|
||||
}
|
||||
|
||||
class GenreDetails extends React.Component<GenreDetailsProps, void> {
|
||||
componentWillMount() {
|
||||
this.props.requestGenreDetails(this.props.params.genreId);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps: GenreDetailsProps) {
|
||||
if (nextProps.params.genreId !== this.props.params.genreId) {
|
||||
nextProps.requestGenreDetails(nextProps.params.genreId);
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
if (this.props.isLoaded) {
|
||||
let albums = this.props.albums;
|
||||
return <div>
|
||||
<h3>Albums</h3>
|
||||
|
||||
<ul className="list-unstyled">
|
||||
{albums.map(album =>
|
||||
<AlbumTile key={ album.AlbumId } album={ album } />
|
||||
)}
|
||||
</ul>
|
||||
</div>;
|
||||
} else {
|
||||
return <p>Loading...</p>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Selects which part of global state maps to this component, and defines a type for the resulting props
|
||||
const provider = provide(
|
||||
(state: ApplicationState) => state.genreDetails,
|
||||
GenreDetailsStore.actionCreators
|
||||
).withExternalProps<{ params: RouteParams }>();
|
||||
|
||||
type GenreDetailsProps = typeof provider.allProps;
|
||||
export default provider.connect(GenreDetails);
|
||||
@@ -0,0 +1,41 @@
|
||||
import * as React from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { provide } from '../../TypedRedux';
|
||||
import { ApplicationState } from '../../store';
|
||||
import * as GenreList from '../../store/GenreList';
|
||||
|
||||
class Genres extends React.Component<GenresProps, void> {
|
||||
componentWillMount() {
|
||||
if (!this.props.genres.length) {
|
||||
this.props.requestGenresList();
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
let { genres } = this.props;
|
||||
|
||||
return <div>
|
||||
<h3>Browse Genres</h3>
|
||||
|
||||
<p>Select from { genres.length || '...' } genres:</p>
|
||||
|
||||
<ul className="list-group">
|
||||
{genres.map(genre =>
|
||||
<li key={ genre.GenreId } className="list-group-item">
|
||||
<Link to={ '/genre/' + genre.GenreId }>
|
||||
{ genre.Name }
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
// Selects which part of global state maps to this component, and defines a type for the resulting props
|
||||
const provider = provide(
|
||||
(state: ApplicationState) => state.genreList,
|
||||
GenreList.actionCreators
|
||||
);
|
||||
type GenresProps = typeof provider.allProps;
|
||||
export default provider.connect(Genres);
|
||||
37
samples/react/MusicStore/ReactApp/components/public/Home.tsx
Normal file
37
samples/react/MusicStore/ReactApp/components/public/Home.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import * as React from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { provide } from '../../TypedRedux';
|
||||
import { ApplicationState } from '../../store';
|
||||
import { actionCreators } from '../../store/FeaturedAlbums';
|
||||
import { AlbumTile } from './AlbumTile';
|
||||
|
||||
class Home extends React.Component<HomeProps, void> {
|
||||
componentWillMount() {
|
||||
if (!this.props.albums.length) {
|
||||
this.props.requestFeaturedAlbums();
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
let { albums } = this.props;
|
||||
return <div>
|
||||
<div className="jumbotron">
|
||||
<h1>MVC Music Store</h1>
|
||||
<img src="/Images/home-showcase.png" />
|
||||
</div>
|
||||
<ul className="row list-unstyled" id="album-list">
|
||||
{albums.map(album =>
|
||||
<AlbumTile key={ album.AlbumId } album={ album } />
|
||||
)}
|
||||
</ul>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
// Selects which part of global state maps to this component, and defines a type for the resulting props
|
||||
const provider = provide(
|
||||
(state: ApplicationState) => state.featuredAlbums,
|
||||
actionCreators
|
||||
);
|
||||
type HomeProps = typeof provider.allProps;
|
||||
export default provider.connect(Home);
|
||||
Reference in New Issue
Block a user