diff --git a/handlers/handlers.go b/handlers/handlers.go
index 350724e..47942b5 100644
--- a/handlers/handlers.go
+++ b/handlers/handlers.go
@@ -79,7 +79,7 @@ func (h *Handler) setHandlers() error {
protected.Use(h.authMiddleware)
protected.POST("/create", h.handleCreate)
protected.POST("/lookup", h.handleLookup)
- protected.POST("/recent", h.handleRecent)
+ protected.GET("/recent", h.handleRecent)
protected.POST("/visitors", h.handleGetVisitors)
h.engine.GET("/api/v1/info", h.handleInfo)
diff --git a/static/src/Home/Home.js b/static/src/Home/Home.js
index 5eaf998..3f55325 100644
--- a/static/src/Home/Home.js
+++ b/static/src/Home/Home.js
@@ -4,8 +4,8 @@ import DatePicker from 'react-datepicker';
import moment from 'moment';
import MediaQuery from 'react-responsive';
import 'react-datepicker/dist/react-datepicker.css';
-import toastr from 'toastr'
+import util from '../util/util'
import CustomCard from '../Card/Card'
import './Home.css'
@@ -15,34 +15,13 @@ export default class HomeComponent extends Component {
handleCustomExpirationChange = expire => this.setState({ expiration: expire })
handleCustomIDChange = (e, { value }) => {
this.customID = value
- fetch("/api/v1/protected/lookup", {
- method: "POST",
- body: JSON.stringify({
- ID: value
- }),
- headers: {
- 'Authorization': window.localStorage.getItem('token'),
- 'Content-Type': 'application/json'
- }
- })
- .then(res => res.ok ? res.json() : Promise.reject(res.json()))
- .then(() => {
- this.setState({ showCustomIDError: true })
- })
- .catch(e => {
- this.setState({ showCustomIDError: false })
- })
+ util.lookupEntry(value, () => this.setState({ showCustomIDError: true }), () => this.setState({ showCustomIDError: false }))
}
- onSettingsChange = (e, { value }) => this.setState({ setOptions: value })
+ onSettingsChange = (e, { value }) => this.setState({ usedSettings: value })
state = {
links: [],
- options: [
- { text: 'Custom URL', value: 'custom' },
- { text: 'Expiration', value: 'expire' },
- { text: 'Password', value: 'protected' }
- ],
- setOptions: [],
+ usedSettings: [],
showCustomIDError: false,
expiration: null
}
@@ -51,34 +30,29 @@ export default class HomeComponent extends Component {
}
handleURLSubmit = () => {
if (!this.state.showCustomIDError) {
- fetch('/api/v1/protected/create', {
- method: 'POST',
- body: JSON.stringify({
- URL: this.url,
- ID: this.customID,
- Expiration: this.state.setOptions.includes("expire") && this.state.expiration ? this.state.expiration.toISOString() : undefined,
- Password: this.state.setOptions.includes("protected") && this.password ? this.password : undefined
- }),
- headers: {
- 'Authorization': window.localStorage.getItem('token'),
- 'Content-Type': 'application/json'
- }
- })
- .then(res => res.ok ? res.json() : Promise.reject(res.json()))
- .then(r => this.setState({
- links: [...this.state.links, [
- r.URL,
- this.url,
- this.state.setOptions.includes("expire") && this.state.expiration ? this.state.expiration.toISOString() : undefined,
- r.DeletionURL
- ]]
- }))
- .catch(e => e instanceof Promise ? e.then(error => toastr.error(`Could not fetch lookup: ${error.error}`)) : toastr.error(`Could not fetch create: ${e}`))
+ util.createEntry({
+ URL: this.url,
+ ID: this.customID,
+ Expiration: this.state.usedSettings.includes("expire") && this.state.expiration ? this.state.expiration.toISOString() : undefined,
+ Password: this.state.usedSettings.includes("protected") && this.password ? this.password : undefined
+ }, r => this.setState({
+ links: [...this.state.links, {
+ shortenedURL: r.URL,
+ originalURL: this.url,
+ expiration: this.state.usedSettings.includes("expire") && this.state.expiration ? this.state.expiration.toISOString() : undefined,
+ deletionURL: r.DeletionURL
+ }]
+ }))
}
}
render() {
- const { links, options, setOptions, showCustomIDError, expiration } = this.state
+ const options = [
+ { text: 'Custom URL', value: 'custom' },
+ { text: 'Expiration', value: 'expire' },
+ { text: 'Password', value: 'protected' }
+ ]
+ const { links, usedSettings, showCustomIDError, expiration } = this.state
return (
@@ -99,12 +73,12 @@ export default class HomeComponent extends Component {
- {setOptions.includes("custom") &&
+ {usedSettings.includes("custom") &&
}
- {setOptions.includes("expire") &&
+ {usedSettings.includes("expire") &&
}
minDate={moment()} />
}
- {setOptions.includes("protected") &&
+ {usedSettings.includes("protected") &&
}
- {links.map((link, i) => )}
+ {links.map((link, i) => )}
)
diff --git a/static/src/Lookup/Lookup.js b/static/src/Lookup/Lookup.js
index ce3b7de..ab154fe 100644
--- a/static/src/Lookup/Lookup.js
+++ b/static/src/Lookup/Lookup.js
@@ -1,7 +1,7 @@
import React, { Component } from 'react'
import { Segment, Header, Form, Input, Card, Button } from 'semantic-ui-react'
-import toastr from 'toastr'
+import util from '../util/util'
import CustomCard from '../Card/Card'
export default class LookupComponent extends Component {
@@ -11,28 +11,16 @@ export default class LookupComponent extends Component {
handleURLChange = (e, { value }) => this.url = value
handleURLSubmit = () => {
let id = this.url.replace(window.location.origin + "/", "")
- fetch("/api/v1/protected/lookup", {
- method: "POST",
- body: JSON.stringify({
- ID: id
- }),
- headers: {
- 'Authorization': window.localStorage.getItem('token'),
- 'Content-Type': 'application/json'
- }
- })
- .then(res => res.ok ? res.json() : Promise.reject(res.json()))
- .then(res => this.setState({
- links: [...this.state.links, [
- res.URL,
- this.url,
- this.VisitCount,
- res.CratedOn,
- res.LastVisit,
- res.Expiration
- ]]
- }))
- .catch(e => e instanceof Promise ? e.then(error => toastr.error(`Could not fetch lookup: ${error.error}`)) : null)
+ util.lookupEntry(id, res => this.setState({
+ links: [...this.state.links, [
+ res.URL,
+ this.url,
+ this.VisitCount,
+ res.CratedOn,
+ res.LastVisit,
+ res.Expiration
+ ]]
+ }))
}
render() {
const { links } = this.state
@@ -42,7 +30,7 @@ export default class LookupComponent extends Component {
- this.urlInput = input} action={{ icon: 'arrow right', labelPosition: 'right', content: 'Lookup' }} type='url' onChange={this.handleURLChange} name='url' placeholder={window.location.origin + "/..."} autoComplete="off"/>
+ this.urlInput = input} action={{ icon: 'arrow right', labelPosition: 'right', content: 'Lookup' }} type='url' onChange={this.handleURLChange} name='url' placeholder={window.location.origin + "/..."} autoComplete="off" />
diff --git a/static/src/Recent/Recent.js b/static/src/Recent/Recent.js
index fca1ef1..8bc1bc7 100644
--- a/static/src/Recent/Recent.js
+++ b/static/src/Recent/Recent.js
@@ -1,27 +1,18 @@
import React, { Component } from 'react'
import { Container, Table, Button, Icon } from 'semantic-ui-react'
-import toastr from 'toastr'
import Moment from 'react-moment';
import util from '../util/util'
export default class RecentComponent extends Component {
state = {
- recent: null
+ recent: {}
}
componentDidMount() {
this.loadRecentURLs()
}
- loadRecentURLs() {
- fetch('/api/v1/protected/recent', {
- method: 'POST',
- headers: {
- 'Authorization': window.localStorage.getItem('token'),
- }
- })
- .then(res => res.ok ? res.json() : Promise.reject(res.json()))
- .then(recent => this.setState({ recent: recent }))
- .catch(e => e instanceof Promise ? e.then(error => toastr.error(`Could load recent URLs: ${error.error}`)) : null)
+ loadRecentURLs = () => {
+ util.getRecentURLs(recent => this.setState({ recent }))
}
onRowClick(id) {
@@ -29,7 +20,7 @@ export default class RecentComponent extends Component {
}
onEntryDeletion(entry) {
- util.deleteEntry(entry.DeletionURL)
+ util.deleteEntry(entry.DeletionURL, this.loadRecentURLs)
}
render() {
@@ -47,7 +38,7 @@ export default class RecentComponent extends Component {
- {recent && Object.keys(recent).map(key =>
+ {Object.keys(recent).map(key =>
{recent[key].Public.URL}
{recent[key].Public.CreatedOn}
{`${window.location.origin}/${key}`}
diff --git a/static/src/Visitors/Visitors.js b/static/src/Visitors/Visitors.js
index 6a1d63b..9cb01c2 100644
--- a/static/src/Visitors/Visitors.js
+++ b/static/src/Visitors/Visitors.js
@@ -1,53 +1,28 @@
import React, { Component } from 'react'
import { Container, Table } from 'semantic-ui-react'
import Moment from 'react-moment';
-import toastr from 'toastr'
+import util from '../util/util'
export default class VisitorComponent extends Component {
state = {
- visitors: [],
- info: null
+ id: "",
+ entry: null,
+ visitors: []
}
componentWillMount() {
this.setState({ id: this.props.match.params.id })
- fetch("/api/v1/protected/lookup", {
- method: "POST",
- body: JSON.stringify({
- ID: this.props.match.params.id
- }),
- headers: {
- 'Authorization': window.localStorage.getItem('token'),
- 'Content-Type': 'application/json'
- }
- })
- .then(res => res.ok ? res.json() : Promise.reject(res.json()))
- .then(info => this.setState({ info }))
- .catch(e => {
- toastr.error(`Could not fetch lookup: ${e}`)
- })
+ util.lookupEntry(this.props.match.params.id, entry => this.setState({ entry }))
this.reloadVisitors()
- this.loop = setInterval(this.reloadVisitors, 1000)
+ this.reloadInterval = setInterval(this.reloadVisitors, 1000)
}
- reloadVisitors = () => {
- fetch('/api/v1/protected/visitors', {
- method: 'POST',
- body: JSON.stringify({
- ID: this.props.match.params.id
- }),
- headers: {
- 'Authorization': window.localStorage.getItem('token'),
- 'Content-Type': 'application/json'
- }
- })
- .then(res => res.ok ? res.json() : Promise.reject(res.json()))
- .then(visitors => this.setState({ visitors }))
- .catch(e => e.done(res => toastr.error(`Could not fetch visitors: ${res}`)))
+ componentWillUnmount() {
+ clearInterval(this.reloadInterval)
}
- componentWillUnmount() {
- clearInterval(this.loop)
+ reloadVisitors = () => {
+ util.getVisitors(this.props.match.params.id, visitors => this.setState({ visitors }))
}
// getUTMSource is a function which generates the output for the utm[...] table column
@@ -60,11 +35,11 @@ export default class VisitorComponent extends Component {
}
render() {
- const { visitors, id, info } = this.state
+ const { visitors, id, entry } = this.state
return (
- {info &&
- Entry with id '{id}' was created at {info.CreatedOn} and redirects to '{info.URL}'. Currently it has {visitors.length} visits.
+ {entry &&
+ Entry with id '{id}' was created at {entry.CreatedOn} and redirects to '{entry.URL}'. Currently it has {visitors.length} visits.
}
diff --git a/static/src/index.js b/static/src/index.js
index 9dd8806..d58a313 100644
--- a/static/src/index.js
+++ b/static/src/index.js
@@ -13,9 +13,10 @@ import Lookup from './Lookup/Lookup'
import Recent from './Recent/Recent'
import Visitors from './Visitors/Visitors'
+import util from './util/util'
export default class BaseComponent extends Component {
state = {
- oAuthOpen: true,
+ oAuthPopupOpened: true,
userData: {},
authorized: false,
activeItem: "",
@@ -25,7 +26,7 @@ export default class BaseComponent extends Component {
handleItemClick = (e, { name }) => this.setState({ activeItem: name })
onOAuthClose = () => {
- this.setState({ oAuthOpen: true })
+ this.setState({ oAuthPopupOpened: true })
}
componentWillMount() {
@@ -33,12 +34,11 @@ export default class BaseComponent extends Component {
.then(d => d.json())
.then(info => this.setState({ info }))
.then(() => this.checkAuth())
- .catch(e => toastr.error(`Could not fetch info: ${e}`))
+ .catch(e => util._reportError(e, "info"))
}
checkAuth = () => {
- const that = this,
- token = window.localStorage.getItem('token');
+ const token = window.localStorage.getItem('token');
if (token) {
fetch('/api/v1/auth/check', {
method: 'POST',
@@ -50,16 +50,14 @@ export default class BaseComponent extends Component {
}
})
.then(res => res.ok ? res.json() : Promise.reject(`incorrect response status code: ${res.status}; text: ${res.statusText}`))
- .then(d => {
- that.setState({
- userData: d,
- authorized: true
- })
- })
+ .then(d => this.setState({
+ userData: d,
+ authorized: true
+ }))
.catch(e => {
toastr.error(`Could not fetch check: ${e}`)
window.localStorage.removeItem('token');
- that.setState({ authorized: false })
+ this.setState({ authorized: false })
})
}
}
@@ -81,9 +79,7 @@ export default class BaseComponent extends Component {
// Open the oAuth window that is it centered in the middle of the screen
var wwidth = 400,
wHeight = 500;
- var wLeft = (window.screen.width / 2) - (wwidth / 2);
- var wTop = (window.screen.height / 2) - (wHeight / 2);
- this._oAuthPopup = window.open(url, '', `width=${wwidth}, height=${wHeight}, top=${wTop}, left=${wLeft}`)
+ this._oAuthPopup = window.open(url, '', `width=${wwidth}, height=${wHeight}, top=${(window.screen.height / 2) - (wHeight / 2)}, left=${(window.screen.width / 2) - (wwidth / 2)}`)
} else {
this._oAuthPopup.location = url;
}
@@ -95,10 +91,10 @@ export default class BaseComponent extends Component {
}
render() {
- const { oAuthOpen, authorized, activeItem, userData, info } = this.state
+ const { oAuthPopupOpened, authorized, activeItem, userData, info } = this.state
if (!authorized) {
return (
-
+
Authentication
diff --git a/static/src/util/util.js b/static/src/util/util.js
index ea1a7fb..296575d 100644
--- a/static/src/util/util.js
+++ b/static/src/util/util.js
@@ -1,10 +1,57 @@
import toastr from 'toastr'
export default class UtilHelper {
- static deleteEntry(url) {
+ static deleteEntry(url, cb) {
fetch(url)
.then(res => res.ok ? res.json() : Promise.reject(res.json()))
- .then(() => this.loadRecentURLs())
- .catch(e => e instanceof Promise ? e.then(error => toastr.error(`Could not delete: ${error.error}`)) : null)
+ .then(cb())
+ .catch(e => this._reportError(e, "delete entry"))
+ }
+ static _constructFetch(url, body, cbSucc, cbErr) {
+ fetch(url, {
+ method: "POST",
+ body: JSON.stringify(body),
+ headers: {
+ 'Authorization': window.localStorage.getItem('token'),
+ 'Content-Type': 'application/json'
+ }
+ })
+ .then(res => res.ok ? res.json() : Promise.reject(res.json()))
+ .then(res => cbSucc ? cbSucc(res) : null)
+ .catch(e => {
+ if (cbErr) {
+ cbErr(e)
+ } else {
+ let name = url.split("/").pop()
+ this._reportError(e, name)
+ }
+ })
+ }
+ static _reportError(e, name) {
+ if (e instanceof Promise) {
+ e.then(error => toastr.error(`Could not fetch ${name}: ${error.error}`))
+ } else {
+ toastr.error(`Could not fetch ${name}: ${e}`)
+ }
+ }
+ static lookupEntry(ID, cbSucc, cbErr) {
+ this._constructFetch("/api/v1/protected/lookup", { ID }, cbSucc, cbErr)
+ }
+ static getVisitors(ID, cbSucc) {
+ this._constructFetch("/api/v1/protected/visitors", { ID }, cbSucc)
+ }
+ static createEntry(entry, cbSucc) {
+ this._constructFetch("/api/v1/protected/create",entry, cbSucc)
+ }
+ static getRecentURLs(cbSucc) {
+ fetch('/api/v1/protected/recent', {
+ headers: {
+ 'Authorization': window.localStorage.getItem('token'),
+ 'Content-Type': 'application/json'
+ }
+ })
+ .then(res => res.ok ? res.json() : Promise.reject(res.json()))
+ .then(res => cbSucc ? cbSucc(res) : null)
+ .catch(e => this._reportError(e, "recent"))
}
};
\ No newline at end of file