Fakultas Ilmu Komputer UI

Commit 5f81b015 authored by M. Reza Qorib's avatar M. Reza Qorib
Browse files

Merge branch 'UserStory3' of https://gitlab.com/PPL2017csui/PPLA1 into Features/react-routing

# Conflicts:
#	assets/js/index.jsx
parents 6dc21553 7ddad4b0
......@@ -11,7 +11,7 @@ export default class Dashboard extends React.Component {
render = () => (
<div>
<TopMenu />
<TopMenu />
{this.props.children}
</div>
)
......
......@@ -14,30 +14,30 @@ export default class VacancyPage extends React.Component {
password: '',
errorFlag: false,
vacancies: [],
student: false,
};
Storage.getUserData().then(r => this.setState({ student: r.student }));
this.handleItemClick = this.handleItemClick.bind(this);
}
handleItemClick = (e, { name }) => this.setState({ activeItem: name });
render() {
const student = Storage.get('user-data').student;
return (
<div className="halamanLowongan">
<Tabs selected={0}>
<Pane label="Semua Lowongan" >
<VacancyList key={1} studentId={student.id} url="/vacancies/" />
</Pane>
<Pane label="Lamaran saya" >
<VacancyList key={2} status="Batal" studentId={student.id} url={`/students/${student.id}/applied-vacancies/`} />
</Pane>
<Pane label="Lamaran Ditandai" >
<VacancyList key={3} studentId={student.id} url={`/students/${student.id}/bookmarked-vacancies/`} />
</Pane>
</Tabs>
</div>
);
}
render = () => (
this.state.student && (
<div className="halamanLowongan">
<Tabs selected={0}>
<Pane label="Semua Lowongan" >
<VacancyList key={1} studentId={this.state.student.id} url="/vacancies/" />
</Pane>
<Pane label="Lamaran saya" >
<VacancyList key={2} status="Batal" studentId={this.state.student.id} url={`/students/${this.state.student.id}/applied-vacancies/`} />
</Pane>
<Pane label="Lamaran Ditandai" >
<VacancyList key={3} studentId={this.state.student.id} url={`/students/${this.state.student.id}/bookmarked-vacancies/`} />
</Pane>
</Tabs>
</div>
)
);
}
import React from 'react';
import ReactTestUtils from 'react-addons-test-utils';
import ApplyConfirmationModal from '../../components/ApplyConfirmationModal';
import Storage from '../../lib/Storage';
import Server from '../../lib/Server';
describe('ApplyConfirmationModal', () => {
const fetchMock = require('fetch-mock');
it('renders without problem', () => {
const applyModal = ReactTestUtils.renderIntoDocument(
<ApplyConfirmationModal id={4} coverLetter="letter" onChangeValue={() => {}} />,
);
expect(applyModal).to.exist;
});
it('test apply without problem', () => {
const applyModal = ReactTestUtils.renderIntoDocument(
<ApplyConfirmationModal id={4} coverLetter="letter" onChangeValue={() => {}} />,
);
const response = { student: { id: 1, name: 2 } };
Storage.set('user-data', response);
fetchMock.post('*', { data: 'value' });
applyModal.open();
applyModal.handleOpen();
applyModal.close();
expect(applyModal.state.header).to.equal('Menghubungkan ke Server');
});
it('test apply with problem', () => {
const applyModal = ReactTestUtils.renderIntoDocument(
<ApplyConfirmationModal id={4} coverLetter="letter" onChangeValue={() => {}} />,
);
const response = { student: { id: 1, name: 2 } };
Storage.set('user-data', response);
fetchMock.post('*', { status: 404, body: response });
applyModal.open();
applyModal.handleOpen();
applyModal.close();
expect(applyModal.state.header).to.equal('Menghubungkan ke Server');
});
});
\ No newline at end of file
......@@ -29,6 +29,8 @@ describe('CancelModal', () => {
});
it('remove vacancy without problem', () => {
const fetchMock = require('fetch-mock');
fetchMock.delete('*', { data: 'value' });
const modalPendaftaran = ReactTestUtils.renderIntoDocument(
<CancelModal id={4} />);
......
/* eslint-disable no-unused-expressions */
import React from 'react';
import ReactTestUtils from 'react-addons-test-utils';
import ModalAlert from '../../components/ModalAlert';
describe('ModalAlert', () => {
it('renders without problem', () => {
const modalAlert = ReactTestUtils.renderIntoDocument(
<ModalAlert id={4} coverLetter="letter" onChangeValue={() => {}}/>);
const modalAlert = ReactTestUtils.renderIntoDocument(<ModalAlert />);
expect(modalAlert).to.exist;
});
});
\ No newline at end of file
it('renders with open state set to false', () => {
const modalAlert = ReactTestUtils.renderIntoDocument(<ModalAlert />);
expect(modalAlert.state.open).to.equal(false);
});
it('modal state should be true when opened', () => {
const modalAlert = ReactTestUtils.renderIntoDocument(<ModalAlert />);
modalAlert.open();
expect(modalAlert.state.open).to.equal(true);
});
it('modal closed properly with open state should be false', () => {
const modalAlert = ReactTestUtils.renderIntoDocument(<ModalAlert />);
modalAlert.close();
expect(modalAlert.state.open).to.equal(false);
});
});
......@@ -94,6 +94,11 @@ describe('Server get test', () => {
Server.sendRequest('/test', 'GET', {}, true).then((data) => { expect(data).to.exist; });
});
it('Check submit method', () => {
fetchMock.get('*', {hue : "hue", hui : "hui"});
Server.submit('/test', {hue : "hue", hui : "hui"}, 'GET', true).then((data) => { expect(data).to.exist; });
});
it('Check isloggedin method', () => {
expect(Server.isLoggedIn()).to.not.exist;
});
......
/* eslint-disable no-unused-expressions */
/* eslint-disable */
import Storage from './../../lib/Storage';
describe('Storage get key', () => {
......@@ -30,4 +30,21 @@ describe('Storage clear key', () => {
expect(Storage.clear()).to.be.not.exist;
expect(Storage.get('test')).to.be.not.exist;
});
});
\ No newline at end of file
});
describe('Storage getUserData test', () => {
const fetchMock = require('fetch-mock');
it('Check Storage when there is no userdata', () => {
fetchMock.get('*', 'hue');
Storage.set('user-data', null); // clear to make sure storage empty
expect(Storage.get('user-data')).to.equal(null);
Storage.getUserData().then(r => {expect(r).to.equal('hue');}); // Storage call
});
it('Check Storage when there is already userdata', () => {
Storage.set('user-data','hue'); // set to make sure storage not empty
Storage.getUserData().then(r => {expect(r).to.equal('hue');});
});
});
import React from 'react';
import { Modal, Button, Icon } from 'semantic-ui-react';
import Server from '../lib/Server';
import Storage from '../lib/Storage';
export default class ApplyConfirmationModal extends React.Component {
static propTypes = {
id: React.PropTypes.number.isRequired,
onChangeValue: React.PropTypes.func.isRequired,
coverLetter: React.PropTypes.string,
status: React.PropTypes.string.isRequired,
};
static defaultProps = {
coverLetter: '',
};
constructor(props) {
super(props);
/* istanbul ignore next */
this.state = {
open: false,
header: 'Menghubungkan ke Server',
content: 'Harap menunggu, permintaan anda sedang diproses...',
};
this.handleOpen = this.handleOpen.bind(this);
}
open = () => this.setState({ open: true });
close = () => this.setState({ open: false });
handleOpen() {
const studentId = Storage.get('user-data').student.id;
const daftarSuccess = 'Pendaftaran anda berhasil direkam. Harap menunggu kabar selanjutnya dari pihak yang terkait\n';
const daftarFailed = 'Maaf pendaftaran yang anda lakukan gagal. Harap ulangi pendaftaran atau hubungi administrator\n';
const requestData = { vacancy_id: this.props.id, cover_letter: this.props.coverLetter };
Server.post(`/students/${studentId}/applied-vacancies/`, requestData).then(() => {
this.setState({
header: 'Pendaftaran Berhasil',
content: daftarSuccess,
});
}, () => {
this.setState({
header: 'Pendaftaran Gagal',
content: daftarFailed,
});
});
}
render() {
const { open } = this.state;
const buttonColor = this.props.status === 'Daftar' ? 'blue' : 'red';
return (
<Modal
open={open}
onOpen={this.open}
onClose={this.close}
size="small"
basic
trigger={<Button color={buttonColor} onClick={this.handleOpen}> {this.props.status} <Icon name="right chevron" /></Button>}
>
<Modal.Header>{this.state.header}</Modal.Header>
<Modal.Content>
<p>{this.state.content}</p>
</Modal.Content>
<Modal.Actions>
<Button icon="checkmark" color="green" content="All Done" onClick={this.props.onChangeValue} />
</Modal.Actions>
</Modal>
);
}
}
import React from 'react';
import { Modal, Button, Icon, TextArea, Form } from 'semantic-ui-react';
import ModalAlert from './ModalAlert';
import { Modal, Button, TextArea, Form } from 'semantic-ui-react';
import ApplyConfirmationModal from './ApplyConfirmationModal';
export default class ApplyModal extends React.Component {
static propTypes = {
......@@ -66,7 +66,7 @@ export default class ApplyModal extends React.Component {
</Modal.Content>
<Modal.Actions>
<ModalAlert
<ApplyConfirmationModal
id={this.props.id}
onChangeValue={this.handleClose}
coverLetter={this.state.coverLetter}
......
import React from 'react';
import { Modal, Button, Icon } from 'semantic-ui-react';
import Server from '../lib/Server';
import Storage from '../lib/Storage';
import { Button, Header, Icon, Modal } from 'semantic-ui-react';
export default class ModalAlert extends React.Component {
static propTypes = {
id: React.PropTypes.number.isRequired,
onChangeValue: React.PropTypes.func.isRequired,
coverLetter: React.PropTypes.string,
status: React.PropTypes.string.isRequired,
};
static defaultProps = {
coverLetter: '',
};
constructor(props) {
super(props);
/* istanbul ignore next */
this.state = {
open: false,
header: 'Menghubungkan ke Server',
content: 'Harap menunggu, permintaan anda sedang diproses...',
};
this.handleOpen = this.handleOpen.bind(this);
this.state = { open: false, header: '', content: '' };
this.open = this.open.bind(this);
this.close = this.close.bind(this);
}
open = () => this.setState({ open: true });
close = () => this.setState({ open: false });
handleOpen() {
const studentId = Storage.get('user-data').student.id;
componentWillUpdate() {
this.fixBody();
}
const daftarSuccess = 'Pendaftaran anda berhasil direkam. Harap menunggu kabar selanjutnya dari pihak yang terkait\n';
const daftarFailed = 'Maaf pendaftaran yang anda lakukan gagal. Harap ulangi pendaftaran atau hubungi administrator\n';
componentDidUpdate() {
this.fixBody();
}
if (this.props.status == 'Daftar') {
const requestData = { vacancy_id: this.props.id, cover_letter: this.props.coverLetter };
fixBody = () => {
const anotherModal = document.getElementsByClassName('ui page modals').length;
if (anotherModal > 0) document.body.classList.add('scrolling', 'dimmable', 'dimmed');
};
Server.post(`/students/${studentId}/applied-vacancies/`, requestData).then(() => {
this.setState({
header: 'Pendaftaran Berhasil',
content: this.daftarSuccess,
});
}, () => {
this.setState({
header: 'Pendaftaran Gagal',
content: this.daftarFailed,
});
});
} else {
open = (header = this.state.header, content = this.state.content) => {
this.setState({ open: true, header, content });
};
}
}
close = () => {
this.setState({ open: false });
};
render() {
const { open } = this.state;
const buttonColor = this.props.status === 'Daftar' ? 'blue' : 'red';
return (
<Modal
open={open}
onOpen={this.open}
onClose={this.close}
size="small"
basic
trigger={<Button color={buttonColor} onClick={this.handleOpen}> {this.props.status} <Icon name="right chevron" /></Button>}
>
<Modal.Header>{this.state.header}</Modal.Header>
<Modal.Content>
<p>{this.state.content}</p>
</Modal.Content>
<Modal.Actions>
<Button icon="checkmark" color="green" content="All Done" onClick={this.props.onChangeValue} />
</Modal.Actions>
</Modal>
);
}
render = () => (
<Modal open={this.state.open} basic size="small">
<Header icon="warning sign" content={this.state.header} />
<Modal.Content>
<p>{this.state.content}</p>
</Modal.Content>
<Modal.Actions>
<Button color="green" inverted onClick={this.close}>
<Icon name="checkmark" /> OK
</Button>
</Modal.Actions>
</Modal>
);
}
import React from 'react';
import { browserHistory } from 'react-router';
import { Modal, Button, Form, Input, TextArea, Header, Icon } from 'semantic-ui-react';
import ModalAlert from './../components/ModalAlert';
import Server from './../lib/Server';
import Storage from './../lib/Storage';
......@@ -17,6 +18,7 @@ export default class RegisterModal extends React.Component {
this.setState({ [e.target.name]: e.target.value });
};
handlePassword = (e) => {
if (e.target.name === 'password') this.passwordField = e.target; else
if (e.target.name === 'password-confirm') this.passwordConfirmField = e.target;
......@@ -32,34 +34,20 @@ export default class RegisterModal extends React.Component {
handleSubmit = (e) => {
e.preventDefault();
const form = new FormData();
Object.keys(this.state).map(k => form.append(k, this.state[k]));
const data = {
'X-CSRFToken': Server.getCookie('csrftoken'),
mode: 'no-cors',
method: 'POST',
body: form,
credentials: 'same-origin',
};
fetch('/api/register/', data)
.then((response) => {
if (response.status < 200 || response.status > 399) throw response.json();
else return response.json();
})
.then((response) => {
alert('Akun berhasil dibuat :)');
Storage.set('user-data', response);
browserHistory.push('/home');
}, error => error.then(r => alert(JSON.stringify(r))));
Server.submit('/register/', this.state).then((response) => {
Storage.set('user-data', response);
browserHistory.push('/home');
}, error => error.then((r) => {
this.modalAlert.open('Gagal Membuat Akun', r.error);
}));
};
render = () => (
<Modal trigger={<Button primary floated="right">Register Here!</Button>} closeIcon="close">
<Header icon="archive" content="Register for More Benefits" />
<Modal.Content>
<ModalAlert ref={(modal) => { this.modalAlert = modal; }} />
<Form onSubmit={this.handleSubmit}>
<Header as="h2" icon textAlign="center">
<Icon name="signup" circular />
......@@ -90,7 +78,7 @@ export default class RegisterModal extends React.Component {
<label htmlFor="name">Nama Perusahaan</label>
<Input onChange={this.handleChange} placeholder="Nama Perusahaan" name="name" required />
</Form.Field>
<Form.Field>
<Form.Field required>
<label htmlFor="logo">Logo</label>
<Input
onChange={this.handleChange}
......@@ -98,6 +86,7 @@ export default class RegisterModal extends React.Component {
icon={{ name: 'attach', circular: true, link: true }}
placeholder="attach logo"
type="File"
required
/>
</Form.Field>
<Form.Field required>
......@@ -110,7 +99,6 @@ export default class RegisterModal extends React.Component {
</Form.Field>
<Button type="submit" floated="right" color="blue">Submit</Button>
</Form>
</Modal.Content>
<Modal.Actions />
</Modal>
......
......@@ -57,13 +57,17 @@ export default class App extends React.Component {
}
});
handleAuth = (nextState, replace) => (
Server.isLoggedIn() || replace({ pathname: '/login' })
);
handleAuth = (nextState, replace) => {
if (!Server.isLoggedIn()) replace({ pathname: '/login' });
Storage.getUserData();
};
handleHome= (nextState, replace) => {
handleHome= (nextState, replace, cb) => {
if (Server.isLoggedIn()) {
Storage.get('user-data').student ? replace({ pathname: '/lowongan' }) : replace({ pathname: '/profile' });
Storage.getUserData().then((data) => {
const next = data.student ? '/lowongan' : '/profile';
replace({ pathname: next }); cb();
});
} else {
replace({ pathname: '/login' });
}
......
......@@ -17,8 +17,22 @@ export default class Server {
return null;
}
static submit(path, data, method = 'POST', useCache = false) {
const form = new FormData();
Object.keys(data).map(k => form.append(k, data[k]));
const requestData = {
'X-CSRFToken': Server.getCookie('csrftoken'),
mode: 'no-cors',
method: 'POST',
body: form,
credentials: 'same-origin',
};
return this.handleFetchRequest(requestData, path, useCache);
}
static sendRequest(path, method, data, useCache = false) {
const url = `/api${path}`;
const csrftoken = this.getCookie('csrftoken');
const headers = {
'content-type': 'application/json',
......@@ -32,7 +46,11 @@ export default class Server {
credentials: 'same-origin',
};
// noinspection JSUnresolvedFunction
return this.handleFetchRequest(requestData, path, useCache);
}
static handleFetchRequest(requestData, path, useCache) {
const url = `/api${path}`;
const request = fetch(url, requestData);
/* istanbul ignore next */
......@@ -43,12 +61,8 @@ export default class Server {
const contentType = response.headers.get('content-type');
const result = contentType && contentType.indexOf('application/json') !== -1 ? response.json() : response.text();
if (response.status < 200 || response.status > 399) {
throw result;
}
else {
return result;
}
if (response.status < 200 || response.status > 399) throw result;
else return result;
});
/* istanbul ignore next */
......
import Server from './../lib/Server';
/** Session Storage Polyfill */
/* eslint-disable */
......@@ -120,14 +122,23 @@ polyfil();
export default class Storage {
static get(key) {
return JSON.parse(sessionStorage.getItem(key));
return JSON.parse(localStorage.getItem(key));
}
static set(key, value) {
return sessionStorage.setItem(key, JSON.stringify(value));
return localStorage.setItem(key, JSON.stringify(value));
}
static clear() {
return sessionStorage.clear();
return localStorage.clear();
}
static getUserData() {
const data = Storage.get('user-data');
/* istanbul ignore next */
return (data != null) ? Promise.resolve(data) : Server.get('/users/me/').then((response) => {
Storage.set(('user-data'), response);