Merge branch 'application-mgt-new' into 'application-mgt-new'

Complete edit release functionality

See merge request entgra/carbon-device-mgt!169
feature/appm-store/pbac
Dharmakeerthi Lasantha 5 years ago
commit 72fbd7d3ad

@ -118,7 +118,6 @@ class FiltersForm extends React.Component {
}); });
}; };
render() { render() {
const {categories, tags, deviceTypes} = this.state; const {categories, tags, deviceTypes} = this.state;
const {getFieldDecorator} = this.props.form; const {getFieldDecorator} = this.props.form;
@ -148,10 +147,7 @@ class FiltersForm extends React.Component {
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
<Divider/>
{/*<Text strong={true}>Category</Text>*/}
{/*<br/><br/>*/}
<Form.Item label="Categories"> <Form.Item label="Categories">
{getFieldDecorator('categories', { {getFieldDecorator('categories', {
rules: [{ rules: [{
@ -178,15 +174,7 @@ class FiltersForm extends React.Component {
</Select> </Select>
)} )}
</Form.Item> </Form.Item>
{/*<Select*/}
{/*mode="multiple"*/}
{/*style={{width: '100%'}}*/}
{/*placeholder="All Categories"*/}
{/*>*/}
{/*<Option key={1}>IoT</Option>*/}
{/*<Option key={2}>EMM</Option>*/}
{/*</Select>*/}
<Divider/>
<Form.Item label="Device Types"> <Form.Item label="Device Types">
{getFieldDecorator('deviceTypes', { {getFieldDecorator('deviceTypes', {
@ -213,7 +201,6 @@ class FiltersForm extends React.Component {
)} )}
</Form.Item> </Form.Item>
{/*<Text strong={true}>Tags</Text>*/}
<Form.Item label="Tags"> <Form.Item label="Tags">
{getFieldDecorator('tags', { {getFieldDecorator('tags', {
rules: [{ rules: [{
@ -240,7 +227,6 @@ class FiltersForm extends React.Component {
)} )}
</Form.Item> </Form.Item>
<Divider/>
<Form.Item label="App Type"> <Form.Item label="App Type">
{getFieldDecorator('appType', {})( {getFieldDecorator('appType', {})(
<Select <Select

@ -20,7 +20,7 @@ class App extends React.Component {
<Router> <Router>
<div> <div>
<Switch> <Switch>
<Redirect exact from="/store" to="/store/apps"/> <Redirect exact from="/store" to="/store/android"/>
{this.routes.map((route) => ( {this.routes.map((route) => (
<RouteWithSubRoutes key={route.path} {...route} /> <RouteWithSubRoutes key={route.path} {...route} />
))} ))}

@ -9,6 +9,7 @@ import AddReview from "./review/AddReview";
import axios from "axios"; import axios from "axios";
import config from "../../../../public/conf/config.json"; import config from "../../../../public/conf/config.json";
import AppInstallModal from "./install/AppInstallModal"; import AppInstallModal from "./install/AppInstallModal";
import CurrentUsersReview from "./review/CurrentUsersReview";
const {Title, Text, Paragraph} = Typography; const {Title, Text, Paragraph} = Typography;
@ -129,14 +130,13 @@ class ReleaseView extends React.Component {
{release.description} {release.description}
</Paragraph> </Paragraph>
<Divider/> <Divider/>
<CurrentUsersReview uuid={release.uuid}/>
<Divider dashed={true}/>
<Text>REVIEWS</Text> <Text>REVIEWS</Text>
<Row> <Row>
<Col lg={18}> <Col lg={18} md={24}>
<DetailedRating type="app" uuid={release.uuid}/> <DetailedRating type="app" uuid={release.uuid}/>
</Col> </Col>
<Col lg={6}>
<AddReview uuid={release.uuid}/>
</Col>
</Row> </Row>
<Reviews type="app" uuid={release.uuid}/> <Reviews type="app" uuid={release.uuid}/>
</div> </div>

@ -52,8 +52,6 @@ class AddReview extends React.Component {
rating: rating rating: rating
}; };
const request = "method=post&content-type=application/json&payload="+JSON.stringify(payload)+"&api-endpoint=/application-mgt-store/v1.0/reviews/"+uuid;
axios.post( axios.post(
config.serverConfig.protocol + "://" + config.serverConfig.hostname + ':' + config.serverConfig.httpsPort + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/reviews/" + uuid, config.serverConfig.protocol + "://" + config.serverConfig.hostname + ':' + config.serverConfig.httpsPort + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/reviews/" + uuid,
payload, payload,
@ -105,8 +103,8 @@ class AddReview extends React.Component {
render() { render() {
return ( return (
<div style={{paddingTop: 20}}> <div>
<Button style={{float: "right"}} type="dashed" onClick={this.showDrawer}> <Button type="primary" onClick={this.showDrawer}>
<Icon type="star"/> Add a review <Icon type="star"/> Add a review
</Button> </Button>
@ -127,7 +125,7 @@ class AddReview extends React.Component {
<TextArea <TextArea
placeholder="Tell others what you think about this app. Would you recommend it, and why?" placeholder="Tell others what you think about this app. Would you recommend it, and why?"
onChange={this.onChange} onChange={this.onChange}
autosize={{minRows: 6, maxRows: 12}} rows={4}
value={this.state.content || ''} value={this.state.content || ''}
style={{marginBottom: 20}} style={{marginBottom: 20}}
/> />

@ -0,0 +1,95 @@
import React from "react";
import {List, message, Typography, Empty, Button, Row, Col} from "antd";
import SingleReview from "./singleReview/SingleReview";
import axios from "axios";
import config from "../../../../../public/conf/config.json";
import AddReview from "./AddReview";
const {Text, Paragraph} = Typography;
class CurrentUsersReview extends React.Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
componentDidMount() {
this.fetchData();
}
fetchData = () => {
const {uuid} = this.props;
axios.get(
config.serverConfig.protocol + "://" + config.serverConfig.hostname + ':' + config.serverConfig.httpsPort + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/reviews/app/user/" + uuid,
).then(res => {
if (res.status === 200) {
const data = res.data.data.data;
this.setState({data});
}
}).catch((error) => {
if (error.response.hasOwnProperty(status) && error.response.status === 401) {
message.error('You are not logged in');
window.location.href = config.serverConfig.protocol + "://" + config.serverConfig.hostname + ':' + config.serverConfig.httpsPort + '/store/login';
} else {
message.error('Something went wrong when trying to get your review... :(');
}
});
};
render() {
const {data} = this.state;
const {uuid} = this.props;
return (
<div>
<Text>MY REVIEW</Text>
<div style={{
overflow: "auto",
paddingTop: 8,
paddingLeft: 24
}}>
{data.length > 0 && (
<div>
<List
dataSource={data}
renderItem={item => (
<List.Item key={item.id}>
<SingleReview uuid={uuid} review={item} isDeletable={true} isEditable={true}/>
</List.Item>
)}
>
</List>
</div>
)}
{data.length === 0 && (
<div>
<Empty
image={Empty.PRESENTED_IMAGE_DEFAULT}
imagestyle={{
height: 60,
}}
description={
<span>Share your experience with your community by adding a review.</span>
}
>
{/*<Button type="primary">Add review</Button>*/}
<AddReview uuid={uuid}/>
</Empty>
</div>
)}
</div>
</div>
);
}
}
export default CurrentUsersReview;

@ -1,23 +1,16 @@
.demo-infinite-container { .infinite-container {
overflow: auto; overflow: auto;
padding: 8px 24px; padding: 8px 24px;
} }
.demo-loading-container { .loading-container {
position: absolute; position: absolute;
bottom: 40px; bottom: 40px;
width: 100%; width: 100%;
text-align: center; text-align: center;
} }
.demo-loading { .loading {
position: absolute; position: absolute;
bottom: -40px; bottom: -40px;
left: 50%; left: 50%;
} }
img.twemoji {
height: 1em;
width: 1em;
margin: 0 .05em 0 .1em;
vertical-align: -0.1em;
}

@ -3,7 +3,7 @@ import {List, message, Avatar, Spin, Button} from 'antd';
import "./Reviews.css"; import "./Reviews.css";
import InfiniteScroll from 'react-infinite-scroller'; import InfiniteScroll from 'react-infinite-scroller';
import SingleReview from "./SingleReview"; import SingleReview from "./singleReview/SingleReview";
import axios from "axios"; import axios from "axios";
import config from "../../../../../public/conf/config.json"; import config from "../../../../../public/conf/config.json";
@ -87,43 +87,33 @@ class Reviews extends React.Component {
}; };
render() { render() {
const review = { const {loading, hasMore, data, loadMore} = this.state;
id: 2, const {uuid} = this.props;
content: "Btw, it was clear to me that I can cancel the 1 year subscription before the free trial week and so I did. Dont understand the negative reviews about that. It has a good collection of excercises, meditations etc. You just answer 5 questions and you get challenges assigned to you. I would have liked something even more personalized. I didnt like the interface. It is a bit messy and difficult to follow your tasks. So, I didnt want to do a full-year subscription. There could be more options.",
rootParentI: -1,
immediateParentId: -1,
createdAt: "Fri, 24 May 2019 17:27:22 IST",
modifiedAt: "Fri, 24 May 2019 17:27:22 IST",
rating: 4,
replies: []
};
// console.log(this.state.loadMore);
// console.log(this.state.data.length);
return ( return (
<div className="demo-infinite-container"> <div className="infinite-container">
<InfiniteScroll <InfiniteScroll
initialLoad={false} initialLoad={false}
pageStart={0} pageStart={0}
loadMore={this.handleInfiniteOnLoad} loadMore={this.handleInfiniteOnLoad}
hasMore={!this.state.loading && this.state.hasMore} hasMore={!loading && hasMore}
useWindow={true} useWindow={true}
> >
<List <List
dataSource={this.state.data} dataSource={data}
renderItem={item => ( renderItem={item => (
<List.Item key={item.id}> <List.Item key={item.id}>
<SingleReview review={item}/> <SingleReview uuid={uuid} review={item} isDeletable={true} isEditable={false}/>
</List.Item> </List.Item>
)} )}
> >
{this.state.loading && this.state.hasMore && ( {loading && hasMore && (
<div className="demo-loading-container"> <div className="loading-container">
<Spin/> <Spin/>
</div> </div>
)} )}
</List> </List>
</InfiniteScroll> </InfiniteScroll>
{!this.state.loadMore && (this.state.data.length >= limit) && (<div style={{textAlign: "center"}}> {!loadMore && (data.length >= limit) && (<div style={{textAlign: "center"}}>
<Button type="dashed" htmlType="button" onClick={this.enableLoading}>Read All Reviews</Button> <Button type="dashed" htmlType="button" onClick={this.enableLoading}>Read All Reviews</Button>
</div>)} </div>)}
</div> </div>

@ -1,52 +0,0 @@
import React from "react";
import {Avatar} from "antd";
import {List, Typography} from "antd";
import StarRatings from "react-star-ratings";
import Twemoji from "react-twemoji";
import "./Reviews.css";
const {Text, Paragraph} = Typography;
const colorList = ['#f0932b', '#badc58', '#6ab04c', '#eb4d4b', '#0abde3', '#9b59b6', '#3498db', '#22a6b3'];
class SingleReview extends React.Component {
render() {
const review = this.props.review;
const randomColor = colorList[Math.floor(Math.random() * (colorList.length))];
const avatarLetter = review.username.charAt(0).toUpperCase();
const content = (
<div style={{marginTop: -5}}>
<StarRatings
rating={review.rating}
starRatedColor="#777"
starDimension="12px"
starSpacing="2px"
numberOfStars={5}
name='rating'
/>
<Text style={{fontSize: 12, color: "#aaa"}} type="secondary"> {review.createdAt}</Text><br/>
<Twemoji options={{className: 'twemoji'}}>
<Paragraph ellipsis={{rows: 3, expandable: true}} style={{color: "#777"}}>
{review.content}
</Paragraph>
</Twemoji>
</div>
);
return (
<div>
<List.Item.Meta
avatar={
<Avatar style={{backgroundColor: randomColor, verticalAlign: 'middle'}} size="large">
{avatarLetter}
</Avatar>
}
title={review.username}
description={content}
/>
</div>
);
}
}
export default SingleReview;

@ -0,0 +1,26 @@
img.twemoji {
height: 1em;
width: 1em;
margin: 0 .05em 0 .1em;
vertical-align: -0.1em;
}
.edit-button {
color: #1890ff;
text-decoration: none;
outline: none;
cursor: pointer;
font-weight: normal;
font-size: 0.8em;
padding-left: 5px;
}
.delete-button {
color: #e74c3c;
text-decoration: none;
outline: none;
cursor: pointer;
font-weight: normal;
font-size: 0.8em;
padding-left: 5px;
}

@ -0,0 +1,90 @@
import React from "react";
import {Avatar} from "antd";
import {List, Typography} from "antd";
import StarRatings from "react-star-ratings";
import Twemoji from "react-twemoji";
import "./SingleReview.css";
import EditReview from "./editReview/EditReview";
const {Text, Paragraph} = Typography;
const colorList = ['#f0932b', '#badc58', '#6ab04c', '#eb4d4b', '#0abde3', '#9b59b6', '#3498db', '#22a6b3','#e84393','#f9ca24'];
class SingleReview extends React.Component {
constructor(props) {
super(props);
this.state = {
content: '',
rating: 0,
color: '#f0932b'
}
}
componentDidMount() {
const {content, rating, username} = this.props.review;
const color = colorList[username.length%10];
this.setState({
content,
rating,
color
});
}
updateCallback = (response) =>{
console.log(response);
const {rating, content} = response;
this.setState({
rating,
content
});
};
render() {
const {review, isEditable, isDeletable, uuid} = this.props;
const {content, rating, color} = this.state;
const {username} = review;
const avatarLetter = username.charAt(0).toUpperCase();
const body = (
<div style={{marginTop: -5}}>
<StarRatings
rating={rating}
starRatedColor="#777"
starDimension="12px"
starSpacing="2px"
numberOfStars={5}
name='rating'
/>
<Text style={{fontSize: 12, color: "#aaa"}} type="secondary"> {review.createdAt}</Text><br/>
<Paragraph style={{color: "#777"}}>
<Twemoji options={{className: 'twemoji'}}>
{content}
</Twemoji>
</Paragraph>
</div>
);
const title = (
<div>
{review.username}
{isEditable && (<EditReview uuid={uuid} review={review} updateCallback={this.updateCallback}/>)}
{isDeletable && (<span className="delete-button">delete</span>)}
</div>
);
return (
<div>
<List.Item.Meta
avatar={
<Avatar style={{backgroundColor: color, verticalAlign: 'middle'}} size="large">
{avatarLetter}
</Avatar>
}
title={title}
description={body}
/>
</div>
);
}
}
export default SingleReview;

@ -0,0 +1,9 @@
.edit-button {
color: #1890ff;
text-decoration: none;
outline: none;
cursor: pointer;
font-weight: normal;
font-size: 0.8em;
padding-left: 5px;
}

@ -0,0 +1,170 @@
import React from "react";
import {Drawer, Button, Icon, Row, Col, Typography, Divider, Input, Spin, notification} from 'antd';
import StarRatings from "react-star-ratings";
import axios from "axios";
import config from "../../../../../../../public/conf/config.json";
import "./EditReview.css";
const {Title} = Typography;
const {TextArea} = Input;
class EditReview extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: false,
content: '',
rating: 0,
loading: false
};
}
componentDidMount() {
const {content,rating,id} = this.props.review;
console.log(this.props.review);
this.setState({
content,
rating
});
}
showDrawer = () => {
this.setState({
visible: true,
loading: false
});
};
onClose = () => {
this.setState({
visible: false,
});
};
changeRating = (newRating, name) => {
this.setState({
rating: newRating
});
};
onChange = (e) => {
this.setState({content: e.target.value})
};
onSubmit = () => {
const {content, rating} = this.state;
const {id} = this.props.review;
const {uuid} = this.props;
this.setState({
loading: true
});
const payload = {
content: content,
rating: rating
};
axios.put(
config.serverConfig.protocol + "://" + config.serverConfig.hostname + ':' + config.serverConfig.httpsPort + config.serverConfig.invoker.uri + config.serverConfig.invoker.store + "/reviews/" + uuid+"/"+id,
payload,
).then(res => {
if (res.status === 200) {
this.setState({
loading: false,
visible: false
});
notification["success"]({
message: 'Done!',
description:
'Your review has been update successfully.',
});
this.props.updateCallback(res.data.data);
} else {
this.setState({
loading: false,
visible: false
});
notification["error"]({
message: 'Something went wrong',
description:
"We are unable to update your review right now.",
});
}
}).catch((error) => {
console.log(error);
if (error.hasOwnProperty("response") && error.response.status === 401) {
window.location.href = config.serverConfig.protocol + "://" + config.serverConfig.hostname + ':' + config.serverConfig.httpsPort + '/store/login';
} else {
this.setState({
loading: false,
visible: false
});
notification["error"]({
message: 'Something went wrong',
description:
"We are unable to add your review right now.",
});
}
});
};
render() {
return (
<span>
<span className="edit-button" onClick={this.showDrawer}>edit</span>
<Drawer
// title="Basic Drawer"
placement="bottom"
closable={false}
onClose={this.onClose}
visible={this.state.visible}
height={400}
>
<Spin spinning={this.state.loading} tip="Posting your review...">
<Row>
<Col lg={8}/>
<Col lg={8}>
<Title level={4}>Edit review</Title>
<Divider/>
<TextArea
placeholder="Tell others what you think about this app. Would you recommend it, and why?"
onChange={this.onChange}
rows={6}
value={this.state.content || ''}
style={{marginBottom: 20}}
/>
<StarRatings
rating={this.state.rating}
changeRating={this.changeRating}
starRatedColor="#777"
starHoverColor="#444"
starDimension="20px"
starSpacing="2px"
numberOfStars={5}
name='rating'
/>
<br/><br/>
<Button onClick={this.onClose} style={{marginRight: 8}}>
Cancel
</Button>
<Button disabled={this.state.rating === 0} onClick={this.onSubmit} type="primary">
Submit
</Button>
</Col>
</Row>
</Spin>
</Drawer>
</span>
);
}
}
export default EditReview;

@ -68,7 +68,7 @@ class NormalLoginForm extends React.Component {
axios.post(config.serverConfig.protocol + "://"+config.serverConfig.hostname+':'+config.serverConfig.httpsPort+config.serverConfig.loginUri, request axios.post(config.serverConfig.protocol + "://"+config.serverConfig.hostname+':'+config.serverConfig.httpsPort+config.serverConfig.loginUri, request
).then(res => { ).then(res => {
if (res.status === 200) { if (res.status === 200) {
window.location = res.data.url; window.location = config.serverConfig.protocol + "://"+config.serverConfig.hostname+':'+config.serverConfig.httpsPort+"/store";
} }
}).catch(function (error) { }).catch(function (error) {
if (error.response.status === 400) { if (error.response.status === 400) {

@ -48,7 +48,6 @@ class Dashboard extends React.Component {
<Layout> <Layout>
<Content style={{padding: '0 0'}}> <Content style={{padding: '0 0'}}>
<Switch> <Switch>
<Redirect exact from="/store" to="/store/android"/>
{this.state.routes.map((route) => ( {this.state.routes.map((route) => (
<RouteWithSubRoutes changeSelectedMenuItem={this.changeSelectedMenuItem} key={route.path} {...route} /> <RouteWithSubRoutes changeSelectedMenuItem={this.changeSelectedMenuItem} key={route.path} {...route} />
))} ))}

Loading…
Cancel
Save