Skip to content

Commit 81d19d6

Browse files
committed
fix: added course view
1 parent 85f614c commit 81d19d6

File tree

19 files changed

+412
-49
lines changed

19 files changed

+412
-49
lines changed

k8s/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ apiVersion: v1
22
description: A Helm chart for kube-ts-react-client
33
name: kube-ts-react-client
44
version: 1.0.0
5-
appVersion: 1.5.0
5+
appVersion: 1.5.5
66
home: https://cloud.docker.com/u/kubejs/repository/docker/kubejs/kube-ts-react-client
77
icon: https://avatars2.githubusercontent.com/u/47761918?s=200&v=4
88
sources:

k8s/values.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ replicaCount: 2
66

77
image:
88
repository: kubejs/kube-ts-react-client
9-
tag: 1.5.0
9+
tag: 1.5.5
1010
pullPolicy: Always
1111
containerPort: 80
1212

src/api/index.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
ResetPasswordOptions,
1010
VerifyAccountOptions,
1111
} from '../redux/auth/actionCreators';
12+
import { CourseDetailsResult } from '../redux/courseDetails/actionCreators';
1213
import { DiscoveryItemsResult } from '../redux/discoveryItems/actionCreators';
1314
import http from '../services/http';
1415
import Category from '../types/items/Category';
@@ -60,18 +61,20 @@ export type SearchParams =
6061
| { [key: string]: string | number }
6162
| URLSearchParams;
6263

63-
export interface DiscoveryItemsOptions {
64+
export type DiscoveryType = 'homepage' | 'course';
65+
export interface BaseGetOptions {
66+
readonly type: DiscoveryType
6467
readonly searchParams?: SearchParams;
6568
}
6669

70+
export type GetDiscoveryItems<T> = (options: BaseGetOptions) => Promise<T>;
71+
6772
export interface Api {
6873
readonly auth: AuthApi;
6974
readonly categories: Facade<Category>;
7075
readonly courses: Facade<Course>;
7176
readonly users: Facade<User>;
72-
readonly getDiscoveryItems: (
73-
options: DiscoveryItemsOptions
74-
) => Promise<DiscoveryItemsResult>;
77+
readonly getDiscoveryItems: GetDiscoveryItems<CourseDetailsResult | DiscoveryItemsResult>;
7578
}
7679

7780
export const normalisePromise = <T>(promise: ResponsePromise): Promise<T> =>
@@ -80,7 +83,6 @@ export const normalisePromise = <T>(promise: ResponsePromise): Promise<T> =>
8083
const createApi = ({ httpClient, token }: Options): Api => {
8184
const baseConfig = {
8285
headers: {
83-
// TODO: extract just token into state - not `Bearer token`
8486
authorization: token as string,
8587
},
8688
};
@@ -128,8 +130,11 @@ const createApi = ({ httpClient, token }: Options): Api => {
128130
prefixUrl: config.apiUrl,
129131
},
130132
}),
131-
getDiscoveryItems: ({ searchParams }: DiscoveryItemsOptions) =>
132-
normalisePromise<DiscoveryItemsResult>(
133+
getDiscoveryItems: <T>({
134+
searchParams,
135+
type,
136+
}: BaseGetOptions): Promise<T> =>
137+
normalisePromise(
133138
httpClient.get('discovery-items', {
134139
searchParams,
135140
})

src/app.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import React, { Fragment, lazy, StrictMode, Suspense } from 'react';
33
import { Route, Switch } from 'react-router';
44
import AuthenticatedRoute from './components/Auth/AuthenticatedRoute';
55
import UnauthenticatedRoute from './components/Auth/UnauthenticatedRoute';
6+
import CourseView from './components/CourseView';
67
import ErrorBoundary from './components/ErrorBoundaries/Page/index';
78
import Layout from './components/Layout';
89
import {
10+
COURSE_VIEW,
911
DASHBOARD,
1012
LOGIN,
1113
REGISTER,
@@ -36,6 +38,7 @@ const App = () => (
3638
<ErrorBoundary>
3739
<Switch>
3840
<Route exact path={ROOT} component={Home} />
41+
<Route exact path={COURSE_VIEW} component={CourseView} />
3942
<Route exact path={RESET_PASSWORD} component={ResetPassword} />
4043
<Route exact path={VERIFY} component={VerifyAccount} />
4144

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// tslint:disable:no-magic-numbers
2+
import { Avatar, Button, Container, Grid, Paper } from '@material-ui/core';
3+
import React, { memo, useEffect } from 'react';
4+
import { useDispatch, useSelector } from 'react-redux';
5+
import { RouteComponentProps } from 'react-router';
6+
import { Redirect } from 'react-router-dom';
7+
import { ROOT } from '../../constants/routes';
8+
import { getCourseDetailsRequested } from '../../redux/courseDetails/actionCreators';
9+
import { State } from '../../redux/rootReducer';
10+
import useStyles from './styles';
11+
12+
export interface Params {
13+
readonly courseSlug: string;
14+
}
15+
16+
const CourseView = ({ match }: RouteComponentProps<Params>) => {
17+
const classes = useStyles();
18+
19+
if (match.params.courseSlug.trim() === '') {
20+
return <Redirect to={ROOT} />;
21+
}
22+
const { course, getCourseDetailsLoading } = useSelector(
23+
(state: State) => state.courseDetails
24+
);
25+
26+
const dispatch = useDispatch();
27+
28+
useEffect(() => {
29+
dispatch(getCourseDetailsRequested(match.params.courseSlug));
30+
}, [match.params.courseSlug]);
31+
32+
if (getCourseDetailsLoading || course === undefined) {
33+
// TODO: make course placeholder
34+
return <div>Loading...</div>;
35+
}
36+
37+
const coursePrice = Number(Math.random() * 10 + 9).toFixed(2);
38+
39+
return (
40+
<div className={classes.root}>
41+
<Container component="main" maxWidth="lg">
42+
<Grid container spacing={3}>
43+
<Grid item xs={12} sm={9}>
44+
<Paper className={classes.paper}>
45+
<h3>{course.title}</h3>
46+
<h4>
47+
<Avatar className={classes.avatar}>
48+
{`${(course.user.firstName as string).substr(0, 1)}${(course
49+
.user.lastName as string).substr(0, 1)}`}
50+
</Avatar>
51+
{course.user.firstName} {course.user.lastName}
52+
</h4>
53+
<div
54+
dangerouslySetInnerHTML={{
55+
__html: course.description as string,
56+
}}
57+
/>
58+
</Paper>
59+
</Grid>
60+
<Grid item xs={12} sm={3}>
61+
<Paper className={classes.paper}>
62+
<img src={course.imageUrl} style={{ width: '100%' }} />
63+
<h4>{${coursePrice}`}</h4>
64+
<Button variant="contained" fullWidth color="primary">
65+
Add to cart
66+
</Button>
67+
<Button variant="contained" fullWidth color="secondary">
68+
Buy now
69+
</Button>
70+
</Paper>
71+
</Grid>
72+
</Grid>
73+
</Container>
74+
</div>
75+
);
76+
};
77+
78+
export default memo(CourseView);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// tslint:disable:no-magic-numbers
2+
import { makeStyles } from '@material-ui/core/styles';
3+
4+
const useStyles = makeStyles(theme => ({
5+
avatar: {
6+
backgroundColor: 'orange',
7+
color: '#fff',
8+
margin: 10,
9+
},
10+
paper: {
11+
color: theme.palette.text.primary,
12+
padding: theme.spacing(2),
13+
},
14+
root: {
15+
flexGrow: 1,
16+
},
17+
}));
18+
19+
export default useStyles;

src/components/CoursesSlider/index.tsx

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ import ChevronRightIcon from '@material-ui/icons/ChevronRight';
1111
import _find from 'ramda/src/find';
1212
import _propEq from 'ramda/src/propEq';
1313
import React from 'react';
14+
import { Link } from 'react-router-dom';
1415
import Slider from 'react-slick';
1516
// tslint:disable:no-import-side-effect
1617
import 'slick-carousel/slick/slick-theme.css';
1718
import 'slick-carousel/slick/slick.css';
1819
import { EnhancedCourse } from '../../redux/discoveryItems/actionCreators';
19-
import User from '../../types/items/User';
2020
import CourseRating from '../CourseRating';
2121
import useStyles from './styles';
2222

@@ -65,42 +65,44 @@ const Slide = ({
6565

6666
return (
6767
<Card className={classes.card}>
68-
<CardMedia
69-
className={classes.cardMedia}
70-
image={course.imageUrl}
71-
title="Image title"
72-
/>
73-
<CardContent className={classes.cardContent}>
74-
<Typography
75-
variant="subtitle1"
76-
component="div"
77-
className={classes.cardTitle}
78-
>
79-
{course.title}
80-
</Typography>
68+
<Link className={classes.courseLink} to={`/courses/${course.slug}`}>
69+
<CardMedia
70+
className={classes.cardMedia}
71+
image={course.imageUrl}
72+
title="Image title"
73+
/>
74+
<CardContent className={classes.cardContent}>
75+
<Typography
76+
variant="subtitle1"
77+
component="div"
78+
className={classes.cardTitle}
79+
>
80+
{course.title}
81+
</Typography>
8182

82-
<Typography
83-
variant="body2"
84-
color="textSecondary"
85-
component="div"
86-
className={classes.cardInstructorTitle}
87-
>
88-
{course.user.firstName} {course.user.lastName}
89-
</Typography>
83+
<Typography
84+
variant="body2"
85+
color="textSecondary"
86+
component="div"
87+
className={classes.cardInstructorTitle}
88+
>
89+
{course.user.firstName} {course.user.lastName}
90+
</Typography>
9091

91-
<CourseRating value={courseRating} />
92+
<CourseRating value={courseRating} />
9293

93-
<div className={classes.cardPrice}>{${coursePrice}`}</div>
94-
</CardContent>
95-
<CardActions>
96-
<Button
97-
size="small"
98-
color="primary"
99-
style={{ textTransform: 'capitalize' }}
100-
>
101-
Add to cart
102-
</Button>
103-
</CardActions>
94+
<div className={classes.cardPrice}>{${coursePrice}`}</div>
95+
</CardContent>
96+
<CardActions>
97+
<Button
98+
size="small"
99+
color="primary"
100+
style={{ textTransform: 'capitalize' }}
101+
>
102+
Add to cart
103+
</Button>
104+
</CardActions>
105+
</Link>
104106
</Card>
105107
);
106108
};
@@ -124,6 +126,7 @@ const CoursesSlider = ({ courses }: Options) => {
124126

125127
return (
126128
<div style={{ maxWidth: '800px' }}>
129+
{/** TODO: create slider placeholder */}
127130
<Slider {...settings} className={classes.slider}>
128131
{courses.map(course => (
129132
<Slide {...{ course, classes }} key={course.id} />

src/components/CoursesSlider/styles.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ const useStyles = makeStyles(theme => ({
4848
textOverflow: 'ellipsis',
4949
wordBreak: 'break-word',
5050
},
51+
courseLink: {
52+
color: theme.palette.action.active,
53+
textDecoration: 'none',
54+
},
5155
icon: {
5256
marginRight: theme.spacing(2),
5357
},

src/components/Home/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@ export interface Options {
1717
}
1818

1919
const Home = ({
20-
discoveryItems: { bestSellers, mostViewed },
20+
discoveryItems: { bestSellers, mostViewed, getDiscoveryItemsLoading },
2121
getDiscoveryItems,
2222
}: Options) => {
2323
const classes = useStyles();
2424

2525
useEffect(() => {
2626
// TODO: fetch resources with nested models
2727
getDiscoveryItems();
28-
}, [bestSellers.courses.length]);
28+
}, [bestSellers.courses.length, mostViewed.courses.length]);
2929

3030
return (
3131
<Fragment>

src/constants/routes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export const RESET_PASSWORD = '/reset-password';
55
export const VERIFY = '/verify';
66
export const LOGIN = '/login';
77
export const DASHBOARD = '/dashboard';
8+
export const COURSE_VIEW = '/courses/:courseSlug'

0 commit comments

Comments
 (0)