Listings Dashboard (#3014)
* init wip react components * wip add dashboard to listing views - create a html.erb view to hold listing/dashboard with javascript_pack_tag - create listingDashboard.jsx in packs - create listingDashboard component and dashboard folder with smaller components - added view to controller and routes * slowly adding some formatting adjusted styling, info * remove listing body from dashboard * add some styling and adjust info * wip adding org listings * wip multi org in dashboard * adjust for multi org * add styling and better multi org support * comment out bump and delete -- save for upcoming PR * reduce lines * listing form has link to listings dashboard instead of listings * adjust header boxes flex direction for mobile * fix organization id query * WIP testing needs more rigorous tests * finalize tests
This commit is contained in:
parent
bd3125bcfd
commit
9e616de9f8
11 changed files with 610 additions and 7 deletions
|
|
@ -574,3 +574,206 @@ form.listings-contact-via-connect {
|
|||
background: rgba(0, 0, 0, 0.8);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.dashboard-listings-container {
|
||||
padding-top: 80px;
|
||||
padding-bottom: 80px;
|
||||
width: 800px;
|
||||
max-width: 96%;
|
||||
margin: auto;
|
||||
|
||||
.rounded-btn {
|
||||
font-size: calc(15px + 0.2vw);
|
||||
font-family: $helvetica-condensed;
|
||||
font-stretch: condensed;
|
||||
padding: 4px 20px;
|
||||
border-radius: 100px;
|
||||
margin-right: 5px;
|
||||
display: inline-block;
|
||||
width: fit-content;
|
||||
margin-bottom: 10px;
|
||||
@include themeable(
|
||||
color,
|
||||
theme-color,
|
||||
$black
|
||||
);
|
||||
@include themeable(
|
||||
background,
|
||||
theme-container-background,
|
||||
lighten($light-medium-gray, 5%)
|
||||
);
|
||||
&:hover {
|
||||
@include themeable(
|
||||
background,
|
||||
theme-container-background-hover,
|
||||
$light-medium-gray
|
||||
);
|
||||
}
|
||||
|
||||
&.active {
|
||||
@include themeable(
|
||||
background,
|
||||
theme-secondary-color,
|
||||
$dark-gray
|
||||
);
|
||||
@include themeable(
|
||||
color,
|
||||
theme-container-background,
|
||||
white
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-listings-header-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 15px;
|
||||
background: $dark-gray;
|
||||
@include themeable(box-shadow, theme-container-box-shadow, $bold-shadow);
|
||||
padding: 15px 0px;
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dashboard-listings-header {
|
||||
width: 43%;
|
||||
text-align: center;
|
||||
color: white;
|
||||
margin: 5px auto;
|
||||
font-size: calc(1.1em + 0.1vw);
|
||||
padding: 18px 0px 7px;
|
||||
padding-left: 10px;
|
||||
font-family: $monospace;
|
||||
border: 1px solid $light-gray;
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin: 1.2%;
|
||||
padding: 1.2%;
|
||||
padding-bottom: 30px;
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
width: 90%;
|
||||
}
|
||||
a {
|
||||
font-size: 0.9em;
|
||||
border: 2px solid #2ad7c5;
|
||||
color: #25c2b2;
|
||||
padding: 6px 16px;
|
||||
margin-top: 6px;
|
||||
border-radius: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-listings-view {
|
||||
|
||||
.dashboard-listing-row {
|
||||
border: var(--theme-container-border, 1px solid #dbdbdb);
|
||||
box-shadow: var(--theme-container-box-shadow, 1px 1px 0px #c2c2c2);
|
||||
padding: 5px 15px;
|
||||
margin-bottom: 13px;
|
||||
border-radius: 3px;
|
||||
position: relative;
|
||||
background: var(--theme-container-background, #fff);
|
||||
|
||||
.listing-org {
|
||||
@include themeable(
|
||||
background,
|
||||
theme-secondary-color,
|
||||
darken($light-gray, 6%)
|
||||
);
|
||||
@include themeable(color, theme-container-background, #fff);
|
||||
margin-right: 5px;
|
||||
padding: 3px 10px;
|
||||
border-radius: 100px;
|
||||
margin-top: 10px;
|
||||
display: inline-block;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: var(--theme-container-color, #0a0a0a);
|
||||
font-weight: 500;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 8px;
|
||||
font-size: 22px;
|
||||
width: 100%;
|
||||
@media screen and (min-width: 800px) {
|
||||
font-size: 34px;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-listing-date {
|
||||
font-size: 0.85em;
|
||||
padding: 3px 0px 6px;
|
||||
color: var(--theme-secondary-color, #666666);
|
||||
}
|
||||
|
||||
.dashboard-listing-category {
|
||||
padding: 10px 15px;
|
||||
a {
|
||||
@include themeable(
|
||||
color,
|
||||
theme-secondary-color,
|
||||
$dark-medium-gray
|
||||
);
|
||||
@include themeable(
|
||||
background,
|
||||
theme-container-accent-background,
|
||||
$light-medium-gray
|
||||
);
|
||||
display: inline-block;
|
||||
margin: 2px;
|
||||
padding: 2px 7px;
|
||||
font-size: 0.77em;
|
||||
border-radius: 3px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-listing-tags {
|
||||
font-size: 0.85em;
|
||||
padding: 3px 0px 6px;
|
||||
a {
|
||||
color: var(--theme-container-color, #0a0a0a);
|
||||
}
|
||||
}
|
||||
|
||||
.listing-row-actions {
|
||||
padding: 7px 0px 10px;
|
||||
|
||||
.pill {
|
||||
background: $bold-blue;
|
||||
padding: 4px 20px;
|
||||
border-radius: 100px;
|
||||
color: white;
|
||||
margin-right: 5px;
|
||||
font-size: 14px;
|
||||
border: 2px solid transparent;
|
||||
margin-top: 6px;
|
||||
display: inline-block;
|
||||
|
||||
&.green {
|
||||
background: $green;
|
||||
color: $black;
|
||||
}
|
||||
|
||||
&.red {
|
||||
background: $red;
|
||||
}
|
||||
|
||||
&.black {
|
||||
background: $dark-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ class ClassifiedListingsController < ApplicationController
|
|||
before_action :set_classified_listing, only: %i[edit update]
|
||||
before_action :set_cache_control_headers, only: %i[index]
|
||||
after_action :verify_authorized, only: %i[edit update]
|
||||
before_action :authenticate_user!, only: %i[edit update new]
|
||||
before_action :authenticate_user!, only: %i[edit update new dashboard]
|
||||
|
||||
def index
|
||||
@displayed_classified_listing = ClassifiedListing.find_by!(slug: params[:slug]) if params[:slug]
|
||||
|
|
@ -82,6 +82,13 @@ class ClassifiedListingsController < ApplicationController
|
|||
redirect_to "/listings"
|
||||
end
|
||||
|
||||
def dashboard
|
||||
@classified_listings = ClassifiedListing.where(user_id: current_user.id)
|
||||
@orgs = Organization.where(id: current_user.organization_memberships.where(type_of_user: "admin").map(&:organization_id))
|
||||
@org_listings = ClassifiedListing.where(organization_id: current_user.organization_memberships.where(type_of_user: "admin").map(&:organization_id))
|
||||
@user_credits = current_user.unspent_credits_count
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mod_page
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<ListingDashboard /> should load listing dashboard 1`] = `
|
||||
preact-render-spy (1 nodes)
|
||||
-------
|
||||
<div class="dashboard-listings-container">
|
||||
<span
|
||||
onClick={[Function onClick]}
|
||||
class="rounded-btn active"
|
||||
>
|
||||
Personal
|
||||
</span>
|
||||
<span
|
||||
onClick={[Function onClick]}
|
||||
class="rounded-btn "
|
||||
>
|
||||
Yoyodyne
|
||||
</span>
|
||||
<span
|
||||
onClick={[Function onClick]}
|
||||
class="rounded-btn "
|
||||
>
|
||||
Infotrode
|
||||
</span>
|
||||
<div class="dashboard-listings-header-wrapper">
|
||||
<div class="dashboard-listings-header">
|
||||
<h3>Listings</h3>
|
||||
<h4>Listings Made: 3</h4>
|
||||
<a href="/listings/new">Create a Listing</a>
|
||||
</div>
|
||||
<div class="dashboard-listings-header">
|
||||
<h3>Credits</h3>
|
||||
<h4>Credits Available: 3</h4>
|
||||
<a
|
||||
href="/credits/purchase"
|
||||
data-no-instant={true}
|
||||
>
|
||||
Buy Credits
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-listings-view">
|
||||
<div class="dashboard-listing-row">
|
||||
<a href="cfp/asdfasdf-2ea8">
|
||||
<h2>asdfasdf</h2>
|
||||
</a>
|
||||
<span class="dashboard-listing-date">Jun 11 </span>
|
||||
<span class="dashboard-listing-category">
|
||||
<a href="/listings/cfp/">cfp</a>
|
||||
</span>
|
||||
<span class="dashboard-listing-tags">
|
||||
<a
|
||||
href="/listings?t=computerscience"
|
||||
data-no-instant={true}
|
||||
>
|
||||
#computerscience
|
||||
</a>
|
||||
<a
|
||||
href="/listings?t=career"
|
||||
data-no-instant={true}
|
||||
>
|
||||
#career
|
||||
</a>
|
||||
</span>
|
||||
<div class="listing-row-actions">
|
||||
<a
|
||||
href="/listings/23/edit"
|
||||
class="dashboard-listing-edit-button cta pill green"
|
||||
>
|
||||
EDIT
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-listing-row">
|
||||
<span class="listing-org">Yoyodyne</span>
|
||||
<a href="events/yoyoyoyoyoooooooo-4jcb">
|
||||
<h2>YOYOYOYOYOOOOOOOO</h2>
|
||||
</a>
|
||||
<span class="dashboard-listing-date">Jun 11 </span>
|
||||
<span class="dashboard-listing-category">
|
||||
<a href="/listings/events/">events</a>
|
||||
</span>
|
||||
<span class="dashboard-listing-tags">
|
||||
<a
|
||||
href="/listings?t=computerscience"
|
||||
data-no-instant={true}
|
||||
>
|
||||
#computerscience
|
||||
</a>
|
||||
<a
|
||||
href="/listings?t=conference"
|
||||
data-no-instant={true}
|
||||
>
|
||||
#conference
|
||||
</a>
|
||||
<a
|
||||
href="/listings?t=career"
|
||||
data-no-instant={true}
|
||||
>
|
||||
#career
|
||||
</a>
|
||||
</span>
|
||||
<div class="listing-row-actions">
|
||||
<a
|
||||
href="/listings/24/edit"
|
||||
class="dashboard-listing-edit-button cta pill green"
|
||||
>
|
||||
EDIT
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-listing-row">
|
||||
<span class="listing-org">Infotrode</span>
|
||||
<a href="cfp/hehhehe-5hld">
|
||||
<h2>hehhehe</h2>
|
||||
</a>
|
||||
<span class="dashboard-listing-date">Jun 11 </span>
|
||||
<span class="dashboard-listing-category">
|
||||
<a href="/listings/cfp/">cfp</a>
|
||||
</span>
|
||||
<span class="dashboard-listing-tags"></span>
|
||||
<div class="listing-row-actions">
|
||||
<a
|
||||
href="/listings/25/edit"
|
||||
class="dashboard-listing-edit-button cta pill green"
|
||||
>
|
||||
EDIT
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
`;
|
||||
113
app/javascript/listings/__tests__/listingDashboard.test.jsx
Normal file
113
app/javascript/listings/__tests__/listingDashboard.test.jsx
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import { h } from 'preact';
|
||||
import { deep } from 'preact-render-spy';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { ListingDashboard } from '../listingDashboard';
|
||||
|
||||
const doc = new JSDOM('<!doctype html><html><body></body></html>');
|
||||
global.document = doc;
|
||||
const l = [{id:23,bumped_at:"2019-06-11T16:45:37.229Z",category:"cfp",organization_id:null,slug:"asdfasdf-2ea8",title:"asdfasdf",updated_at:"2019-06-11T16:45:37.237Z",user_id:11,tag_list:["computerscience","career"],author:{name:"MarioSee",username:"mariocsee",profile_image_90:"/uploads/user/profile_image/11/e594d777-b57b-41d6-a793-5d8127bd11b3.jpeg"}},{id:24,bumped_at:"2019-06-11T16:59:16.312Z",category:"events",organization_id:2,slug:"yoyoyoyoyoooooooo-4jcb",title:"YOYOYOYOYOOOOOOOO",updated_at:"2019-06-11T16:59:16.316Z",user_id:11,tag_list:["computerscience","conference","career"],author:{name:"Yoyodyne",username:"org3737",profile_image_90:"/uploads/organization/profile_image/2/5edb1e49-bea9-4e99-bc32-acc10c52a935.png"}},{id:25,bumped_at:"2019-06-11T17:01:25.143Z",category:"cfp",organization_id:3,slug:"hehhehe-5hld",title:"hehhehe",updated_at:"2019-06-11T17:01:25.169Z",user_id:11,tag_list:[],author:{name:"Infotrode",username:"org5254",profile_image_90:"/uploads/organization/profile_image/3/04d4e1f1-c2e0-4147-81e2-bc8a2657296b.png"}}];
|
||||
global.document.body.innerHTML = `<div id="classifieds-listings-dashboard" data-listings=${JSON.stringify(l)} data-usercredits="3" data-orglistings=${JSON.stringify([{id:24,bumped_at:"2019-06-11T16:59:16.312Z",category:"events",organization_id:2,slug:"yoyoyoyoyoooooooo-4jcb",title:"YOYOYOYOYOOOOOOOO",updated_at:"2019-06-11T16:59:16.316Z",user_id:11,tag_list:["computerscience","conference","career"],author:{name:"Yoyodyne",username:"org3737",profile_image_90:"/uploads/organization/profile_image/2/5edb1e49-bea9-4e99-bc32-acc10c52a935.png"}},{id:25,bumped_at:"2019-06-11T17:01:25.143Z",category:"cfp",organization_id:3,slug:"hehhehe-5hld",title:"hehhehe",updated_at:"2019-06-11T17:01:25.169Z",user_id:11,tag_list:[],author:{name:"Infotrode",username:"org5254",profile_image_90:"/uploads/organization/profile_image/3/04d4e1f1-c2e0-4147-81e2-bc8a2657296b.png"}}])} data-orgs=${JSON.stringify([{id:2,name:"Yoyodyne",slug:"org3737",unspent_credits_count:1},{id:3,name:"Infotrode",slug:"org5254",unspent_credits_count:1}])} ></div>`;
|
||||
global.window = doc.defaultView;
|
||||
|
||||
const listingState = { listings:
|
||||
[ { id: 23,
|
||||
bumped_at: '2019-06-11T16:45:37.229Z',
|
||||
category: 'cfp',
|
||||
organization_id: null,
|
||||
slug: 'asdfasdf-2ea8',
|
||||
title: 'asdfasdf',
|
||||
updated_at: '2019-06-11T16:45:37.237Z',
|
||||
user_id: 11,
|
||||
tag_list: [Array],
|
||||
author: [Object] },
|
||||
{ id: 24,
|
||||
bumped_at: '2019-06-11T16:59:16.312Z',
|
||||
category: 'events',
|
||||
organization_id: 2,
|
||||
slug: 'yoyoyoyoyoooooooo-4jcb',
|
||||
title: 'YOYOYOYOYOOOOOOOO',
|
||||
updated_at: '2019-06-11T16:59:16.316Z',
|
||||
user_id: 11,
|
||||
tag_list: [Array],
|
||||
author: [Object] },
|
||||
{ id: 25,
|
||||
bumped_at: '2019-06-11T17:01:25.143Z',
|
||||
category: 'cfp',
|
||||
organization_id: 3,
|
||||
slug: 'hehhehe-5hld',
|
||||
title: 'hehhehe',
|
||||
updated_at: '2019-06-11T17:01:25.169Z',
|
||||
user_id: 11,
|
||||
tag_list: [],
|
||||
author: [Object] } ],
|
||||
orgListings:
|
||||
[ { id: 24,
|
||||
bumped_at: '2019-06-11T16:59:16.312Z',
|
||||
category: 'events',
|
||||
organization_id: 2,
|
||||
slug: 'yoyoyoyoyoooooooo-4jcb',
|
||||
title: 'YOYOYOYOYOOOOOOOO',
|
||||
updated_at: '2019-06-11T16:59:16.316Z',
|
||||
user_id: 11,
|
||||
tag_list: [Array],
|
||||
author: [Object] },
|
||||
{ id: 25,
|
||||
bumped_at: '2019-06-11T17:01:25.143Z',
|
||||
category: 'cfp',
|
||||
organization_id: 3,
|
||||
slug: 'hehhehe-5hld',
|
||||
title: 'hehhehe',
|
||||
updated_at: '2019-06-11T17:01:25.169Z',
|
||||
user_id: 11,
|
||||
tag_list: [],
|
||||
author: [Object] } ],
|
||||
orgs:
|
||||
[ { id: 2,
|
||||
name: 'Yoyodyne',
|
||||
slug: 'org3737',
|
||||
unspent_credits_count: 1 },
|
||||
{ id: 3,
|
||||
name: 'Infotrode',
|
||||
slug: 'org5254',
|
||||
unspent_credits_count: 1 } ],
|
||||
userCredits: '3',
|
||||
selectedListings: 'user' };
|
||||
|
||||
describe('<ListingDashboard />', () => {
|
||||
it('should load listing dashboard', () => {
|
||||
const tree = deep(<ListingDashboard />);
|
||||
expect(tree).toMatchSnapshot();
|
||||
})
|
||||
|
||||
describe('should load the proper elements', () => {
|
||||
const context = deep(<ListingDashboard />);
|
||||
expect(context.component()).toBeInstanceOf(ListingDashboard);
|
||||
context.setState(listingState);
|
||||
|
||||
it('for user and org buttons', () => {
|
||||
expect(context.find('.rounded-btn').at(0).text()).toEqual('Personal')
|
||||
expect(context.find('.rounded-btn').at(1).text()).toEqual(listingState.orgs[0].name)
|
||||
expect(context.find('.rounded-btn').at(2).text()).toEqual(listingState.orgs[1].name)
|
||||
|
||||
context.find('.rounded-btn').at(1).simulate('click');
|
||||
expect(context.find('.rounded-btn').at(1).attr('className')).toEqual('rounded-btn active')
|
||||
expect(context.state('selectedListings')).toEqual(listingState.orgs[0].id)
|
||||
})
|
||||
|
||||
it('for listing and credits header', () => {
|
||||
expect(context.find('.dashboard-listings-header-wrapper').exists()).toEqual(true);
|
||||
expect(context.find('.dashboard-listings-header').exists()).toEqual(true);
|
||||
|
||||
expect(context.find('.dashboard-listings-header').at(0).childAt(0).text()).toEqual('Listings');
|
||||
expect(context.find('.dashboard-listings-header').at(0).childAt(2).text()).toEqual('Create a Listing');
|
||||
|
||||
expect(context.find('.dashboard-listings-header').at(1).childAt(0).text()).toEqual('Credits');
|
||||
expect(context.find('.dashboard-listings-header').at(1).childAt(2).text()).toEqual('Buy Credits');
|
||||
})
|
||||
|
||||
it('for listingRow view', () => {
|
||||
expect(context.find('.dashboard-listings-view').exists()).toEqual(true);
|
||||
expect(context.find('.dashboard-listings-view').children().text()).toEqual(context.find('ListingRow').text());
|
||||
})
|
||||
})
|
||||
})
|
||||
43
app/javascript/listings/dashboard/listingRow.jsx
Normal file
43
app/javascript/listings/dashboard/listingRow.jsx
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { h } from 'preact';
|
||||
|
||||
export const ListingRow = ({listing}) => {
|
||||
|
||||
const tagLinks = listing.tag_list.map(tag => (
|
||||
<a href={`/listings?t=${tag}`} data-no-instant>
|
||||
#
|
||||
{tag}
|
||||
{' '}
|
||||
|
||||
</a>
|
||||
));
|
||||
|
||||
const listingDate = listing.bumped_at ? (new Date(listing.bumped_at.toString())).toLocaleDateString("default", {day: "2-digit", month: "short"}) : (new Date(listing.updated_at.toString())).toLocaleDateString("default", {day: "2-digit", month: "short"}) ;
|
||||
const orgName = (l) => (l.organization_id) ? (<span className="listing-org">{l.author.name}</span>) : '';
|
||||
|
||||
return(
|
||||
<div className="dashboard-listing-row">
|
||||
{orgName(listing)}
|
||||
<a href={`${`${listing.category }/${ listing.slug}`}`}>
|
||||
<h2>
|
||||
{listing.title}
|
||||
</h2>
|
||||
</a>
|
||||
<span className="dashboard-listing-date">
|
||||
{listingDate}
|
||||
{' '}
|
||||
</span>
|
||||
<span className="dashboard-listing-category"><a href={`/listings/${listing.category}/`}>{listing.category}</a></span>
|
||||
<span className="dashboard-listing-tags">{tagLinks}</span>
|
||||
<div className="listing-row-actions">
|
||||
{/* <a className="dashboard-listing-bump-button cta pill black">BUMP</a> */}
|
||||
<a href={`/listings/${listing.id}/edit`} className="dashboard-listing-edit-button cta pill green">EDIT</a>
|
||||
{/* <a className="dashboard-listing-delete-button cta pill red">DELETE</a> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ListingRow.propTypes = {
|
||||
listing: PropTypes.object.isRequired,
|
||||
};
|
||||
69
app/javascript/listings/listingDashboard.jsx
Normal file
69
app/javascript/listings/listingDashboard.jsx
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import { h, Component } from 'preact';
|
||||
import { ListingRow } from './dashboard/listingRow';
|
||||
|
||||
export class ListingDashboard extends Component {
|
||||
state = {
|
||||
listings: [],
|
||||
orgListings: [],
|
||||
orgs: [],
|
||||
userCredits: 0,
|
||||
selectedListings: "user",
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const t = this;
|
||||
const container = document.getElementById('classifieds-listings-dashboard')
|
||||
let listings = [];
|
||||
let orgs = [];
|
||||
let orgListings = [];
|
||||
listings = JSON.parse(container.dataset.listings);
|
||||
orgs = JSON.parse(container.dataset.orgs);
|
||||
orgListings = JSON.parse(container.dataset.orglistings);
|
||||
const userCredits = container.dataset.usercredits;
|
||||
t.setState({ listings, orgListings, orgs, userCredits });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { listings, orgListings, userCredits, orgs, selectedListings } = this.state
|
||||
|
||||
const showListings = (selected, userListings, organizationListings) => {
|
||||
return (selected === "user") ? userListings.map(listing => <ListingRow listing={listing} />) : organizationListings.map(listing => (listing.organization_id === selected) ? <ListingRow listing={listing} /> : '');
|
||||
}
|
||||
|
||||
const orgButtons = orgs.map(org => (
|
||||
<span onClick={() => this.setState({selectedListings: org.id})} className={"rounded-btn " + (selectedListings === org.id ? 'active': '')}>
|
||||
{org.name}
|
||||
</span>
|
||||
))
|
||||
|
||||
const listingLength = (selected, userListings, organizationListings) => {
|
||||
return (selected === "user") ? (<h4>Listings Made: {userListings.length}</h4>) : (<h4>Listings Made: {organizationListings.filter((listing) => (listing.organization_id == selected)).length}</h4>);
|
||||
}
|
||||
|
||||
const creditCount = (selected, userCreds, organizations) => {
|
||||
return (selected === "user") ? (<h4>Credits Available: {userCredits}</h4>) : (<h4>Credits Available: {organizations.find((org) => (org.id === selected)).unspent_credits_count}</h4>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="dashboard-listings-container">
|
||||
<span onClick={() => this.setState({selectedListings: "user"})} className={"rounded-btn " + (selectedListings === "user" ? 'active': '')}>Personal</span>
|
||||
{orgButtons}
|
||||
<div className="dashboard-listings-header-wrapper">
|
||||
<div className="dashboard-listings-header">
|
||||
<h3>Listings</h3>
|
||||
{listingLength(selectedListings, listings, orgListings)}
|
||||
<a href='/listings/new'>Create a Listing</a>
|
||||
</div>
|
||||
<div className="dashboard-listings-header">
|
||||
<h3>Credits</h3>
|
||||
{creditCount(selectedListings, userCredits, orgs)}
|
||||
<a href="/credits/purchase" data-no-instant>Buy Credits</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="dashboard-listings-view">
|
||||
{showListings(selectedListings, listings, orgListings)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ export class Listings extends Component {
|
|||
const params = this.getQueryParams();
|
||||
const t = this;
|
||||
const algoliaId = document.querySelector("meta[name='algolia-public-id']")
|
||||
.content;
|
||||
.content;
|
||||
const algoliaKey = document.querySelector("meta[name='algolia-public-key']")
|
||||
.content;
|
||||
const env = document.querySelector("meta[name='environment']").content;
|
||||
|
|
@ -52,9 +52,9 @@ export class Listings extends Component {
|
|||
t.setUser()
|
||||
|
||||
document.body.addEventListener('keydown', t.handleKeyDown)
|
||||
|
||||
/*
|
||||
The width of the columns also changes when the browser is resized
|
||||
|
||||
/*
|
||||
The width of the columns also changes when the browser is resized
|
||||
so we will also call this function on window resize to recalculate
|
||||
each grid item's height to avoid content overflow
|
||||
*/
|
||||
|
|
@ -342,6 +342,7 @@ export class Listings extends Component {
|
|||
<a href="/listings" className={category === '' ? 'selected' : ''} onClick={e => this.selectCategory(e, '')} data-no-instant>all</a>
|
||||
{categoryLinks}
|
||||
<a href='/listings/new' className='classified-create-link'>Create a Listing</a>
|
||||
<a href='/listings/dashboard' className='classified-create-link'>Manage Listings</a>
|
||||
</div>
|
||||
<div className="classified-filters-tags" id="classified-filters-tags">
|
||||
<input type="text" placeholder="search" id="listings-search" autoComplete="off" defaultValue={query} onKeyUp={e => this.handleQuery(e)} />
|
||||
|
|
|
|||
15
app/javascript/packs/listingDashboard.jsx
Normal file
15
app/javascript/packs/listingDashboard.jsx
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { h, render } from 'preact';
|
||||
import { ListingDashboard } from '../listings/listingDashboard';
|
||||
|
||||
function loadElement() {
|
||||
const root = document.getElementById('classifieds-listings-dashboard');
|
||||
if (root) {
|
||||
render(<ListingDashboard />, root, root.firstElementChild);
|
||||
}
|
||||
}
|
||||
|
||||
window.InstantClick.on('change', () => {
|
||||
loadElement();
|
||||
});
|
||||
|
||||
loadElement();
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<%= form_with(model: classified_listing, local: true) do |form| %>
|
||||
<header>
|
||||
<a href="/listings" class="listings-back-button">< go back to listings</a>
|
||||
<a href="/listings/dashboard" class="listings-back-button">< return to listings dashboard</a>
|
||||
<h2>Create a Community Listing</h2>
|
||||
</header>
|
||||
<div class="classified-form-inner">
|
||||
|
|
|
|||
17
app/views/classified_listings/dashboard.html.erb
Normal file
17
app/views/classified_listings/dashboard.html.erb
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
<div id="classifieds-listings-dashboard" data-listings="<%= @classified_listings.to_json(
|
||||
only: %i[title tag_list bumped_at updated_at category id user_id slug organization_id],
|
||||
include: {
|
||||
author: { only: %i[username name], methods: %i[username profile_image_90] },
|
||||
},
|
||||
)%>" data-usercredits="<%= @user_credits %>"
|
||||
data-orglistings="<%= @org_listings.to_json(
|
||||
only: %i[title tag_list bumped_at updated_at category id user_id slug organization_id],
|
||||
include: {
|
||||
author: { only: %i[username name], methods: %i[username profile_image_90] },
|
||||
},
|
||||
) %>" data-orgs="<%= @orgs.to_json(
|
||||
only: %i[id name slug unspent_credits_count]
|
||||
) %>" >
|
||||
</div>
|
||||
<%= javascript_pack_tag "listingDashboard", defer: true %>
|
||||
|
|
@ -139,7 +139,7 @@ Rails.application.routes.draw do
|
|||
resources :tag_adjustments, only: [:create]
|
||||
resources :rating_votes, only: [:create]
|
||||
resources :page_views, only: %i[create update]
|
||||
resources :classified_listings, path: :listings, only: %i[index new create edit update delete]
|
||||
resources :classified_listings, path: :listings, only: %i[index new create edit update delete dashboard]
|
||||
resources :credits, only: %i[index new create]
|
||||
resources :buffer_updates, only: [:create]
|
||||
resources :reading_list_items, only: [:update]
|
||||
|
|
@ -148,6 +148,7 @@ Rails.application.routes.draw do
|
|||
|
||||
get "/chat_channel_memberships/find_by_chat_channel_id" => "chat_channel_memberships#find_by_chat_channel_id"
|
||||
get "/credits/purchase" => "credits#new"
|
||||
get "/listings/dashboard" => "classified_listings#dashboard"
|
||||
get "/listings/:category" => "classified_listings#index"
|
||||
get "/listings/:category/:slug" => "classified_listings#index"
|
||||
get "/listings/:category/:slug/:view" => "classified_listings#index",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue